【问题标题】:User input confirmation用户输入确认
【发布时间】:2018-12-16 14:58:48
【问题描述】:

在尝试对 WPF 采用更通用的方法(类似于 MVVM 的方式)之后,我遇到了一个问题。

假设我有具有三个字符串属性的经典 Person 类 - FirstName、LastName、Fullname。前两个是用户通过两个TextBoxes输入的,FullName是只读的,打印在TextBlock中。

我有一个带有两个文本框的堆栈面板。我将 Person 实例绑定到 stackpanel 的 DataContext。它看起来像这样:

<StackPanel x:Name="InputData" DataContext="{Binding Person}">
                    <Text="{Binding FirstName, Mode=TwoWay}"/>
                    <Text="{Binding LastName, Mode=TwoWay}"/>
                </StackPanel>
<TextBlock Text="{Binding FullName}"/>

在 c# 中:

public MainWindow()
{
    InitializeComponent();

    Person PP= new Person ();

    InputData.DataContext = PP;
}

public class Person : INotifyPropertyChanged
{
    string _firstName = "Bob";
    string _lastName = "Smith";

    public string FirstName
    {
        get { return _firstName; }
        set
        {
            _firstName = value;
            OnPropertyChanged("FullName");
        }
    }

    public string LastName
    {
        get { return _lastName; }
        set
        {
            _lastName = value;
            OnPropertyChanged("FullName");
        }
    }

    public string FullName
    {
        get { return string.Format("{0} {1}", FirstName, LastName); }
    }


    public event PropertyChangedEventHandler PropertyChanged;
    private void OnPropertyChanged(string property)
    {
        if (PropertyChanged != null)
            PropertyChanged(this, new PropertyChangedEventArgs(property));
    }
}

现在我想避免意外更改 pp。以前我使用事件和 MessageBoxes 进行管理 - 在 StackPanel 失去焦点或用户按下 Enter 后出现一个消息框并询问是否更改 pp。

如果用户选择“否”,那么一切都会恢复到编辑前的原始状态。

否则 pp 和所有文本框都会保存新值。

现在在尝试绑定和 MVVM 之后,我真的不想回到所有这些事件,那么正确的方法是什么?

【问题讨论】:

  • 这个问题可能会被标记,因为它太宽泛而且很大程度上是主观的。简单的答案是“Windows 对话框非常老派,不要在 MVVM 应用程序中使用它们”。如果您决定无论如何,那么有多种方法可以这样做,每种方法都有自己的优点和缺点。如果您想要一个核心 MVVM 纯粹主义方法,请查看the article I wrote on this topic 以及我的示例代码。

标签: c# wpf mvvm


【解决方案1】:

这是一个非常基本且详细的示例,说明如何执行您要查找的操作。实现这一点的组件可以而且应该被分解为进一步的处理组,但你会明白这一点的。

我将在这里包含 3 个类,ViewModel、PreviousState 和 RelayCommand。然后我将在视图中使用纯绑定。按原样运行此代码,它将通过纯 MVVM 以您希望的方式工作。然而;花点时间来解读正在发生的事情,因为有更深入但更简单的方法来实现这一结果。我相信这是一个使用纯 MVVM 的受过良好教育的示例,可以帮助您了解如何实现这一点。注意:这里使用了一些流行的技术,为了帮助您了解更多,需要一个关于 MVVM 和 XAML 的短期课程。我的希望是您可以从中挑选出足够多的内容,以便更多地了解它并对其进行修改以满足您的需求。

我将所有文件放在同一个命名空间中,并将 ViewModel、RelayCommand 和 PreviousState 类型都放在一个文件中,只是为了简化发布此答案。

using System;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Windows.Input;

namespace Question_Answer_WPF_App
{
    public class PersonViewModel : INotifyPropertyChanged
    {
        string firstName = "Bob";
        string lastName = "Smith";
        string fullName;

        public PersonViewModel()
        {
            CommitStateCommand = new RelayCommand((obj) => CommitChanges(),(obj) => CanExecuteCommands());
            RevertStateCommand = new RelayCommand((obj) => RevertChanges(),(obj) => CanExecuteCommands());
            CommitChanges();
        }

        public event PropertyChangedEventHandler PropertyChanged;
        private void Notify([CallerMemberName] string property = "") => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(property));

        public string FirstName
        {
            get => firstName;
            set
            {
                firstName = value;
                Notify();
                UpdateChanges();
            }
        }

        public string LastName
        {
            get => lastName;
            set
            {
                lastName = value;
                Notify();
                UpdateChanges();
            }
        }

        public string FullName
        {
            get => fullName;
            private set
            {
                fullName = value;
                Notify();
            }
        }

        private PersonState PreviousState { get; } = new PersonState();
        public RelayCommand CommitStateCommand { get; }
        public RelayCommand RevertStateCommand { get; }

        private void UpdateChanges()
        {
            FullName = $"{FirstName} {LastName}";
            CommitStateCommand.UpdateCanExecute();
            RevertStateCommand.UpdateCanExecute();
        }

        private void CommitChanges()
        {
            PreviousState.FirstName = FirstName;
            PreviousState.LastName = LastName;
            UpdateChanges();
        }

        private void RevertChanges()
        {
            FirstName = PreviousState?.FirstName;
            LastName = PreviousState?.LastName;
            CommitChanges();
        }

        private bool CanExecuteCommands() => PreviousState?.FirstName != FirstName || PreviousState?.LastName != LastName;
    }

    internal class PersonState
    {
        public string FirstName { get; set; }
        public string LastName { get; set; }
    }

    public class RelayCommand : ICommand
    {
        public Action<object> ExecuteFunction { get; }
        public Predicate<object> CanExecutePredicate { get; }
        public event EventHandler CanExecuteChanged;
        public void UpdateCanExecute() => CanExecuteChanged?.Invoke(this, EventArgs.Empty);

        public RelayCommand(Action<object> executeFunction) : this(executeFunction, (obj) => true) { }
        public RelayCommand(Action<object> executeFunction, Predicate<object> canExecutePredicate)
        {
            ExecuteFunction = executeFunction;
            CanExecutePredicate = canExecutePredicate;
        }

        public bool CanExecute(object parameter) => CanExecutePredicate?.Invoke(parameter) ?? true;
        public void Execute(object parameter) => ExecuteFunction.Invoke(parameter);
    }
}

MainWindow.xaml

<Window x:Class="Question_Answer_WPF_App.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:Question_Answer_WPF_App"
        mc:Ignorable="d"
        Title="MainWindow"
        Height="450"
        Width="800">

    <Window.DataContext>
        <local:PersonViewModel />
    </Window.DataContext>

    <Window.Resources>
        <Style TargetType="Button">
            <Setter Property="Width" Value="150" />
            <Setter Property="Margin" Value="4" />
        </Style>
    </Window.Resources>

    <StackPanel>
        <TextBlock Text="First Name"/>
        <TextBox Text="{Binding FirstName, Mode=TwoWay}"/>

        <TextBlock Text="Last Name"/>
        <TextBox Text="{Binding LastName, Mode=TwoWay}"/>

        <TextBlock Text="Full Name"/>
        <TextBlock Text="{Binding FullName}" />

        <StackPanel Orientation="Horizontal">
            <Button Content="Commit" 
                    Command="{Binding CommitStateCommand}" />
            <Button Content="Revert" 
                    Command="{Binding RevertStateCommand}" />
        </StackPanel>    
    </StackPanel>

</Window>

应用启动(无法提交或恢复)

名字已更改(可以提交或恢复)

姓氏已更改(仍可提交或恢复)

提交存储新更改(不能再提交或恢复)

Reverting Reverts Changes(不能再提交或恢复)

【讨论】:

    【解决方案2】:

    有多种方法可以做到这一点。
    您之前处理事件的方法(可能?)还不错。

    我只需在您的程序/产品的 MVVM 库中创建一个类,其中包含要从您的 UI 库订阅的事件和方法。

    这里有一个例子来证明这一点。

    MVVM:

    public delegate void ExceptionEvent(Exception e);
    
    public static class MessageHelper
    {
        #region Public API
    
        public static void OnException(Exception e)
        {
            Exception?.Invoke(e);
        }
    
        #endregion
    
        #region Events
    
        public static event ExceptionEvent Exception;
    
        #endregion
    }
    

    用户界面:

    static class MessageResolver
    {
        #region Constructor
    
        static MessageResolver()
        {
            MessageHelper.Exception += OnException;
        }
    
        #endregion
    
        #region Events
    
        static void OnException(Exception e)
        {
            new ExceptionDialog(e).ShowDialog();
        }
    
        #endregion
    }
    

    您只需在 UI 助手类中打开一个对话框。您可以将其更改为 EventArgs [...] 但您明白了。

    基本上,您会指示助手做某事。
    UI 级别负责执行正确的操作(例如显示对话框或窗口)。

    【讨论】:

      猜你喜欢
      • 2016-01-11
      • 2015-11-28
      • 2018-07-16
      • 2019-10-30
      • 2015-03-21
      • 1970-01-01
      • 2020-11-12
      • 1970-01-01
      • 2014-04-19
      相关资源
      最近更新 更多