C# WPF MVVM:如何基于事件更新UI控制器

C# WPF MVVM:如何基于事件更新UI控制器,c#,wpf,mvvm,C#,Wpf,Mvvm,我的UserControl中有两个TextBox控制器,我们称它们为TextBox1和TextBox2 在我的旧代码中,当触发TextBox2TextChanged事件时,我会更新TextBox1背景。在xaml.cs中使用事件处理程序,这很简单 private void textBox_TextChanged(object sender, TextChangedEventArgs e) { // use controllers Names. } 然而,我读到这违反了

我的UserControl中有两个TextBox控制器,我们称它们为TextBox1和TextBox2

在我的旧代码中,当触发TextBox2TextChanged事件时,我会更新TextBox1背景。在xaml.cs中使用事件处理程序,这很简单

    private void textBox_TextChanged(object sender, TextChangedEventArgs e) {
     // use controllers Names.
    }
然而,我读到这违反了MVVM标准。基本上就是不要在xaml.cs中添加额外的代码

在我寻找答案的过程中,我发现了两种我比较理解的方法:

1-有些人建议我使用PropertyChanged来触发另一个事件。我注意到,直到文本框失去焦点,PropertyChanged事件才会触发。这不是我要找的。我希望TextBox1在用户向TextBox2输入内容后立即更新。然而,我仍然不知道该在哪里告诉代码 “如果文本框文本已更改,则更改文本框1背景”

2-另一种方法是使用行为,这对我来说是全新的,我能够立即在TextBox2上启动事件TextChanged,但我不知道如何访问TextBox1属性


我的问题是:在MVVM方法中,处理我正在寻找的需求的正确方法是什么

第二种方法是要走的路。在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命名空间: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>
最后,在ViewName.xaml文件中,添加这个名称空间
xmlns:i=”http://schemas.microsoft.com/expression/2010/interactivity“
。您可能需要添加对System.Windows.Interactive的引用。然后添加以下内容以将按钮事件绑定到命令:

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


虽然要完成一些简单的事情需要大量的代码,但在某些情况下它确实是有用的。最好学习所有的方法,并使用完全符合您需要的方法。

我会尽量在xaml中保留特定于UI的内容,并在这种情况下使用触发器。 检查以下关于触发器和空值转换器的文章


正如Bradly Uffner前面提到的,您应该修改bindung并添加
UpdateSourceTrigger=“PropertyChanged”
,以便立即触发更改。

好吧,如果可能并且在这种情况下完全可能的话,我总是喜欢遵循
MVVM

您考虑的是
视图
(当TextBox2文本发生变化时更新TextBox1背景),而不是业务逻辑(当业务层[例如模型]中发生某种情况时更新TextBox1背景)[属性改变其值

您应该有一个具有TextBox1和TextBox2的视图,以及一个具有某些属性的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的内容更改时(在属性的
集合中),您应该将WhateverMeansTextBox1HasChanged设置为true

最后,必须使用转换器将TextBox1文本绑定到WhateverInputsTextBox1属性,将TextBox2文本绑定到WhateverInputsTextBox2属性,将TextBox1背景绑定到WhateverMeansTextBox1HasChanged属性,以将真值转换为颜色,将假值转换为其他颜色(选中)。请记住在需要时将绑定设置为UpdateSourceTracger=“PropertyChanged”(输入数据时会将数据移动到
ViewModel

通过这种方式,您可以将
视图的所有业务逻辑
放入
视图模型
中,如果您想测试它,那么您就可以测试它,因为所有职责都已正确分配


另一个好处是(至少对我来说)当我看到“
accountnumberchange
”时
TextBox
的背景发生变化时,更容易理解开发人员的意图而不是编辑
TextBox2
时。

您可以在视图模型中执行所有逻辑。此特定示例使用NuGet软件包(免责声明:我是此软件包的作者)对于引发
PropertyChanged
通知的基本
ViewModel
类,但您可以使用您想要的任何系统,只要它的属性实现
INotifyPropertyChanged


在本例中,您在第一个
文本框中输入的字母越多,第二个
文本框的背景越蓝

第一个
TextBox
将其
Text
属性绑定到视图模型上的
Text
属性。该绑定将
UpdateSourceTrigger
设置为
PropertyChanged
,以便绑定在每次属性更改时更新视图模型,而不仅仅是在控件失去焦点时

第二个
文本框
将其
背景
属性绑定到视图模型上名为
背景色
SolidColorBrush
属性

在视图模型上,
文本框的setter
包含确定第二个
文本框的颜色的逻辑

使用
Color
而不是
solidcolorbush
,以及
IValueConverter
可以将
Color
更改为
Brush
,这可能会实现得更好一些,但它应该是一个不错的起点<
<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>
/* These properties should implement PropertyChanged, I'm too lazy */
public string WhateverInputsTextBox1 { get; set; }
public string WhateverInputsTextBox2 { get; set; }
public bool WhateverMeansTextBox1HasChanged { get; set; }
<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);
            }
        }
    }
}