欢迎您访问 最编程 本站为您分享编程语言代码,编程技术文章!
您现在的位置是: 首页

XAML 中的 Material Design 如何理解 DialogHost

最编程 2024-04-26 22:25:40
...

首先,本文假设您已经使用 MDIX 设置了一个项目。此外,它推测您了解模型-视图-视图模型 (MVVM) 模式。但是,DialogHost也可以在没有 MVVM 的情况下使用。

小试牛刀DialogHost

Dialog是Material Design In XAML中非常重要且强大的一个控件,首先我们从一个简单的例子开始。

<Window x:Class="MaterilDesign.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:MaterilDesign" 
        xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">

    <Window.DataContext>
        <local:MainWindowViewModel />
    </Window.DataContext>

    <materialDesign:DialogHost CloseOnClickAway="True">
        <materialDesign:DialogHost.DialogContent>
            <Grid Margin="16">
                <TextBlock Text="我的第一个DialogHost" FontSize="20"/>
            </Grid>
        </materialDesign:DialogHost.DialogContent>

        <Button Command="{x:Static materialDesign:DialogHost.OpenDialogCommand}" Width="160" Content="打开" Margin="0,64,0,0"/>
    </materialDesign:DialogHost>
</Window>

然后在App.Xaml中加入Material Design的默认样式,并添加主题颜色,这里不明白为什么这么写的话,可以去看一下官方文档,或者等待后续文章的更新。

<Application x:Class="MaterilDesign.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:local="clr-namespace:MaterilDesign" 
             xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
             StartupUri="MainWindow.xaml">
    <Application.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesignTheme.Defaults.xaml" />
                <ResourceDictionary Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesignTheme.Defaults.xaml" />
                <materialDesign:BundledTheme BaseTheme="Inherit"  PrimaryColor="Blue"  SecondaryColor="Lime"
                                             ColorAdjustment="{materialDesign:ColorAdjustment}" />
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
    </Application.Resources>
</Application>

上边的代码运行起来就是下边这样一个非常简单的对话框了。

DialogHost 由三个单独的 UI 组件组成:宿主控件、覆盖层和对话框。宿主控件包含应将对话框放在其上的内容。通常,它放置在 XAML 的根目录附近,以便它涵盖所有内容。叠加层是覆盖主机控件内所有内容的变暗区域。最后,对话框本身包含要显示的内容。默认情况下,对话框显示在弹出窗口内部。由于弹出窗口是一个单独的窗口,因此对话框可以大于其父窗口。如果要将对话框限制到其父窗口,则可以将静态资源样式 MaterialDesignEmbeddedDialogHost 应用于 DialogHost。

 显示和关闭对话框

DialogHost 适用于同时使用 MVVM 和Winform风格的应用程序。因此,有几种方法可以显示对话框。你可以选择在应用中使用这些选项中的任意一种。

(1)使用路由命令

DialogHost提供了两个用于显示和隐藏对话框的路由命令;OpenDialogCommand 和 CloseDialogCommand。这是最简单的纯 XAML 选项,因为它只是在元素树上向上移动,直到遇到 DialogHost 控件。将其设置为 Command 属性,它将显示对话框。下边是打开和关闭的命令。

<Button Command="{x:Static materialDesign:DialogHost.OpenDialogCommand}"/>
<Button Command="{x:Static materialDesign:DialogHost.CloseDialogCommand}"/>

(2)绑定DialogHost的IsOpen 属性

为了对对话框的状态进行简单的编程控制,DialogHost 提供了一个 IsOpen 属性。切换此属性的状态可以控制对话框显示或隐藏。属性还可以与数据绑定配对,使其易于在 MVVM 体系结构中使用。下边代码涉及到使用Mvvm框架Prism,使用前需要首先安装Prism.Wpf

<materialDesign:DialogHost IsOpen="{Binding IsDialogOpen}">
    <materialDesign:DialogHost.DialogContent>
        <Grid Margin="16">
            <TextBlock Text="我的第一个DialogHost" FontSize="20"/>
        </Grid>
    </materialDesign:DialogHost.DialogContent>
    <Button Command="{Binding ShowDialogCommand}" Width="160" Content="打开" Margin="0,64,0,0"/>
</materialDesign:DialogHost>
using Prism.Commands;
using Prism.Mvvm;

namespace MaterilDesign
{
    public class MainWindowViewModel : BindableBase
    {
        private bool _IsDialogOpen;
        public bool IsDialogOpen
        {
            get => _IsDialogOpen;
            set => SetProperty(ref _IsDialogOpen, value);
        }

        public DelegateCommand ShowDialogCommand => new(OnShowDialog);

        public MainWindowViewModel(){}

        private void OnShowDialog()
        {
            IsDialogOpen = true;
        }
    }
}

或者直接在按钮的点击事件中编写DialogHost.IsOpen = true;的代码,需要在xaml中定义控件的Name为DialogHost。也可以同样达到打开对话框的效果。

(3)关闭对话框

若要使对话框在用户单击叠加层时自动关闭,请将 CloseOnClickAway 属性设置为“True”。

<materialDesign:DialogHost CloseOnClickAway="True">

   ...

</materialDesign:DialogHost>

以上打开关闭对话框的方法都需要在Xaml中首先创建DialogHost对象,下边说明如何通过代码动态创建打开和关闭DialogHost

(4)动态创建对话框。

为了更好地控制对话框,DialogHost 类上有几个静态的 Show 方法。所有这些方法都返回 Tasks,并应用作异步方法。对话框的内容在 XAML 中指定,然后在前面的示例中显示或隐藏。使用 Show 方法时,必须传递对话框的内容。这允许创建动态对话框。

var dialogContent = new TextBlock
{
   Text = "动态对话框!",
   Margin = new Thickness(20)
};
await MaterialDesignThemes.Wpf.DialogHost.Show(dialogContent);

通过上边这种方法也可以打开一个对话框,但是如果需要在代码中创建对话框UI。更好的办法是为对话框的UI创建一个数据模板,并将数据对象作为对话框的内容传递,在数据模板中绑定数据。

<Window x:Class="MaterilDesign.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:MaterilDesign" 
        xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">

    <Window.DataContext>
        <local:MainWindowViewModel />
    </Window.DataContext>

    <materialDesign:DialogHost CloseOnClickAway="True">
        <Button Command="{Binding ShowDialogCommand}" Width="160" Content="打开" Margin="0,64,0,0"/>
        <materialDesign:DialogHost.DialogContentTemplate>
            <DataTemplate DataType="local:Person">
                <StackPanel Margin="20">
                    <TextBlock Text="{Binding Name}" />
                    <TextBlock Text="{Binding Age}" />
                </StackPanel>
            </DataTemplate>
        </materialDesign:DialogHost.DialogContentTemplate>
    </materialDesign:DialogHost>
    
</Window>
using MaterialDesignThemes.Wpf;
using Prism.Commands;
using Prism.Mvvm;

namespace MaterilDesign
{
    public class MainWindowViewModel : BindableBase
    {
        public DelegateCommand ShowDialogCommand => new(OnShowDialog);

        public MainWindowViewModel(){}

        private async void OnShowDialog()
        {
            var person = new Person
            {
                Name = "测试",
                Age = 12,
            };
            await DialogHost.Show(person);
        }
    }

    public class Person
    {
        public string? Name { get; set; }
        public int? Age { get; set; }
    }
}

Show 方法还包括在打开或关闭对话框时调用的回调委托的重载。您还可以直接在 DialogHost 上注册 DialogOpen 和 DialogClosed 事件。这些回调的事件参数包含一个 DialogSession 对象。此会话对象可以更新已显示的对话的内容或关闭可见对话框。

using MaterialDesignThemes.Wpf;
using Prism.Commands;
using Prism.Mvvm;

namespace MaterilDesign
{
    public class MainWindowViewModel : BindableBase
    {
        private DialogSession? _dialogOpeneSession;

        public DelegateCommand ShowDialogCommand => new(OnShowDialog);

        public MainWindowViewModel(){}

        private async void OnShowDialog()
        {
            var person = new Person
            {
                Name = "测试",
                Age = 12,
            };
            await DialogHost.Show(person, new DialogOpenedEventHandler((object sender, DialogOpenedEventArgs args) =>
             {
                 // 将Session赋值给私有字段,方便其他方法对内容进行更行操作
                 _dialogOpeneSession = args.Session;
                 var newPerson = new Person
                 {
                     Name = "张三",
                     Age = 12,
                 };

                 // 更新Session内容
                 _dialogOpeneSession.UpdateContent(newPerson);

                 //关闭对话框,将_dialogOpeneSession置为null,避免错误应用
                 _dialogOpeneSession.Close();
                 _dialogOpeneSession = null;
             }),new DialogClosingEventHandler((object sender,DialogClosingEventArgs args) =>
             {
                 // 获取Session中的内容,将结果推送到有需要的地方,获取返回值的一种方法
                 // 关闭事件
             }));
        }
    }

    public class Person
    {
        public string? Name { get; set; }
        public int? Age { get; set; }
    }
}

最后,还有用于传递对话标识符的其他重载。如果只有一个 DialogHost 实例,则 Show 方法将自动使用它。但是,在具有多个 DialogHost 实例的情况下,必须指定一个对话框标识符。此唯一 ID 标识要使用的对话框主机控件。

<materialDesign:DialogHost Identifier="MyDialogHost">
...
</materialDesign:DialogHost>
await MaterialDesignThemes.Wpf.DialogHost.Show(person, "MyDialogHost");

(5)返回值

使用静态 Show 方法显示的对话框也可以返回结果值。这些结果值可以是所需的对象。

object result = await MaterialDesignThemes.Wpf.DialogHost.Show(...);
//处理返回的结果值

根据对话框的关闭方式,有几种方法可以指定返回值。

  • 如果使用对话框关闭命令,请在使用该命令的同一元素上设置 CommandParameter。
  • 如果使用 CloseOnClickAway,请在 DialogHost 上设置 CloseOnClickAwayParameter。
  • 如果使用对话框会话,请将参数传递给 Close 方法。
  • 属性不支持传递结果。必须使用其他方法之一。

尽管 XAML 对话主机是 MDIX 库中最强大的控件之一,但它也是最容易被误解的控件之一。但是,只需稍加努力,它就可以使在应用程序中使用“模式”对话框变得轻而易举。它可以改善用户界面的美感并简化用户体验。

 

下一篇将说明在实际生产中DialogHost遇到的两个问题及解决方案。

 

声明,本文在Material Design in XAML – How to Make Sense of the DialogHost基础之上进行了扩展撰写。

涉及使用软件框架版本如下:

Mvvm框架 Prism8+

UI框架MaterialDesignThemes4.4.0

Net版本 5.0