用户输入确认

时间:2018-07-08 23:59:19

标签: c# wpf mvvm

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

让我们说我有三个字符串属性的经典Person类-FirstName,LastName和Fullname。用户通过两个TextBox输入前两个,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。以前,我使用事件和MessageBox进行管理-在StackPanel失去焦点或用户按下Enter之后出现了一个消息框,询问是否更改pp。

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

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

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

2 个答案:

答案 0 :(得分:0)

有多种方法可以实现这一目标。
您以前使用事件的方法(可能是?)还不错。

我只是在程序/产品的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
}

UI:

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级别负责执行正确的操作(例如,显示对话框或窗口)。

答案 1 :(得分:0)

这是一个非常基本且冗长的示例,说明如何执行您要寻找的内容。做到这一点的组件可以并且应该细分为进一步的处理组,但是您会从中得到灵感。

我将在此处包括3个类,即ViewModel,PreviousState和RelayCommand。然后,我将在View中使用纯绑定。按原样运行此代码,它将通过纯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>

应用启动(无法提交或还原) enter image description here

名字更改(可以提交或还原) enter image description here

姓氏已更改(仍可以提交或还原) enter image description here

提交存储新更改(无法再提交或还原) enter image description here

还原还原更改(无法再提交或还原) enter image description here