WPF通过附加属性控制窗口关闭
问题描述
最近在进行业务扩展时,我发现我之前封装的 DialogServie 问题越来越多,整个设计思路一点也不 SOLID。这里我简单描述一下:
DialogServie 采用单例模式。内部定义了一个列表,用于存放当前系统所有打开的窗口实例,然后上层通过调用
Show方法来创建并显示一个窗口,调用Close方法关闭创建,这两个关键函数都有一个重要参数,就是待操作窗口句柄对应的标识,只要标识传递对了,就能对相应的窗口就行操作。那么问题来了,这样岂不是任何地方只要能获取这个标识,就能操作这个窗口了,那到时候窗口一多不就乱套了。这对于整个系统的安全性来说不好,所以我有必要把它进行重构。
我希望达到的最终效果是,通过抽象工厂,谁想要创建窗口,谁就通过这个工厂来创建,因为每个窗口都会对应着一个 ViewModel,所以谁要是想关闭当前窗口,那就要当前窗口对应的 ViewMoel 具备关闭自己的功能。这样就保证了谁挖的坑到时候就自己负责填,每个坑位互不影响,谁也不要想在乱占坑位。
问题解决
基于 MVVM 的 WPF 客户端,在 ViewModel 执行窗口的弹出和关闭是很正常的需求,对于如何优雅地解决这个需求确实需要值得思考一下。这里,我罗列了两种我遇到的场景来进行分享。
场景一
假如我们要创建的窗口外观样式不一样,不同窗口都是不同的 Window 。那么这种情况要相对好解决一些,因为我们可以直接将我们要创建的窗口类型传递给窗口工厂,然后将创建的实例句柄返回给上层的消费者。
我们知道,对于通过调用 ShowDiaog() 的方式显示的窗口,我们可以通过设置其 DialogResult 为 False 就能将其窗口关闭,但是由于这个属性不是依赖属性,不能直接使用数据绑定,所以我们需要通过附加属性的方式来解决这个问题。示例代码如下所示:
public class ContentDialogExtension
{
public static bool? GetDialogResult(DependencyObject obj)
{
return (bool?)obj.GetValue(DialogResultProperty);
}
public static void SetDialogResult(DependencyObject obj, bool? value)
{
obj.SetValue(DialogResultProperty, value);
}
// Using a DependencyProperty as the backing store for DialogResult. This enables animation, styling, binding, etc...
public static readonly DependencyProperty DialogResultProperty =
DependencyProperty.RegisterAttached("DialogResult", typeof(bool?), typeof(ContentDialogExtension), new PropertyMetadata(null, (d, e) =>
{
if (d is Window self)
{
self.DialogResult = e.NewValue as bool?;
}
}));
}
然后,我们需要在对应的 ViewModel 中定义相同类型的 属性用于前台数据绑定,我这里的示例代码如下所示:
public class OtherViewModel : BindableBase
{
private bool? _dialogResult;
public bool? DialogResult
{
get { return _dialogResult; }
set { SetProperty(ref _dialogResult, value); }
}
private ICommand _closeCommand;
public ICommand CloseCommand
{
get
{
if (_closeCommand == null)
{
_closeCommand = new DelegateCommand(() =>
{
this.DialogResult = true;
});
}
return _closeCommand;
}
}
}
最后,在 XAML 中进行数据绑定即可,示例代码如下所示:
<Window
x:Class="BlankCoreApp1.Controls.DefaultDialog"
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:local="clr-namespace:BlankCoreApp1.Controls"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Title="DefaultDialog"
Width="800"
Height="450"
local:ContentDialogExtension.DialogResult="{Binding DialogResult}"
mc:Ignorable="d">
<Grid>
<Button
HorizontalAlignment="Center"
VerticalAlignment="Center"
Command="{Binding CloseCommand}"
Content="Close Me" />
</Grid>
</Window>
注:由于我这里定义的属性名称和系统的属性名称是一样的,所以有可能会报错,我们可以将其修改为 Code Behind 的方式,示例代码如下所示:
var bind = new Binding()
{
Path = new PropertyPath(nameof(viewModel.DialogResult)),
Source = viewModel
};
SetBinding(ContentDialogExtension.DialogResultProperty, bind);
对于 DialogService 的代码就相对简单,我这里给出的示例代码如下所示:
public class DialogService
{
public static T CreateDialog<T>(object viewmodel) where T : Window, new()
{
var dlg = Activator.CreateInstance<T>();
dlg.DataContext = viewmodel;
return dlg;
}
}
最后,上层调用就直接通过如下方式调用即可:
var dlg = DialogService.CreateDialog<DefaultDialog>(new OtherViewModel());
dlg.ShowDialog();
场景二
假如我们要创建的窗口外观一样,就是内容不一样,我们希望通过 DataTemplate 的方式来动态创建窗口,然后在其内部也要支持关闭当前窗口。
对于这种需求,我们首先应该想到的是需要提取一个允许在 ViewModel 中关闭对应窗口的接口,然后让其继承该接口。这里,我定义了如下的示例接口:
public interface IClosable
{
bool? DialogResult { get; }
}
那其对应的 ViewModel 就应该继承并实现该接口,示例代码如下所示:
public class OtherViewModel : BindableBase, IClosable
{
private bool? _dialogResult;
public bool? DialogResult
{
get { return _dialogResult; }
set { SetProperty(ref _dialogResult, value); }
}
private ICommand _closeCommand;
public ICommand CloseCommand
{
get
{
if (_closeCommand == null)
{
_closeCommand = new DelegateCommand(() =>
{
this.DialogResult = true;
});
}
return _closeCommand;
}
}
}
接着,我们就需要创建一个窗口模板,让其能显示传递进来的 DataTemplate,示例代码如下所示:
<Window.Resources>
<DataTemplate x:Key="dt">
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
<Button Command="{Binding CloseCommand}" Content="Close Me" />
</StackPanel>
</DataTemplate>
</Window.Resources>
接着,我们定义窗口模板,其对应的前后台示例代码如下所示:
<tx:ExWindow
x:Class="Tx.Themes.Dialogs.ContentDialog"
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:local="clr-namespace:Tx.Themes.Dialogs"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:tx="clr-namespace:Tx.Themes.Controls"
HorizontalContentAlignment="Stretch"
VerticalContentAlignment="Stretch"
Foreground="White"
ResizeMode="NoResize"
ShowInTaskbar="False"
SizeToContent="WidthAndHeight"
WindowStartupLocation="CenterOwner"
WindowStyle="SingleBorderWindow"
mc:Ignorable="d" />
public partial class ContentDialog : ExWindow
{
public ContentDialog(IClosable viewModel, DataTemplate dataTemplate)
{
var bind = new Binding()
{
Path = new PropertyPath(nameof(viewModel.DialogResult)),
Source = viewModel
};
SetBinding(ContentDialogExtension.DialogResultProperty, bind);
Init(viewModel, dataTemplate, enableResize);
}
private void Init(object viewModel, DataTemplate dataTemplate)
{
InitializeComponent();
Owner = Application.Current.Windows.OfType<Window>().FirstOrDefault(x => x.IsActive);
Content = viewModel;
ContentTemplate = dataTemplate;
}
}
然后,我们就需要定义窗口工厂,示例代码如下所示:
public static class DialogFactory
{
public static ContentDialog CreateDialogWithClosable<T>(T ViewModel, DataTemplate dataTemplate, bool enableResize = false)
where T : IClosable => new ContentDialog(ViewModel, dataTemplate, enableResize);
}
最后,上层调用就直接通过如下方式调用即可:
var dlg = DialogFactory.CreateDialogWithClosable(new NewProjectViewModel(), dt);
dlg.Title = "新建项目";
dlg.ShowDialog();
总结
当然了,我上面说的这几种实现方式你或许觉得麻烦,对软件结构设计不关心,那我这里可以给你介绍一种终极解决方案:消息机制,这种方式可以让你的代码可以肆意游走于任何地方,基本不会受到限制,具体怎么使用可以参考 MVVMLight 里面的 Message,这里就不展开说了,感兴趣的朋友可以自己在 Github 上看看官方源码。
自由是相对了,那些看似自由的天空或许就是无尽的地狱之渊。
相关参考
WPF通过附加属性控制窗口关闭的更多相关文章
- 2017年11月20日 WinForm窗体 窗口无边框可移动&&窗口阴影 控制窗口关闭/最小化
弹框 MessageBox.Show(); 清空 clear() 字符串拼接 string 公共控件 button 按钮 checkbox 复选框 checklistbox 多个复选框 combobo ...
- WPF 使用 AppBar 将窗口停靠在桌面上,让其他程序不占用此窗口的空间(附我封装的附加属性)
原文:WPF 使用 AppBar 将窗口停靠在桌面上,让其他程序不占用此窗口的空间(附我封装的附加属性) 本文介绍如何使用 Windows 的 AppBar 相关 API 实现固定停靠在桌面上的特殊窗 ...
- 【转】【WPF】WPF 登录窗口关闭时打开主窗口
在WPF中设计登录窗口关闭时打开主窗口,自动生成的App.xaml不能满足要求, 1.把App.xaml的属性窗口中的生成操作设定为 无 2.添加Program类 static class Progr ...
- WPF 解决弹出模态窗口关闭后,主窗口不在最前
本文告诉大家如何解决这个问题,在 WPF 的软件,弹出一个模态窗口.使用另一个窗口在模态窗口前面.从任务栏打开模态窗口.关闭模态窗口.这时发现,主窗口会在刚才使用的另一个窗口下面 这是 Windows ...
- WPF Adorner+附加属性 实现控件友好提示
标题太空泛,直接上图 无论是在验证啊,还是提示方面等一些右上角的角标之类的效果,我们会怎么做? 这里介绍一种稍微简单一些的方法,利用附加属性和Adorner来完成. 例如WPF自带的控件上要加这样的效 ...
- [WPF疑难] 继承自定义窗口
原文 [WPF疑难] 继承自定义窗口 [WPF疑难] 继承自定义窗口 周银辉 项目中有不少的弹出窗口,按照美工的设计其外边框(包括最大化,最小化,关闭等按钮)自然不同于Window自身的,但每个弹出框 ...
- [WPF疑难]如何禁用窗口上的关闭按钮
原文 [WPF疑难]如何禁用窗口上的关闭按钮 [WPF疑难]如何禁用窗口上的关闭按钮 周银辉 哈哈,主要是调用Rem ...
- DotNetBar怎样控制窗口样式
DotNetBar怎样控制窗口样式 老帅 在C#中使用控件DevComponents.DotNetBar时,怎样创建一个美丽的窗口.并控制窗口样式呢? 1.新建一个DotNetBar窗口 ...
- 【.net 深呼吸】WPF 中的父子窗口
与 WinForm 不同,WPF 并没有 MDI 窗口,但 WPF 的窗口之间是可以存在“父子”关系的. 我们会发现,Window 类公开了一个属性叫 Owner,这个属性是可读可写的,从名字上我们也 ...
随机推荐
- Alpha冲刺 (2/10)
Part.1 开篇 队名:彳艮彳亍团队 组长博客:戳我进入 作业博客:班级博客本次作业的链接 Part.2 成员汇报 组员1(组长)柯奇豪 过去两天完成了哪些任务 学习并配置了ssm框架(用于前后端交 ...
- OpenStack-Ocata版+CentOS7.6 云平台环境搭建 —7.网络服务Neutron配置
网络服务Neutron本章节结束如何安装并配置网络服务(neutron)采用:ref:`provider networks <network1>`或:ref:`self-service n ...
- lombok的介绍及使用
参考:https://blog.csdn.net/motui/article/details/79012846 介绍 在项目中使用Lombok可以减少很多重复代码的书写.比如说getter/sette ...
- Win10下音频设备无法播放音乐问题定位
最近一直在调试音频设备,由于音频设备需要在不同的采样率下面转换,所以会经常导致我的win10无法播放和录音. 刚开始在网上搜了相关的知识,但是一直没找到有效的解决方案.后来,无奈之下,使用了微软的声音 ...
- Python集成开发工具Pycharm的使用方法:复制,撤销上一步....
复制行,在代码行光标后,输入Ctrl + d ,即为复制一行,输入多次即为复制多行 撤销上一步操作:Ctrl + z 为多行代码加注释# 代码选中的条件下,同时按住 Ctrl+/,被选中行被注释,再 ...
- 51Node 1051---最大子矩阵和
题目链接 一个M*N的矩阵,找到此矩阵的一个子矩阵,并且这个子矩阵的元素的和是最大的,输出这个最大的值. 例如:3*3的矩阵: -1 3 -1 2 -1 3 -3 1 2 和最大的子矩阵是 ...
- linux中grep/egrep的使用
grep也是linux中查找的一个利器,运维.程序员必掌握的 下面针对grep的参数进行说明: --color 重点标记匹配到项grep "a word" datafile -- ...
- 7z 程序打包 Demo
最近准备做一个用户端 异常收集的程序 需要收集用户机器的程序日志和相关信息 准备打包发回来 所以研究了一下7Z 文件压缩 做一个笔记吧 遇到的问题: 1:VS2008 遇到 loadlibrary ...
- 【NGINX】配置文件
######Nginx配置文件nginx.conf中文详解##### #定义Nginx运行的用户和用户组 user www www; #nginx进程数,建议设置为等于CPU总核心数. worker_ ...
- 可重入锁 & 不可重入锁
可重入锁指同一个线程可以再次获得之前已经获得的锁,避免产生死锁. Java中的可重入锁:synchronized 和 java.util.concurrent.locks.ReentrantLock. ...