Stylet框架
编辑时间:2023/8/25
1.Stylet简介
Stylet是一个小巧但功能强大的MVVM框架,灵感来自Caliburn.Micro。其目的是进一步降低复杂性和魔力(译者注:Caliburn.Micro有很多让人抓狂的约定,看起来像魔法,这对新手而言一点都不友好),让不熟悉任何MVVM框架的人(同事)更快地跟上速度。
它还提供了Caliburn.Micro中不可用的功能,包括自己的IoC容器,简单的ViewModel验证,甚至是与MVVM兼容的MessageBox。
低LOC数量和非常全面的测试套件使其成为使用和验证/验证SOUP具有高开销的项目的一个有吸引力的选择,其模块化工具包架构意味着它很容易使用你喜欢的部分,或者替换你不喜欢的部分。
2.Prism与Stylet对比
Prism 优势:
- 模块化架构: Prism 鼓励使用模块化架构,可以将应用程序拆分为独立的模块,每个模块有自己的视图、视图模型和服务。这有助于分离关注点,提高应用程序的可维护性。
- 事件聚合器: Prism 提供了事件聚合器,允许模块之间进行松耦合的通信。模块可以发布和订阅事件,从而实现解耦的通信方式。
- 导航支持: Prism 提供了强大的导航支持,可以管理复杂的导航流程和导航参数。
- 依赖注入: Prism 集成了依赖注入容器,可以帮助管理应用程序中的依赖关系,使代码更加可测试和可扩展。
- 文档和社区支持: Prism 有较多的文档和社区支持,有大量的教程、示例和解答。
Prism 劣势:
- 学习曲线: 由于 Prism 提供了丰富的功能,初学者可能需要一些时间来掌握其概念和用法。
- 较大的库: 由于 Prism 提供了许多功能,它的库大小相对较大,这可能会在一些资源受限的项目中造成一些影响。
Stylet 优势:
- 轻量级: Stylet 是一个相对轻量级的框架,专注于提供基本的 MVVM 支持,适用于小到中型的应用程序。
- 简单直观: Stylet 的设计使其非常简单直观,对于那些不需要复杂功能的项目来说,它可以更容易上手。
- 性能: 由于 Stylet 较为精简,可能在某些情况下具有更好的性能表现。
Stylet 劣势:
- 功能有限: Stylet 的功能相对较少,特别是在涉及复杂的模块化、导航和事件通信时可能会有限制。
- 缺乏广泛的社区支持: 相比于 Prism,Stylet 的社区规模可能较小,因此找到解决方案和资源可能会更具挑战性。
3.创建stylet程序
3.1创建一个WPF程序,添加stylet引用
3.2创建一个Bootstrapper.cs的类并编辑
using Stylet;
using System;
using System.Windows.Threading;
using System.Windows;
using System.Data;
using WPF_Style.ViewModel;
namespace WPF_Style
{
public class Bootstrapper : Bootstrapper<ViewModel.ViewModels.MainViewModel>
{
}
}
3.3更改App.xaml文件
<Application x:Class="WPF_Style.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WPF_Style"
xmlns:s="https://github.com/canton7/Stylet">
<Application.Resources>
<s:ApplicationLoader>
<s:ApplicationLoader.Bootstrapper>
<local:Bootstrapper />
</s:ApplicationLoader.Bootstrapper>
</s:ApplicationLoader>
</Application.Resources>
</Application>
3.4创建Views文件与ViewModels进行自动绑定使用
3.5编写xaml代码与C#代码
<Window
x:Class="WPF_Style.Views.MainView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:WPF_Style.Views"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:s="https://github.com/canton7/Stylet"
Title="MainView"
Width="800"
Height="450"
mc:Ignorable="d">
<Grid>
<TextBox
Width="165"
Height="39"
Margin="291,110,0,0"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Text="{Binding UserName}"
TextWrapping="Wrap" />
<Button
Width="109"
Height="39"
Margin="319,178,0,0"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Click="{s:Action btnClick}"
Content="刷新" />
</Grid>
</Window>
using Stylet;
using System;
namespace WPF_Style.ViewModels
{
public class MainViewModel : Screen
{
public MainViewModel()
{
}
#region 属性
private string _userName = "2222";
public string UserName
{
get { return _userName; }
set { SetAndNotify(ref _userName, value); }
}
#endregion
#region 事件
public void btnClick(object sender, EventArgs e)
{
UserName = "123456";
}
#endregion
}
}
4.增强用法
4.1IOC注入
public class Bootstrapper : Bootstrapper<MainViewModel>
{
protected override void OnStart()
{
// This is called just after the application is started, but before the IoC container is set up.
// Set up things like logging, etc
}
protected override void ConfigureIoC(IStyletIoCBuilder builder)
{
// Bind your own types. Concrete types are automatically self-bound.
//builder.Bind<ITestService>().To<TestService>().InSingletonScope();
//builder.AddModule<FeatureSetModule>()
}
protected override void Configure()
{
// This is called after Stylet has created the IoC container, so this.Container exists, but before the
// Root ViewModel is launched.
// Configure your services, etc, in here
}
protected override void OnLaunch()
{
// This is called just after the root ViewModel has been launched
// Something like a version check that displays a dialog might be launched from here
}
protected override void OnExit(ExitEventArgs e)
{
// Called on Application.Exit
}
protected override void OnUnhandledException(DispatcherUnhandledExceptionEventArgs e)
{
// Called on Application.DispatcherUnhandledException
}
}
private readonly ITestService _test;
private readonly IModule _module;
public MainViewModel(ITestService test,IModule module)
{
_test = test;
_module = module;
_module.Initialize();
}
4.2命令绑定
<Button
Width="109"
Height="39"
Command="{s:Action btnClick}"
Content="按钮1" />
<Button
Width="109"
Height="39"
Click="{s:Action btnClick}"
Content="按钮2" />
<Button
Width="109"
Height="39"
s:View.ActionTarget="{Binding MainViewModel}"
Command="{s:Action btnClick}"
Content="按钮3" />
4.3订阅-发布模式
实现订阅-发布模式通常是通过使用内置的事件聚合器(Event Aggregator)来实现的。事件聚合器允许不同的部分(例如视图模型)在没有直接依赖的情况下进行通信,从而实现松耦合。
定义事件:
public class MyEvent
{
public string Message { get; set; }
}
订阅者视图模型:
using Stylet;
public class SubscriberViewModel : Screen, IHandle<MyEvent>
{
private readonly IEventAggregator _eventAggregator;
public string ReceivedMessage { get; set; }
public SubscriberViewModel(IEventAggregator eventAggregator)
{
_eventAggregator = eventAggregator;
_eventAggregator.Subscribe(this); // 订阅事件
}
// 实现 IHandle<MyEvent> 接口的方法,处理事件
public void Handle(MyEvent message)
{
ReceivedMessage = message.Message;
}
}
发布者视图模型:
using Stylet;
public class PublisherViewModel : Screen
{
private readonly IEventAggregator _eventAggregator;
public PublisherViewModel(IEventAggregator eventAggregator)
{
_eventAggregator = eventAggregator;
}
public void PublishEvent()
{
var myEvent = new MyEvent
{
Message = "Hello from the publisher!"
};
_eventAggregator.Publish(myEvent); // 发布事件
}
}
4.4IScreen和Screen
IScreen是一个接口,用于标识一个类作为视图模型。
- IScreenState:用于激活、停用和关闭 ViewModel。具有Activate 、Deactivate 和 Close方法,以及用于跟踪屏幕状态更改的事件和属性。
- IGuardClose:用于询问 ViewModel 是否可以关闭。有一个方法CanCloseAsync。
- IViewAware:有时 ViewModel 需要了解其视图(何时附加、它是什么等)。此接口通过属性View和方法AttachView允许这样做。
- IHaveDisplayName:有一个DisplayName属性。这个名称被用作使用窗口管理器显示的窗口和对话框的标题,对于像TabControls这样的东西也很有用。
- IChild:对于 ViewModel 来说,知道 Conductor 在管理它的是什么(例如,请求关闭它)可能是有利的。如果 ViewModel 实现了IChild ,它将被告知这一点。
Screen有一些虚拟方法,如果你愿意,我们鼓励你覆盖:
- OnInitialActivate:第一次激活屏幕时调用,并且永远不会再调用。对于设置您不想在构造函数中设置的内容非常有用。
- OnActivate:在屏幕激活时调用。仅当屏幕尚未激活时才会被调用。
- OnDeactivate:在屏幕停用时调用。仅当屏幕尚未停用时才会被调用。
- OnClose:在屏幕关闭时调用。只会被调用一次。仅在屏幕停用时调用。
- OnViewLoaded:在触发 View 的Loaded事件时调用。
- CanCloseAsync:当Conductor想知道Screen是否可以关闭时调用,默认情况下,返回Task.FromResult(this.CanClose).但您可以在此处添加自己的异步逻辑。
- CanClose:默认情况下调用CanCloseAsync。如果要决定是否可以同步关闭,请覆盖CanClose 。如果要异步决定,请覆盖 CanCloseAsync。
- RequestClose(bool? dialogResult = null):当您想向自己的Conductor请求关闭时,您可以调用此方法。如果需要在对话框中显示,则使用 DialogResult 参数。
Screen 派生自PropertyChangedBase,因此很容易引发 PropertyChanged 通知。
4.5Conductor
Conductor
:Conductor
是最基本的 Conductors 类型,它实现了IScreen
接口,并提供了管理子视图模型的基本方法。- 您可以使用
ActivateItem
方法来激活一个子视图模型。通过激活子视图模型,它将被显示在 UI 中,并且OnActivate
方法将会被调用。 - 使用
DeactivateItem
方法可以停用子视图模型,并将其从 UI 中移除。OnDeactivate
方法将会被调用。
Conductor.OneActive
:Conductor.OneActive
是Conductor
的一种特殊类型。它专门用于管理多个子视图模型,但只允许一个活动的子视图模型显示。- 当激活一个新的子视图模型时,之前的子视图模型将被停用并从 UI 中移除。
Conductor.Collection.OneActive
:Conductor.Collection.OneActive
是用于管理一组子视图模型的 Conductors 类型。- 它允许您在一个 UI 容器中显示多个子视图模型,但只允许一个子视图模型是活动的。
- 您可以使用
Items
属性来管理子视图模型的集合。
代码示例:
using Stylet;
public class ParentViewModel : Conductor<IScreen>.Collection.OneActive
{
public void ShowChildViewModel()
{
var childViewModel = new ChildViewModel();
ActivateItem(childViewModel); // 激活子视图模型
}
}
public class ChildViewModel : Screen
{
// 子视图模型的属性和逻辑
}
<Window x:Class="MyNamespace.ConductorViewModel"
xmlns:s="https://github.com/canton7/Stylet" ....>
<ContentControl s:View.Model="{Binding ActiveItem}"/>
</Window>
4.6BindableCollection
BindableCollection
继承自**System.Collections.ObjectModel.ObservableCollection
**,因此它继承了 ObservableCollection 的功能,如自动通知界面更改。
• 新增方法:AddRange,RemoveRange,Refresh
• 线程安全
代码示例:
using Stylet;
using System.Collections.ObjectModel;
public class MyViewModel : Screen
{
private readonly BindableCollection<string> _items = new BindableCollection<string>();
public BindableCollection<string> Items
{
get { return _items; }
}
public MyViewModel()
{
_items.Add("Item 1");
_items.Add("Item 2");
_items.Add("Item 3");
}
public void AddNewItem()
{
_items.Add("New Item");
}
public void RemoveItem(string item)
{
_items.Remove(item);
}
}
4.7ValidationException
ValidationException
是一个异常类,用于表示数据验证失败时的异常。当使用数据绑定或验证时,如果数据不符合预期的规则或条件,就可以引发 ValidationException
来表示出现了验证错误。
代码示例:
using Stylet;
using System;
public class MyViewModel : Screen
{
private string _name;
public string Name
{
get { return _name; }
set
{
if (string.IsNullOrWhiteSpace(value))
{
throw new ValidationException("Name cannot be empty.");
}
_name = value;
}
}
public void Save()
{
try
{
// 在保存数据之前,进行数据验证
Name = "";
// 保存数据逻辑
Console.WriteLine("Data saved successfully.");
}
catch (ValidationException ex)
{
Console.WriteLine($"Validation error: {ex.Message}");
}
}
}
4.8StyletIoC
StyletIoC
是 “Stylet” 框架中的一个轻量级的依赖注入容器。它允许您管理应用程序中的依赖关系,并在需要时自动解析和注入这些依赖关系。使用 StyletIoC
,您可以实现松散耦合、可测试和可维护的应用程序架构。
- 注册服务:
- 您可以使用
StyletIoC
的Builder
对象来注册服务和它们的实现。 - 通常,在应用程序的启动代码中,您会初始化
StyletIoC
容器,并使用.Bind<T>().To<TImplementation>()
或其他方法来注册您的服务。
- 您可以使用
- 解析依赖:
- 当您需要使用某个服务时,您可以通过
StyletIoC
来解析它。只需从容器中请求所需的服务类型即可。
- 当您需要使用某个服务时,您可以通过
- 生命周期管理:
StyletIoC
支持不同的生命周期管理选项,如瞬态(Transient)、单例(Singleton)等。- 您可以使用
.InSingletonScope()
或.InTransientScope()
方法来设置服务的生命周期。
- 构造函数注入:
StyletIoC
支持通过构造函数进行依赖注入。当您创建视图模型或服务实例时,构造函数中声明的依赖将会自动被解析和注入。
代码示例:
using Stylet;
public class MyService
{
public string GetMessage()
{
return "Hello from MyService!";
}
}
public class MyViewModel : Screen
{
private readonly MyService _myService;
public MyViewModel(MyService myService)
{
_myService = myService;
}
public string DisplayMessage => _myService.GetMessage();
}
public class Bootstrapper : Bootstrapper<ShellViewModel>
{
protected override void ConfigureIoC(IStyletIoCBuilder builder)
{
builder.Bind<MyService>().ToSelf().InSingletonScope();
}
}