尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

WPF MVVM开发中Stylet的IWindowManager应用解析

WPF MVVM开发中Stylet的IWindowManager应用解析 1. WPF/Stylet中的IWindowManager核心价值解析在WPF MVVM开发中窗口管理一直是个痛点。传统方式需要ViewModel直接操作View这严重违反了MVVM的分离原则。我在多个工业控制项目中深刻体会到当需要弹出等待窗口、确认对话框或自定义消息框时Stylet的IWindowManager接口提供了优雅的解决方案。与Prism等框架的对话框服务相比Stylet的窗口管理器有三大不可替代的优势完全基于约定优于配置的原则无需复杂初始化原生支持异步/等待模式与Stylet的ViewModel生命周期完美集成特别是在上位机软件开发中当需要处理长时间运行的PLC通信或视觉检测任务时一个可取消的等待窗口能极大改善用户体验。通过IWindowManager.ShowDialog()方法我们可以实现模态阻塞式对话框如确认删除操作进度展示窗口带取消按钮自适应内容的消息提示框2. 环境配置与基础使用2.1 基础项目搭建首先通过NuGet安装Stylet核心包Install-Package Stylet在App.xaml中启用Stylet的BootstrapperApplication.Resources ResourceDictionary s:Bootstrapper x:Keybootstrapper s:Bootstrapper.BootstrapperType x:Type TypeNameYourNamespace.Bootstrapper, YourAssembly/ /s:Bootstrapper.BootstrapperType /s:Bootstrapper /ResourceDictionary /ApplicationResources创建继承Stylet.Bootstrapper的启动类public class Bootstrapper : BootstrapperShellViewModel { protected override void ConfigureIoC(IStyletIoCBuilder builder) { builder.BindIWindowManager().ToWindowManager().InSingletonScope(); } }2.2 基础对话框调用在ViewModel中注入并使用窗口管理器public class MainViewModel { private readonly IWindowManager _windowManager; public MainViewModel(IWindowManager windowManager) { _windowManager windowManager; } public void ShowAlert() { _windowManager.ShowMessageBox( 这是一条重要提示, 操作确认, MessageBoxButton.OK, MessageBoxImage.Information); } }3. 高级窗口管理实战3.1 自定义等待窗口实现创建等待窗口ViewModelpublic class ProgressDialogViewModel : Screen { private string _message; public string Message { get _message; set SetAndNotify(ref _message, value); } private bool _canCancel; public bool CanCancel { get _canCancel; set SetAndNotify(ref _canCancel, value); } private bool _isCancelled; public bool IsCancelled { get _isCancelled; private set SetAndNotify(ref _isCancelled, value); } public void Cancel() { IsCancelled true; RequestClose(true); } }配套的ProgressDialogView.xamlWindow xmlnshttp://schemas.microsoft.com/winfx/2006/xaml/presentation xmlns:xhttp://schemas.microsoft.com/winfx/2006/xaml WindowStartupLocationCenterOwner ResizeModeNoResize SizeToContentWidthAndHeight StackPanel Margin20 ProgressBar IsIndeterminateTrue Height20 Width200/ TextBlock Text{Binding Message} Margin0,10,0,0/ Button Content取消 Command{s:Action Cancel} Visibility{Binding CanCancel, Converter{x:Static s:BoolToVisibilityConverter.Instance}} Margin0,10,0,0 Width80 HorizontalAlignmentCenter/ /StackPanel /Window使用示例public async Task LongRunningOperation() { var vm new ProgressDialogViewModel { Message 正在处理数据..., CanCancel true }; _windowManager.ShowDialog(vm); try { await Task.Run(() { // 模拟耗时操作 for (int i 0; i 100; i) { if (vm.IsCancelled) break; Thread.Sleep(100); } }); } finally { if (vm.IsActive) { await _windowManager.TryCloseAsync(vm); } } }3.2 动态内容对话框创建支持动态内容的对话框public class DynamicDialogViewModel : Screen { public object DialogContent { get; } public string Title { get; } public DynamicDialogViewModel(object content, string title) { DialogContent content; Title title; } }对应的DynamicDialogView.xaml使用ContentControlWindow xmlns:shttps://github.com/canton7/Stylet Title{Binding Title} ContentControl s:View.Model{Binding DialogContent}/ /Window使用方式var contentVm new UserInputViewModel(); var dialogVm new DynamicDialogViewModel(contentVm, 请输入参数); var result _windowManager.ShowDialog(dialogVm); if (result true) { // 处理用户输入 }4. 工业级应用技巧4.1 线程安全调用模式在异步操作中安全更新UIpublic class SafeProgressViewModel : Screen { private readonly IWindowManager _windowManager; public SafeProgressViewModel(IWindowManager windowManager) { _windowManager windowManager; } public async Task ProcessDataAsync() { var progressVm new ProgressDialogViewModel(); // 必须在UI线程显示对话框 await Execute.OnUIThreadAsync(() _windowManager.ShowDialog(progressVm)); try { await Task.Run(() { // 后台线程工作 for (int i 0; i 100; i) { // 通过Execute.OnUIThread安全更新 Execute.OnUIThread(() { progressVm.Message $已完成 {i}%; }); Thread.Sleep(50); } }); } finally { await Execute.OnUIThreadAsync(() _windowManager.TryCloseAsync(progressVm)); } } }4.2 对话框结果处理模式增强型结果处理方案public enum CustomDialogResult { Ok, Cancel, Retry, Ignore } public class CustomDialogViewModel : Screen { public CustomDialogResult Result { get; private set; } public void SetResult(CustomDialogResult result) { Result result; RequestClose(true); } } // 使用示例 var vm new CustomDialogViewModel(); _windowManager.ShowDialog(vm); switch (vm.Result) { case CustomDialogResult.Ok: // 处理确定操作 break; case CustomDialogResult.Retry: // 处理重试逻辑 break; // 其他情况处理 }5. 性能优化与异常处理5.1 窗口复用策略实现窗口池管理public class DialogPool : IDisposable { private readonly ConcurrentDictionaryType, StackScreen _pool new(); private readonly IWindowManager _windowManager; public DialogPool(IWindowManager windowManager) { _windowManager windowManager; } public T GetViewModelT() where T : Screen, new() { var type typeof(T); if (_pool.TryGetValue(type, out var stack) stack.TryPop(out var vm)) { return (T)vm; } return new T(); } public void ReturnViewModel(Screen viewModel) { var type viewModel.GetType(); var stack _pool.GetOrAdd(type, _ new StackScreen()); stack.Push(viewModel); } public void Dispose() { foreach (var stack in _pool.Values) { while (stack.TryPop(out var vm)) { (vm as IDisposable)?.Dispose(); } } _pool.Clear(); } }5.2 健壮性增强实践异常处理包装器public static class WindowManagerExtensions { public static async Taskbool ShowDialogWithRetry( this IWindowManager windowManager, Screen viewModel, int maxRetries 3) { int attempts 0; while (attempts maxRetries) { try { return await windowManager.ShowDialogAsync(viewModel) true; } catch (Exception ex) when (attempts maxRetries - 1) { attempts; await Task.Delay(100 * attempts); // 可添加日志记录 } } return false; } }6. 实际项目集成案例6.1 机器视觉检测流程典型视觉检测对话框流程public async Task RunInspectionAsync() { var progressVm new ProgressDialogViewModel { Message 正在初始化相机..., CanCancel true }; var showTask Execute.OnUIThreadAsync(() _windowManager.ShowDialog(progressVm)); try { // 初始化硬件 await InitializeCameraAsync(progressVm); progressVm.Message 正在采集图像...; var image await CaptureImageAsync(); progressVm.Message 正在处理图像...; var result await ProcessImageAsync(image); progressVm.Message 生成检测报告...; await GenerateReportAsync(result); await Execute.OnUIThreadAsync(() _windowManager.ShowMessageBox(检测完成, 结果, MessageBoxButton.OK, MessageBoxImage.Information)); } catch (OperationCanceledException) { await Execute.OnUIThreadAsync(() _windowManager.ShowMessageBox(操作已取消, 提示, MessageBoxButton.OK, MessageBoxImage.Warning)); } catch (Exception ex) { await Execute.OnUIThreadAsync(() _windowManager.ShowMessageBox($检测失败: {ex.Message}, 错误, MessageBoxButton.OK, MessageBoxImage.Error)); } finally { if (progressVm.IsActive) { await Execute.OnUIThreadAsync(() _windowManager.TryCloseAsync(progressVm)); } } }6.2 数据库操作确认流程带数据绑定的确认对话框public class DeleteConfirmationViewModel : Screen { public string ItemName { get; } public bool BackupBeforeDelete { get; set; } public DeleteConfirmationViewModel(string itemName) { ItemName itemName; } } // 使用示例 public async Task DeleteItemAsync(DataItem item) { var vm new DeleteConfirmationViewModel(item.Name); if (await _windowManager.ShowDialogAsync(vm) true) { try { if (vm.BackupBeforeDelete) { await BackupItemAsync(item); } await _dataService.DeleteAsync(item.Id); } catch (Exception ex) { _windowManager.ShowMessageBox($删除失败: {ex.Message}, 错误); } } }7. 样式定制与主题集成7.1 自定义对话框样式创建统一样式资源Style TargetTypeWindow x:KeyDialogWindowStyle Setter PropertyWindowStyle ValueNone/ Setter PropertyAllowsTransparency ValueTrue/ Setter PropertyBackground ValueTransparent/ Setter PropertyWindowStartupLocation ValueCenterOwner/ Setter PropertySizeToContent ValueWidthAndHeight/ Setter PropertyTemplate Setter.Value ControlTemplate TargetTypeWindow Border Background#CC000000 Padding50 Border Background{DynamicResource WindowBackgroundBrush} CornerRadius5 BorderThickness1 BorderBrush{DynamicResource BorderBrush} Grid Grid.RowDefinitions RowDefinition HeightAuto/ RowDefinition Height*/ RowDefinition HeightAuto/ /Grid.RowDefinitions TextBlock Text{TemplateBinding Title} Style{StaticResource DialogTitleStyle}/ ContentPresenter Grid.Row1/ StackPanel Grid.Row2 OrientationHorizontal HorizontalAlignmentRight Button Content确定 Command{s:Action Accept} Style{StaticResource DialogButtonStyle}/ Button Content取消 Command{s:Action Cancel} Style{StaticResource DialogButtonStyle}/ /StackPanel /Grid /Border /Border /ControlTemplate /Setter.Value /Setter /Style应用到ViewModelpublic class StyledDialogViewModel : Screen { public override void OnViewLoaded() { if (View is Window window) { window.Style Application.Current.FindResource(DialogWindowStyle) as Style; } } }7.2 动态主题切换响应系统主题变化public class ThemeAwareDialogViewModel : Screen { private readonly IEventAggregator _eventAggregator; public ThemeAwareDialogViewModel(IEventAggregator eventAggregator) { _eventAggregator eventAggregator; _eventAggregator.Subscribe(this); } public void Handle(ThemeChangedEvent message) { if (View is Window window) { window.Background new SolidColorBrush(message.NewTheme.BackgroundColor); } } protected override void OnClose() { _eventAggregator.Unsubscribe(this); base.OnClose(); } }8. 测试与调试技巧8.1 单元测试策略使用Moq测试窗口交互[Test] public void Should_ShowConfirmation_When_DeletingItem() { // Arrange var windowManagerMock new MockIWindowManager(); windowManagerMock.Setup(x x.ShowMessageBox(It.IsAnystring(), It.IsAnystring())) .Returns(true); var vm new MainViewModel(windowManagerMock.Object); // Act vm.DeleteCommand.Execute(null); // Assert windowManagerMock.Verify(x x.ShowMessageBox( 确定要删除此项吗?, 确认删除, MessageBoxButton.YesNo, MessageBoxImage.Question), Times.Once); }8.2 诊断窗口泄漏窗口生命周期监控public class WindowTracker { private static readonly ListWeakReferenceWindow _windows new(); public static void Track(Window window) { window.Closed (s, e) { lock (_windows) { _windows.RemoveAll(w w.TryGetTarget(out var target) target window); } }; lock (_windows) { _windows.Add(new WeakReferenceWindow(window)); } } public static int GetActiveWindowCount() { lock (_windows) { _windows.RemoveAll(w !w.TryGetTarget(out _)); return _windows.Count; } } } // 在窗口构造函数中调用 public partial class CustomDialog : Window { public CustomDialog() { InitializeComponent(); WindowTracker.Track(this); } }9. 性能对比与选型建议9.1 与其它方案对比特性Stylet IWindowManagerPrism DialogServiceHandyControl DialogMVVM兼容性★★★★★★★★★☆★★★☆☆异步支持★★★★★★★★☆☆★★☆☆☆样式定制灵活性★★★★☆★★★☆☆★★★★★学习曲线★★★☆☆★★★★☆★★☆☆☆复杂场景支持★★★★★★★★★☆★★★☆☆项目活跃度★★★☆☆★★★★★★★★★☆9.2 选型决策树是否需要深度MVVM支持是 → 选择Stylet或Prism是否需要高级异步功能 → Stylet是否需要成熟生态系统 → Prism否 → 考虑HandyControl等UI库项目是否已使用Stylet是 → 优先使用IWindowManager否 → 评估引入成本是否需要高度定制化的对话框是 → Stylet自定义Window否 → 使用内置解决方案10. 扩展与进阶方向10.1 多语言支持实现创建本地化服务public interface ILocalizationService { string Translate(string key); } public class LocalizedDialogViewModel : Screen { private readonly ILocalizationService _localization; public string Title _localization.Translate(DeleteConfirmationTitle); public string Message string.Format( _localization.Translate(DeleteConfirmationMessage), ItemName); public string ItemName { get; } public LocalizedDialogViewModel(ILocalizationService localization, string itemName) { _localization localization; ItemName itemName; } }10.2 动态窗口布局根据内容调整布局public class AdaptiveDialogViewModel : Screen { public ObservableCollectionDialogSection Sections { get; } new(); public AdaptiveDialogViewModel() { Sections.Add(new TextSection { Content 基础信息 }); Sections.Add(new InputSection { FieldName 用户名 }); // 可根据条件动态添加不同部分 } } public abstract class DialogSection : PropertyChangedBase { public abstract FrameworkElement CreateView(); } public class InputSection : DialogSection { private string _fieldName; public string FieldName { get _fieldName; set SetAndNotify(ref _fieldName, value); } public override FrameworkElement CreateView() { return new StackPanel { Orientation Orientation.Horizontal, Children { new TextBlock { Text FieldName, Width 100 }, new TextBox { Width 200 } } }; } }在多年的WPF开发实践中我发现窗口管理是最容易被低估的模块。良好的对话框交互能显著提升用户体验而混乱的窗口管理则会导致维护噩梦。Stylet的IWindowManager在简洁性和功能性之间取得了完美平衡特别是在处理复杂业务流程时其清晰的API设计和强大的异步支持让开发者能专注于业务逻辑而非UI细节。
返回列表