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

StackOverflow https://stackoverflow.com/questions/1224144

Вопрос

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

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

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

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

Спасибо

Это было полезно?

Решение

Просто используйте 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.Вам нужно сделать что-то вроде По умолчанию или Исходное значение свойство, и сравните с этим.В противном случае, как вы узнаете, каково было "первоначальное значение"?Вы не можете определить, изменилось ли значение, если нет чего-то, содержащего исходное значение для сравнения.

Итак, привяжитесь к свойству 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.Свойство, чтобы найти нужное текстовое поле

Я почти уверен, что проверка e.newValue != e.oldValue избыточна, поскольку привязка не должна была измениться.Я также полагаю, что может существовать способ реализовать привязку, чтобы dependecyObject был текстовым полем, а не вашим объектом...

Редактировать если вы уже наследуете от любого класса 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

    }

 }
}

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top