【问题标题】:Modal Dialog with WPF using MVVM使用 MVVM 的 WPF 模态对话框
【发布时间】:2020-07-27 17:49:39
【问题描述】:

互联网上有大量关于这个主题的文章,但我就是无法理解它。大多数文章都使用后面的代码,但我想坚持使用“纯”MVVM,因为我尝试学习它。另外,我明确不想使用任何其他框架(MVVMlight、Ninject ...)。我只想坚持 WPF 提供的功能。我知道这被问了很多,但我发现要么不是 mvvm,要么不够具体。

我的任务很简单:我想看看最简单的打开模式对话框的解决方案,向它发送一个字符串,然后在关闭它时从对话框中获取一个字符串。

因此,我在 MainWindow.xaml 中设置了一个文本输入字段 (TextBox)、一个按钮(应该打开模式对话框)和一个显示我打算从对话框接收的消息的文本块。

对话框有一个 TextBlock,显示来自 MainWindow.xaml 的用户输入,以及一个用于输入一些文本的 TextBox,以及一个按钮。您猜对了:您按下按钮,我在文本字段中键入的消息将返回到 MainWindow.xaml。另请参阅我包含的图片 - 我认为这很不言自明。

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

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

这是我实现 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));
    }
}

这里是中继命令的基类:

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);
    }

}

最后是我的 ViewModel: 第一视图模型:

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)
    }
}

第二视图模型:

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

我的解决方案中有一个名为“Models”的文件夹,但为了简单起见,它是空的。

我知道有一些带有帮助类或服务的解决方案——任何与 mvvm 相关的东西都可以。我也知道,像我想要的那样简单的任务这样做是安静的“矫枉过正”,并且附带的编写代码比为此目的合理的要多得多。但再说一遍:我想了解这一点,并了解我在做什么。

非常感谢您!

【问题讨论】:

标签: c# wpf mvvm dialog


【解决方案1】:

wrote an article about this subject 并提供了一个库和示例应用程序。文章本身很长……因为不是小题大做……但是导致出现对话框可以这么简单:

this.Dialogs.Add(new CustomDialogBoxViewModel()); // dialog box appears here

更新:我刚刚注意到我在那个包中的 MvvmDialogs 库实际上是在引用 MvvmLite。这是我开发它时的残留物,库本身不需要它,所以你可以完全删除引用。

【讨论】:

  • 感谢您的回复。文字很多,我需要一些时间来阅读它。您在回复中提供的代码行确实需要在调用视图的 .xaml 中进行一些准备,不是吗?或者你只是调用视图?但话又说回来:如何通过调用视图模型来显示视图?并考虑到视图模型应该知道注意它的视图?也许你可以指出一个答案,我敢肯定,其他寻求帮助的人也会发现它很有用。
  • 文本很多,但您无需阅读全部内容即可使用该库。关于您的问题,您确实需要在 MainWindow 中添加一个绑定到 Dialogs 集合的行为;之后它会为你处理一切。您还需要为每种对话框类型创建一个 DataTemplates...以便行为知道为任何给定的视图模型创建哪个视图,但这只是 3 行非常简单的 XML。最好的办法是看看我文章的最后一部分,这将帮助您确定它是否适合您的情况。祝你好运!
【解决方案2】:

找到一个编程问题的纯 MVVM 解决方案,这在其他情况下可能很简单,但通常不是一项简单的任务。但是,创建帮助类库是“一次编写,多次使用”的场景,因此无论需要多少代码,您都不必为每次使用都重新生成它。

我在 MVVM 中处理消息对话框的首选方法是两部分服务模块。

  1. View 向 DialogService 注册其数据上下文(其 ViewModel),因为它可能想要显示一个对话框 - 服务将使用 View 的 UI 上下文来执行此操作。

  2. 每次显示对话框时,ViewModel 都会调用注入的对话框服务。使用 async / await 模式调用 MessageDialog 服务,而不需要 ViewModel 中的某种其他形式的回调。

所以现在,从 ViewModel 显示 MessageDialog 就像

await _dialogService.ShowMessageAsync(this, "Hello from the dialog service.", perDialogIcon.Information, "Mvvm Dialog Service").ConfigureAwait(false);

var response = await _dialogService.ShowDialogAsync(this, perDialogButton.YesNo, "Do you want to continue?", perDialogIcon.Question, "Mvvm Dialog Service").ConfigureAwait(false);

我在blog post 上对此进行了更详细的介绍。

顺便说一句,您的 ViewModel 属性看起来有点奇怪 - 您正在设置支持字段值,然后将其传递给您的 OnPropertyChanged() 方法,在该方法中再次设置该值。

【讨论】:

  • OP 希望有一个绑定到 SecondViewViewModel 的视图的模式对话框。您展示了如何包装 MessageBox 对话框。
  • 感谢您的链接 - 我刚刚飞过它:它是否特别是关于 MessageBoxes 或者它也可以应用于模式对话框(我知道,有点相同的情况,但我想交换数据,而不仅仅是对 DialogResults 做出反应)。关于“奇怪”的视图模型:你是对的 :) 我有时会忘记我引用了该值,并使用我的 OnPropertyChanged 事件一次性完成。
猜你喜欢
  • 1970-01-01
  • 2021-05-31
  • 2011-09-09
  • 1970-01-01
  • 2010-12-12
  • 1970-01-01
  • 1970-01-01
  • 2014-02-13
  • 1970-01-01
相关资源
最近更新 更多