简体   繁体   English

如何按照 MVVM 设计模式处理对话框

[英]How to handle dialogs following the MVVM design pattern

I'm using Material Design for WPF to show a dialog which receives some inputs from the user, and I would like to return a value when it's closed.我正在为 WPF 使用 Material Design 来显示一个对话框,该对话框从用户那里接收一些输入,我想在它关闭时返回一个值。 Here is the sample code:这是示例代码:

VM's method that opens the dialog VM打开对话框的方法

private async void OnOpenDialog()
{
    var view = new TakeInputDialogView();
    
    var result = await DialogHost.Show(view, "RootDialog", ClosingEventHandler);
}

Dialog's VM code Dialog的VM代码

public class TakeSomeInputDialogViewModel : ViewModelBase
{
    private string _name;
    public string Name
    {
        get => _name;
        set
        {
            SetProperty(ref _name, value);
            SaveCommand.RaiseCanExecuteChanged();
        }
    }

    public bool IsNameInvalid => CanSave();

    public DelegateCommand SaveCommand { get; }

    public TakeSomeInputDialogViewModel()
    {
        SaveCommand = new DelegateCommand(OnSave, CanSave);
    }

    private void OnSave()
    {
        DialogHost.Close("RootDialog");
    }

    private bool CanSave()
    {
        return !string.IsNullOrEmpty(Name);
    }
}

When the user clicks save I would like to return Name or some object that will be constructed depending on the user's input.当用户单击保存时,我想返回 Name 或将根据用户输入构建的一些 object。

Side note: I'm also using Prism library, but decided to go with Material Design dialog because I can't locate the dialog in a correct place, when I open the dialog via prism I could only open it in the center of screen or in the center of owner, but I have a single window which hosts sidebar, menu control and content control, and I need to open the dialog in the middle of content control which I wasn't able to achieve.旁注:我也在使用 Prism 库,但决定使用 Material Design 对话框 go,因为我无法在正确的位置找到对话框,当我通过棱镜打开对话框时,我只能在屏幕中央打开它,或者在所有者的中心,但我有一个 window,它承载侧边栏、菜单控件和内容控件,我需要在内容控件中间打开对话框,这是我无法实现的。

PS: I could bind the DataContext of the Dialog to the VM that opens it, but I might have many dialogs and the code might grow too big. PS:我可以将 Dialog 的DataContext绑定到打开它的 VM,但我可能有很多对话框并且代码可能会变得太大。

Never show a dialog from the View Model. This is not necessary and will eliminate the benefits MVVM gives you.永远不要显示视图 Model 中的对话框。这不是必需的,并且会消除 MVVM 给您带来的好处。

Rule of thumb经验法则

The MVVM dependency graph: MVVM 依赖关系图:

View ---> View Model ---> Model

Note that the dependencies are on application level.请注意,依赖关系在应用程序级别。 They are component dependencies and not class dependencies (although class dependencies derive from the constraints introduced by the component dependencies).它们是组件依赖关系,而不是 class 依赖关系(尽管 class 依赖关系源自组件依赖关系引入的约束)。
The above dependency graph translates to the following rules:上面的依赖图转化为以下规则:

In MVVM the View Model does not know the View: the View is nonexistent.在 MVVM 中,视图 Model 不知道视图:视图不存在。
Therefore, if the View is nonexistent for the View Model it is not aware of UI: the View Model is View agnostic.因此,如果视图 Model 的视图不存在,则它不知道 UI:视图 Model 是视图不可知的。
As a consequence, the View Model has no interest in displaying dialogs.因此,视图 Model 对显示对话框没有兴趣。 It doesn't know what a dialog is.它不知道什么是对话。
A "dialog" is an information exchange between two subjects, in this case the user and the application. “对话”是两个主体之间的信息交换,在本例中是用户和应用程序。
If the View Model is View agnostic it also has no idea of the user to have a dialog with.如果 View Model 与 View 无关,它也不知道要与之对话的用户。
The View Model doesn't show dialog controls nor does it handle their flow.视图 Model 不显示对话框控件,也不处理它们的流程。

Code-behind is a compiler feature ( partial class ).代码隐藏是编译器功能( partial class )。
MVVM is a design pattern. MVVM 是一种设计模式。
Because by definition a design pattern is language and compiler agnostic, it doesn't rely on or require language or compiler details.因为根据定义,设计模式与语言和编译器无关,所以它不依赖或不需要语言或编译器细节。
This means a compiler feature can never violate a design pattern.这意味着编译器功能永远不会违反设计模式。
Therefore code-behind can't violate MVVM.因此代码隐藏不能违反 MVVM。
Since MVVM is a design pattern only design choices can violate it.由于 MVVM 是一种设计模式,因此只有设计选择才能违反它。

Because XAML doesn't allow to implement complex logic we will always have to come back to C# (code-behind) to implement it.因为 XAML 不允许实现复杂的逻辑,所以我们总是必须回到 C#(代码隐藏)来实现它。

Also the most important UI design rule is to prevent the application from collecting wrong data.同样最重要的 UI 设计规则是防止应用程序收集错误的数据。 You do this by:你这样做:
a) don't show input options that produce an invalid input in the UI. a) 不显示在 UI 中产生无效输入的输入选项。 For example remove or disable the invalid items of a ComboBox , so taht the user can only select valid items.例如删除或禁用ComboBox的无效项目,因此用户只能 select 有效项目。 b) use data validation and the validation feedback infrastructure of WPF: implement INotifyDataErrorInfo to let the View signal the user that his input is invalid (eg composition of a password) and requires correction. b) 使用 WPF 的数据验证和验证反馈基础结构:实施INotifyDataErrorInfo让 View 向用户发出信号,表明他的输入无效(例如,密码的组成)并需要更正。 c) use file picker dialogs to force the user to provide only valid paths to the application: the user can only pick what really exists in the filesystem. c) 使用文件选择器对话框强制用户只提供有效的应用程序路径:用户只能选择文件系统中真正存在的内容。

Following the above principles遵循以上原则

  • will eliminate the need of your application to actively interact with the user (to show dialogs) for 99% of all cases.在 99% 的情况下,您的应用程序将无需主动与用户交互(显示对话框)。
    In a perfect application all dialogs should be input forms or OS controlled system dialogs.在完美的应用程序中,所有对话框都应输入 forms 或操作系统控制的系统对话框。
  • ensure data integrity (which is even more important).确保数据完整性(这一点更为重要)。

Example例子

The following complete example shows how to show dialogs without violating the MVVM design pattern.以下完整示例展示了如何在不违反 MVVM 设计模式的情况下显示对话框。
It shows two cases它显示了两种情况

  • Show a user initiated dialog ("Create User")显示用户启动的对话框(“创建用户”)
  • Show an application initiated dialog (HTTP connection lost)显示应用程序启动的对话框(HTTP 连接丢失)

Because most dialogs have an "OK" and a "Cancel" button or only a "OK" button, we can easily create a single and reusable dialog.因为大多数对话框都有一个“确定”和一个“取消”按钮或只有一个“确定”按钮,所以我们可以轻松地创建一个单一且可重用的对话框。
This dialog is named OkDialog in the example and extends Window .此对话框在示例中名为OkDialog并扩展Window

The example also shows how to implement a way to allow the application to actively communicate with the user, without violating the MVVM design rules.该示例还展示了如何在不违反 MVVM 设计规则的情况下实现一种允许应用程序主动与用户通信的方法。
The example achieves this by having the View Model expose related events that the View can handle.该示例通过让视图 Model 公开视图可以处理的相关事件来实现此目的。 For example, the View can decide to show a dialog (eg, a message box).例如,View 可以决定显示一个对话框(例如,一个消息框)。 In this example, the View will handle a ConnectionLost event raised by a View Model class. The View handles this event by showing a notification to the user.在此示例中,View 将处理由 View Model class 引发的ConnectionLost事件。View 通过向用户显示通知来处理此事件。

Because Window is a ContentControl we make use of the ContentContrl.ContentTemplate property:因为Window是一个ContentControl ,所以我们使用ContentContrl.ContentTemplate属性:
when we assign a data model to the Window.Content property and a corresponding DataTemplate to the Window.ContentTemplate property, we can create individually designed dialogs by using a single dialog type ( OkDialog ) as content host.当我们将数据 model 分配给Window.Content属性并将相应的DataTemplate分配给Window.ContentTemplate属性时,我们可以通过使用单一对话框类型 ( OkDialog ) 作为内容宿主来创建单独设计的对话框。

This solution is MVVM conform because View and View Model are still well separated in term of responsibilities.这个解决方案符合 MVVM,因为 View 和 View Model 在职责方面仍然很好地分离。
Every solution is fine that follows the pattern WPF uses to display validation errors (event, exception or Binding based) or progress bars (usually Binding based).每个解决方案都可以遵循 WPF 用于显示验证错误(事件、异常或基于绑定)或进度条(通常基于绑定)的模式。

It's important to ensure that the UI logic does not bleed into the View Model.重要的是要确保 UI 逻辑不会渗入 View Model。
The View Model should never wait for a user response.视图 Model 不应该等待用户响应。 Just like data validation doesn't make the View Model wait for valid input.就像数据验证不会让 View Model 等待有效输入一样。

The example is easy to convert to support the Dependency Injection pattern.该示例很容易转换为支持依赖注入模式。

Reusable key classes of the pattern模式的可重用关键类

DialogId.cs DialogId.cs

public enum DialogId
{
  Default = 0,
  CreateUserDialog,
  HttpConnectionLostDialog
}

IOkDialogViewModel.cs IOkDialogViewModel.cs

// Optional interface. To be implemented by a dialog view model class
interface IOkDialogViewModel : INotifyPropertyChanged
{
  // The title of the dialog
  string Title { get; }

  // Use this to validate the current view model state/data.
  // Return 'false' to disable the "Ok" button.
  // This method is invoked by the OkDialog before executing the OkCommand.
  bool CanExecuteOkCommand();

  // Called after the dialog was successfully closed
  void ExecuteOkCommand();
}

OkDialog.xaml.cs OkDialog.xaml.cs

public partial class OkDialog : Window
{
  public static RoutedCommand OkCommand { get; } = new RoutedCommand("OkCommand", typeof(MainWindow));

  public OkDialog(object contentViewModel)
  {
    InitializeComponent();

    var okCommandBinding = new CommandBinding(OkDialog.OkCommand, ExecuteOkCommand, CanExecuteOkCommand);
    _ = this.CommandBindings.Add(okCommandBinding);

    this.DataContext = contentViewModel;
    this.Content = contentViewModel;

    this.DataContextChanged += OnDataContextChanged;
  }

  // If there is no explicit Content, use the DataContext
  private void OnDataContextChanged(object sender, DependencyPropertyChangedEventArgs e) => this.Content ??= e.NewValue;

  // If the content view model doesn't implement the optional IOkDialogViewModel just enable the command source.
  private void CanExecuteOkCommand(object sender, CanExecuteRoutedEventArgs e)
    => e.CanExecute = (this.Content as IOkDialogViewModel)?.CanExecuteOkCommand() ?? true;

  private void ExecuteOkCommand(object sender, ExecutedRoutedEventArgs e)
    => this.DialogResult = true;
}

OkDialog.xaml OkDialog.xaml

Window Height="450" Width="800"
       Title="{Binding Title}">
  <Window.Template>
    <ControlTemplate TargetType="Window">
      <Grid>
        <Grid.RowDefinitions>
          <RowDefinition /> <!-- Content row (dynamic) -->          
          <RowDefinition Height="Auto" /> <!-- Dialog button row (static) -->          
        </Grid.RowDefinitions>

        <!-- Dynamic content -->
        <ContentPresenter Grid.Row="0" />

        <StackPanel Grid.Row="1"
                    Orientation="Horizontal"
                    HorizontalAlignment="Right">
          <Button Content="Ok"
                  IsDefault="True"
                  Command="{x:Static local:OkDialog.OkCommand}" />
          <Button Content="Cancel"
                  IsCancel="True" /> <!-- Setting 'IsCancel' to 'true'  will automaitcally close the dialog on click -->
        </StackPanel>
      </Grid>
    </ControlTemplate>
  </Window.Template>
</Window>

Helper classes to complete the example帮助类来完成示例

MainWindow.xaml.cs MainWindow.xaml.cs
The dialog is always displayed from a component of the View.该对话框始终从视图的组件中显示。

partial class MainWindow : Window
{
  // By creating a RoutedCommand, we conveniently enable every child control of this view to invoke the command.
  // Based on the CommandParameter, this view will decide which dialog or dialog content to load.
  public static RoutedCommand ShowDialogCommand { get; } = new RoutedCommand("ShowDialogCommand", typeof(MainWindow));

  // Map dialog IDs to a view model class type
  private Dictionary<DialogId, Type> DialogIdToViewModelMap { get; }

  public MainWindow()
  {
    InitializeComponent();

    var mainViewModel = new MainViewModel();

    // Show a notification dialog to the user when the HTTP connection is down
    mainViewModel.ConnectionLost += OnConnectionLost;

    this.DataContext = new MainViewModel();
     
    this.DialogIdToViewModelMap = new Dictionary<DialogId, Type>()
    {
      { DialogId.CreateUserDialog, typeof(CreateUserViewModel) }
      { DialogId.HttpConnectionLostDialog, typeof(MainViewModel) }
    };

    // Register the routed command
    var showDialogCommandBinding = new CommandBinding(
      MainWindow.ShowDialogCommand, 
      ExecuteShowDialogCommand, 
      CanExecuteShowDialogCommand);
    _ = CommandBindings.Add(showDialogCommandBinding);
  }

  private void CanExecuteShowDialogCommand(object sender, CanExecuteRoutedEventArgs e)
    => e.CanExecute = e.Parameter is DialogId;

  private void ExecuteShowDialogCommand(object sender, ExecutedRoutedEventArgs e)
    => ShowDialog((DialogId)e.Parameter);

  private void ShowDialog(DialogId parameter)
  {
    if (!this.DialogIdToViewModelMap.TryGetValue(parameter, out Type viewModelType)
      || !this.MainViewModel.TryGetViewModel(viewModelType, out object viewModel))
    {
      return;
    }

    var dialog = new OkDialog(viewModel);
    bool isDialogClosedSuccessfully = dialog.ShowDialog().GetValueOrDefault();
    if (isDialogClosedSuccessfully && viewModel is IOkDialogViewModel okDialogViewModel)
    {
      // Because of data bindng the collected data is already inside the view model.
      // We can now notify it that the dialog has closed and the data is ready to process.
      // Implementing IOkDialogViewModel is optional. At this point the view model could have already handled
      // the collected data via the PropertyChanged notification or property setter.
      okDialogViewModel.ExecuteOkCommand();
    }
  }

  private void OnConnectionLost(object sender, EventArgs e)
    => ShowDialog(DialogId.HttpConnectionLostDialog);
}

MainWindow.xaml主窗口.xaml

<Window>
  <Button Content="Create User"
          Command="{x:Static local:MainWindow.ShowDialogCommand}"
          CommandParameter="{x:Static local:DialogId.CreateUserDialog}"/>
</Window>

App.xaml申请.xaml
The implicit DataTemplate for the content of the OkDialog . OkDialog内容的隐式DataTemplate

<ResourceDictionary>

  <!-- The client area of the dialog content. 
       "Ok" and "Cancel" button are fixed and not part of the client area. 
       This enforces a homogeneous look and feel for all dialogs -->
  <DataTemplate DataType="{x:Type local:CreateUserViewModel}">
    <TextBox Text="{Binding UserName}" />
  </DataTemplate>

  <DataTemplate DataType="{x:Type local:MainViewModel}">
    <TextBox Text="HTTP connection lost." />
  </DataTemplate>

UserCreatedEventArgs.cs UserCreatedEventArgs.cs

public class UserCreatedEventArgs : EventArgs
{
  public UserCreatedEventArgs(User createdUser) => this.CreatedUser = createdUser;

  public User CreatedUser { get; }
}

CreateUserViewModel.cs创建用户视图模型.cs

// Because this view model wants to be explicitly notified by the dialog when it closes,
// it implements the optional IOkDialogViewModel interface
public class CreateUserViewModel : 
  IOkDialogViewModel,
  INotifyPropertyChanged, 
  INotifyDataErrorInfo
{
  // UserName binds to a TextBox in the dialog's DataTemplate. (that targets CreateUserViewModel)
  private string userName;
  public string UserName
  {
    get => this.userName;
    set
    {
      this.userName = value;
      OnPropertyChanged();
    }
  }

  public string Title => "Create User";
  private DatabaseRepository Repository { get; } = new DatabaseRepository();

  bool IOkDialogViewModel.CanExecuteOkCommand() => this.UserName?.StartsWith("@") ?? false;

  void IOkDialogViewModel.ExecuteOkCommand()
  {
    var newUser = new User() { UserName = this.UserName };

    // Assume that e.g. the MainViewModel observes the Repository
    // and gets notified when a User was created or updated
    this.Repository.SaveUser(newUser);

    OnUserCreated(newUser);
  }

  public event EventHandler<UserCreatedEventArgs> UserCreated;

  public event PropertyChangedEventHandler? PropertyChanged;

  protected virtual void OnUserCreated(User newUser)
    => this.UserCreated?.Invoke(this, new UserCreatedEventArgs(newUser));

  protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = "")
    => this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}

MainViewModel.cs MainViewModel.cs

class MainViewModel : INotifyPropertyChanged
{
  public CreateUserViewModel CreateUserViewModel { get; }
  public event EventHandler ConnectionLost;
  private Dictionary<Type, object> ViewModels { get; }
  private HttpService HttpService { get; } = new HttpService();

  public MainViewModel()
  {
    this.CreateUserViewModel = new CreateUserViewModel();

    // Handle the created User (optional)
    this.CreateUserViewModel.UserCreated += OnUserCreated;

    this.ViewModels = new Dictionary<Type, object> 
    { 
      { typeof(CreateUserViewModel), this.CreateUserViewModel }, 
      { typeof(MainViewModel), this }, 
    };
  }
 
  public bool TryGetViewModel(Type viewModelType, out object viewModel)
    => this.ViewModels.TryGetValue(viewModelType, out viewModel);

  private void OnUserCreated(object? sender, UserCreatedEventArgs e)
  {
    User newUser = e.CreatedUser;
  }

  private void SendHttpRequest(Uri url)
  {
    this.HttpService.ConnectionTimedOut += OnConnectionTimedOut;
    this.HttpService.Send(url);
    this.HttpService.ConnectionTimedOut -= OnConnectionTimedOut;
  }

  private void OnConnectionTimedOut(object sender, EventArgs e)
    => OnConnectionLost();

  private void OnConnectionLost()
    => this.ConnectionLost?.Invoke(this, EventArgs.Empt8y);
}

User.cs用户.cs

class User
{
  public string UserName { get; set; }
}

DatabaseRepository.cs数据库资料库.cs

class DatabaseRepository
{}

HttpService.cs HttpService.cs

class HttpService
{
  public event EventHandler ConnectionTimedOut;
}

You only really need one window in a wpf application, because a window is a content control.在 wpf 应用程序中,您实际上只需要一个 window,因为 window 是一个内容控件。

You can template out the entire content using an approach called viewmodel first and data templating.您可以使用一种称为视图模型优先和数据模板化的方法来模板化整个内容。

That window would look like: window 看起来像:

<Window ....
    Title="{Binding Title}" 
    Content="{Binding}"
        >
</Window>

You would then have a base window viewmodel which exposes a title property.然后,您将拥有一个公开 title 属性的基本 window 视图模型。

When you present a viewmodel to an instance of this window you'd by default just see the.ToString() of that viewmodel appear as content.当您将视图模型呈现给此 window 的实例时,默认情况下您只会看到该视图模型的.ToString() 显示为内容。 To make it give you some controls you need a datatemplate.为了让它给你一些控制,你需要一个数据模板。

You associate viewmodel type with control type using DataType.您使用 DataType 将视图模型类型与控件类型相关联。

Put all your markup in usercontrols.将所有标记放在用户控件中。 This includes your markup for mainwindow.这包括您对主窗口的标记。 At startup you can show an empty instance of TheOnlyWindowIneed then set datacontext asynchronously.在启动时,您可以显示 TheOnlyWindowIneed 的空实例,然后异步设置数据上下文。 Then go get any data or do anything expensive you need.然后 go 获取任何数据或做任何你需要的昂贵的事情。 Once a skeleton mainwindow is up and visible.一旦骨架主窗口启动并可见。 Showing a busy indicator.显示繁忙指示符。

Your datatemplates would all go in a resource dictionary which is merged in app.xaml.您的数据模板将全部 go 合并到 app.xaml 中的资源字典中。

An example一个例子

    <DataTemplate DataType="{x:Type local:MyBlueViewModel}">
        <local:MyBlueUserControl/>
    </DataTemplate>

If you now do如果你现在做

 var win = new TheOnlyWindowIneed { Content = new MyBlueViewModel() };
 win.Owner=this;
 win.ShowDialog();

Then you get a dialog shown which has the window that code is in as a parent.然后你会看到一个对话框,其中包含代码作为父级的 window。 The datacontext is your MyBlueViewModel and your entire dialog is filled with a MyBlueUserControl.数据上下文是您的 MyBlueViewModel,您的整个对话框都充满了 MyBlueUserControl。

You probably want Yes/No buttons and you probably want some standardised UI.你可能想要是/否按钮,你可能想要一些标准化的用户界面。

ConfirmationUserControl can look like: ConfirmationUserControl 看起来像:

<UserControl.....
<Grid>
    <Grid.RowDefinitions>
        <RowDefinition Height="Auto"/>
        <RowDefinition Height="*"/>
        <RowDefinition Height="32"/>
    </Grid.RowDefinitions>
    <ContentPresenter Content="{Binding ConfirmationMessage}"
                      Grid.Row="1"/>
    <Button HorizontalAlignment="Left"
            Content="Yes"
            Command="{Binding YesCommand}"
            />
    <Button HorizontalAlignment="Left"
            Content="No"
            Command="{Binding NoCommand}"
            />
</Grid>
</UserControl>

ConfirmationViewModel would expose the usual Title, ConfirmationMessage can be Another viewmodel ( and usercontrol pair ). ConfirmationViewModel 会公开通常的标题,ConfirmationMessage 可以是另一个视图模型(和用户控件对)。 This is then data templated out into UI in a similar fashion.然后以类似的方式将数据模板化到 UI 中。

YesCommand would be a public iCommand which is set from whatever shows the dialog. YesCommand 将是一个公共 iCommand,它是根据显示对话框的任何内容设置的。 This passes in whatever logic is to happen when the user clicks Yes.当用户单击“是”时,这将传递任何逻辑。 If it's deleting then it has code calls a delete method.如果它正在删除,那么它有代码调用删除方法。 It could use a lambda and capture the context of the owning viewmodel or it could have an explicit instance of a class passed in. The latter being more unit test friendly.它可以使用 lambda 并捕获所属视图模型的上下文,或者它可以传入一个 class 的显式实例。后者对单元测试更友好。

The call to this dialog is at the end of some code you have in your viewmodel.对此对话框的调用是在您的视图模型中的一些代码的末尾。

There is no code waiting for the result.没有等待结果的代码。

The code that would be is in yescommand.将是的代码在 yes 命令中。

NoCommand might just close the parent window. By putting code that actions the result in yes command you probably do not need to "know" in the calling code what the user chose. NoCommand 可能只是关闭父 window。通过将执行结果的代码放在 yes 命令中,您可能不需要在调用代码中“知道”用户选择了什么。 It's already been handled.已经处理了。

You might therefore decide NoCommand uses some generic window closing approach:因此,您可能决定 NoCommand 使用一些通用的 window 关闭方法:

public class GenericCommands
{
    public static readonly ICommand CloseCommand =
             new RelayCommand<Window>(o =>
             {   
                 if(o == null)
                 {
                     return;
                 }
                 if(o is Window)
                 {
                     ((Window)o).Close();
                 }
             }
             );

That needs a reference to the window which is passed in by parameter这需要引用参数传入的 window

    Command="{x:Static ui:GenericCommands.CloseCommand}"
    CommandParameter="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}"

The viewmodel still needs to somehow initiate showing the window. viewmodel 仍然需要以某种方式开始显示 window。

You only have the one window though.不过你只有一个 window。 You can put code in that and initiate showing a window from the one and only window.您可以将代码放入其中,然后从唯一的 window 开始显示 window。

All you need at a minimum is a dependency property and a callback.您至少需要一个依赖属性和一个回调。

public partial class TheOnlyWindowIneed : Window
{
    public object? ShowAdialogue
    {
        get
        {
            return (object?)GetValue(ShowAdialogueProperty);
        }
        set
        {
            SetValue(ShowAdialogueProperty, value);
        }
    }
    public static readonly DependencyProperty ShowAdialogueProperty =
        DependencyProperty.Register("ShowAdialogue",
                    typeof(object),
                    typeof(TheOnlyWindowIneed),
                    new FrameworkPropertyMetadata(null
                        , new PropertyChangedCallback(ShowAdialogueChanged)
                        )
                    );
    private static void ShowAdialogueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var win = new TheOnlyWindowIneed { Content = e.NewValue };
        win.Owner = d as TheOnlyWindowIneed;
        win.ShowDialog();
    }

Here when you bind ShowAdialogue to a property in your viewmodel, when you set that property to an instance of a dialogue viewmodel, it should show a dialog with that viewmodel as datacontext and as explained that will be templated out into UI.在这里,当您将 ShowAdialogue 绑定到视图模型中的属性时,当您将该属性设置为对话视图模型的实例时,它应该显示一个对话框,其中该视图模型作为数据上下文,并且如所解释的那样将被模板化到 UI 中。

A perhaps more elegant way to handle this would be to use the pub/sub pattern and the community mvvm toolkit messenger.处理这个问题的一种可能更优雅的方法是使用发布/订阅模式和社区 mvvm 工具包信使。 You can send the viewmodel you want to show in a dialogue via messenger, subscribe in mainwindow and act in that handler.您可以通过信使发送要在对话中显示的视图模型,在主窗口中订阅并在该处理程序中执行操作。

If you just want to show some info then there is another option to consider.如果您只想显示一些信息,那么可以考虑另一种选择。 If all your users are using win10+ then you can easily use toast.如果你所有的用户都在使用 win10+ 那么你可以轻松使用 toast。 The toast that pops up can have buttons in it but is usually just used to show messages.弹出的 toast 可以有按钮,但通常只用于显示消息。

Add the nuget package Notifications.wpf添加 nuget package Notifications.wpf

You can show toast in your mainwindow or where notifications usually pop up in win10.您可以在您的主窗口或通常在 win10 中弹出通知的地方显示 toast。 In mainwindow:在主窗口中:

    <toast:NotificationArea x:Name="MainWindowNotificationsArea"
                            Position="BottomRight" 
                            MaxItems="3"
                            Grid.Column="1"/>

In a viewmodel:在视图模型中:

        var notificationManager = new NotificationManager();
        notificationManager.Show(
            new NotificationContent { 
                Title = "Transaction Saved", 
                Message = $"Transaction id {TransactionId}" },
            areaName: "MainWindowNotificationsArea");

The view and viewmodel are totally decoupled, you don't need any reference to anything.视图和视图模型完全分离,您不需要任何引用。 NotificationContent can be much richer than shown here. NotificationContent 可以比这里显示的更丰富。

I think your app might also have to target win10 as minimum O/S.我认为您的应用程序可能还必须将 win10 作为最低操作系统。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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