[英]Modal Dialog with WPF using MVVM
There are tons and tons of articles around the inte.net about this topic, but I just can't wrap my head around it. inte.net 上有无数关于这个主题的文章,但我就是无法理解它。 Most articles use code behind, but I want to stick to "pure" MVVM since I try to learn it.
大多数文章都使用代码隐藏,但我想坚持使用“纯”MVVM,因为我尝试学习它。 Also, I explicitly don't want to use any other framework (MVVMlight, Ninject...).
此外,我明确不想使用任何其他框架(MVVMlight、Ninject...)。 I just want to stick to what WPF has to offer.
我只想坚持 WPF 提供的内容。 I know this got asked a lot, but what I found either was not mvvm or was not specific enough.
我知道这个问题被问了很多,但我发现要么不是 mvvm,要么不够具体。
My task is simple: I want to see the most simple solution of opening a modal dialog, send it a string, and get a string from the dialog back upon closing it.我的任务很简单:我想看到最简单的解决方案,即打开模式对话框,向其发送一个字符串,并在关闭对话框时从对话框中取回一个字符串。
Therefore I set up my MainWindow.xaml with a text input field (TextBox), a button (that should open the modal dialog) and a textblock that will show the message I intend to receive from the dialog.因此,我设置了 MainWindow.xaml,其中包含一个文本输入字段 (TextBox)、一个按钮(应打开模态对话框)和一个文本块,该文本块将显示我打算从对话框接收的消息。
The dialog has a TextBlock, showing the user-input from MainWindow.xaml, and a TextBox to enter some text, and a button.该对话框有一个 TextBlock,显示来自 MainWindow.xaml 的用户输入,以及一个用于输入一些文本的 TextBox 和一个按钮。 You guessed it: you press the button, and the message I typed into the textfield get's returned to MainWindow.xaml.
您猜对了:您按下按钮,然后我在文本字段中输入的消息返回到 MainWindow.xaml。 Please refer also to the images I've included - I think it's pretty self-explanatory.
另请参阅我包含的图片 - 我认为这是不言自明的。
MainWindow.xaml主窗口.xaml
<Window x:Class="Dialogs.MainWindow"
...
Title="First View (Main Window)" Height="240" Width="630">
<Grid>
<StackPanel>
<StackPanel Orientation="Horizontal" Margin="10">
<TextBlock Text="Main View sayz: "/>
<TextBox Width="360" Margin="10,0,0,30"/>
</StackPanel>
<Button Content="Send to Second View" Command="{Binding SendToSecondViewCommand}" Width="200"/>
<StackPanel Orientation="Horizontal" Margin="10,30,10,10">
<TextBlock Text="Second View replies: "/>
<TextBlock Width="360"/>
</StackPanel>
</StackPanel>
</Grid>
</Window>
SecondView.xaml SecondView.xaml
<UserControl x:Class="Dialogs.SecondView"
...
d:DesignHeight="240" d:DesignWidth="630" Background="BlanchedAlmond">
<Grid>
<StackPanel>
<StackPanel Orientation="Horizontal" Margin="10">
<TextBlock Text="This is what First View sayz: "/>
<TextBlock Width="360"/>
</StackPanel>
<StackPanel Orientation="Horizontal" Margin="10">
<TextBlock Text="Second View replies: "/>
<TextBox Width="360" Margin="10,0,0,30"/>
</StackPanel>
<Button Content="Reply to First View" Command="{Binding ReplyToFirstViewCommand}" Width="200"/>
</StackPanel>
</Grid>
</UserControl>
Here is how I implemented INotifyPropertyChanged (It's actually a.cs file named BaseClasses; I know it's not named properly...)这是我实现 INotifyPropertyChanged 的方式(它实际上是一个名为 BaseClasses 的.cs 文件;我知道它没有正确命名......)
public abstract class NotifyPropertyChangedBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged<T>(ref T variable, T value,
[CallerMemberName] string propertyName = null)
{
variable = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
And here my base class for relay commands:这里是我的中继命令基地 class:
public class CommandDelegateBase : ICommand
{
public delegate void ExecuteDelegate(object parameter);
public delegate bool CanExecuteDelegate(object paramerter);
private ExecuteDelegate execute;
private CanExecuteDelegate canExecute;
public CommandDelegateBase(ExecuteDelegate _execute, CanExecuteDelegate _canExecute = null)
{
execute = _execute;
canExecute = _canExecute;
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public bool CanExecute(object parameter)
{
return canExecute?.Invoke(parameter) ?? true;
}
public void Execute(object parameter)
{
execute.Invoke(parameter);
}
}
Lastly my ViewModels: FirstViewModel:最后是我的 ViewModels:FirstViewModel:
public class FirstViewViewModel: NotifyPropertyChangedBase
{
private string _sendText;
public string SendText
{
get { return _sendText; }
set
{
_sendText = value;
OnPropertyChanged(ref _sendText, value);
}
}
public ICommand SendToSecondViewCommand { get; set; }
public FirstViewViewModel()
{
SendToSecondViewCommand = new CommandDelegateBase(SendExecuteCommand, SendCanExecuteCommand);
}
private bool SendCanExecuteCommand(object paramerter)
{
return true;
}
private void SendExecuteCommand(object parameter)
{
//Do stuff to :
// a) show the second view as modal dialog
// b) submit what I just wrote (SendText)
}
}
SecondViewModel:第二视图模型:
public class SecondViewViewModel : NotifyPropertyChangedBase
{
private string _replyText;
public string ReplyText
{
get { return _replyText; }
set
{
_replyText = value;
OnPropertyChanged(ref _replyText, value);
}
}
public ICommand ReplyToFirstViewCommand { get; set; }
public SecondViewViewModel()
{
ReplyToFirstViewCommand = new CommandDelegateBase(ReplyExecuteCommand, ReplyCanExecuteCommand);
}
private bool ReplyCanExecuteCommand(object paramerter)
{
return true;
}
private void ReplyExecuteCommand(object parameter)
{
//Do stuff to :
// a) close the second view
// b) reply what I just wrote (ReplyText) back to First View.
}
}
I have a folder called "Models" in my solution but for the sake of simplicity it's empty.我的解决方案中有一个名为“模型”的文件夹,但为了简单起见,它是空的。
I know there are solutions with helper classes or services - what ever pertains mvvm will do.我知道有帮助类或服务的解决方案——mvvm 会做的任何事情。 I also do know that doing this for such a simple task as what I want is quiet "overkill", and has a lot more writing code coming with it than it would be justifyable for this purpose.
我也确实知道,为我想要的这样一个简单的任务执行此操作是安静的“矫枉过正”,并且随之而来的编写代码比为此目的合理的要多得多。 But again: I'd like to learn this, and understand what I am doing.
但是再一次:我想学习这个,并了解我在做什么。
Thank you so much in advance!非常感谢您!
I wrote an article about this subject and provided a library and sample application.我写了一篇关于这个主题的文章并提供了一个库和示例应用程序。 The article itself is long...because it's not a trivial topic...but causing a dialog box to appear can be as simple as this:
文章本身很长......因为它不是一个微不足道的话题......但是导致对话框出现可以像这样简单:
this.Dialogs.Add(new CustomDialogBoxViewModel()); // dialog box appears here
UPDATE: I just noticed that my MvvmDialogs library in that package is actually referencing MvvmLite.更新:我刚刚注意到我在 package 中的 MvvmDialogs 库实际上引用了 MvvmLite。 That's a vestigial remnant from when I was developing it though, the library itself doesn't need it, so you can remove the reference altogether.
这是我开发它时留下的残余物,库本身不需要它,因此您可以完全删除引用。
Finding an MVVM pure solution to a programming problem, which may be straightforward in other contexts, is often not a simple task.为编程问题寻找 MVVM 纯解决方案,这在其他情况下可能很简单,但通常不是一项简单的任务。 However, creating a library of helper classes is a "write once, use many times" scenario, so no matter how much code is required, you don't have to reproduce it for every usage.
但是,创建帮助类库是一种“编写一次,使用多次”的方案,因此无论需要多少代码,您都不必每次使用都重现它。
My preferred method for handling message dialogs in MVVM is a two part service module.我在 MVVM 中处理消息对话框的首选方法是一个由两部分组成的服务模块。
The View registers its data context (its ViewModel) with the DialogService as potentially wanting to display a dialog - the service will use the View's UI context to do so when it does. View 向 DialogService 注册它的数据上下文(它的 ViewModel),因为它可能想要显示一个对话框——当它这样做时,该服务将使用 View 的 UI 上下文来这样做。
The ViewModel calls the injected dialog service each time a dialog should be displayed.每次应显示对话框时,ViewModel 都会调用注入的对话框服务。 Calls to the MessageDialog service are made using the async / await pattern, rather than requiring some other form of callback in the ViewModel.
对 MessageDialog 服务的调用是使用 async / await 模式进行的,而不是需要在 ViewModel 中进行某种其他形式的回调。
So now, displaying a MessageDialog from a ViewModel is as simple as所以现在,从 ViewModel 显示 MessageDialog 就像
await _dialogService.ShowMessageAsync(this, "Hello from the dialog service.", perDialogIcon.Information, "Mvvm Dialog Service").ConfigureAwait(false);
or要么
var response = await _dialogService.ShowDialogAsync(this, perDialogButton.YesNo, "Do you want to continue?", perDialogIcon.Question, "Mvvm Dialog Service").ConfigureAwait(false);
I covered this in more detail on a blog post .我在博客文章中对此进行了更详细的介绍。
As an aside, your ViewModel properties look a bit wierd - you're setting the backing-field value, then passing it into your OnPropertyChanged()
method where the value is set again.顺便说一句,您的 ViewModel 属性看起来有点奇怪 - 您正在设置支持字段值,然后将其传递到再次设置值的
OnPropertyChanged()
方法中。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.