【问题标题】:WPF MVVM : how to Update UI controllers based on EventWPF MVVM:如何根据事件更新 UI 控制器
【发布时间】: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 相关且不包含任何业务逻辑的文本框的颜色。我更愿意在后面的代码中使用事件处理程序更改背景。

标签: c# wpf mvvm


【解决方案1】:

您可以在 View-Model 中执行所有这些逻辑。此特定示例使用AgentOctal.WpfLib NuGet 包(免责声明:我是此包的作者)作为引发PropertyChanged 通知的基本ViewModel 类,但您可以使用任何您想要的系统,只要它的属性实现INotifyPropertyChanged.


在此示例中,您在第一个 TextBox 中输入的字母越多,第二个 TextBox 的背景就越蓝。

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

第二个TextBoxBackground 属性绑定到视图模型上名为BackgroundColorSolidColorBrush 属性。

在视图模型上,TextBox 的设置器包含确定第二个TextBox 的颜色的逻辑。

通过使用Color 而不是SolidColorBrushIValueConverter 可以将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的背景。
    • DoOnTextChangedExecute函数中,修改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 中吗?我可以做类似 的事情吗?
    • @user3382285 你不能。我用一个简短的例子更新了我的答案。
    • 虽然您的 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 是否有标准的事件处理方式!我希望有像 这样简单的东西,但似乎并非如此!
    • @user3382285 PropertyChanged 是数据绑定的核心。这对于 MVVM 模式来说是绝对必要的。
    • 您应该理解的主要思想是,当有人将某些内容写入 TextBox2 时,您不想更改 TextBox1 背景,而当有人更改您的业务的某些值时,您希望这样做。跨度>
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-08-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-12-20
    • 2014-11-23
    相关资源
    最近更新 更多