7

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

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

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

如果原始值更改,如何更改文本框的背景颜色?我考虑过创建一个设置背景颜色的触发器,但我应该绑定什么?我不想为每个控制状态的控件创建一个额外的属性,无论它是否被更改。

谢谢

4

6 回答 6

12

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

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

于 2009-08-13T00:51:40.477 回答
4

您将需要使用值转换器(将字符串输入转换为颜色输出),最简单的解决方案包括向 EmployeeViewModel 添加至少一个属性。您需要创建某种DefaultOriginalValue属性,并与之进行比较。否则,你怎么知道“原始值”是什么?您无法判断该值是否已更改,除非有一些东西持有原始值进行比较。

因此,绑定到 text 属性并将输入字符串与视图模型上的原始值进行比较。如果已更改,请返回突出显示的背景颜色。如果匹配,则返回正常的背景颜色。如果要从单个文本框中比较FirstNameLastName ,则需要使用多重绑定。

我已经构建了一个示例来演示它是如何工作的:

<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}}"/>
于 2009-08-03T19:38:57.640 回答
3

如果您使用 MVVM 范式,则应将 ViewModel 视为具有模型和视图之间的适配器的角色。

不期望 ViewModel 在各个方面都完全不知道 UI 的存在,而是不知道任何特定的UI。

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

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

如果答案是肯定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.

于 2009-08-09T11:55:49.693 回答
1

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

于 2009-08-03T19:49:33.173 回答
1

一个完全不同的方法是不实现 INotifyPropertyChanged 而是从 DependencyObject 或 UIElement 下降

他们使用 DependencyProperty 实现绑定您可以事件仅使用一个事件处理程序和用户 e.Property 来查找正确的文本框

我很确定 e.NewValue != e.OldValue 检查是多余的,因为绑定不应该改变。我也相信可能有一种方法可以实现绑定,所以dependecyObject是文本框而不是你的对象......

编辑如果您已经从任何 WPF 类(如控件或用户控件)继承,您可能没问题,并且您不需要更改为 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

    }

 }
}
于 2009-08-11T20:53:55.957 回答
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>

于 2009-08-11T20:41:39.303 回答