使用MVVM Light工具包显示对话框

20
我有一个ViewModel,需要在按钮点击时显示模态窗口(使用ShowDialog())。 ViewModel捕获单击命令,但我不想在ViewModel中执行window.ShowDialog()。 我知道在MVVM Light中有一个DialogMessage,但该消息框是用于显示消息框而不是WPF模态窗口。
有什么办法可以做到这一点吗?

请查看以下链接:https://dev59.com/YHPYa4cB1Zd3GeqPehOz#16994523 - reggaeguitar
4个回答

22

您应该使用 Messenger 类。在 View 上注册一条消息以显示窗口,然后在需要显示时调用 Messenger 类的 Send 方法。

您可以这样做:

    //do this in the code-behind file of your View
    Messenger.Default.Register<string>(this, ShowWindow);
    
    private void ShowWindow(string message)
    {
        // your logic here
    }
    
    // In the ViewModel
    Messenger.Default.Send(“Some text”);

14

这是我在使用MVVM-Light Toolkit自定义对话框时所使用的代码。

首先,在应用程序中的某个地方定义这四个类。其中MessageBase类是Toolkit的一部分。

public class ShowChildWindowMessage : MessageBase { }
public class HideChildWindowMessage : MessageBase { }
public class DisplaySomeContentMessage : MessageBase { }
public class DisplaySomeOtherContentMessage : MessageBase { }

其次,您需要一个“子窗口”控件。创建一个具有以下内容的 XAML 文件:

<Window x:Class="ChildWindowView"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        DataContext="{Binding Path=ChildWindowBinding, Source={StaticResource Locator}}"
        Title="{Binding Path=CurrentContent.DisplayName}"
        MinWidth="300" MinHeight="125" SizeToContent="WidthAndHeight"
        ShowInTaskbar="False" WindowState="Normal" ResizeMode="NoResize"
        WindowStartupLocation="CenterOwner" SnapsToDevicePixels="True">

    <Grid>
        <ContentPresenter Content="{Binding Path=CurrentContent}" />
    </Grid>
</Window>

接下来,在此XAML文件的代码后台中添加以下内容:

public partial class ChildWindowView : Window
{
    public ChildWindowView(Window owner)
    {
        InitializeComponent();
        Owner = owner;

        Closing += (s, e) => 
        {
            // window reused so just hide
            e.Cancel = true;
            Messenger.Default.Send(new HideChildWindowMessage());
        };
    }

}

第三步,在MainWindow.xaml的代码后台中添加以下内容:

public partial class MainWindowView : Window
{
    private ChildWindowView m_childWindowView;

    public MainWindowView()
    {
        InitializeComponent();
        Closing += (s, e) => ViewModelLocator.CleanUp();
        Loaded += (s, e) =>
        {
            m_childWindowView = new ChildWindowView(this);
        };

        Messenger.Default.Register<ShowChildWindowMessage>(this, (msg) => m_childWindowView.ShowDialog());
        Messenger.Default.Register<HideChildWindowMessage>(this, (msg) => m_childWindowView.Hide());
    }
}

第四步,定义以下视图模型:

public class ChildWindowVM : ViewModelBase
{
    private ViewModelBase m_currentContent;
    public ViewModelBase CurrentContent
    {
        get { return m_currentContent; }
        set
        {
            m_currentContent = value;
            RaisePropertyChanged("CurrentContent");

            if (m_currentContent != null)
            {
                Messenger.Default.Send(new ShowChildWindowMessage());
            }
        }
    }

    public ChildWindowVM()
    {
        Messenger.Default.Register<DisplaySomeContentMessage>(this, (msg) => CurrentContent = ViewModelLocator.SomeContentVm);
        Messenger.Default.Register<DisplaySomeOtherContentMessage>(this, (msg) => CurrentContent = ViewModelLocator.SomeOtherContentVm);
    }
}

第五步,您需要创建XAML文件和视图模型来显示自定义对话框中想要展示的内容。在本例中,我的内容视图模型名称为SomeContent和SomeOtherContent。当然,您可以将其替换为您想要的任何名称。

最后,为了使其起作用,您必须通过将以下内容添加到应用程序资源中,将内容视图模型绑定到它们相应的XAML文件:

<DataTemplate DataType="{x:Type viewmodels:SomeContentVM}">
    <views:SomeContentView/>
</DataTemplate>

<DataTemplate DataType="{x:Type viewmodels:SomeOtherContentVM}">
    <views:SomeOtherContentView/>
</DataTemplate>

一旦您完成了所有设置,添加新内容(XAML和视图模型)以在子窗口中显示就变得很简单。要显示内容,只需使用Messenger类调用相应的消息即可:

Messenger.Default.Send(new DisplaySomeContentMessage ());

如果您需要我澄清任何部分,请告诉我。


但是使用这种方法如何返回对话框结果? - Vagif Abilov
有趣!我决定在我的MainWindow.xaml中拥有一个子窗口列表(Dictionary<Guid,Window>)。在上述方法中,ViewModels被创建了两次,因此另一种方法是通过将DataContext =“{Binding Content,RelativeSource = {RelativeSource FindAncestor,AncestorType = {x:Type ContentPresenter}}}”添加到DataTemplates来更紧密地耦合ViewModels。 - sky-dev
@sky-dev,ViewModels 是如何“被创建两次”的? - bugged87
在这段代码中,经过几天的调试,我认为你是对的。我的 ViewModels 被创建两次,因为我一直在使用 View-first 的 MVVM 模式,在 ChildWindow 的内容控件中第二次创建了 ViewModel。现在我已经更新了代码,转而使用 ViewModel first,我非常喜欢这种方式。它似乎真的可以节省创建新视图的开销,因为我可以在同一个 ResourceDictionary 中指定它们(视图)(如果我选择这样做的话)。我仍然使用你的 ChildWindow 的变体,可以同时打开多个子窗口。 - sky-dev
如果您最终使用此解决方案,并尝试连续显示具有新视图模型的相同对话框/视图,则会遇到似乎不会刷新的问题。 在此处查看我使用的解决方案以解决此问题:刷新对话框修复 - Cole W
显示剩余2条评论

0

您可以按照以下方式定义接口及其实现。当然,使用依赖注入容器时,您需要执行类似于此的操作。

NInjectKernel.Bind<IMessageBoxService>().To<MessageBoxService>();

你的 ViewModel 会长成这个样子。

    private IMessageBoxService _MBService;
    public DropboxSettingsViewModel(IDropboxService dbService, IMessageBoxService mbService)
    {
        if (dbService == null)
            throw new ArgumentNullException("IDropboxService is null");

        _DropboxService = dbService;

        if (mbService == null)
            throw new ArgumentNullException("MessageBoxService is null");

        _MBService = mbService;

    }

您的点击命令执行方法如下。

    private void ConfigureDropboxExecute(object obj)
    {

        _MBService.Show("Error Occured Authenticating dropbox", "Dropbox Authentication", MessageBoxButton.OK, MessageBoxImage.Error, MessageBoxResult.OK);

    }


public interface  IMessageBoxService
{
    MessageBoxResult Show(string messageBoxText);
    MessageBoxResult Show(string messageBoxText, string caption);
    MessageBoxResult Show(Window owner, string messageBoxText);
    MessageBoxResult Show(string messageBoxText, string caption, MessageBoxButton button);
    MessageBoxResult Show(Window owner, string messageBoxText, string caption);
    MessageBoxResult Show(string messageBoxText, string caption, MessageBoxButton button, MessageBoxImage icon);
    MessageBoxResult Show(Window owner, string messageBoxText, string caption, MessageBoxButton button);
    MessageBoxResult Show(string messageBoxText, string caption, MessageBoxButton button, MessageBoxImage icon, MessageBoxResult defaultResult);
    MessageBoxResult Show(Window owner, string messageBoxText, string caption, MessageBoxButton button, MessageBoxImage icon);
    MessageBoxResult Show(string messageBoxText, string caption, MessageBoxButton button, MessageBoxImage icon, MessageBoxResult defaultResult, MessageBoxOptions options);
    MessageBoxResult Show(Window owner, string messageBoxText, string caption, MessageBoxButton button, MessageBoxImage icon, MessageBoxResult defaultResult);
    MessageBoxResult Show(Window owner, string messageBoxText, string caption, MessageBoxButton button, MessageBoxImage icon, MessageBoxResult defaultResult, MessageBoxOptions options);
}

使用 System.Windows;

public class MessageBoxService : IMessageBoxService
{
    public MessageBoxResult Show(string messageBoxText)
    {
        return MessageBox.Show(messageBoxText);
    }

    public MessageBoxResult Show(Window owner, string messageBoxText)
    {
        return MessageBox.Show(owner, messageBoxText);
    }

    public MessageBoxResult Show(string messageBoxText, string caption)
    {
        return MessageBox.Show(messageBoxText, caption);
    }

    public MessageBoxResult Show(Window owner, string messageBoxText, string caption)
    {
        return MessageBox.Show(owner, messageBoxText, caption);
    }

    public MessageBoxResult Show(string messageBoxText, string caption, MessageBoxButton button)
    {
        return MessageBox.Show(messageBoxText, caption, button);
    }

    public MessageBoxResult Show(Window owner, string messageBoxText, string caption, MessageBoxButton button)
    {
        return MessageBox.Show(owner, messageBoxText, caption, button);
    }

    public MessageBoxResult Show(string messageBoxText, string caption, MessageBoxButton button, MessageBoxImage icon)
    {
        return MessageBox.Show(messageBoxText, caption, button, icon);
    }

    public MessageBoxResult Show(Window owner, string messageBoxText, string caption, MessageBoxButton button, MessageBoxImage icon)
    {
        return MessageBox.Show(owner, messageBoxText, caption, button, icon);
    }

    public MessageBoxResult Show(string messageBoxText, string caption, MessageBoxButton button, MessageBoxImage icon, MessageBoxResult defaultResult)
    {
        return MessageBox.Show(messageBoxText, caption, button, icon, defaultResult);
    }

    public MessageBoxResult Show(Window owner, string messageBoxText, string caption, MessageBoxButton button, MessageBoxImage icon, MessageBoxResult defaultResult)
    {
        return MessageBox.Show(owner, messageBoxText, caption, button, icon, defaultResult);
    }

    public MessageBoxResult Show(string messageBoxText, string caption, MessageBoxButton button, MessageBoxImage icon, MessageBoxResult defaultResult, MessageBoxOptions options)
    {
        return MessageBox.Show(messageBoxText, caption, button, icon, defaultResult, options);
    }

    public MessageBoxResult Show(Window owner, string messageBoxText, string caption, MessageBoxButton button, MessageBoxImage icon, MessageBoxResult defaultResult, MessageBoxOptions options)
    {
        return MessageBox.Show(owner, messageBoxText, caption, button, icon, defaultResult, options);
    }
}

0

对于所有想要一个非常简单的解决方案并且可以接受不是百分之百纯净 MVVM 的人:
我想要从主窗口打开连接对话框并执行了以下操作

首先,给我的 MainWindow 命名:

<Window x:Name="MainWindow">

然后我在我的MainWindowViewModel中创建了一个命令:

public ICommand AddInterfaceCommand
{
    get
    {
        return new RelayCommand<Window>((parentWindow) =>
        {
            var wizard = new ConnectionWizard();
            wizard.Owner = parentWindow;
            wizard.ShowDialog();
        }
    }
}

我将我的按钮绑定到了MainWindow上的命令,并传递了窗口本身(对话框的父窗口):

<Button Command="{Binding AddInterfaceCommand}" CommandParameter="{Binding ElementName=MainWindow}">Add interface</Button>

就这些了。

唯一的注意事项是从对话框的Viewmodel获取返回值可能会很困难。我不需要那个功能。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接