1. WPF/C#中使用Stylet的IWindowManager实现窗体交互
在WPF应用程序开发中,窗体管理和交互是一个核心功能。Stylet作为一款轻量级的MVVM框架,提供了强大的IWindowManager接口来简化窗体操作。今天我们就来深入探讨如何利用这个接口实现等待窗体、对话框和消息框等常见交互场景。
1.1 IWindowManager基础功能解析
IWindowManager是Stylet框架中的核心服务之一,它封装了WPF窗体的创建、显示和生命周期管理。与直接使用WPF原生的Window类相比,IWindowManager提供了以下优势:
- 自动处理ViewModel和View的绑定
- 支持依赖注入
- 提供统一的异步API
- 内置对话框和消息框功能
基础使用方法非常简单,首先需要在Bootstrapper中注册服务:
csharp复制public class Bootstrapper : Bootstrapper<ShellViewModel> {
protected override void Configure() {
// 默认配置就包含了IWindowManager
}
}
然后在ViewModel中通过构造函数注入:
csharp复制public class MainViewModel {
private readonly IWindowManager windowManager;
public MainViewModel(IWindowManager windowManager) {
this.windowManager = windowManager;
}
}
1.2 显示等待窗体的实现方案
在处理耗时操作时,显示等待窗体是提升用户体验的重要手段。使用IWindowManager可以优雅地实现这一功能。
1.2.1 基本等待窗体实现
首先创建等待窗体的ViewModel:
csharp复制public class ProgressViewModel : Screen {
private string message;
public string Message {
get => message;
set => SetAndNotify(ref message, value);
}
public ProgressViewModel(string initialMessage = "处理中...") {
Message = initialMessage;
}
}
然后在主ViewModel中显示等待窗体:
csharp复制public async Task LongRunningOperation() {
var progress = new ProgressViewModel();
windowManager.ShowWindow(progress);
try {
await Task.Run(() => {
// 模拟耗时操作
Thread.Sleep(5000);
});
}
finally {
windowManager.Close(progress);
}
}
1.2.2 带进度条的进阶实现
对于需要显示进度的情况,可以扩展ProgressViewModel:
csharp复制public class ProgressViewModel : Screen {
private int progressValue;
public int ProgressValue {
get => progressValue;
set => SetAndNotify(ref progressValue, value);
}
public void UpdateProgress(int value, string status = null) {
ProgressValue = value;
if(status != null) Message = status;
}
}
使用时可以通过依赖注入将ProgressViewModel传入需要进度报告的服务中。
1.3 对话框的灵活运用
对话框是应用程序中不可或缺的交互方式,IWindowManager提供了强大的对话框支持。
1.3.1 基本对话框实现
显示一个简单的确认对话框:
csharp复制public async Task ConfirmAction() {
var result = await windowManager.ShowDialog(
new ConfirmDialogViewModel("确定要执行此操作吗?"));
if(result == true) {
// 用户点击了确定
}
}
ConfirmDialogViewModel可以这样实现:
csharp复制public class ConfirmDialogViewModel : Screen {
public string Message { get; }
public ConfirmDialogViewModel(string message) {
Message = message;
}
public void Confirm() {
RequestClose(true);
}
public void Cancel() {
RequestClose(false);
}
}
1.3.2 自定义对话框参数传递
对于更复杂的对话框,可以通过属性传递参数:
csharp复制public class UserInputDialogViewModel : Screen {
public string InputText { get; set; }
public int SelectedOption { get; set; }
public IEnumerable<int> AvailableOptions { get; }
= Enumerable.Range(1, 10);
public void Submit() {
RequestClose(true);
}
}
调用时:
csharp复制var dialog = new UserInputDialogViewModel();
var result = await windowManager.ShowDialog(dialog);
if(result == true) {
var input = dialog.InputText;
var option = dialog.SelectedOption;
}
1.4 消息框的便捷使用
Stylet的IWindowManager内置了消息框功能,比WPF原生的MessageBox更加强大和灵活。
1.4.1 基本消息提示
csharp复制// 简单消息
windowManager.ShowMessage("操作已完成");
// 带标题的消息
windowManager.ShowMessage("文件保存成功", "系统提示");
// 带按钮的消息
var result = windowManager.ShowMessage(
"是否保存修改?",
"确认",
MessageBoxButton.YesNoCancel);
switch(result) {
case MessageBoxResult.Yes:
// 保存操作
break;
case MessageBoxResult.No:
// 不保存
break;
case MessageBoxResult.Cancel:
// 取消
break;
}
1.4.2 自定义消息框样式
通过创建自定义的MessageBoxViewModel和对应的View,可以完全控制消息框的外观和行为:
csharp复制public class CustomMessageBoxViewModel : Screen {
public string Message { get; }
public MessageBoxIcon Icon { get; }
public CustomMessageBoxViewModel(string message, MessageBoxIcon icon) {
Message = message;
Icon = icon;
}
// 可以添加自定义命令和逻辑
}
注册自定义消息框:
csharp复制public class Bootstrapper : Bootstrapper<ShellViewModel> {
protected override void Configure() {
container.PerRequest<CustomMessageBoxViewModel>();
// 替换默认的消息框实现
container.RegisterInstance<IMessageBoxViewModel>(
c => c.Get<CustomMessageBoxViewModel>());
}
}
1.5 高级技巧与最佳实践
在实际项目中使用IWindowManager时,有以下经验值得分享:
1.5.1 窗体生命周期管理
- 使用
ShowWindow()和Close()配对调用,避免内存泄漏 - 对于模态对话框,总是检查
ShowDialog()的返回值 - 考虑使用
using语句确保窗体正确释放
csharp复制public async Task SafeDialogOperation() {
using(var dialog = new ProgressViewModel()) {
windowManager.ShowWindow(dialog);
try {
await LongRunningTask();
}
finally {
windowManager.Close(dialog);
}
}
}
1.5.2 异步操作处理模式
推荐使用async/await模式处理耗时操作:
csharp复制public async Task LoadDataWithProgress() {
var progress = new ProgressViewModel();
windowManager.ShowWindow(progress);
try {
progress.Message = "正在加载数据...";
var data = await dataService.LoadDataAsync();
progress.Message = "处理数据...";
await ProcessDataAsync(data);
}
catch(Exception ex) {
await windowManager.ShowMessage($"错误: {ex.Message}");
}
finally {
windowManager.Close(progress);
}
}
1.5.3 依赖注入的最佳实践
- 避免在ViewModel中直接new窗体ViewModel,应通过容器获取
- 使用工厂方法创建临时实例
- 考虑使用装饰器模式增强窗体功能
csharp复制public class MainViewModel {
private readonly IWindowManager windowManager;
private readonly Func<string, ProgressViewModel> progressFactory;
public MainViewModel(
IWindowManager windowManager,
Func<string, ProgressViewModel> progressFactory)
{
this.windowManager = windowManager;
this.progressFactory = progressFactory;
}
public async Task DoWork() {
var progress = progressFactory("处理中...");
windowManager.ShowWindow(progress);
// ...
}
}
1.6 常见问题与解决方案
在实际开发中,可能会遇到以下典型问题:
1.6.1 窗体位置控制问题
问题:弹出的对话框不在父窗体中心显示。
解决方案:
csharp复制// 在子ViewModel中设置WindowStartupLocation
public class ChildViewModel : Screen {
protected override void OnViewLoaded() {
var view = GetView() as Window;
if(view != null) {
view.WindowStartupLocation = WindowStartupLocation.CenterOwner;
}
}
}
1.6.2 对话框结果处理异常
问题:对话框关闭后结果处理不正确。
解决方案:
csharp复制public async Task SafeDialogOperation() {
var dialog = new ConfirmDialogViewModel();
bool? result = null;
try {
result = await windowManager.ShowDialog(dialog);
}
catch(Exception) {
// 处理异常情况
}
if(result == true) {
// 确定操作
}
}
1.6.3 内存泄漏排查
问题:窗体关闭后内存没有释放。
检查点:
- 确保所有事件订阅都已取消
- 检查是否有静态引用
- 使用弱引用模式处理跨窗体引用
csharp复制public class PublisherViewModel {
private readonly List<WeakReference<ISubscriber>> subscribers
= new List<WeakReference<ISubscriber>>();
public void Subscribe(ISubscriber subscriber) {
subscribers.Add(new WeakReference<ISubscriber>(subscriber));
}
public void NotifySubscribers() {
foreach(var weakRef in subscribers.ToArray()) {
if(weakRef.TryGetTarget(out var subscriber)) {
subscriber.OnEvent();
}
else {
subscribers.Remove(weakRef);
}
}
}
}
1.7 性能优化建议
对于需要频繁创建和销毁窗体的场景,考虑以下优化措施:
1.7.1 窗体池技术
实现一个简单的窗体池来重用窗体实例:
csharp复制public class WindowPool<T> where T : Screen {
private readonly ConcurrentBag<T> pool = new ConcurrentBag<T>();
private readonly Func<T> factory;
public WindowPool(Func<T> factory) {
this.factory = factory;
}
public T Get() {
if(pool.TryTake(out var window)) {
return window;
}
return factory();
}
public void Return(T window) {
// 重置窗体状态
pool.Add(window);
}
}
1.7.2 虚拟化技术
对于包含列表的复杂窗体,使用UI虚拟化提高性能:
xaml复制<ListBox VirtualizingStackPanel.IsVirtualizing="True"
VirtualizingStackPanel.VirtualizationMode="Recycling"
ScrollViewer.IsDeferredScrollingEnabled="True">
<!-- 列表内容 -->
</ListBox>
1.7.3 异步加载策略
对于资源密集型窗体,实现异步加载:
csharp复制public class HeavyViewModel : Screen {
private bool isLoaded;
protected override async void OnActivate() {
if(isLoaded) return;
try {
await LoadDataAsync();
isLoaded = true;
}
catch(Exception ex) {
// 处理错误
}
}
}
通过合理使用Stylet的IWindowManager接口,我们可以构建出既美观又高效的WPF应用程序窗体交互系统。关键在于理解MVVM模式的核心思想,并充分利用框架提供的各种便利功能。