简体   繁体   English

使用 MVVM 的模态对话框 WPF

[英]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!非常感谢您!

主窗口.xaml SecondView.xaml 我的解决方案的结构

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 中处理消息对话框的首选方法是一个由两部分组成的服务模块。

  1. 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 上下文来这样做。

  2. 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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM