【问题标题】:WPF Custom Modal Dialog using MVVM使用 MVVM 的 WPF 自定义模式对话框
【发布时间】:2021-05-31 11:22:57
【问题描述】:

问题

我正在努力将按下哪个按钮的结果从我的CustomDialogUserControl.xaml.cs 传递给我的CustomDialogService.cs

背景

我可以通过我的CustomDialogService.cs 使用MessageBox.Show,一切都很好,看起来像这样:

我创建了自己的CustomDialogUserControl 来实现覆盖窗口,方法是在我的应用程序窗口中使用x:Name="MainGrid",并在需要对话框时以编程方式添加我的用户控件的子项。

我只是在努力实现如何将按下哪个按钮传递给我的DialogService.cs的功能

项目结构

MainWindowViewModel.cs

视图模型非常简单。它继承自实现INotifyPropertyChangedViewModelBase。我也在使用基本的RelayCommand 实现。当按下其中一个按钮时,我会打电话给我的CustomDialogService

public class MainWindowViewModel : ViewModelBase
{
    private string _text = "This is my MainWindowViewModel";

    public string Text
    {
        get { return _text; }
        set 
        { 
            _text = value;
            OnPropertyChanged();
        }
    }

    public ICommand OpenDefaultCommand { get; set; }
    public ICommand OpenCustomCommand { get; set; }

    ICustomDialogService _customDialogService;

    public MainWindowViewModel(ICustomDialogService customDialogService)
    {
        OpenDefaultCommand = new RelayCommand(OpenDefault, CanOpen);
        OpenCustomCommand = new RelayCommand(OpenCustom, CanOpen);
        _customDialogService = customDialogService;
    }

    // Uses default message box
    private void OpenDefault(object obj)
    {
        var result = _customDialogService.ShowOKDialogDefault("My title", "My message");

        if (result == CustomDialogResult.OK)
            Text = "Default OK was clicked";
        else
            Text = "Default Cancel was clicked";
    }

    // Uses custom user control
    private void OpenCustom(object obj)
    {
        var result = _customDialogService.ShowOKDialogCustom("My title", "My message");

        if (result == CustomDialogResult.OK)
            Text = "Custom OK was clicked";
        else
            Text = "Custom Cancel was clicked";
    }

    private bool CanOpen(object arg)
    {
        return true;
    }
}

CustomDialogService.cs

这是我在从我的CustomDialogUserControl 按下哪个按钮时遇到问题的地方。在使用内置MessageBoxShowOKDialogDefault 中,一切都很好。

public enum CustomDialogResult
{
    OK, Yes, No, Cancel
}

public class CustomDialogService : ICustomDialogService
{
    public CustomDialogResult ShowOKDialogDefault(string title, string message)
    {
        // Uses default MessageBox
        var result = MessageBox.Show(message, title, MessageBoxButton.OKCancel);

        if (result == MessageBoxResult.OK)
            return CustomDialogResult.OK;
        else
            return CustomDialogResult.Cancel;
    }

    public CustomDialogResult ShowOKDialogCustom(string title, string message)
    {
        // Uses custom user control
        var customDialog = new CustomDialogUserControl(title, message);
        ((MainWindow)System.Windows.Application.Current.MainWindow).MainGrid.Children.Add(customDialog);

        // --- THE ISSUE ---
        // How do I return the result here telling me which button was pressed in my User Control?
        return CustomDialogResult.Cancel;

    }

    // TODO: Implement other dialogs
    public CustomDialogResult ShowYesNoDialog(string title, string message)
    {
        return CustomDialogResult.Cancel;
    }
}

CustomDialogUserControl.xaml.cs

这是我的自定义用户控件。我正在尝试遵循 MVVM,并且我读到使用用户控件背后的代码是可以接受的。我正在使用依赖属性来绑定TitleMessage。问题是,我有按钮的点击事件,但老实说我不知道​​如何将结果返回给我的CustomDialogService

public partial class CustomDialogUserControl : UserControl
{
    public CustomDialogUserControl(string title, string message)
    {
        InitializeComponent();
        Title = title;
        Message = message;
    }

    public string Title
    {
        get { return (string)GetValue(TitleProperty); }
        set { SetValue(TitleProperty, value); }
    }

    public static readonly DependencyProperty TitleProperty =
        DependencyProperty.Register("Title", typeof(string), typeof(CustomDialogUserControl));


    public string Message
    {
        get { return (string)GetValue(MessageProperty); }
        set { SetValue(MessageProperty, value); }
    }

    public static readonly DependencyProperty MessageProperty =
        DependencyProperty.Register("Message", typeof(string), typeof(CustomDialogUserControl));

    private void OK_Click(object sender, RoutedEventArgs e)
    {
        // My issue
        var result = CustomDialogResult.OK;
        ((MainWindow)System.Windows.Application.Current.MainWindow).MainGrid.Children.Remove(this);
    }

    private void Cancel_Click(object sender, RoutedEventArgs e)
    {
        // My issue
        var result = CustomDialogResult.Cancel;
        ((MainWindow)System.Windows.Application.Current.MainWindow).MainGrid.Children.Remove(this);
    }

任何帮助将不胜感激。感觉解决方案很简单,但我已经花了很多时间在这上面,我想我应该向社区寻求帮助。

如果我的代码中有任何其他突出的地方破坏了 MVVM 模式,请告诉我。

根据 user2250152 的回复更新

感谢您的回复 - 我自己尝试过这种方法,但是当我打开对话框时,MainWindowViewModel 中的 Text 属性在我按下任何按钮之前更改为 OK。请参阅下面的快照:

这些是我根据回复对代码所做的更改以供将来参考:

CustomDialogUserControl.xaml.cs

public CustomDialogResult Result { get; set; }

    private void OK_Click(object sender, RoutedEventArgs e)
    {
        // My issue
        Result = CustomDialogResult.OK;
        ((MainWindow)System.Windows.Application.Current.MainWindow).MainGrid.Children.Remove(this);
    }

    private void Cancel_Click(object sender, RoutedEventArgs e)
    {
        // My issue
        Result = CustomDialogResult.Cancel;
        ((MainWindow)System.Windows.Application.Current.MainWindow).MainGrid.Children.Remove(this);
    }

CustomDialogService.cs

public CustomDialogResult ShowOKDialogCustom(string title, string message)
    {
        // Uses custom user control
        var customDialog = new CustomDialogUserControl(title, message);
        ((MainWindow)System.Windows.Application.Current.MainWindow).MainGrid.Children.Add(customDialog);

        return customDialog.Result;
    }

【问题讨论】:

  • MVVM 和视图模型组件中的对话框处理不能一起使用。 MVVM 要求视图模型不知道视图,这意味着它必须对视图绝对被动,这意味着视图模型不应该主动触发对话框。你甚至已经实现了一个逻辑来决定显示什么样的对话框。您甚至可以创建对话框实例来直接显示它们。这不是 MVVM。
  • 您基本上只需要将自定义对话框的可见性从折叠状态切换为可见状态。相关逻辑必须在视图中。然后将命令附加到关闭或接受按钮。此命令应在作为 UserControl 的 DataContext 的视图模型中定义。
  • “我正在尝试关注 MVVM,并且我读到使用代码背后的用户控件是可以接受的。” - 绝对。那你为什么决定从视图模型而不是视图的代码隐藏中显示对话框?不要这样做。

标签: wpf mvvm dialog user-controls overlay


【解决方案1】:

我将在CustomDialogUserControl.xaml.cs 中创建一个CustomDialogResult 类型的新属性Result,并将结果分配给OK_ClickCancel_Click

public CustomDialogResult Result { get; set; }
...
private void OK_Click(object sender, RoutedEventArgs e)
{
    Result = CustomDialogResult.OK;
    ((MainWindow)System.Windows.Application.Current.MainWindow).MainGrid.Children.Remove(this);
}

private void Cancel_Click(object sender, RoutedEventArgs e)
{
    Result = CustomDialogResult.Cancel;
    ((MainWindow)System.Windows.Application.Current.MainWindow).MainGrid.Children.Remove(this);
}

CustomDialogService.cs阅读结果

public CustomDialogResult ShowOKDialogCustom(string title, string message)
{
    // Uses custom user control
    var customDialog = new CustomDialogUserControl(title, message);
    ((MainWindow)System.Windows.Application.Current.MainWindow).MainGrid.Children.Add(customDialog);
    return customDialog.Result;
}

【讨论】:

  • 感谢您的回复,我已根据您的建议更新了我的问题。但是,使用该方法将正确的状态传递到主视图模型时,我仍然遇到问题。查看更新中的快照以了解我的意思。
  • @TheEngineer 尝试调试您的代码。可能当您调用 ((MainWindow)System.Windows.Application.Current.MainWindow).MainGrid.Children.Add(customDialog);在您单击“确定”或“取消”按钮之前,它不会阻塞线程并且 ShowOKDialogCustom 已结束。也许使用 WaitHandle 等待自定义对话框中的某个按钮被按下会有所帮助。或者创建一个继承自 Window 的自定义窗口并在其上调用 ShowDialog。
  • 我明白你对 WaitHandle 的意思 - 如果我让它工作,我会试一试并更新帖子。就您从 Window 继承的第二种方法而言-这对我有用吗?我自己尝试做这样的事情,但问题是我的对话框不是窗口,它只是一个用户控件,在主网格中添加了一个透明矩形。当我尝试从 Window 继承时,我将 Window 边框和按钮添加到我的用户控件中。
  • 可能从视图模型调用(MainWindow)System.Windows.Application.Current.MainWindow,应该引起您的注意,做出了一些错误的设计选择,这显然违反了 MVVM 模式。
  • @BionicCode 我没有在我的视图模型中调用(MainWindow)System.Windows.Application.Current.MainWindow,而是在我的 DialogService 中,ViewModel 只知道 IDialogService 接口。我认为这种方法可以保持关注点分离?
【解决方案2】:

Result 属性添加到您的CustomDialogUserControl.xaml.cs 类,并根据您的更新在按钮的Click 事件处理程序中设置此属性。

我自己尝试过这种方法,但是当我打开对话框时,MainWindowViewModel 中的 Text 属性会在我按下任何按钮之前更改为 OK。

由于Result 的默认值为CustomDialogResult.OK,或者无论调用enum 的第一个选项,您应该将属性类型更改为Nullable<Result> (Result?) 或添加一个Undefined 选项到enum

然后您可以根据属性的值正确设置Text属性,例如:

private void OpenDefault(object obj)
{
    var result = _customDialogService.ShowOKDialogDefault("My title", "My message");

    Text = string.Empty;
    if (result.HasValue)
    {
        if (result.Value == CustomDialogResult.OK)
            Text = "Default OK was clicked";
        else
            Text = "Default Cancel was clicked";
    }
}

【讨论】:

  • 感谢您的回复。我尝试了您的两个建议,但这并没有解决问题。我做了更多的挖掘并找到了答案here。问题是我需要阻塞线程并等到用户响应按钮单击后再从方法返回。
猜你喜欢
  • 2020-07-27
  • 2016-01-26
  • 1970-01-01
  • 2011-09-09
  • 2013-12-14
  • 1970-01-01
  • 1970-01-01
  • 2018-09-27
相关资源
最近更新 更多