一、前言
在WPF企业级应用开发中,我们常常需要确保某个窗口在同一时间只能有一个实例存在,同时还需要支持窗口的关联关闭(关闭父窗体时,所有子窗体也会关闭)。
为了实现这些需求,我们采用一种侵入性较低的实现方式,通过创建一个通用的WindowSingletonBase
类来管理窗口的单例行为。
在这里,我们实现线程安全的单例窗口管理、完善的父子窗口生命周期控制以及灵活的参数化窗口构造,为WPF应用提供一套健壮的窗口管理解决方案。
二、实现目标
1. 单例窗口实现
- 支持无参和有参构造
- 通过
GetInstance()
方法获取或创建实例 - 窗口关闭时自动释放资源
2. 父子窗口关联
- 建立父子窗口链路关系
- 父窗口关闭时自动关闭所有子窗口
- 子窗口关闭时自动从父窗口的集合中移除
三、代码实现
1.创建 WindowSingletonBase 封装单例逻辑
通过泛型基类WindowSingletonBase<T>
封装单例逻辑,同时提供父子窗口管理功能,既保证了功能的完整性,又不会对业务窗口类造成过多约束。
cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
namespace STC.Common.View.Component
{
/// <summary>
/// 窗口单例基类,提供单例管理和父子窗口关联功能
/// </summary>
/// <typeparam name="T">继承自Window的窗口类型</typeparam>
public class WindowSingletonBase<T> where T : Window
{
// 线程安全锁
private static readonly object _lock = new object();
// 单例实例
private static T _instance;
// 窗口构造方法
private static Func<object[], T> _constructor;
// 窗口所拥有子窗口集合
private static readonly List<Window> _childWindows = new List<Window>();
/// <summary>
/// 注册窗口构造函数
/// </summary>
public static void RegisterConstructor(Func<object[], T> constructor)
{
lock (_lock)
{
_constructor = constructor;
}
}
public static T Instance
{
get { return GetInstance(); }
}
/// <summary>
/// 获取窗口单例实例
/// </summary>
public static T GetInstance(params object[] args)
{
lock (_lock)
{
if (_instance == null || !_instance.IsLoaded)
{
if (_constructor == null)
{
throw new InvalidOperationException("can not found constructor");
}
_instance = _constructor(args);
}
return _instance;
}
}
/// <summary>
/// 释放当前实例
/// </summary>
public static void ReleaseInstance()
{
lock (_lock)
{
if (_instance != null)
{
_instance = null;
}
}
}
/// <summary>
/// 添加子窗口并建立父子关系
/// </summary>
public static void AddChildWindow(Window childWindow)
{
if (childWindow == null)
throw new ArgumentNullException(nameof(childWindow));
lock (_lock)
{
if (!_childWindows.Contains(childWindow))
{
childWindow.Owner = _instance; // 设置父子关系
_childWindows.Add(childWindow);
}
}
}
/// <summary>
/// 从子窗口集合中移除指定窗口
/// </summary>
public static void RemoveChild(Window childWindow)
{
lock (_lock)
{
if (_childWindows.Contains(childWindow))
{
_childWindows.Remove(childWindow);
}
}
}
/// <summary>
/// 关闭所有子窗口
/// </summary>
public static void CloseAllChildren()
{
lock (_lock)
{
foreach (var window in _childWindows.ToList()) // ToList避免修改集合
{
if (window != null)
{
window.Close();// 触发子窗口的Closed事件
}
}
_childWindows.Clear();
}
}
/// <summary>
/// 显示或激活窗口
/// </summary>
public static void ShowOrActive()
{
if (_instance != null)
{
if (_instance.IsVisible)
{
_instance.Activate();
}
else
{
_instance.Show();
}
}
}
/// <summary>
/// 关闭窗口
/// </summary>
public static void CloseWindow()
{
if (_instance != null)
{
if (_instance.IsVisible)
{
_instance.Close();
}
}
}
}
}
2.使用案例
在步骤1中,创建了WindowSingletonBase用来管理单例窗口。现在我们来使用WindowSingletonBase。
在需要单例运行的窗体中,做三件事:
- 1.注册构造函数。
- 2.创建实例
- 3.重写OnClose方法(用于释放资源)
2.1 无参构造窗体-使用单例窗体
ParentWindow.cs
cs
public partial class ParentWindow : Window
{
static ParentWindow()
{
// 注册无参构造函数
WindowSingletonBase<ParentWindow>.RegisterConstructor(args => new ParentWindow());
}
public ParentWindow()
{
InitializeComponent();
}
/// <summary>
/// 声明单例窗口 Instance
/// </summary>
public static ParentWindow CreateInstance()
{
return WindowSingletonBase<ParentWindow>.GetInstance();
}
protected override void OnClosed(EventArgs e)
{
base.OnClosed(e);
// 释放单例实例并关闭所有子窗口
WindowSingletonBase<ParentWindow>.ReleaseInstance();
WindowSingletonBase<ParentWindow>.CloseAllChildren();
}
private void OpenChildWindow(object sender, RoutedEventArgs e)
{
// 获取子窗口实例并显示
var childWindow = ChildWindow.CreateInstance();
WindowSingletonBase<ParentWindow>.AddChildWindow(childWindow);
WindowSingletonBase<ChildWindow>.ShowOrActivate();
}
}
ChildWindow.cs
cs
public partial class ChildWindow : Window
{
static ChildWindow()
{
// 注册无参构造函数(方式1)
// WindowSingletonBase<ChildWindow>.RegisterConstructor(args => new ChildWindow());
// 注册无参构造函数(方式2)
WindowSingletonBase<ChildWindow>.RegisterConstructor(delegate (object[] args)
{
return new ChildWindow();
});
}
public ChildWindow()
{
InitializeComponent();
}
/// <summary>
/// 声明单例窗口
/// </summary>
public static ChildWindow CreateInstance()
{
return WindowSingletonBase<ChildWindow>.GetInstance();
}
/// <summary>
/// 关闭窗口
/// </summary>
protected override void OnClosed(EventArgs e)
{
base.OnClosed(e);
// 从父窗体的子窗口列表中移除
WindowSingletonBase<ParentWindow>.RemoveChild(this);
WindowSingletonBase<ChildWindow>.ReleaseInstance();
}
}
2.2 有参构造窗体-使用单例窗体
ParentWindow.cs
cs
public partial class ParentWindow : Window
{
static ParentWindow()
{
// 注册无参构造函数
WindowSingletonBase<ParentWindow>.RegisterConstructor(args => new ParentWindow());
}
public ParentWindow()
{
InitializeComponent();
}
/// <summary>
/// 声明单例窗口 Instance
/// </summary>
public static ParentWindow CreateInstance()
{
return WindowSingletonBase<ParentWindow>.GetInstance();
}
protected override void OnClosed(EventArgs e)
{
base.OnClosed(e);
// 释放单例实例并关闭所有子窗口
WindowSingletonBase<ParentWindow>.ReleaseInstance();
WindowSingletonBase<ParentWindow>.CloseAllChildren();
}
private void OpenChildWindow(object sender, RoutedEventArgs e)
{
// 获取子窗口实例并显示
var childWindow = ChildWindow.CreateInstance("张三", 18);
WindowSingletonBase<ParentWindow>.AddChildWindow(childWindow);
WindowSingletonBase<ChildWindow>.ShowOrActivate();
}
}
ChildWindow.cs
cs
public partial class ChildWindow : Window
{
static ChildWindow()
{
// 注册无参构造函数
WindowSingletonBase<ChildWindow>.RegisterConstructor(delegate (object[] args)
{
return new AddGroupWin(args[0] as string, (int)args[1]);
});
}
public ChildWindow(string name, int age)
{
InitializeComponent();
}
/// <summary>
/// 声明单例窗口
/// </summary>
public static ChildWindow CreateInstance(string name, int age)
{
return WindowSingletonBase<ChildWindow>.GetInstance(name, age);
}
/// <summary>
/// 关闭窗口
/// </summary>
protected override void OnClosed(EventArgs e)
{
base.OnClosed(e);
// 从父窗体的子窗口列表中移除
WindowSingletonBase<ParentWindow>.RemoveChild(this);
WindowSingletonBase<ChildWindow>.ReleaseInstance();
}
}
2.3 有参构造窗体-窗体刷新
如果这个弹窗是在列表中,点击编辑按钮弹窗,我们需要每次点击的时候都刷新窗口。可以在CreateInstance方法中做一些改变。
ParentWindow.cs
cs
public partial class ParentWindow : Window
{
static ParentWindow()
{
// 注册无参构造函数
WindowSingletonBase<ParentWindow>.RegisterConstructor(args => new ParentWindow());
}
public ParentWindow()
{
InitializeComponent();
}
/// <summary>
/// 声明单例窗口 Instance
/// </summary>
public static ParentWindow CreateInstance()
{
return WindowSingletonBase<ParentWindow>.GetInstance();
}
protected override void OnClosed(EventArgs e)
{
base.OnClosed(e);
// 释放单例实例并关闭所有子窗口
WindowSingletonBase<ParentWindow>.ReleaseInstance();
WindowSingletonBase<ParentWindow>.CloseAllChildren();
}
private void OpenChildWindow(object sender, RoutedEventArgs e)
{
// 获取子窗口实例并显示
var childWindow = ChildWindow.CreateInstance("张三", 18);
WindowSingletonBase<ParentWindow>.AddChildWindow(childWindow);
WindowSingletonBase<ChildWindow>.ShowOrActivate();
}
}
ChildWindow.cs
cs
public partial class ChildWindow : Window
{
// 演示需要(不推荐在ChildWindow中声明viewModel)
private ChildWindowViewModel viewModel;
private static ChildWindow _childWindow;
static ChildWindow()
{
// 注册无参构造函数
WindowSingletonBase<ChildWindow>.RegisterConstructor(delegate (object[] args)
{
return new AddGroupWin(args[0] as string, (int)args[1]);
});
}
public ChildWindow(string name, int age)
{
InitializeComponent();
}
/// <summary>
/// 声明单例窗口
/// </summary>
public static ChildWindow CreateInstance(string name, int age)
{
if (_childWindow!=null)
{
ChildWindowWinViewModel model = _childWindow.viewModel as ChildWindowWinViewModel;
// 点击的是当前展示的数据,则返回当前window
if (model != null && model.name == name && model.age == age)
{
return _childWindow;
}
else
{
// 点击的是另一条数据,则重新new viewModel,重新设置 _childWindow的DataContext
// 在这里省略ChildWindowWinViewModel代码,根据情况自行编写
_childWindow.viewModel = new ChildWindowWinViewModel(id, instId, _childWindow);
_childWindow.DataContext = _childWindow.viewModel;
return _childWindow;
}
}
// 如果 _childWindow 是空的,则声明单例窗口。
_childWindow = WindowSingletonBase<ChildWindow>.GetInstance(name, age);
return _childWindow;
}
/// <summary>
/// 关闭窗口
/// </summary>
protected override void OnClosed(EventArgs e)
{
base.OnClosed(e);
// 从父窗体的子窗口列表中移除
WindowSingletonBase<ParentWindow>.RemoveChild(this);
WindowSingletonBase<ChildWindow>.ReleaseInstance();
}
}