关于
官网:
A small, yet powerful framework, designed for building applications across all XAML platforms. Its strong support for MVVM patterns will enable you to build your solution quickly, without the need to sacrifice code quality or testability.
用于创建各类型的XAML平台应用的精简而又强大的框架。强力支持MVVM类的模式,使你的项目更快的建立,并且不牺牲代码质量以及可测试性。
官网:https://caliburnmicro.com
是一个以ViewModel为中心的MVVM框架
框架基本使用
引用
下载这两个都是
入口改造:
BooterStrapperBase
新建一个入口文件:StartUp,继承类BooterStrapperBase
启动ViewModel 的 页面是View
默认约定 MainViewModel --> MainView
public class StartUp:BootstrapperBase
{
public StartUp()
{
// 初始化
this.Initialize();
}
protected override async void OnStartup(object sender, StartupEventArgs e)
{
// 启动ViewModel 的 页面是View
// 默认约定 MainViewModel --> MainView
// 启动
await DisplayRootViewForAsync<MainViewModel>();
}
}
在APP中跳转给删掉,然后构造函数中调用:
public App()
{
new StartUp();
}
通知属性与命令
基本绑定:
属性绑定(PropertyChangedBase)
ViewModel继承类PropertyChangedBase
public class MainViewModel : PropertyChangedBase
{
private int _value = 50;
public int Value
{
get { return _value; }
set
{
//_value = value;
// 通知属性
//NotifyOfPropertyChange();
Set<int>(ref _value, value);
// 所有属性都通知 刷新
//this.Refresh();
// 这里通知按钮检查使用状态 条件就是CanButtonText
NotifyOfPropertyChange(() => CanButtonTest);
}
}
}
通知属性:以下三种方式都可以用
NotifyOfPropertyChange();
------------------------
Set<int>(ref _value, value);
------------------------
// 所有属性都通知 刷新
//this.Refresh();
页面绑定方式:
使用 Name 绑定 或者 Binding
建议使用 Name
<TextBlock Text="{Binding Value}" />
<TextBlock Name="Value" />
命令:事件->命令
也是根据Name 绑定,没有Command
<Button Content="Button" Name="ButtonTest"/>
C#代码:直接写方法名称即可
public void ButtonTest()
{
Value++;
}
传值绑定:利用 behaviors 进行传值
<Button Content="事件命令">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<cm:ActionMessage MethodName="EventAction">
<cm:Parameter Value="1"/>
<cm:Parameter Value="2"/>
<cm:Parameter Value="3"/>
</cm:ActionMessage>
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>
C#代码:可以直接获取参数
public void EventAction(int a,int b,int c)
{
}
命令(是否可执行)
因为命令绑定还是通过name来绑定的,所以可以写个方法来判断是否可以执行按钮:
命名规范:Can + Name名称
public bool CanButtonTest { get => Value < 60; }
但是这个按钮,需要被调用,所以在value set的时候调用:
private int _value = 50;
public int Value
{
get { return _value; }
set
{
Set<int>(ref _value, value);
// 这里通知按钮检查使用状态 条件就是CanButtonText
NotifyOfPropertyChange(() => CanButtonTest);
}
}
智能对象参数
这里如果TextBox的Name和AAAAction方法中的参数名称一模一样,可以直接映射到参数中
<TextBox Text="12" Name="aaa" />
<Button Content="智能对象参数--命令" Name="AAAAction"/>
public void AAAAction(int aaa)
{
}
短语法
<!--短语法-命令:写法
可以方法小括号+参数-->
<TextBox Text="12" Name="tb" />
<Button Content="短语法--命令"
cm:Message.Attach="[Event Click] = [Action EventAction(1,2,tb.Text)]"/>
短语法代码解释:
<Button Content="短语法--命令" ... />
:这是一个WPF中的按钮控件,Content
属性设置了按钮上显示的文本为“短语法--命令”。cm:Message.Attach
:这是Caliburn.Micro框架提供的一个附加属性(Attached Property),用于将XAML元素的事件绑定到视图模型中的方法或命令上。注意,这里的cm
是一个命名空间前缀,它需要在XAML文件的顶部通过xmlns:cm="clr-namespace:Caliburn.Micro;assembly=Caliburn.Micro"
这样的声明来指定Caliburn.Micro命名空间的URI。[Event Click]
:这指定了要绑定的事件是按钮的点击(Click)事件。Caliburn.Micro允许开发者通过简短的语法来指定事件类型,而不是使用传统的XAML事件处理器。[Action EventAction(1,2,tb.Text)]
:这指定了当点击事件发生时,应该调用视图模型中的EventAction
方法,并传递三个参数:两个整数1
和2
,以及tb.Text
。Caliburn.Micro会尝试将这些参数与视图模型中的方法参数进行匹配,并自动调用该方法。
特殊参数 $ + 命令
- $eventArgs:事件参数
<Button Content="短语法--命令--$eventArgs"
cm:Message.Attach="[Event Click] = [Action EventAction($eventArgs)]"/>
- $dataContext:当前控件的DataContext
<Button Content="短语法--命令--$dataContext"
cm:Message.Attach="[Event Click] = [Action EventAction($dataContext)]"/>
- $view:对应的View对象 可以直接操作控件
<Button Content="短语法--命令--$view"
cm:Message.Attach="[Event Click] = [Action EventAction($view)]"/>
- $this:当前界面的ViewModel
<Button Content="短语法--命令--$this"
cm:Message.Attach="[Event Click] = [Action EventAction($this)]"/>
- 自定义参数
自定义参数需要在StartUp其实页构造函数中自定义:
StartUp代码:
public StartUp()
{
// 初始化
this.Initialize();
// 自定义命令参数 只能写在ViewModel 和项目起始中使用,不可以在View中使用
MessageBinder.SpecialValues.Add("$xiaoHai", OnXiaoHaiValue);
}
private object OnXiaoHaiValue(ActionExecutionContext context)
{
// 返回自定义参数
return "Little Sea";
}
XAML中使用:
<Button Content="短语法--命令--自定义$xiaoHai"
cm:Message.Attach="[Event Click] = [Action EventAction($xiaoHai)]"/>
辅助对象
事件聚合器:IEventAggregator/Ihandle
用来做消息传递
使用事件聚合器的时候 需要在起始页注入IOC容器:
重写这两个方法:Configure GetInstance
protected override void Configure()
{
// 注册IOC容器
_container = new SimpleContainer();
// 注册接口和实现
this._container.Singleton<IEventAggregator, EventAggregator>();
this._container.Singleton<IWindowManager, WindowManager>();
// 注册ViewModel
this._container.PerRequest<MainViewModel>();
}
protected override object GetInstance(Type service, string key)
{
// 解析ViewModel之前 获取实例
return this._container.GetInstance(service, key);
}
或者使用静态方法:
IoC.Get<IEventAggregator>().SubscribeOnUIThread(this);
订阅 需要 IEventAggregator ,注入或者静态方法都行
// 订阅按照哪种线程进行执行订阅逻辑
// 永远在后台线程执行
//eventAggregator.SubscribeOnBackgroundThread(this);
// 在发布者的线程中执行
//eventAggregator.SubscribeOnPublishedThread(this);
// 永远在UI线程中执行
//eventAggregator.SubscribeOnUIThread(this);
发布:
public async void Send()
{
// 点击按钮的时候 执行发布动作
// 消息内容是根据消息类型进行匹配的
await _eventAggregator.PublishOnCurrentThreadAsync(new Message { Callback = OnCallback });
//await _eventAggregator.PublishOnCurrentThreadAsync("Hello");
//_eventAggregator.PublishOnBackgroundThreadAsync(this);
//_eventAggregator.SubscribeOnUIThread(this);
}
接受信息:需要继承接口:IHandle<T>
注意:如果需要接受多个参数的订阅 就写多个接口即可
Object:类型:什么类型的都可以接受
实现方法:
public Task HandleAsync(Message message, CancellationToken cancellationToken)
{
// 最终的执行线程并不是由发布者决定的 是由订阅者决定
var id = Thread.CurrentThread.ManagedThreadId;
return Task.CompletedTask;
}
执行逻辑:
先订阅--> 触发-->接受
可以根据泛型T,来实现委托的等复杂操作;
案例:
接受之后,返回给触发方一个结果:
数据类型:
public class Message
{
public Action<int> Callback { get; set; }
}
订阅方:
eventAggregator.Subscribe(this);
触发:
public async void Send()
{
await _eventAggregator.PublishOnCurrentThreadAsync(new Message { Callback = OnCallback });
}
private void OnCallback(int obj)
{
}
接受:
public Task HandleAsync(Message message, CancellationToken cancellationToken)
{
// 有数据返回到发布方
// 被动触发,触发之后,经过逻辑处理,希望有个数据返回到调用方
int value = 789;
message.Callback?.Invoke(value);
return Task.CompletedTask;
}
同过委托把参数789传给触发方。
弹窗:ViewModel
窗口管理器:IWindowManager
中不能直接操作View层的实例 new SubWindow()
需要注入:窗口管理器:IWindowManager
-- 是直接操作ViewModel 然后系统自动反射出View的。不过ViewModel 需要IOC注入
public async void Open()
{
// 触发打开另一个窗口
// 打开一个模态窗口 卡线程
// 这里也可以使用注入的方式 传值
var vm = IoC.Get<AViewModel>();
vm.Title = "Title";
var result = await _windowManager.ShowDialogAsync(vm);
if (result == true)
{
Value = vm.Value;
}
// 打开一个一般的窗口 和Show一样
//_windowManager.ShowWindowAsync(this);
// 打开一个Popup
//_windowManager.ShowPopupAsync(this);
}
同过VM进行拿弹窗的值,或者取弹窗的值
Screen
弹窗可以继承Screen类,来实现返回值的关闭时dialogResult的true 或者 false
public class AViewModel : Screen
{
public void OnClose()
{
// 通过这个方法来判断返回的是true 还是false
this.TryCloseAsync(true);
}
}
生命周期
前提需要继承:Screen 类
- 页面加载
protected override void OnViewLoaded(object view)
{
base.OnViewLoaded(view);
}
- 准备加载 在 OnViewLoaded 之前触发
protected override void OnViewReady(object view)
{
base.OnViewReady(view);
}
- 页面处于激活窗口的时候
protected override Task OnActivateAsync(CancellationToken cancellationToken)
{
return base.OnActivateAsync(cancellationToken);
}
- 离开正在激活窗口
protected override Task OnDeactivateAsync(bool close, CancellationToken cancellationToken)
{
return base.OnDeactivateAsync(close, cancellationToken);
}
- 是否能够关闭窗口
public override Task<bool> CanCloseAsync(CancellationToken cancellationToken = default)
{
return base.CanCloseAsync(cancellationToken);
}
单区域或者多页面显示:Conductor<T>
所有页面用同一个接口进行管理:每个页面都需要继承这个接口 方便IOC注入
public interface IChildViewModel{ }
注册IOC界面:
protected override void Configure()
{
// 注册IOC容器
_container = new SimpleContainer();
// 注册接口和实现
this._container.Singleton<IEventAggregator, EventAggregator>();
this._container.Singleton<IWindowManager, WindowManager>();
// 注册ViewModel
this._container.PerRequest<MainViewModel>();
// 多页面注册 注意Key ,通过Key找到对应的ViewModel
this._container.Singleton<IChildViewModel, AViewModel>("A");
this._container.Singleton<IChildViewModel, BViewModel>("B");
this._container.Singleton<IChildViewModel, CViewModel>("C");
}
在调用的地方MainView中写入按钮触发:
因为需要用到一个方法,所以需要使用到CM中的附加属性Action
注意传参,这个参数切记和IOC注入的时候Key保持一致,一会直接通过参数取对应的ViewModel
<StackPanel>
<Button Content="打开 A View" Margin="5" cm:Message.Attach="[Event Click] = [Action ShowPage('A')]"/>
<Button Content="打开 B View" Margin="5" cm:Message.Attach="[Event Click] = [Action ShowPage('B')]"/>
<Button Content="打开 C View" Margin="5" cm:Message.Attach="[Event Click] = [Action ShowPage('C')]"/>
</StackPanel>
<!--单页显示:ContentControl 用来显示被激活的内容
ActiveItem:这个属性自动绑定被激活的内容-->
<ContentControl x:Name="ActiveItem" Grid.Column="1" Margin="10" />
<!--多页显示:TabControl 用来显示被激活的内容
Items:这个属性自动绑定被激活的内容-->
<!--<TabControl x:Name="Items" Grid.Column="1" Margin="10" />-->
MainViewModel:
- 如果使用的是ContenControl的时候 --> Conductor<object>
- 使用TabControl --> Conductor<object>.Collection.OneActive
- OneActive:只有一个是被激活的对象
- AllActive:所有的页面都是激活的,没有失去激活状态切换
单页面模式:
public class MainViewModel:Conductor<object>
{
public async void ShowPage(string name)
{
// 通过key获取对应的viewModel
var vm = IoC.Get<IChildViewModel>(name);
await this.ActivateItemAsync(vm);
}
}
多页面模式:
public class MainViewModel:Conductor<object>.Collection.OneActive
{
public async void ShowPage(string name)
{
// 通过key获取对应的viewModel
var vm = IoC.Get<IChildViewModel>(name);
await this.ActivateItemAsync(vm);
}
}
协同任务组件:IResult
导航:INavigationService
自定义View匹配:ViewLocator
默认的是View结尾的界面和ViewModel 匹配
public StartUp()
{
// 初始化
this.Initialize();
ViewLocator.LocateForModelType = new Func<Type, DependencyObject, object, UIElement>(OnLocateForModelType);
}
// 通过类型匹配ViewModel
private UIElement OnLocateForModelType(Type type, DependencyObject d, object obj)
{
// type 就是当前注入的 ViewModel
string name = type.FullName;
name = name.Replace(".ViewModels", ".Views");
if (name.EndsWith("Model"))
name = name.Substring(0, name.Length - 5);
// 获取到对应的View然后 创建实例并且返回
var t = Assembly.GetExecutingAssembly().GetType(name);
return (UIElement)Activator.CreateInstance(t);
}