管理 Windows 实例的高效方法 —— 使用 WindowExtensions
类
在现代的 Windows 应用程序开发中,尤其是使用 WPF(Windows Presentation Foundation)时,管理多个窗口实例是一个常见的需求。为了确保应用程序的用户体验流畅且一致,开发者常常需要控制窗口的创建、显示以及位置管理。本文将深入解析一个基于 C# 的 WindowExtensions
扩展类,实现单例化窗口、激活已有窗口以及保存窗口位置的高效方法。
目录
背景与需求
在开发桌面应用程序时,常常需要管理多个不同类型的窗口。例如,应用程序可能包含主窗口、设置窗口、帮助窗口等。为了优化用户体验,通常希望:
- 单例化窗口:确保每种类型的窗口在应用程序中只有一个实例,避免资源浪费和潜在的用户混淆。
- 激活已有窗口:如果窗口已经打开,直接激活并置顶,而不是创建新的实例。
- 保存窗口位置:当用户关闭窗口后,保存其位置,下次打开时恢复到上次的位置。
手动管理这些需求可能会导致重复代码和复杂的逻辑控制。因此,利用扩展方法和并发集合,可以简化这一过程。
代码概述
以下是 WindowExtensions
类的完整代码实现:
点击查看代码
/// <summary>
/// Windows 扩展类
/// </summary>
public static class WindowExtensions
{
// 使用 ConcurrentDictionary 支持多种窗口类型
private static readonly ConcurrentDictionary<Type, Window> _activeWindows = new ConcurrentDictionary<Type, Window>();
// 保存关闭窗口位置
private static readonly ConcurrentDictionary<Type, Point> _savedLocations = new ConcurrentDictionary<Type, Point>();
/// <summary>
/// 泛型方法,处理特定类型的窗口
/// 单例化窗口,存在就激活
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="window"></param>
public static void ShowActive<T>(this T window) where T : Window, new()
{
Application.Current.Dispatcher.Invoke(() =>
{
var windowType = typeof(T);
var existingWindow = _activeWindows.GetOrAdd(windowType, _ => window);
if (existingWindow.IsVisible)
{
if (existingWindow.WindowState == WindowState.Minimized)
{
existingWindow.WindowState = WindowState.Normal;
}
existingWindow.Activate();
}
else
{
// 恢复窗口位置
var previousLocation = _savedLocations.GetOrAdd(windowType, new Point(0, 0));
if (previousLocation != default(Point))
{
window.Left = previousLocation.X;
window.Top = previousLocation.Y;
}
_activeWindows[windowType] = window;
window.Closed += (s, e) =>
{
// 保存此类型窗口关闭的位置
_savedLocations[windowType] = new Point(window.Left, window.Top);
_activeWindows.TryRemove(windowType, out _);
};
window.Show();
}
});
}
}
详细解析
让我们逐步拆解这段代码,了解其内部机制和实现逻辑。
使用 ConcurrentDictionary
管理窗口实例
// 使用 ConcurrentDictionary 支持多种窗口类型
private static readonly ConcurrentDictionary<Type, Window> _activeWindows = new ConcurrentDictionary<Type, Window>();
// 保存关闭窗口位置
private static readonly ConcurrentDictionary<Type, Point> _savedLocations = new ConcurrentDictionary<Type, Point>();
_activeWindows
:用于存储当前活动的窗口实例。ConcurrentDictionary 确保在多线程环境下的线程安全。
_savedLocations
:用于保存每种窗口类型关闭时的屏幕位置(左上角的坐标),以便下次打开时恢复。
泛型方法 ShowActive<T>
的实现
public static void ShowActive<T>(this T window) where T : Window, new()
{
Application.Current.Dispatcher.Invoke(() =>
{
var windowType = typeof(T);
var existingWindow = _activeWindows.GetOrAdd(windowType, _ => window);
if (existingWindow.IsVisible)
{
if (existingWindow.WindowState == WindowState.Minimized)
{
existingWindow.WindowState = WindowState.Normal;
}
existingWindow.Activate();
}
else
{
// 恢复窗口位置
var previousLocation = _savedLocations.GetOrAdd(windowType, new Point(0, 0));
if (previousLocation != default(Point))
{
window.Left = previousLocation.X;
window.Top = previousLocation.Y;
}
_activeWindows[windowType] = window;
window.Closed += (s, e) =>
{
// 保存此类型窗口关闭的位置
_savedLocations[windowType] = new Point(window.Left, window.Top);
_activeWindows.TryRemove(windowType, out _);
};
window.Show();
}
});
}
关键点解析:
-
泛型约束:方法
ShowActive<T>
使用泛型,约束 T 必须继承自 Window 并且有无参数的构造函数(new())。 -
调度器调用:Application.Current.Dispatcher.Invoke 确保所有 UI 操作在主线程上执行,避免跨线程操作异常。
-
获取或添加窗口实例:
- GetOrAdd 方法尝试从 _activeWindows 中获取现有窗口实例。
- 如果不存在,则添加传入的 window 实例。
- 窗口可见性判断:
- 如果窗口已可见,则激活并置顶。
- 如果窗口未可见,则恢复上次保存的位置。
- 窗口关闭事件:
- 订阅窗口的 Closed 事件,在窗口关闭时保存位置并移除窗口实例。
- 显示窗口:
- 显示窗口。
窗口位置的保存与恢复
窗口位置的保存与恢复是 ShowActive<T>
方法的核心功能。
// 恢复窗口位置
var previousLocation = _savedLocations.GetOrAdd(windowType, new Point(0, 0));
if (previousLocation != default(Point))
{
window.Left = previousLocation.X;
window.Top = previousLocation.Y;
}
...
window.Closed += (s, e) =>
{
// 保存此类型窗口关闭的位置
_savedLocations[windowType] = new Point(window.Left, window.Top);
_activeWindows.TryRemove(windowType, out _);
};
_savedLocations.GetOrAdd(windowType, new Point(0, 0))
:尝试从 _savedLocations
中获取窗口类型对应的位置,如果不存在,则返回默认位置(左上角坐标为(0,0))。
window.Left = previousLocation.X;
:设置窗口的左上角坐标 X。
window.Top = previousLocation.Y;
:设置窗口的左上角坐标 Y。
// 保存此类型窗口关闭的位置
_savedLocations[windowType] = new Point(window.Left, window.Top);
_savedLocations[windowType] = new Point(window.Left, window.Top);
:保存窗口关闭时的位置。
// 保存此类型窗口关闭的位置
_savedLocations[windowType] = new Point(window.Left, window.Top);
_activeWindows.TryRemove(windowType, out _);
:移除窗口实例。
如何使用 WindowExtensions
在应用程序的各个窗口中,只需调用 ShowActive<T>
方法即可实现窗口的单例化、激活以及位置保存。
例如,在主窗口中:
// 假设有一个 SettingsWindow 类继承自 Window
var settingsWindow = new SettingsWindow();
settingsWindow.ShowActive();
在设置窗口中:
// 主窗口中的按钮点击事件
private void OpenSettings_Click(object sender, RoutedEventArgs e)
{
var settingsWindow = new SettingsWindow();
settingsWindow.ShowActive();
}
优势与最佳实践
- 简化代码:利用扩展方法和并发集合,可以简化窗口管理的复杂逻辑。
- 线程安全:使用 ConcurrentDictionary 保证线程安全。
- 可扩展性:可以扩展支持更多窗口类型。
- 代码可读性:代码结构清晰,命名符合规范。
总结
本文介绍了 WindowExtensions
类,它可以简化窗口管理的复杂逻辑,并提供单例化窗口、激活已有窗口以及保存窗口位置的高效方法。