找回密码
 立即注册
首页 业界区 业界 Stylet启动机制详解:从Bootstrap到View显示

Stylet启动机制详解:从Bootstrap到View显示

雌鲳签 2025-8-18 12:26:22
前言

今天以Stylet.Samples.Hello这个demo为例,学习一下Stylet的启动机制。
平常我们新建一个WPF程序结构是这样的:
1.png

启动之后就是这样的:
2.png

为什么启动之后是这样的呢?
3.png

我们知道是因为在App.xaml中我们设置了StartupUri="MainWindow.xaml"。
现在来看看Stylet.Samples.Hello的结构:
4.png

再来看看它的App.xaml:
  1.    
  2.         <s:ApplicationLoader>
  3.             <s:ApplicationLoader.Bootstrapper>
  4.                 <local:HelloBootstrapper/>
  5.             </s:ApplicationLoader.Bootstrapper>
  6.         </s:ApplicationLoader>
  7.     </Application.Resources>
  8. </Application>
复制代码
我们发现它删掉了StartupUri,然后多了一个。
说明启动起来就显示ShellView的玄机就在其中!!
Stylet启动机制

Stylet的启动流程可以分为以下几个关键阶段:

  • WPF应用程序启动(Application.Startup)
  • ApplicationLoader初始化(XAML解析阶段)
  • Bootstrapper配置与启动
  • IoC容器初始化
  • 根视图模型创建
  • 视图定位与创建
  • 窗口显示
看似一个简单的启动过程,其实作者做了很多的处理!!
现在就让我们来看看吧!!
阶段1:WPF应用程序启动
  1.    
  2.         <s:ApplicationLoader>
  3.             <s:ApplicationLoader.Bootstrapper>
  4.                 <local:HelloBootstrapper/>
  5.             </s:ApplicationLoader.Bootstrapper>
  6.         </s:ApplicationLoader>
  7.     </Application.Resources>
  8. </Application>
复制代码
当WPF解析App.xaml时,会创建ApplicationLoader实例。ApplicationLoader是Stylet提供的特殊资源字典,继承自ResourceDictionary,ApplicationLoader的构造函数会立即执行,加载Stylet的基础资源。
来看下ApplicationLoader的源码:
  1. public class ApplicationLoader : ResourceDictionary
  2. {
  3.     private readonly ResourceDictionary styletResourceDictionary;
  4.    
  5.     public ApplicationLoader()
  6.     {
  7.         // 加载Stylet的基础样式资源
  8.         this.styletResourceDictionary = new ResourceDictionary() {
  9.             Source = new Uri("pack://application:,,,/Stylet;component/Xaml/StyletResourceDictionary.xaml", UriKind.Absolute)
  10.         };
  11.         this.LoadStyletResources = true;
  12.     }
  13.     public IBootstrapper Bootstrapper
  14.     {
  15.         get => this._bootstrapper;
  16.         set
  17.         {
  18.             this._bootstrapper = value;
  19.             // 关键:立即调用Setup方法
  20.             this._bootstrapper.Setup(Application.Current);
  21.         }
  22.     }
  23. }
复制代码
当XAML解析器遇到时,会:

  • 创建HelloBootstrapper实例
  • 设置给ApplicationLoader.Bootstrapper属性
  • 触发Bootstrapper.Setup(Application.Current)调用
阶段2:Bootstrapper配置与启动
HelloBootstrapper:
  1. public class HelloBootstrapper : Bootstrapper<ShellViewModel>
  2. {
  3.     // 空实现!所有功能都在基类中
  4. }
复制代码
虽然HelloBootstrapper看起来很简单,但它继承的Bootstrapper提供了完整的启动逻辑。
现在来看看HelloBootstrapper的继承链:
  1.   HelloBootstrapper
  2.     ↓
  3. Bootstrapper<ShellViewModel>
  4.     ↓
  5. StyletIoCBootstrapperBase
  6.     ↓
  7. BootstrapperBase
复制代码
刚刚在阶段1中看到的Setup方法在BootstrapperBase中,现在来看看:
  1. public void Setup(Application application)
  2. {
  3.   this.Application = application;
  4.   
  5.   // 设置UI线程调度器
  6.   Execute.Dispatcher = new ApplicationDispatcher(this.Application.Dispatcher);
  7.   // 关键:注册Startup事件
  8.   this.Application.Startup += (o, e) => this.Start(e.Args);
  9.   
  10.   // 注册其他生命周期事件
  11.   this.Application.Exit += (o, e) =>
  12.   {
  13.       this.OnExit(e);
  14.       this.Dispose();
  15.   };
  16.   
  17.   this.Application.DispatcherUnhandledException += (o, e) =>
  18.   {
  19.       LogManager.GetLogger(typeof(BootstrapperBase)).Error(e.Exception, "Unhandled exception");
  20.       this.OnUnhandledException(e);
  21.   };
  22. }
复制代码
Setup方法在XAML解析阶段就被调用,但实际的启动逻辑在Application.Startup事件触发时才执行,这确保了所有配置都在UI线程上完成。
阶段3:IoC容器初始化
现在重点看看这个:
  1.   // 关键:注册Startup事件
  2.   this.Application.Startup += (o, e) => this.Start(e.Args);
复制代码
我们看到作者为this.Application.Startup事件增加了事件处理函数/程序 (o, e) => this.Start(e.Args)。
现在我们来看看这个事件处理函数/程序:
  1. public virtual void Start(string[] args)
  2. {
  3.   // 1. 保存命令行参数
  4.   this.Args = args;
  5.   this.OnStart();
  6.   
  7.   // 2. 配置Bootstrapper(主要是IoC容器)
  8.   this.ConfigureBootstrapper();
  9.   
  10.   // 3. 注册ViewManager到应用程序资源
  11.   this.Application?.Resources.Add(View.ViewManagerResourceKey, this.GetInstance(typeof(IViewManager)));
  12.   
  13.   // 4. 用户自定义配置
  14.   this.Configure();
  15.   
  16.   // 5. 显示根视图
  17.   this.Launch();
  18.   
  19.   // 6. 启动完成通知
  20.   this.OnLaunch();
  21. }
复制代码
先来看看StyletIoC容器配置:
  1. protected sealed override void ConfigureBootstrapper()
  2. {
  3.   var builder = new StyletIoCBuilder();
  4.   builder.Assemblies = new List(new List() { this.GetType().Assembly });
  5.   // 用户自定义IoC配置
  6.   this.ConfigureIoC(builder);
  7.   
  8.   // 默认配置
  9.   this.DefaultConfigureIoC(builder);
  10.   
  11.   // 构建容器
  12.   this.Container = builder.BuildContainer();
  13. }
  14. protected virtual void DefaultConfigureIoC(StyletIoCBuilder builder)
  15. {
  16.   // 配置ViewManager
  17.   var viewManagerConfig = new ViewManagerConfig()
  18.   {
  19.       ViewFactory = this.GetInstance,
  20.       ViewAssemblies = new List() { this.GetType().Assembly }
  21.   };
  22.   builder.Bind<ViewManagerConfig>().ToInstance(viewManagerConfig).AsWeakBinding();
  23.   // 注册核心服务
  24.   builder.Bind<IViewManager>().And<ViewManager>().To<ViewManager>().InSingletonScope();
  25.   builder.Bind<IWindowManager>().To<WindowManager>().InSingletonScope();
  26.   builder.Bind<IEventAggregator>().To<EventAggregator>().InSingletonScope();
  27.   
  28.   // 自动绑定特性
  29.   builder.Autobind();
  30. }
复制代码
注册的默认服务:

  • IViewManager → ViewManager(视图管理器)
  • IWindowManager → WindowManager(窗口管理器)
  • IEventAggregator → EventAggregator(事件聚合器)
阶段4:根视图模型创建
现在来看看Bootstrapper:
  1. public abstract class Bootstrapper<TRootViewModel> : StyletIoCBootstrapperBase
  2.   where TRootViewModel : class
  3. {
  4.   private TRootViewModel _rootViewModel;
  5.   // 延迟加载根视图模型
  6.   protected virtual TRootViewModel RootViewModel =>
  7.       this._rootViewModel ??= this.Container.Get<TRootViewModel>();
  8.   // 启动时显示根视图
  9.   protected override void Launch()
  10.   {
  11.       this.DisplayRootView(this.RootViewModel);
  12.   }
  13. }
复制代码
关键点:

  • 使用延迟加载模式,只有在需要时才创建TRootViewModel
  • 通过IoC容器解析ShellViewModel,支持构造函数注入
  • 在HelloBootstrapper中,TRootViewModel就是ShellViewModel
阶段5:视图定位与创建
现在来看看DisplayRootView方法:
  1. protected virtual void DisplayRootView(object rootViewModel)
  2. {
  3.   var windowManager = (IWindowManager)this.GetInstance(typeof(IWindowManager));
  4.   windowManager.ShowWindow(rootViewModel);
  5. }
复制代码
现在来看看ShowWindow方法:
  1. public void ShowWindow(object viewModel)
  2. {
  3.   this.CreateWindow(viewModel, false, null).Show();
  4. }
复制代码
现在来看看CreateWindow方法:
  1. protected virtual Window CreateWindow(object viewModel, bool isDialog, IViewAware ownerViewModel)
  2. {
  3.   // 1. 通过ViewManager创建视图
  4.   UIElement view = this.viewManager.CreateAndBindViewForModelIfNecessary(viewModel);
  5.   
  6.   // 2. 确保视图是Window类型
  7.   if (view is not Window window)
  8.   {
  9.       throw new StyletInvalidViewTypeException(...);
  10.   }
  11.   
  12.   // 3. 设置窗口属性
  13.   if (viewModel is IHaveDisplayName haveDisplayName)
  14.   {
  15.       window.SetBinding(Window.TitleProperty, new Binding("DisplayName"));
  16.   }
  17.   
  18.   // 4. 设置窗口位置
  19.   if (window.WindowStartupLocation == WindowStartupLocation.Manual)
  20.   {
  21.       window.WindowStartupLocation = WindowStartupLocation.CenterScreen;
  22.   }
  23.   
  24.   return window;
  25. }
复制代码
现在再来看看CreateAndBindViewForModelIfNecessary方法:
  1.   public virtual UIElement CreateAndBindViewForModelIfNecessary(object model)
  2. {
  3.     if (model is IViewAware modelAsViewAware && modelAsViewAware.View != null)
  4.     {
  5.         logger.Info("ViewModel {0} already has a View attached to it. Not attaching another", model);
  6.         return modelAsViewAware.View;
  7.     }
  8.     return this.CreateAndBindViewForModel(model);
  9. }
复制代码
在这里得到了ViewModel所绑定的View。
阶段6:ShellView显示全过程
  1. WPF Application
  2.     ↓
  3. Application Startup
  4.     ↓
  5. ApplicationLoader.Setup()
  6.     ↓
  7. Bootstrapper.Setup()
  8.     ↓
  9. 注册Startup事件
  10.     ↓
  11. Application.Startup触发
  12.     ↓
  13. Bootstrapper.Start()
  14.     ↓
  15. ConfigureBootstrapper()
  16.     ↓
  17. IoC容器构建
  18.     ↓
  19. Launch()调用
  20.     ↓
  21. DisplayRootView(ShellViewModel)
  22.     ↓
  23. WindowManager.ShowWindow()
  24.     ↓
  25. ViewManager.CreateViewForModel()
  26.     ↓
  27. 定位ShellView类型
  28.     ↓
  29. 创建ShellView实例
  30.     ↓
  31. 绑定ShellView到ShellViewModel
  32.     ↓
  33. 显示ShellView窗口
复制代码
看似一个简单的操作,其实作者做了很多工作。
从这个探索中我们可以了解什么?

  • 声明式配置:通过XAML配置启动器
  • 约定优于配置:自动的ViewModel-View映射
  • 依赖注入:自动的依赖解析
  • 生命周期管理:完整的应用程序生命周期钩子
  • 可扩展性:通过继承和重写实现自定义
Stylet的设计哲学是让简单的事情保持简单,让复杂的事情成为可能。通过理解这些基础机制,你可以构建出更加优雅和可维护的WPF应用程序。

来源:豆瓜网用户自行投稿发布,如果侵权,请联系站长删除

相关推荐

您需要登录后才可以回帖 登录 | 立即注册