Изменить цвет фона для текстового поля WPF в измененном состоянии - PullRequest
6 голосов
/ 03 августа 2009

У меня есть класс EmployeeViewModel с 2 свойствами «FirstName» и «LastName». В классе также есть словарь с изменениями свойств. (Класс реализует INotifyPropertyChanged и IDataErrorInfo, все хорошо.

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

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

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

Thx

Ответы [ 6 ]

10 голосов
/ 13 августа 2009

Просто используйте 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>

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

4 голосов
/ 03 августа 2009

Вам нужно будет использовать преобразователь значений (преобразование ввода строки в вывод цвета), и самое простое решение заключается в добавлении как минимум еще одного свойства в 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>

А вот код для Window:

/// <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 (потому что я думаю, что это выглядит немного лучше), привязку фона можно сделать точно так же:

<TextBox Name="textBox" Text="{Binding Path=DefaultString, Mode=OneTime}"
         Background="{Binding ElementName=textBox, Path=Text, Converter={StaticResource changedDefaultColorConverter}}"/>
3 голосов
/ 09 августа 2009

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

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

Таким образом, 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>

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

1 голос
/ 12 августа 2009

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

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

Я почти уверен, что проверка 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

    }

 }
}
1 голос
/ 03 августа 2009

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

0 голосов
/ 12 августа 2009

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...