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

为什么启动之后是这样的呢?
我们知道是因为在App.xaml中我们设置了
StartupUri="MainWindow.xaml"
。
现在来看看Stylet.Samples.Hello
的结构:

再来看看它的App.xaml:
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应用程序启动
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>
当WPF解析App.xaml时,会创建ApplicationLoader实例。ApplicationLoader是Stylet提供的特殊资源字典,继承自ResourceDictionary,ApplicationLoader的构造函数会立即执行,加载Stylet的基础资源。
来看下ApplicationLoader的源码:
csharp
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:
csharp
public class HelloBootstrapper : Bootstrapper<ShellViewModel>
{
// 空实现!所有功能都在基类中
}
虽然HelloBootstrapper看起来很简单,但它继承的Bootstrapper提供了完整的启动逻辑。
现在来看看HelloBootstrapper的继承链:
csharp
HelloBootstrapper
↓
Bootstrapper<ShellViewModel>
↓
StyletIoCBootstrapperBase
↓
BootstrapperBase
刚刚在阶段1中看到的Setup
方法在BootstrapperBase中,现在来看看:
csharp
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容器初始化
现在重点看看这个:
csharp
// 关键:注册Startup事件
this.Application.Startup += (o, e) => this.Start(e.Args);
我们看到作者为this.Application.Startup
事件增加了事件处理函数/程序 (o, e) => this.Start(e.Args)
。
现在我们来看看这个事件处理函数/程序:
csharp
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容器配置:
csharp
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>
:
csharp
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
方法:
csharp
protected virtual void DisplayRootView(object rootViewModel)
{
var windowManager = (IWindowManager)this.GetInstance(typeof(IWindowManager));
windowManager.ShowWindow(rootViewModel);
}
现在来看看ShowWindow
方法:
csharp
public void ShowWindow(object viewModel)
{
this.CreateWindow(viewModel, false, null).Show();
}
现在来看看CreateWindow
方法:
csharp
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
方法:
csharp
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显示全过程
csharp
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应用程序。