Изменение цвета фона для текстового поля WPF в измененном состоянии

У меня есть класс EmployeeViewModel с 2 свойствами "FirstName"и " LastName". Класс также имеет словарь с изменениями свойств. (Класс реализует INotifyPropertyChanged и IDataErrorInfo, все в порядке.

на мой взгляд, есть поле:

<TextBox x:Name="firstNameTextBox" Text="{Binding Path=FirstName}" />

Как изменить цвет фона текстового поля, если исходное значение изменилось? Я думал о создании триггера, который устанавливает цвет фона, но к чему я должен связать? Я не хочу создавать дополнительное свойство для каждого элемента управления, который содержит состояние, в котором он был изменен или нет.

Thx

6 ответов


просто используйте MultiBinding с тем же свойством дважды, но имейте 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>

никаких дополнительных свойств или логики не требуется, и вы, вероятно, можете обернуть все это в собственное расширение разметки. Надеюсь, это поможет.


вам нужно будет использовать конвертер стоимостью (преобразование ввода строки В вывод цвета) и самое простое решение включает в себя добавление по крайней мере еще одного свойства к вашему EmployeeViewModel. Вам нужно сделать какой-то по умолчанию или OriginalValue свойство, и сравните с этим. В противном случае, как вы узнаете, что такое "первоначальная стоимость"? Вы не можете сказать, изменилось ли значение, если нет чего-то, содержащего исходное значение для сравнения против.

Итак, привязка к свойству text и сравнение входной строки с исходным значением в модели представления. Если он изменился, верните выделенный цвет фона. Если он совпадает, верните нормальный цвет фона. Вам нужно будет использовать мульти-привязку, если вы хотите сравнить "имя" и фамилия вместе из одного текстового поля.

Я построил пример, который демонстрирует, как это может работа:

<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 Name="textBox" Text="{Binding Path=DefaultString, Mode=OneTime}"
         Background="{Binding ElementName=textBox, Path=Text, Converter={StaticResource changedDefaultColorConverter}}"/>

если вы используете парадигму MVVM, вы должны рассматривать ViewModels как имеющие роль адаптеров между моделью и представлением.

не ожидается, что ViewModel будет полностью агностиком существования пользовательского интерфейса во всех отношениях, но будет агностиком любого конкретные пользовательский интерфейс.

таким образом, ViewModel может (и должен) иметь функциональность как можно большего количества конвертеров. Практическим примером здесь будет это:

будет ли пользовательский интерфейс требовать знать, равен ли текст строке по умолчанию?

если ответ да, это достаточная причина для реализации IsDefaultString свойство на 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
    }
}
связать 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>

это распространяет текст обратно в ViewModel на TextBox потеря фокуса, что вызывает пересчет IsTextDefault. Если вам нужно сделать это много раз или много свойства, вы даже можете приготовить какой-то базовый класс, как DefaultManagerViewModel.


вы можете добавить в свой ViewModel логические свойства, такие как IsFirstNameModified и IsLastNameModified, и используйте триггер для изменения фона, если текстовое поле в соответствии с этими свойствами. Или вы можете связать Background к этим свойствам, с преобразователем, который возвращает Brush из bool...


полным другим способом было бы не реализовывать INotifyPropertyChanged и вместо этого спускаться из DependencyObject или UIElement

они реализуют привязку с помощью DependencyProperty Вы можете использовать только один обработчик событий и пользователь e.Свойство для поиска текстового поля rigth

Я уверен, что e.Ньювалю != e.Проверка OldValue является избыточной, поскольку привязка не должна изменяться. Я также верю, что может быть способ реализовать привязку, поэтому dependecyObject это текстовое поле, а не ваш объект...

Edit если вы уже наследуете от любого класса WPF (например, control или usercontrol), вы, вероятно, в порядке, и вам не нужно менять на UIElement, поскольку большинство WPF наследуют от этого класса

затем вы можете иметь:

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

    }

 }
}

вариант последнего ответа может быть для alwais быть в измененном состоянии, если значение не является значением по умолчанию.

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