在尝试使用更通用的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之后,我真的不想回到所有这些事件,那么正确的方法是什么?
答案 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>