在更改状态下更改WPF文本框的背景颜色

时间:2009-08-03 19:21:55

标签: wpf data-binding textbox background

我有一个EmployeeViewModel类,它有2个属性“FirstName”和“LastName”。该类还有一个包含属性更改的字典。 (该类实现了INotifyPropertyChanged和IDataErrorInfo,一切都很好。

在我看来,有一个文本框:

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

如果原始值发生变化,如何更改文本框的背景颜色?我想过创建一个触发器来设置背景颜色,但我应该绑定什么? 我不想为每个控件创建一个额外的属性,这个控件保持更改状态。

THX

6 个答案:

答案 0 :(得分:10)

只需使用具有相同属性的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>

不需要额外的属性或逻辑,您可以将它全部包装到您自己的标记扩展中。希望有所帮助。

答案 1 :(得分:4)

您需要使用value converter(将字符串输入转换为颜色输出),最简单的解决方案是向EmployeeViewModel添加至少一个属性。您需要制作某种默认 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}}"/>

答案 2 :(得分:3)

如果您正在使用MVVM范例,则应将ViewModel视为在Model和View之间具有适配器角色。

不希望ViewModel完全不知道UI的存在方式,而是与任何特定的 UI无关。

因此,ViewModel可以(并且应该)具有尽可能多的转换器的功能。这里的实际例子如下:

UI是否需要知道文本是否等于默认字符串?

如果答案是,那么就足以在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这样的基类。

答案 3 :(得分:1)

您可以添加到ViewModel布尔属性,如IsFirstNameModifiedIsLastNameModified,并使用触发器根据这些属性更改文本框的背景。或者您可以将Background绑定到这些属性,并使用从bool返回Brush的转换器...

答案 4 :(得分:1)

完全不同的方法是不实现INotifyPropertyChanged,而是从DependencyObject或UIElement下载

他们使用DependencyProperty实现绑定 您可能只使用一个事件处理程序和用户e.Property来查找rigth文本框

我很确定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

    }

 }
}

答案 5 :(得分:0)

最后一个答案的变体可能是始终处于修改状态,除非该值是默认值。

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