在已更改状态下更改 WPF 文本框的背景颜色
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/1224144/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Change Background Color for WPF textbox in changed-state
提问by ollifant
I have a class EmployeeViewModel with 2 properties "FirstName" and "LastName". The class also has a dictionary with the changes of the properties. (The class implements INotifyPropertyChanged and IDataErrorInfo, everything is fine.
我有一个 EmployeeViewModel 类,它有 2 个属性“FirstName”和“LastName”。该类还有一个包含属性变化的字典。(该类实现了 INotifyPropertyChanged 和 IDataErrorInfo,一切正常。
In my view there is a textbox:
在我看来,有一个文本框:
<TextBox x:Name="firstNameTextBox" Text="{Binding Path=FirstName}" />
How can I change the background color of the textbox, if the original value changed? I thought about creating a trigger which sets the background color but to what should I bind? I don't want to created an additional property for every control which holds the state wheter the one was changed or not.
如果原始值发生变化,如何更改文本框的背景颜色?我想创建一个触发器来设置背景颜色,但我应该绑定什么?我不想为每个控件都创建一个附加属性,无论该控件是否更改,该属性都保存状态。
Thx
谢谢
回答by Bryan Anderson
Just use a MultiBinding with the same property twice but have Mode=OneTime on one of the bindings. Like this:
只需使用具有相同属性的 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
And in the xaml:
在 xaml 中:
<TextBox Text="{Binding TestText}">
<TextBox.Background>
<MultiBinding Converter="{StaticResource BackgroundConverter}">
<Binding Path="TestText" />
<Binding Path="TestText" Mode="OneTime" />
</MultiBinding>
</TextBox.Background>
</TextBox>
No extra properties or logic required and you could probably wrap it all into your own markup extension. Hope that helps.
不需要额外的属性或逻辑,您可以将其全部包装到您自己的标记扩展中。希望有帮助。
回答by Charlie
You will need to use a value converter(converting string input to color output) and the simplest solution involves adding at least one more property to your EmployeeViewModel. You need to make some sort of a Defaultor OriginalValueproperty, and compare against that. Otherwise, how will you know what the "original value" was? You cannot tell if the value changed unless there is something holding the original value to compare against.
您将需要使用值转换器(将字符串输入转换为颜色输出),最简单的解决方案是向您的 EmployeeViewModel 中添加至少一个属性。您需要创建某种Default或OriginalValue属性,并与之进行比较。否则,你怎么知道“原始值”是什么?您无法判断该值是否发生了变化,除非有一些东西可以与原始值进行比较。
So, bind to the text property and compare the input string to the original value on the view model. If it has changed, return your highlighted background color. If it matches, return the normal background color. You will need to use a multi-binding if you want to compare the FirstNameand LastNametogether from a single textbox.
因此,绑定到 text 属性并将输入字符串与视图模型上的原始值进行比较。如果它已更改,请返回突出显示的背景颜色。如果匹配,则返回正常的背景颜色。如果要从单个文本框中比较FirstName和LastName,则需要使用多重绑定。
I have constructed an example that demonstrates how this could work:
我构建了一个示例来演示这是如何工作的:
<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>
And here is the code-behind for the Window:
这是窗口的代码隐藏:
/// <summary>
/// Interaction logic for Window11.xaml
/// </summary>
public partial class Window11 : Window
{
public static string DefaultString
{
get { return "John Doe"; }
}
public Window11()
{
InitializeComponent();
}
}
Finally, here is the converter you use:
最后,这是您使用的转换器:
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();
}
}
And even though I wrapped a border around the TextBox (because I think that looks a little better), the Background binding can be done exactly the same way:
即使我在 TextBox 周围包裹了一个边框(因为我认为它看起来更好一些),Background 绑定也可以完全相同的方式完成:
<TextBox Name="textBox" Text="{Binding Path=DefaultString, Mode=OneTime}"
Background="{Binding ElementName=textBox, Path=Text, Converter={StaticResource changedDefaultColorConverter}}"/>
回答by Kenan E. K.
If you're using the MVVM paradigm, you should consider the ViewModels as having the role of adapters between the Model and the View.
如果您使用 MVVM 范式,您应该将 ViewModel 视为在模型和视图之间扮演适配器的角色。
It is not expected of the ViewModel to be completely agnostic of the existence of a UI in every way, but to be agnostic of any specificUI.
不期望 ViewModel 在任何方面都完全不知道 UI 的存在,而是不知道任何特定的UI。
So, the ViewModel can (and should) have the functionality of as many Converters as possible. The practical example here would be this:
因此,ViewModel 可以(并且应该)具有尽可能多的转换器的功能。这里的实际例子是这样的:
Would a UI require to know if a text is equal to a default string?
UI 是否需要知道文本是否等于默认字符串?
If the answer is yes, it's sufficient reason to implement an IsDefaultString
property on a ViewModel.
如果答案是yes,则有充分的理由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
}
}
Then bind the TextBox
like this:
然后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>
This propagates text back to the ViewModel upon TextBox
losing focus, which causes a recalculation of the IsTextDefault
. If you need to do this a lot of times or for many properties, you could even cook up some base class like DefaultManagerViewModel
.
这会在TextBox
失去焦点时将文本传播回 ViewModel ,从而导致重新计算IsTextDefault
. 如果您需要多次执行此操作或针对许多属性执行此操作,您甚至可以编写一些基类,例如DefaultManagerViewModel
.
回答by Thomas Levesque
You could add to your ViewModel boolean properties like IsFirstNameModified
and IsLastNameModified
, and use a trigger to change the background if the textbox according to these properties. Or you could bind the Background
to these properties, with a converter that returns a Brush
from a bool...
您可以添加到您的 ViewModel 布尔属性,例如IsFirstNameModified
and IsLastNameModified
,如果文本框根据这些属性,则使用触发器来更改背景。或者,您可以Background
使用Brush
从布尔值返回 a 的转换器将 绑定到这些属性...
回答by JC.
A complete diferent way would be to not implement INotifyPropertyChanged and instead descend from DependencyObject or UIElement
一个完全不同的方法是不实现 INotifyPropertyChanged 而是从 DependencyObject 或 UIElement 下降
They implement the binding using DependencyProperty You may event use only one event handler and user e.Property to find the rigth textbox
他们使用 DependencyProperty 实现绑定您可以仅使用一个事件处理程序和用户 e.Property 来查找正确的文本框
I'm pretty sure the e.NewValue != e.OldValue check is redundant as the binding should not have changed. I also beleive there may be a way to implement the binding so the dependecyObject is the textbox and not your object...
我很确定 e.NewValue != e.OldValue 检查是多余的,因为绑定不应该改变。我也相信可能有一种方法可以实现绑定,因此dependecyObject 是文本框而不是您的对象...
Edit if you already inherit from any WPF class (like control or usercontrol) you are probably ok and you don't need to change to UIElement as most of WPF inherit from that class
编辑如果您已经从任何 WPF 类(如控件或用户控件)继承,您可能没问题,并且您不需要更改为 UIElement,因为大多数 WPF 继承自该类
Then you can have:
然后你可以有:
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
}
}
}
回答by JC.
A variation of the last answer could be to alwais be in the modified state unless the value is the default value.
最后一个答案的变体可能是始终处于修改状态,除非该值是默认值。
<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>