変更状態のWPFテキストボックスの背景色を変更する
-
11-07-2019 - |
質問
2つのプロパティ" FirstName"を持つクラスEmployeeViewModelがあります。および「姓」。クラスには、プロパティの変更を含む辞書もあります。 (クラスはINotifyPropertyChangedおよびIDataErrorInfoを実装します。すべてが正常です。
私の見解では、テキストボックスがあります:
<TextBox x:Name="firstNameTextBox" Text="{Binding Path=FirstName}" />
元の値が変更された場合、テキストボックスの背景色を変更するにはどうすればよいですか?背景色を設定するトリガーを作成することを考えましたが、何にバインドする必要がありますか? コントロールが変更されたかどうかの状態を保持するすべてのコントロールに対して、追加のプロパティを作成する必要はありません。
Thx
解決
同じプロパティを持つMultiBindingを2回使用しますが、バインディングの1つにMode = OneTimeを指定します。このように:
Public Class MVCBackground
Implements IMultiValueConverter
Public Function Convert(ByVal values() As Object, ByVal targetType As System.Type, ByVal parameter As Object, ByVal culture As System.Globalization.CultureInfo) As Object Implements System.Windows.Data.IMultiValueConverter.Convert
Static unchanged As Brush = Brushes.Blue
Static changed As Brush = Brushes.Red
If values.Count = 2 Then
If values(0).Equals(values(1)) Then
Return unchanged
Else
Return changed
End If
Else
Return unchanged
End If
End Function
Public Function ConvertBack(ByVal value As Object, ByVal targetTypes() As System.Type, ByVal parameter As Object, ByVal culture As System.Globalization.CultureInfo) As Object() Implements System.Windows.Data.IMultiValueConverter.ConvertBack
Throw New NotImplementedException()
End Function
End Class
そしてxamlで:
<TextBox Text="{Binding TestText}">
<TextBox.Background>
<MultiBinding Converter="{StaticResource BackgroundConverter}">
<Binding Path="TestText" />
<Binding Path="TestText" Mode="OneTime" />
</MultiBinding>
</TextBox.Background>
</TextBox>
追加のプロパティやロジックは不要で、おそらくすべてを独自のマークアップ拡張機能にラップできます。お役に立てば幸いです。
他のヒント
値コンバーター<を使用する必要があります(文字列入力をカラー出力に変換する)および最も簡単な解決策は、少なくとも1つのプロパティをEmployeeViewModelに追加することです。何らかの種類の Default または OriginalValue プロパティを作成し、それと比較する必要があります。そうでなければ、「元の価値」が何であるかをどうやって知るのでしょうか?だった?比較する元の値を保持するものがない限り、値が変更されたかどうかはわかりません。
したがって、textプロパティにバインドし、入力文字列をビューモデルの元の値と比較します。変更されている場合は、強調表示されている背景色を返します。一致する場合、通常の背景色を返します。単一のテキストボックスから FirstName と LastName を一緒に比較する場合は、マルチバインディングを使用する必要があります。
これがどのように機能するかを示す例を作成しました:
<Window x:Class="TestWpfApplication.Window11"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:TestWpfApplication"
Title="Window11" Height="300" Width="300"
DataContext="{Binding RelativeSource={RelativeSource Self}}">
<Window.Resources>
<local:ChangedDefaultColorConverter x:Key="changedDefaultColorConverter"/>
</Window.Resources>
<StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock>Default String:</TextBlock>
<TextBlock Text="{Binding Path=DefaultString}" Margin="5,0"/>
</StackPanel>
<Border BorderThickness="3" CornerRadius="3"
BorderBrush="{Binding ElementName=textBox, Path=Text, Converter={StaticResource changedDefaultColorConverter}}">
<TextBox Name="textBox" Text="{Binding Path=DefaultString, Mode=OneTime}"/>
</Border>
</StackPanel>
ウィンドウのコードビハインドは次のとおりです。
/// <summary>
/// Interaction logic for Window11.xaml
/// </summary>
public partial class Window11 : Window
{
public static string DefaultString
{
get { return "John Doe"; }
}
public Window11()
{
InitializeComponent();
}
}
最後に、使用するコンバーターは次のとおりです。
public class ChangedDefaultColorConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
string text = (string)value;
return (text == Window11.DefaultString) ?
Brushes.Transparent :
Brushes.Yellow;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
そして、TextBoxの周りに境界線をラップしたとしても(少し良く見えると思うので)、Backgroundバインディングはまったく同じ方法で行うことができます:
<TextBox Name="textBox" Text="{Binding Path=DefaultString, Mode=OneTime}"
Background="{Binding ElementName=textBox, Path=Text, Converter={StaticResource changedDefaultColorConverter}}"/>
MVVMパラダイムを使用している場合、ViewModelがモデルとビューの間のアダプターの役割を持っていると考える必要があります。
ViewModelがあらゆる点でUIの存在を完全に不可知ではなく、特定の UIを不可知であることは期待されていません。
したがって、ViewModelは可能な限り多くのコンバーターの機能を持つことができます(また、そうすべきです)。ここでの実用的な例は次のとおりです。
テキストがデフォルトの文字列に等しいかどうかをUIが知る必要がありますか?
答えが yes の場合、ViewModelに IsDefaultString
プロパティを実装するのに十分な理由です。
public class TextViewModel : ViewModelBase
{
private string theText;
public string TheText
{
get { return theText; }
set
{
if (value != theText)
{
theText = value;
OnPropertyChanged("TheText");
OnPropertyChanged("IsTextDefault");
}
}
}
public bool IsTextDefault
{
get
{
return GetIsTextDefault(theText);
}
}
private bool GetIsTextDefault(string text)
{
//implement here
}
}
次に、 TextBox
を次のようにバインドします:
<TextBox x:Name="textBox" Background="White" Text="{Binding Path=TheText, UpdateSourceTrigger=LostFocus}">
<TextBox.Resources>
<Style TargetType="TextBox">
<Style.Triggers>
<DataTrigger Binding="{Binding IsTextDefault}" Value="False">
<Setter Property="TextBox.Background" Value="Red"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBox.Resources>
</TextBox>
これは、 TextBox
がフォーカスを失うと、ViewModelにテキストを伝播し、 IsTextDefault
の再計算を引き起こします。これを何度も、または多くのプロパティで行う必要がある場合は、 DefaultManagerViewModel
などの基本クラスを作成することもできます。
Viewcodeに IsFirstNameModified
や IsLastNameModified
などのブール型プロパティを追加し、これらのプロパティに応じてテキストボックスの背景を変更するトリガーを使用できます。または、ブールから Brush
を返すコンバーターを使用して、 Background
をこれらのプロパティにバインドすることもできます...
完全に異なる方法は、INotifyPropertyChangedを実装せず、代わりにDependencyObjectまたはUIElementから派生することです
DependencyPropertyを使用してバインディングを実装します イベントは、1つのイベントハンドラーとユーザーe.Propertyのみを使用して、正しいテキストボックスを見つけることができます
バインディングが変更されてはならないため、e.NewValue!= e.OldValueチェックは冗長であると確信しています。また、dependecyObjectがテキストボックスであり、オブジェクトではないため、バインディングを実装する方法があるかもしれません...
WPFクラス(コントロールやユーザーコントロールなど)を既に継承している場合は編集できます。おそらく、ほとんどのWPFがそのクラスを継承するため、UIElementに変更する必要はありません
その後、次のことができます:
using System.Windows;
namespace YourNameSpace
{
class PersonViewer:UIElement
{
//DependencyProperty FirstName
public static readonly DependencyProperty FirstNameProperty =
DependencyProperty.Register("FirstName", typeof (string), typeof (PersonViewer),
new FrameworkPropertyMetadata("DefaultPersonName", FirstNameChangedCallback));
public string FirstName {
set { SetValue(FirstNameProperty, value); }
get { return (string) GetValue(FirstNameProperty); }
}
private static void FirstNameChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e) {
PersonViewer owner = d as PersonViewer;
if (owner != null) {
if(e.NewValue != e.OldValue && e.NewValue != "DefaultPersonName" ) {
//Set Textbox to changed state here
}
}
}
public void AcceptPersonChanges() {
//Set Textbox to not changed here
}
}
}
最後の回答のバリエーションは、値がデフォルト値でない限り、常に変更された状態になることです。
<TextBox.Resources>
<Style TargetType="{x:Type TextBox}">
<Style.Triggers>
<Trigger Property="IsLoaded" Value="True">
<Setter Property="TextBox.Background" Value="Red"/>
</DataTrigger>
</Style.Triggers>
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource Self}, Path=Text" Value="DefaultValueHere">
<Setter Property="TextBox.Background" Value=""/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBox.Resources>