Stylet启动机制详解:从Bootstrap到View显示
前言
今天以Stylet.Samples.Hello这个demo为例,学习一下Stylet的启动机制。
平常我们新建一个WPF程序结构是这样的:

启动之后就是这样的:

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

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

再来看看它的App.xaml:
<Application x:Class="Stylet.Samples.Hello.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:s="https://github.com/canton7/Stylet"
xmlns:local="clr-namespace:Stylet.Samples.Hello">
<Application.Resources>
<s:ApplicationLoader>
<s:ApplicationLoader.Bootstrapper>
<local:HelloBootstrapper/>
</s:ApplicationLoader.Bootstrapper>
</s:ApplicationLoader>
</Application.Resources>
</Application>
我们发现它删掉了StartupUri,然后多了一个<s:ApplicationLoader>。
说明启动起来就显示ShellView的玄机就在其中!!
Stylet启动机制
Stylet的启动流程可以分为以下几个关键阶段:
- WPF应用程序启动(Application.Startup)
- ApplicationLoader初始化(XAML解析阶段)
- Bootstrapper配置与启动
- IoC容器初始化
- 根视图模型创建
- 视图定位与创建
- 窗口显示
看似一个简单的启动过程,其实作者做了很多的处理!!
现在就让我们来看看吧!!
阶段1:WPF应用程序启动
<Application x:Class="Stylet.Samples.Hello.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:s="https://github.com/canton7/Stylet"
xmlns:local="clr-namespace:Stylet.Samples.Hello">
<Application.Resources>
<s:ApplicationLoader>
<s:ApplicationLoader.Bootstrapper>
<local:HelloBootstrapper/>
</s:ApplicationLoader.Bootstrapper>
</s:ApplicationLoader>
</Application.Resources>
</Application>
当WPF解析App.xaml时,会创建ApplicationLoader实例。ApplicationLoader是Stylet提供的特殊资源字典,继承自ResourceDictionary,ApplicationLoader的构造函数会立即执行,加载Stylet的基础资源。
来看下ApplicationLoader的源码:
public class ApplicationLoader : ResourceDictionary
{
private readonly ResourceDictionary styletResourceDictionary;
public ApplicationLoader()
{
// 加载Stylet的基础样式资源
this.styletResourceDictionary = new ResourceDictionary() {
Source = new Uri("pack://application:,,,/Stylet;component/Xaml/StyletResourceDictionary.xaml", UriKind.Absolute)
};
this.LoadStyletResources = true;
}
public IBootstrapper Bootstrapper
{
get => this._bootstrapper;
set
{
this._bootstrapper = value;
// 关键:立即调用Setup方法
this._bootstrapper.Setup(Application.Current);
}
}
}
当XAML解析器遇到<local:HelloBootstrapper/>时,会:
- 创建HelloBootstrapper实例
- 设置给ApplicationLoader.Bootstrapper属性
- 触发Bootstrapper.Setup(Application.Current)调用
阶段2:Bootstrapper配置与启动
HelloBootstrapper:
public class HelloBootstrapper : Bootstrapper<ShellViewModel>
{
// 空实现!所有功能都在基类中
}
虽然HelloBootstrapper看起来很简单,但它继承的Bootstrapper提供了完整的启动逻辑。
现在来看看HelloBootstrapper的继承链:
HelloBootstrapper
↓
Bootstrapper<ShellViewModel>
↓
StyletIoCBootstrapperBase
↓
BootstrapperBase
刚刚在阶段1中看到的Setup方法在BootstrapperBase中,现在来看看:
public void Setup(Application application)
{
this.Application = application;
// 设置UI线程调度器
Execute.Dispatcher = new ApplicationDispatcher(this.Application.Dispatcher);
// 关键:注册Startup事件
this.Application.Startup += (o, e) => this.Start(e.Args);
// 注册其他生命周期事件
this.Application.Exit += (o, e) =>
{
this.OnExit(e);
this.Dispose();
};
this.Application.DispatcherUnhandledException += (o, e) =>
{
LogManager.GetLogger(typeof(BootstrapperBase)).Error(e.Exception, "Unhandled exception");
this.OnUnhandledException(e);
};
}
Setup方法在XAML解析阶段就被调用,但实际的启动逻辑在Application.Startup事件触发时才执行,这确保了所有配置都在UI线程上完成。
阶段3:IoC容器初始化
现在重点看看这个:
// 关键:注册Startup事件
this.Application.Startup += (o, e) => this.Start(e.Args);
我们看到作者为this.Application.Startup事件增加了事件处理函数/程序 (o, e) => this.Start(e.Args)。
现在我们来看看这个事件处理函数/程序:
public virtual void Start(string[] args)
{
// 1. 保存命令行参数
this.Args = args;
this.OnStart();
// 2. 配置Bootstrapper(主要是IoC容器)
this.ConfigureBootstrapper();
// 3. 注册ViewManager到应用程序资源
this.Application?.Resources.Add(View.ViewManagerResourceKey, this.GetInstance(typeof(IViewManager)));
// 4. 用户自定义配置
this.Configure();
// 5. 显示根视图
this.Launch();
// 6. 启动完成通知
this.OnLaunch();
}
先来看看StyletIoC容器配置:
protected sealed override void ConfigureBootstrapper()
{
var builder = new StyletIoCBuilder();
builder.Assemblies = new List<Assembly>(new List<Assembly>() { this.GetType().Assembly });
// 用户自定义IoC配置
this.ConfigureIoC(builder);
// 默认配置
this.DefaultConfigureIoC(builder);
// 构建容器
this.Container = builder.BuildContainer();
}
protected virtual void DefaultConfigureIoC(StyletIoCBuilder builder)
{
// 配置ViewManager
var viewManagerConfig = new ViewManagerConfig()
{
ViewFactory = this.GetInstance,
ViewAssemblies = new List<Assembly>() { this.GetType().Assembly }
};
builder.Bind<ViewManagerConfig>().ToInstance(viewManagerConfig).AsWeakBinding();
// 注册核心服务
builder.Bind<IViewManager>().And<ViewManager>().To<ViewManager>().InSingletonScope();
builder.Bind<IWindowManager>().To<WindowManager>().InSingletonScope();
builder.Bind<IEventAggregator>().To<EventAggregator>().InSingletonScope();
// 自动绑定特性
builder.Autobind();
}
注册的默认服务:
- IViewManager → ViewManager(视图管理器)
- IWindowManager → WindowManager(窗口管理器)
- IEventAggregator → EventAggregator(事件聚合器)
阶段4:根视图模型创建
现在来看看Bootstrapper<TRootViewModel>:
public abstract class Bootstrapper<TRootViewModel> : StyletIoCBootstrapperBase
where TRootViewModel : class
{
private TRootViewModel _rootViewModel;
// 延迟加载根视图模型
protected virtual TRootViewModel RootViewModel =>
this._rootViewModel ??= this.Container.Get<TRootViewModel>();
// 启动时显示根视图
protected override void Launch()
{
this.DisplayRootView(this.RootViewModel);
}
}
关键点:
- 使用延迟加载模式,只有在需要时才创建TRootViewModel
- 通过IoC容器解析ShellViewModel,支持构造函数注入
- 在HelloBootstrapper中,TRootViewModel就是ShellViewModel
阶段5:视图定位与创建
现在来看看DisplayRootView方法:
protected virtual void DisplayRootView(object rootViewModel)
{
var windowManager = (IWindowManager)this.GetInstance(typeof(IWindowManager));
windowManager.ShowWindow(rootViewModel);
}
现在来看看ShowWindow方法:
public void ShowWindow(object viewModel)
{
this.CreateWindow(viewModel, false, null).Show();
}
现在来看看CreateWindow方法:
protected virtual Window CreateWindow(object viewModel, bool isDialog, IViewAware ownerViewModel)
{
// 1. 通过ViewManager创建视图
UIElement view = this.viewManager.CreateAndBindViewForModelIfNecessary(viewModel);
// 2. 确保视图是Window类型
if (view is not Window window)
{
throw new StyletInvalidViewTypeException(...);
}
// 3. 设置窗口属性
if (viewModel is IHaveDisplayName haveDisplayName)
{
window.SetBinding(Window.TitleProperty, new Binding("DisplayName"));
}
// 4. 设置窗口位置
if (window.WindowStartupLocation == WindowStartupLocation.Manual)
{
window.WindowStartupLocation = WindowStartupLocation.CenterScreen;
}
return window;
}
现在再来看看CreateAndBindViewForModelIfNecessary方法:
public virtual UIElement CreateAndBindViewForModelIfNecessary(object model)
{
if (model is IViewAware modelAsViewAware && modelAsViewAware.View != null)
{
logger.Info("ViewModel {0} already has a View attached to it. Not attaching another", model);
return modelAsViewAware.View;
}
return this.CreateAndBindViewForModel(model);
}
在这里得到了ViewModel所绑定的View。
阶段6:ShellView显示全过程
WPF Application
↓
Application Startup
↓
ApplicationLoader.Setup()
↓
Bootstrapper.Setup()
↓
注册Startup事件
↓
Application.Startup触发
↓
Bootstrapper.Start()
↓
ConfigureBootstrapper()
↓
IoC容器构建
↓
Launch()调用
↓
DisplayRootView(ShellViewModel)
↓
WindowManager.ShowWindow()
↓
ViewManager.CreateViewForModel()
↓
定位ShellView类型
↓
创建ShellView实例
↓
绑定ShellView到ShellViewModel
↓
显示ShellView窗口
看似一个简单的操作,其实作者做了很多工作。
从这个探索中我们可以了解什么?
- 声明式配置:通过XAML配置启动器
- 约定优于配置:自动的ViewModel-View映射
- 依赖注入:自动的依赖解析
- 生命周期管理:完整的应用程序生命周期钩子
- 可扩展性:通过继承和重写实现自定义
Stylet的设计哲学是让简单的事情保持简单,让复杂的事情成为可能。通过理解这些基础机制,你可以构建出更加优雅和可维护的WPF应用程序。
Stylet启动机制详解:从Bootstrap到View显示的更多相关文章
- JVM类加载机制详解(二)类加载器与双亲委派模型
在上一篇JVM类加载机制详解(一)JVM类加载过程中说到,类加载机制的第一个阶段加载做的工作有: 1.通过一个类的全限定名(包名与类名)来获取定义此类的二进制字节流(Class文件).而获取的方式,可 ...
- JVM类加载机制详解
引言 如下图所示,JVM类加载机制分为五个部分:加载,验证,准备,解析,初始化,下面我们就分别来看一下这五个过程. 加载 在加载阶段,虚拟机需要完成以下三件事情: 1)通过一个类的全限定名来获取定义此 ...
- java面试题之----JVM架构和GC垃圾回收机制详解
JVM架构和GC垃圾回收机制详解 jvm,jre,jdk三者之间的关系 JRE (Java Run Environment):JRE包含了java底层的类库,该类库是由c/c++编写实现的 JDK ( ...
- JVM的垃圾回收机制详解和调优
JVM的垃圾回收机制详解和调优 gc即垃圾收集机制是指jvm用于释放那些不再使用的对象所占用的内存.java语言并不要求jvm有gc,也没有规定gc如何工作.不过常用的jvm都有gc,而且大多数gc都 ...
- Android 核心分析 之八Android 启动过程详解
Android 启动过程详解 Android从Linux系统启动有4个步骤: (1) init进程启动 (2) Native服务启动 (3) System Server,Android服务启动 (4) ...
- Linux 内存机制详解宝典
Linux 内存机制详解宝典 在linux的内存分配机制中,优先使用物理内存,当物理内存还有空闲时(还够用),不会释放其占用内存,就算占用内存的程序已经被关闭了,该程序所占用的内存用来做缓存使用,对于 ...
- fabric网络环境启动过程详解
这篇文章对fabric的网络环境启动过程进行讲解,也就是我们上节讲到的启动测试fabric网络环境时运行network_setup.sh这个文件的执行流程 fabric网络环境启动过程详解 上一节我们 ...
- Android应用AsyncTask处理机制详解及源码分析
1 背景 Android异步处理机制一直都是Android的一个核心,也是应用工程师面试的一个知识点.前面我们分析了Handler异步机制原理(不了解的可以阅读我的<Android异步消息处理机 ...
- 第14章 启动文件详解—零死角玩转STM32-F429系列
第14章 启动文件详解 全套200集视频教程和1000页PDF教程请到秉火论坛下载:www.firebbs.cn 野火视频教程优酷观看网址:http://i.youku.com/firege ...
- Tomcat5的web应用启动顺序详解
Tomcat5的web应用启动顺序详解 [收藏此页] [打印] 作者:佚名 2007-07-17 内容导航: 第1页 [IT168技术文档]摘要: 应用Tomcat对于我们来讲实在是司空见惯 ...
随机推荐
- Servlet中过滤器、监听器和拦截器的区别
基本概念 过滤器(Filter):当你有一堆东西的时候,你只希望选择符合你要求的某一些东西.定义这些要求的工具,就是过滤器.就是对请求起到过滤的作用:在监听器之后servlet之前对请求进行过滤. ...
- Spring 注解之@Primary注解
当一个接口有多个不同实现类时,使用注解@Autowired时会报 org.springframework.beans.factory.NoUniqueBeanDefinitionException ...
- vivo Pulsar万亿级消息处理实践(1)-数据发送原理解析和性能调优
作者:vivo 互联网大数据团队- Quan Limin 本文是vivo互联网大数据团队<vivo Pulsar万亿级消息处理实践>系列文章第1篇. 文章以Pulsar client模块中 ...
- PicGo使用简明教程及踩坑记录
PicGo使用简明教程及踩坑记录 PicGo使用 我现在用的博客的记录方式是Typora+PicGo+阿里云oss,这一套配置好后就非常方便了,可以快捷上传图片到云服务器,并且阿里云的速度也是我试过的 ...
- 总决赛定档!“天翼云息壤杯”高校AI大赛巅峰之战即将打响!
近日,为梦想添翼,让AI发光--"天翼云息壤杯"高校AI大赛总决赛时间正式揭晓.总决赛将于2025年7月1日至7月17日在北京举办.届时,来自全国各地上百支成功晋级的优秀队伍和特邀 ...
- HyperWorks二维网格划分及拓扑改进
Step 01:载入模型 Exercise_3a.hm. Step 02:2D 网格划分. (1) 进入 automesh 面板. 图 3-13 设置 automesh 面板网格控制参数 (2) 指定 ...
- SQLServer常用个技巧(一):根据某字符截取后面的字符串,String转int
SELECT GOODS_CD AS goodsCd, 原字符串 reverse( GOODS_CD ) AS dCsdoog, 颠倒 LEFT ( reverse( GOODS_CD ), char ...
- AI领域又新增协议: AG-UI
随着AI的快速发展正在重塑技术生态,协议的演进速度尤为迅猛.一个令人头疼的问题浮现了:不同的AI智能体和前端应用之间就像说着不同语言的人,无法顺畅交流.开发者们需要为每个智能体单独编写接口,维护成本高 ...
- PLY 模型文件简析
PLY 模型文件简析 参考链接 wiki需要FFFQQQ TIPS 主要是一些英文的简析,但是一句话,网上讲的不清楚,特此说明 property list uchar int vertex_indic ...
- iPaaS 与 API 管理:企业数字化转型的双引擎
一.企业集成的残酷现实 根据IDC 2024 年数字化转型报调研显示:大中型企业平均部署数十至数百个业务系统,涵盖 ERP.CRM.SaaS 应用及物联网平台等,但仅约 30% 的系统实现标准化集成. ...