WPF中实现单例窗口解决方案

一、前言

在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();
    }
}
相关推荐
奈斯。zs11 分钟前
JavaWeb02——基础标签及样式(黑马视频笔记)
前端·笔记·html
白仑色12 分钟前
AJAX表单验证项目实战:实时用户名检查
前端·javascript·ajax·表单验证·py
卸载引擎25 分钟前
【Electron】electron-vite中基于electron-builder与electron-updater实现程序远程自动更新,附源码
前端·javascript·electron
Robbie丨Yang42 分钟前
CSS 工作原理
前端·css
酒渣1 小时前
css动态样式
前端·css
转转技术团队1 小时前
从“v我50”到“疯狂星期四”:HTTPS如何用47天寿命的证书挡住中间人
前端
zeqinjie1 小时前
Flutter 使用 AI Cursor 快速完成一个图表封装【提效】
前端·flutter
真上帝的左手1 小时前
24. 前端-js框架-Vue
前端·javascript·vue.js
3Katrina2 小时前
《Stitch的使用指南以及AI新开发模式杂谈》
前端
无羡仙2 小时前
按下回车后,网页是怎么“跳”出来的?
前端·node.js