【问题标题】:Confirmation Window In MVVM design patternMVVM 设计模式中的确认窗口
【发布时间】:2015-08-06 08:18:27
【问题描述】:

您好,我想在单击按钮时显示确认窗口。 我正在尝试使用 MVVM 设计模式进行开发,我已经实现了,但我不认为在 viewModel 中调用视图
是这样做的正确方法。我已经附上了代码,请通过它是否正确

<Window x:Class="MessegeBox_Demo_2.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
        <Grid>
            <Button Content="Ckick ME" HorizontalAlignment="Left" 
                    Command="{Binding GetMessegeboxCommand}"
                    Margin="200,131,0,0" VerticalAlignment="Top" Width="75"/>

        </Grid>
    </Window>

public class MainWindowViewModel : ViewModelBaseClass
{
   private ICommand _getMessegeboxCommand;
   public ICommand GetMessegeboxCommand
   {
      get
      {
          return _getMessegeboxCommand ?? (_getMessegeboxCommand = new MessegeBox_Demo_2.Command.realyCommand(() => ShowUsercontrol(), true));
      }
   }
   private void ShowUsercontrol()
   {
      MessegeBox_Demo_2.View.Window1 mbox = new View.Window1();
      mbox.ShowDialog();
   }
}

【问题讨论】:

  • 您可以实现dialog service 来打开对话框或使用messages 以MVVM 方式实现关注点的干净分离。
  • 这里有几个关于如何显示新视图(例如对话框窗口)以响应 ViewModel 事件的问题。你已经做的很好,只要它有效。从纯粹的观点来看,您可以看看使用 DependencyProperties 为 View 提供一种机制,用于检测 ViewModel 是否需要确认窗口,而无需 VeiwModel 直接调用 View。

标签: c# wpf mvvm


【解决方案1】:

最简单的方法是实现一个对话服务并使用依赖注入将服务注入到视图模型中。可以依赖于接口但不依赖于具体实现。
以下是我使用的界面:

namespace DialogServiceInterfaceLibrary
{
    public enum MessageBoxButton  
    {
    // Summary:
    //     The message box displays an OK button.
    OK = 0,
    //
    // Summary:
    //     The message box displays OK and Cancel buttons.
    OKCancel = 1,
    //
    // Summary:
    //     The message box displays Yes, No, and Cancel buttons.
    YesNoCancel = 3,
    //
    // Summary:
    //     The message box displays Yes and No buttons.
    YesNo = 4,
    }

    public enum MessageBoxResult
    {
    // Summary:
    //     The message box returns no result.
    None = 0,
    //
    // Summary:
    //     The result value of the message box is OK.
    OK = 1,
    //
    // Summary:
    //     The result value of the message box is Cancel.
    Cancel = 2,
    //
    // Summary:
    //     The result value of the message box is Yes.
    Yes = 6,
    //
    // Summary:
    //     The result value of the message box is No.
    No = 7,
    }

    // Summary:
    //     Specifies the icon that is displayed by a message box.
    public enum MessageBoxIcon
    {
    // Summary:
    //     No icon is displayed.
    None = 0,
    //
    // Summary:
    //     The message box contains a symbol consisting of white X in a circle with
    //     a red background.
    Error = 16,
    //
    // Summary:
    //     The message box contains a symbol consisting of a white X in a circle with
    //     a red background.
    Hand = 16,
    //
    // Summary:
    //     The message box contains a symbol consisting of white X in a circle with
    //     a red background.
    Stop = 16,
    //
    // Summary:
    //     The message box contains a symbol consisting of a question mark in a circle.
    Question = 32,
    //
    // Summary:
    //     The message box contains a symbol consisting of an exclamation point in a
    //     triangle with a yellow background.
    Exclamation = 48,
    //
    // Summary:
    //     The message box contains a symbol consisting of an exclamation point in a
    //     triangle with a yellow background.
    Warning = 48,
    //
    // Summary:
    //     The message box contains a symbol consisting of a lowercase letter i in a
    //     circle.
    Information = 64,
    //
    // Summary:
    //     The message box contains a symbol consisting of a lowercase letter i in a
    //     circle.
    Asterisk = 64,
    }

    public interface IDialogService
    {
        bool OpenFileDialog(bool checkFileExists,string Filter, out string FileName);
        void OpenGenericDialog(object Context,IRegionManager RegionManager);
    MessageBoxResult ShowMessageBox(string message, string caption, MessageBoxButton buttons, MessageBoxIcon icon);
    }

以及实现:

public class DialogService : IDialogService
{
    public bool OpenFileDialog(bool checkFileExists, string Filter, out string FileName) 
    {
        FileName = ""; 
        OpenFileDialog openFileDialog = new OpenFileDialog();
        openFileDialog.Multiselect = false;
        //openFileDialog.Filter = "All Image Files | *.jpg;*.png | All files | *.*";
        openFileDialog.Filter = Filter;
        openFileDialog.CheckFileExists = checkFileExists;
        bool result = ((bool)openFileDialog.ShowDialog());
        if (result)
        {
            FileName = openFileDialog.FileName;
        }
        return result;
    }


    public void OpenGenericDialog(object Context,IRegionManager RegionManager)
    {
        GenericDialogWindow dlg = new GenericDialogWindow(Context,RegionManager);
        dlg.Owner = System.Windows.Application.Current.MainWindow;
        dlg.Show();
    }

    public MessageBoxResult ShowMessageBox(string message, string caption, MessageBoxButton buttons, MessageBoxIcon icon)
    {
        return (DialogServiceInterfaceLibrary.MessageBoxResult)System.Windows.MessageBox.Show(message, caption, 
            (System.Windows.MessageBoxButton)buttons,
            (System.Windows.MessageBoxImage)icon);
    }
}

然后将 IDialogservice 注入到视图模型中。 接口和具体实现通常在不同的程序集中

MainWindowViewModel(IDialogService dialogservice){
    _dialogservice = dialogservice;
}

private void ShowUsercontrol()
{
   _dialogservice.ShowMessageBox(... //you get what i mean ;-)
}

dialogservice 能够像打开文件对话框一样打开标准窗口对话框。也可以使用通用版本,但是您需要了解 prism,这有点复杂。 Prism 还允许使用交互请求从视图模型到视图进行通信。我更喜欢在 prism 中工作的方式,但如果你不了解 prism,请忘记那句话。对于一个简单的确认窗口来说,它可能太复杂了。像这样的简单对话服务非常适合简单的确认窗口。

通过视图模型的构造函数注入您的依赖项,您已经朝着使用控制反转容器的正确方向前进。并且允许更轻松的单元测试,因为您可以模拟注入的接口以检查它们是否被正确调用等等。

【讨论】:

  • 感谢您的帮助,我是否需要为此添加任何外部 DLL
  • 这取决于您的要求。如果你想严格一点,接口和实现有一个单独的程序集。您需要参考接口组件。如果您不使用 IOC 容器,则还需要对 impl 程序集的引用。
  • 能否请您向我解释一下这两点 1.IRegionManager RegionManager 2 。其中存在命名空间 GenericDialogWindow
  • 属于棱镜,你不需要 OpenGenericDialog 只是为了你的目的而把它放在外面。您只需要一个简单的确认对话框,即 ShowMessageBox 方法。
  • 谢谢你现在它可以工作了......首先我在 IRegionManager 中感到困惑......再次感谢它帮助了很多兄弟......
【解决方案2】:
  • DialogService 方法比消息传递机制更适合这种场景。它更直接、更易于调试、更易于编写、更易于理解,您不需要任何 3rd 方框架等。

  • 提示:依赖注入很好,但是你通常需要相当多的服务,比如NavigationServiceDialogServiceNotificationService 等,如果你需要将它们注入到很多视图模型中,ctors变得很大,很无聊,重复相同的注入等等。除了依赖注入,你可以使用任何其他“可测试”的方法。

由于 DialogService 在所有视图模型中都是相同的,因此您不必将其注入每个视图模型,但您可以使用某种 AppContext 类或服务定位器。

ViewModelBase 类示例:

public class ViewModelBase : BindableBase
{
      public virtual IDialogService DialogService 
      { 
         get { return AppContext.Current.DialogService; } 
      }
}

具体视图模型示例:

public class HomePageViewModel : ViewModelBase
{
      ...

      public void Cancel_CommandExecute()
      { 
         var dlgResult = DialogService.ShowMessageBox("Do you really want to discard unsaved changes?", "Confirm Exit", DialogButtons.YesNo);
         if (dlgResult != MessageBoxResult.Yes) return;
      }
}

对话服务:

public interface IDialogService
{
    MessageBoxResult ShowMessageBox(string messageBoxText, string caption = null, MessageBoxButton buttons = MessageBoxButton.OK, MessageBoxImage icon = MessageBoxImage.None, MessageBoxResult defaultResult = MessageBoxResult.None);
}

public class DialogService : IDialogService
{
    public virtual MessageBoxResult ShowMessageBox(string messageBoxText, string caption = null, MessageBoxButton buttons = MessageBoxButton.OK, MessageBoxImage icon = MessageBoxImage.None, MessageBoxResult defaultResult = MessageBoxResult.None)
    { 
       return MessageBox.Show(messageBoxText, caption, buttons, icon, defaultResult);
    }
}

在您的测试中,您可以模拟AppContext.Current,也可以覆盖ViewModelBase.DialogService 属性。

也许这不是嘲笑DialogService 的最干净的方法,但它是一种务实的方法。由于您没有在每个视图模型中注入和存储 DialogService 实例,因此它使您的视图模型代码更清晰、更具可读性和可维护性。您的视图模型仍然与视图分离、可测试、可混合等。

【讨论】:

  • 基类是否应该继承自BindableBase?假设有人想要一个不包括 Prism 的简单案例?
【解决方案3】:

您可以在后面的代码中定义和填写绑定。由于后面的代码是 View 的一部分,因此在其中调用 Messagebox 不会破坏 MVVM 模式。这样,您可以在设置 Binding 的值之前显示确认对话框。

你需要的代码后面的代码是:

public partial class MainWindow : Window
{
    private DependencyProperty myDP;

    public MainWindow()
    {
        ...
        Binding myBinding = new Binding();
        myBinding.Path = new PropertyPath("myValue"); //myValue is a variable in ViewModel
        myBinding.Source = DataContext;
        myDP = DependencyProperty.Register("myValue", typeof(/*class or primitive type*/), typeof(MainWindow));
        BindingOperations.SetBinding(this, myDP, myBinding);
        ...
    }

    private void Button_Click(object sender, RoutedEventArgs e)
    {
        MessageBoxResult result = MessageBox.Show("Do you really want to do that?", "", MessageBoxButton.YesNo);
        if (result == MessageBoxResult.Yes
        {
            SetValue(myDP, /*value*/); //this sets the Binding value. myValue in ViewModel is set
        }
    }
}

点击按钮时调用Button_Click-方法,添加

Click="Button_Click"

到您的按钮的 XAML 定义。

【讨论】:

  • 1.我不同意“ViewModel 和 View 不应该以任何其他方式进行通信,而不是 Bindings”的说法。您不应该在视图模型中创建视图或访问,但您可以从视图中访问视图模型。 2. 您的方法无法测试与对话框相关的逻辑,例如 viewmodel 是否显示对话框或它对不同答案的反应
【解决方案4】:

如果您想坚持 MVVM 模式,则不应(永远)在视图模型中调用 UI 方法。

从 ViewModel 打开/关闭窗口的正确方法是使用 MVVMLight 的 Messanger 或 Prism 的 EventAggregator 向 View 发送消息。

EventAggregator 允许您的 ViewModel 向订阅者列表发送消息。当您订阅特定消息时,您会附加一个要执行的函数。

我了解到您正在学习此模式的机制,因此您可以编写自己的 EventAggregator 并使用它。

【讨论】:

  • 您不需要事件机制来显示确认对话框。 Prism 有不同的机制:InteractionRequests。
【解决方案5】:

我为此使用了 MVVM Light 消息传递。 PRISM 库也提供了一种很好的方法。

为了处理视图模型触发的交互和视图中控件触发的交互,Prism 库提供了 InteractionRequests 和 InteractionRequestTriggers,以及自定义 InvokeCommandAction 操作。 InvokeCommandAction 用于将包含事件的触发器连接到 WPF 命令。

在 ViewModel 中创建一个 InteractionRequest 属性:

public InteractionRequest<IConfirmation> ConfirmationRequest { get; private set; }

像这样调用交互:

private void RaiseConfirmation()
{
    this.ConfirmationRequest.Raise(
        new Confirmation { Content = "Confirmation Message", Title = "Confirmation" },
        c => { InteractionResultMessage = c.Confirmed ? "The user accepted." : "The user cancelled."; });
}

要使用交互请求,您需要在视图的 XAML 代码中定义相应的 InteractionRequestTrigger:

<prism:InteractionRequestTrigger SourceObject="{Binding ConfirmationRequest, Mode=OneWay}">
    <prism:PopupWindowAction IsModal="True" CenterOverAssociatedObject="True"/>
</prism:InteractionRequestTrigger>

Interactivity QuickStart Using the Prism Library 5.0 for WPF

【讨论】:

    猜你喜欢
    • 2010-10-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-08-31
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多