Change Background Color for WPF textbox in changed-state

前端 未结 6 1625
故里飘歌
故里飘歌 2020-12-15 01:19

I have a class EmployeeViewModel with 2 properties \"FirstName\" and \"LastName\". The class also has a dictionary with the changes of the properties. (The class implements

相关标签:
6条回答
  • 2020-12-15 01:37

    You could add to your ViewModel boolean properties like IsFirstNameModified and IsLastNameModified, and use a trigger to change the background if the textbox according to these properties. Or you could bind the Background to these properties, with a converter that returns a Brush from a bool...

    0 讨论(0)
  • 2020-12-15 01:44

    A complete diferent way would be to not implement INotifyPropertyChanged and instead descend from DependencyObject or UIElement

    They implement the binding using DependencyProperty You may event use only one event handler and user e.Property to find the rigth textbox

    I'm pretty sure the e.NewValue != e.OldValue check is redundant as the binding should not have changed. I also beleive there may be a way to implement the binding so the dependecyObject is the textbox and not your object...

    Edit if you already inherit from any WPF class (like control or usercontrol) you are probably ok and you don't need to change to UIElement as most of WPF inherit from that class

    Then you can have:

    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
    
        }
    
     }
    }
    
    0 讨论(0)
  • 2020-12-15 01:45

    A variation of the last answer could be to alwais be in the modified state unless the value is the default value.

     <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>
    

    0 讨论(0)
  • 2020-12-15 01:48

    If you're using the MVVM paradigm, you should consider the ViewModels as having the role of adapters between the Model and the View.

    It is not expected of the ViewModel to be completely agnostic of the existence of a UI in every way, but to be agnostic of any specific UI.

    So, the ViewModel can (and should) have the functionality of as many Converters as possible. The practical example here would be this:

    Would a UI require to know if a text is equal to a default string?

    If the answer is yes, it's sufficient reason to implement an IsDefaultString property on a ViewModel.

    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
        }
    }
    

    Then bind the TextBox like this:

    <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>
    

    This propagates text back to the ViewModel upon TextBox losing focus, which causes a recalculation of the IsTextDefault. If you need to do this a lot of times or for many properties, you could even cook up some base class like DefaultManagerViewModel.

    0 讨论(0)
  • 2020-12-15 01:49

    Just use a MultiBinding with the same property twice but have Mode=OneTime on one of the bindings. Like this:

    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
    

    And in the xaml:

    <TextBox Text="{Binding TestText}">
        <TextBox.Background>
            <MultiBinding Converter="{StaticResource BackgroundConverter}">
                <Binding Path="TestText"    />
                <Binding Path="TestText" Mode="OneTime" />
            </MultiBinding>
        </TextBox.Background>
    </TextBox>
    

    No extra properties or logic required and you could probably wrap it all into your own markup extension. Hope that helps.

    0 讨论(0)
  • 2020-12-15 01:55

    You will need to use a value converter (converting string input to color output) and the simplest solution involves adding at least one more property to your EmployeeViewModel. You need to make some sort of a Default or OriginalValue property, and compare against that. Otherwise, how will you know what the "original value" was? You cannot tell if the value changed unless there is something holding the original value to compare against.

    So, bind to the text property and compare the input string to the original value on the view model. If it has changed, return your highlighted background color. If it matches, return the normal background color. You will need to use a multi-binding if you want to compare the FirstName and LastName together from a single textbox.

    I have constructed an example that demonstrates how this could work:

    <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>
    

    And here is the code-behind for the Window:

    /// <summary>
    /// Interaction logic for Window11.xaml
    /// </summary>
    public partial class Window11 : Window
    {
        public static string DefaultString
        {
            get { return "John Doe"; }
        }
    
        public Window11()
        {
            InitializeComponent();
        }
    }
    

    Finally, here is the converter you use:

    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();
        }
    }
    

    And even though I wrapped a border around the TextBox (because I think that looks a little better), the Background binding can be done exactly the same way:

    <TextBox Name="textBox" Text="{Binding Path=DefaultString, Mode=OneTime}"
             Background="{Binding ElementName=textBox, Path=Text, Converter={StaticResource changedDefaultColorConverter}}"/>
    
    0 讨论(0)
提交回复
热议问题