WPF MVVM:如何根据事件更新 UI 控制器
Posted
技术标签:
【中文标题】WPF MVVM:如何根据事件更新 UI 控制器【英文标题】:WPF MVVM : how to Update UI controllers based on Event 【发布时间】:2018-08-08 19:29:21 【问题描述】:我的 UserControl 中有 2 个 TextBox 控制器,我们称它们为 TextBox1 和 TextBox2。
在我的旧代码中,当触发 TextBox2 TextChanged 事件时,我会更新 TextBox1 背景。在 xaml.cs 中使用事件处理程序,这很简单直接。
private void textBox_TextChanged(object sender, TextChangedEventArgs e)
// use controllers Names.
但是我读到这违反了 MVVM 标准。基本上就是不要在xaml.cs中添加额外的代码!
在寻找答案的过程中,我发现了 2 种我有点理解的方法:
1- 有人建议我使用 PropertyChanged 来触发另一个事件。我注意到在 TextBox 失去焦点之前不会触发 PropertyChanged 事件。这不是我要找的。我希望 TextBox1 在用户向 TextBox2 输入内容后立即更新。但是,我仍然不确定在哪里告诉代码 “如果 TextBox TextChanged,则更改 TextBox1 背景”。
2- 另一种方法是使用对我来说全新的行为,我能够立即在 TextBox2 上触发 TextChanged 事件,但我不知道如何访问 TextBox1 属性!
我的问题:处理我在 MVVM 方法中寻找的要求的正确方法是什么?
【问题讨论】:
我会在你的视图模型上为每个Background
公开属性Textbox
,并使用绑定连接它们。然后在 Textbox.Text
绑定到的任何属性的设置器中,您可以更新该属性。绑定会将更新后的值推送到控件。
这种方法是否需要文本框失去焦点才能调用 setter?
关于第 1 点,您的 xaml 中可能缺少 UpdateSourceTrigger=PropertyChanged
。这将在属性更改后立即更新。 IE。如果你绑定到Text
属性,它会在每次有新输入时触发。
@user3382285 默认情况下会,但如果你在绑定中添加UpdateSourceTrigger="PropertyChanged"
,它将在文本的每次更改时触发。
后面的代码不违反 MVVM 标准。在 .xaml.cs 文件中做 UI 相关人员是完全可以的。在您的情况下,您正在更改仅与 UI 相关且不包含任何业务逻辑的文本框的颜色。我更愿意在后面的代码中使用事件处理程序更改背景。
【参考方案1】:
您可以在 View-Model 中执行所有这些逻辑。此特定示例使用AgentOctal.WpfLib NuGet 包(免责声明:我是此包的作者)作为引发PropertyChanged
通知的基本ViewModel
类,但您可以使用任何您想要的系统,只要它的属性实现INotifyPropertyChanged
.
在此示例中,您在第一个 TextBox
中输入的字母越多,第二个 TextBox
的背景就越蓝。
第一个TextBox
的Text
属性绑定到视图模型上的Text
属性。绑定将UpdateSourceTrigger
设置为PropertyChanged
,以便绑定在每次属性更改时更新视图模型,而不仅仅是在控件失去焦点时。
第二个TextBox
的Background
属性绑定到视图模型上名为BackgroundColor
的SolidColorBrush
属性。
在视图模型上,TextBox
的设置器包含确定第二个TextBox
的颜色的逻辑。
通过使用Color
而不是SolidColorBrush
和IValueConverter
可以将Color
更改为Brush
,这可能会更好地实现,但它应该作为一个体面的服务器起点。
所有代码都存在于视图模型中,代码隐藏是空的。
XAML:
<Window
x:Class="VmBindingExample.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:VmBindingExample"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Title="MainWindow"
Width="525"
Height="350"
mc:Ignorable="d">
<Window.DataContext>
<local:MainWindowVm />
</Window.DataContext>
<StackPanel Margin="20" Orientation="Vertical">
<TextBox
Margin="4"
MaxLength="10"
Text="Binding Path=Text, UpdateSourceTrigger=PropertyChanged" />
<TextBox Margin="4" Background="Binding BackgroundColor">The color of this will reflect the length of the first textbox.</TextBox>
</StackPanel>
</Window>
视图模型:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using AgentOctal.WpfLib;
namespace VmBindingExample
using System.Windows.Media;
public class MainWindowVm : ViewModel
private string _text;
public string Text
get
return _text;
set
SetValue(ref _text, value);
byte red = (byte)(255 / 10 * (10 - _text.Length));
BackgroundColor = new SolidColorBrush(Color.FromArgb(255, red, 255, 255));
private Brush _backgroundColor;
public Brush BackgroundColor
get
return _backgroundColor;
set
SetValue(ref _backgroundColor, value);
【讨论】:
【参考方案2】:我会尽量在 xaml 中保留 UI 特定的内容,并在这种情况下使用触发器。 查看以下关于触发器和转换器的文章以获取空值。 DataTrigger where value is NOT null?
正如 Bradly Uffner 之前提到的,您应该修改绑定并添加 UpdateSourceTrigger="PropertyChanged"
,以便立即触发更改。
【讨论】:
对我来说,“UI 特定的东西”是一种代码味道。几乎总是有一个业务需求产生可以在 ViewModel 中建模的需求。【参考方案3】:第二种方法是要走的路。在您的 viewmodel 中,添加 ICommand DoOnTextChanged
和依赖属性 BackgroundColor
。
DoOnTextChanged
命令与TextBox1 的TextChanged 事件绑定
使用转换器将BackgroundColor
属性绑定到TextBox2的背景。
在DoOnTextChanged
的Execute函数中,修改BackgroundColor
的属性即可。
如果您使用的是 MVVMLight,绑定到 ICommand 很容易。首先添加这两个命名空间xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
和xmlns:cmd="clr-namespace:GalaSoft.MvvmLight.Command;assembly=GalaSoft.MvvmLight.Platform"
并执行以下操作:
<TextBox>
<i:Interaction.Triggers>
<i:EventTrigger EventName="TextChanged" >
<cmd:EventToCommand Command="Binding DoOnTextChanged" PassEventArgsToCommand="False" >
</cmd:EventToCommand>
</i:EventTrigger>
</i:Interaction.Triggers>
</TextBox>
更新
由于 OP 使用的是普通 wpf/Xaml,我正在使用普通 wpf 的实现来更新我的答案。
在你的项目中添加以下两个帮助类:
public class ExecuteCommand : TriggerAction<DependencyObject>
public static readonly DependencyProperty CommandProperty = DependencyProperty.Register("Command", typeof(ICommand), typeof(ExecuteCommand));
public ICommand Command
get
return GetValue(CommandProperty) as ICommand;
set
SetValue(CommandProperty, value);
protected override void Invoke(object parameter)
if (Command != null)
if (Command.CanExecute(parameter))
Command.Execute(parameter);
public class EventCommand : ICommand
private Action<object> func;
public EventCommand(Action<object> func)
this.func = func;
public bool CanExecute(object parameter)
//Use your logic here when required
return true;
public event EventHandler CanExecuteChanged;
public void Execute(object parameter)
if (func != null)
func(parameter);
在您的 ViewModel 中,实现 INotifyPropertyChanged 并添加以下 ICommand 和 Background 属性。
public class MainViewModel : INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
public MainViewModel(IDataService dataService)
BackColor = Brushes.Aqua;
DoOnTextChanged = new EventCommand((obj => BackColor = BackColor == Brushes.BurlyWood ? Brushes.Chartreuse : Brushes.BurlyWood));
public ICommand DoOnTextChanged get; set;
private Brush backColor;
public Brush BackColor
get
return backColor;
set
backColor = value;
if (PropertyChanged != null)
PropertyChanged.Invoke(this, new PropertyChangedEventArgs("BackColor"));
最后,在您的 ViewName.xaml 文件中,添加此命名空间 xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
。您可能需要添加对 System.Windows.Interactivity 的引用。然后添加以下内容以将按钮事件绑定到命令:
<TextBox>
<i:Interaction.Triggers>
<i:EventTrigger EventName="TextChanged" >
<local:ExecuteCommand Command="Binding DoOnTextChanged"></local:ExecuteCommand>
</i:EventTrigger>
</i:Interaction.Triggers>
</TextBox>
<TextBox Background="Binding BackColor"></TextBox>
虽然完成一些简单的事情需要很多代码,但在某些情况下它确实很有帮助。最好学习所有方法并使用最适合您需要的方法。
【讨论】:
第二个选项是“行为”。我没有看到您在回答中完全描述行为。不是说你的答案是错误的,但似乎没有讨论行为。 @BradleyUffner 请注意第一个要点。我只是给出基本方向。 我不知道我可以将命令绑定到 TextChanged!这是在 xaml 中吗?我可以做类似EventToCommand
示例应该没问题,但它似乎比将逻辑放入视图模型的 getter 更多工作。它将具有相同的最终结果,并且与 MVVM 兼容。【参考方案4】:
好吧,我总是喜欢在可能的情况下关注MVVM
,在这种情况下完全有可能:
您正在考虑View
(当 TextBox2 TextChanged 时更新 TextBox1 背景)而不是从业务逻辑方面考虑(当业务层 [例如模型] 发生某些事情时更新 TextBox1 背景 [a属性改变了它的值?
您应该有一个带有 TextBox1 和一个 TextBox2 的 View 以及一个带有一些属性的 ViewModel,例如:
/* These properties should implement PropertyChanged, I'm too lazy */
public string WhateverInputsTextBox1 get; set;
public string WhateverInputsTextBox2 get; set;
public bool WhateverMeansTextBox1HasChanged get; set;
那么你应该在whatevernInputsTextBox1的内容发生变化时(在属性的set
中)设置WhateverMeansTextBox1HasChanged为true。
最后,您必须使用转换器将 true 转换为一种颜色,将 false 转换为不同的颜色,将 TextBox1 文本绑定到WhateverInputsTextBox1 属性,将TextBox2 文本绑定到WhateverInputsTextBox2 属性,并将TextBox1 背景绑定到WhateverMeansTextBox1HasChanged 属性(检查@ 987654321@)。请记住在需要的地方将绑定设置为 UpdateSourceTrigger="PropertyChanged"(这会在输入数据时将数据移动到 ViewModel
)。
这样您就可以将View
的所有业务逻辑都放入ViewModel
中,并且您可以根据需要对它进行测试,因为正确分配了所有职责。
另一个好处是(至少对我而言)当我看到TextBox
的背景在“AccountNumberChanged
”而不是在编辑TextBox2
时发生变化时,更容易理解开发人员的意图。
【讨论】:
感谢您的回答。对我来说很奇怪,大多数 MVVM 解决方案最终都使用 PropertyChanged,然后在设置器中执行逻辑。虽然有一个事件可以直接使用TextChanged。 MVVM 是否有标准的事件处理方式!我希望有像PropertyChanged
是数据绑定的核心。这对于 MVVM 模式来说是绝对必要的。
您应该理解的主要思想是,当有人将某些内容写入 TextBox2 时,您不想更改 TextBox1 背景,而当有人更改您的业务的某些值时,您希望这样做。跨度>
以上是关于WPF MVVM:如何根据事件更新 UI 控制器的主要内容,如果未能解决你的问题,请参考以下文章
WPF 在 MVVM 模式下实现窗口后台代码与ViewModel交互