WPF MVVM 模式下的弹窗

proginn468312

共 3559字,需浏览 8分钟

 ·

2020-07-25 00:25

c96e2b70765dc144f3ffc8af3bd76be4.webp

WPF MVVM 模式下的弹窗

独立观察员 2020 年 7 月 15 日

一、总体展示

首先看看用户控件在设计页面的大致效果:

a6d7c9ca237480c45b3af1eb56c75a06.webp

 

中间部分自然就是确认弹框了,由标题、内容、确认按钮、取消按钮、倒计时、关闭按钮组成,指定了大小范围:

556b7fc5433aeb2df3b3b28fb8381a41.webp

 

外层还有个 Grid,没有指定大小,所以使用时会铺满容器,再配上带透明度的背景色,可以当作蒙版,避免用户继续操作后面的界面,达到模态弹窗的效果:

83bd613bd1c07a9dedd41effa5fd0e5f.webp

 

确认弹框,手动关闭、点击取消按钮、超时关闭这三种情况下会输出相关信息(需传入记录信息的委托方法),点击确认按钮则可以继续执行业务方法。

ca3c680b95fa9aedcb469abb48f59a2a.webp

 

还有一种是信息弹框,区别是不用于执行业务方法,也不输出信息 (操作结果),只是用于提示用户,且默认标题和默认超时时间不同(可修改):

a1c1d6c3a2740c34a780764b5ad10b58.webp

 

二、用户控件前端

新建 WPF 用户控件后,贴入以下代码:

<Grid Background="#905F9EA0">    <Grid Background="Gainsboro" MinWidth="300" MinHeight="200" MaxWidth="400" MaxHeight="300">        <Grid.RowDefinitions>            <RowDefinition Height="28">RowDefinition>            <RowDefinition Height="28">RowDefinition>            <RowDefinition Height="*">RowDefinition>            <RowDefinition Height="Auto">RowDefinition>        Grid.RowDefinitions>
<DockPanel Height="28" Background="SteelBlue"> <TextBox Text="{Binding DialogTitle, TargetNullValue=' 注意 ', FallbackValue=' 注意 '}" Height="26" Width="Auto" Background="SteelBlue" BorderThickness="0" HorizontalAlignment="Left" VerticalAlignment="Center" Foreground="White" FontSize="16" Focusable="False" IsHitTestVisible="False" IsTabStop="False" VerticalContentAlignment="Center" Padding="2,0,0,0"> TextBox> <Button x:Name="BtnClose" Command="{Binding CloseCommand}" Height="26" Width="26" HorizontalAlignment="Right" VerticalAlignment="Center" FontSize="16" Background="Transparent" Foreground="White" BorderThickness="0" >XButton> DockPanel>
<StackPanel Grid.Row="1" Orientation="Horizontal" FlowDirection="RightToLeft"> <TextBox VerticalContentAlignment="Center" Text="{Binding LeftTime, FallbackValue=20, TargetNullValue=20}" FontSize="16" Background="Transparent" Foreground="Coral" BorderThickness="0" Margin="5,0">TextBox> StackPanel>
<TextBlock Grid.Row="2" FontSize="16" Text="{Binding DialogMessage, FallbackValue=' 是否确认操作?', TargetNullValue=' 是否确认操作?'}" VerticalAlignment="Center" HorizontalAlignment="Center">TextBlock>
<StackPanel Grid.Row="3" Orientation="Horizontal" FlowDirection="RightToLeft" VerticalAlignment="Center" Margin="0, 10"> <Button x:Name="BtnConfirm" Command="{Binding ConfirmCommand}" Content="{Binding DialogConfirmBtnText, TargetNullValue=' 确认 ', FallbackValue=' 确认 '}" FontSize="16" Background="SteelBlue" Foreground="White" Margin="10, 0" Width="120">Button> <Button x:Name="BtnCancel" Command="{Binding CancelCommand}" Content="{Binding DialogCancelBtnText, TargetNullValue=' 取消 ', FallbackValue=' 取消 '}" FontSize="16" Background="SteelBlue" Foreground="White" Margin="10, 0" Width="120">Button> StackPanel> Grid>Grid>

 

就是简单做了下布局和样式,然后做了数据绑定和命令绑定,我们移步到后台来看。

 

三、用户控件后台

由于使用了 MVVM 模式,所以页面的后台代码中没多少内容:

/// <summary>/// [dlgcy] WPF MVVM 确认弹框;/// public partial class UC_ConfirmBox : UserControl{    public UC_ConfirmBox ()    {        InitializeComponent ();    }
/// /// 绑定 VM 中的 IsShowDialog /// public bool IsShowDialog { get { return (bool) GetValue (IsShowDialogProperty); } set { SetValue (IsShowDialogProperty, value); } }
public static readonly DependencyProperty IsShowDialogProperty = DependencyProperty.Register ("IsShowDialog", typeof (bool), typeof (UC_ConfirmBox), new PropertyMetadata (false, (obj, args) => { if (args.NewValue is bool newValue) { try { var control = obj as UC_ConfirmBox; control.Visibility = newValue ? Visibility.Visible : Visibility.Collapsed; } catch (Exception ex) { Console.WriteLine (ex.ToString ()); MessageBox.Show ($"{ex.Message}"); } } }));}

 

建了个依赖属性,用于使用用户控件时绑定。这个是绑定 ViewModel 中的同名属性 IsShowDialog 的(是否显示弹窗),实际上,不用这个依赖属性而直接用 Visibility 绑定 IsShowDialog(ViewModel 中的),然后加上相关转换器也可以,但那样对用户不太友好,所以这里直接在依赖属性中进行 Visibility 的判断。(关于依赖属性的使用可以看本人之前的文章《WPF 用户控件的自定义依赖属性在 MVVM 模式下的使用备忘》)。

 

然后注意一点,这里并没有直接将 DataContext 关联 ViewModel,而是要在使用用户控件时再绑定(大家觉得我做得对吗),这个后面还会说到。

 

四、用户控件对应的 ViewModel

这里代码比较多,就不贴出来了,最后会给出代码托管地址。ViewModel 整体结构如下:

8fd3d4cd582003c5e35c609a10b790db.webp

 

ConfirmBoxViewModel 上有个特性 AddINotifyPropertyChangedInterface,这个是一个第三方的包 PropertyChanged.Fody 提供的,加上之后,类的公共自动属性就具有了属性变动通知功能。那么为什么还要继承 BindableBase (实现了 InotifyPropertyChanged 的基类,参考《WPF 原生绑定和命令功能使用指南》)呢?原因是,如果在属性的 get/set 中做了一些操作的话,Fody 对该属性好像就不起作用了,所以补救一下。

 

(1) 弹框时阻塞业务流程

先来看看 “成员” 部分:

8eeb8e14fe5c5002129be7f09dee74b3.webp

 

有个线程同步对象 AutoResetEvent,缺省设置为阻塞线程,由上图可见,在弹框隐藏时会取消阻塞,那么阻塞的时机自然就是弹框显示后:

057dfc026bfe7d95bf42bae65ddafa8c.webp

 

上图显示的确认框帮助类的 “弹出确认框” 方法中,由于是使用异步调用,所以阻塞不会影响 UI 线程。阻塞方法可以指定超时时间,超时或者用户没有点击确认按钮则直接返回,否则,则执行传入的委托方法,即实际的业务方法。

 

另一个 “弹出消息框” 方法则相较简单,只是简单阻塞了一段 “消息框超时时间”:

afca909e738a42258544714aa15b39fa.webp

 

(2) 倒计时

上一小节开头处给出的” 成员图” 中,还有一个定时器类型对象 _timer,就是用于倒计时功能的。计时器在弹窗弹出时开始启动,代码位于 IsShowDialog 属性的 Set 方法中(见” 成员图”)。

计时器的执行方法在构造函数中绑定,执行方法内部,每隔一秒(声明时设定)将剩余时间减 1,减为 0 时停止,并执行关闭命令。此处和弹窗阻塞超时那里可能有功能冗余,当然,从另一方面来说,也可以看作是双重保险。

2a042632afaaead1c9f3f79a7283458c.webp

 

(3) 其它

Bindable” 区域中剩余的属性都没有做特殊处理。

命令的使用可以参考前文提到的文章,命令的处理逻辑则比较简单,就是设置是否显示和是否确认:

8e1d383d4d3b643fad9e916ed8a1f6a9.webp

 

五、使用

使用时,引入用户控件命名空间之后,将其与主界面平级摆放,实际就是覆盖在主界面上方,然后设置其 Visibility 属性为 “Collapsed”,不可见也不占用空间,避免影响主界面的开发:

ec3cb78286357f03f7aecc7c1f2f4497.webp

 

IsShowDialog=”{Binding IsShowDialog}” 也是固定这样设置即可,用于配合 DataContext 控制显示隐藏,而 DataContext 则是绑定了主页面 ViewModel 中相关的用户控件的 ViewModel 对象。

 

调用的时候要注意异步的问题,使用辅助类 ConfirmBoxHelper 中的两个方法即可:

16c6147d1deb95ced43ddc6da207f7b9.webp

 

给出文字版:

// 前台;<uc:UC_ConfirmBox DataContext="{Binding DialogVm}" Visibility="Collapsed" IsShowDialog="{Binding IsShowDialog}">
// ViewModel;public ConfirmBoxViewModel DialogVm { get; set; } = new ConfirmBoxViewModel ();
// 使用;await ConfirmBoxHelper.ShowMessage (DialogVm, "操作前通知", 6);
await ConfirmBoxHelper.ShowConfirm (DialogVm, "您确定要进行此操作吗?", () =>{ #region 业务方法
//。。。#endregion
}, ShowInfo);
await ConfirmBoxHelper.ShowMessage (DialogVm, "操作后通知");

 

六、地址

这个是在 XMPP 通信 Demo 项目中写的,项目地址为:

https://gitee.com/dlgcy/XmppPractice

Dotnet9网站常驻编辑


长按关注我,

欢迎技术交流!

-好东西要转发,设为"星标"★抢先看-

27ecd5578b8b98d6286681b587df68b5.webp点击阅读原文,查看Dotnet9站点更多关于WPF的博文。

浏览 41
点赞
评论
收藏
分享

手机扫一扫分享

举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

举报