- Prism程序入口、View ViewModel关联、数据绑定、数据校验、cmd
Prism程序入口、View ViewModel关联、数据绑定、数据校验、cmd
这是一篇朝夕Jovan老师讲Prism的课堂学习笔记。
关于Prism框架
Prism.Core:【Prism.dll】实现MVVM的核心功能,属于一个与平台无关的项目
Prism.Wpf:【Prism.Wpf】包含了DialogService,Region,Module,Navigation,其他的一些WPF的功能
Prism.Unity:【Prism.Unity.Wpf】,IOC容器
Prism.Unity=>Prism.Wpf=>Prism.Core
创建启动程序
第一种初始化方式:8.0版本以前只能使用PrismBootstrapper
- 添加启动类:Bootstrapper
public class Bootstrapper : PrismBootstrapper { protected override DependencyObject CreateShell() { return Container.Resolve<FirstWindow>(); } protected override void RegisterTypes(IContainerRegistry containerRegistry) { } }
- 修改App.cs
public partial class App { protected override void OnStartup(StartupEventArgs e) { base.OnStartup(e); var bs = new Bootstrapper(); bs.Run(); } }
第二种初始化方式:8.0版本开始提供一种新的方式PrismApplication【全局资源管控,项目启动】
两者类似,只不过把Bootstrapper类的东西,拿到App.cs实现
- App.xaml 中删除程序的入口:
StartupUrl="Main.xaml"
- App.xaml 中引入Prism名称空间
<prism:PrismApplication x:Class="PrismLesson.App" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:Zhaoxi.PrismLesson" xmlns:prism="http://prismlibrary.com/" > <Application.Resources> </Application.Resources> </prism:PrismApplication>
- 修改App.cs,继承PrismApplication,并重写CreateShell方法
public partial class App : PrismApplication { protected override Window CreateShell() { return Container.Resolve<FirstWindow>(); } protected override void RegisterTypes(IContainerRegistry containerRegistry) { } }
View与ViewModel的多种关联方法
使用方法
- View中引入名称空间:
xmlns:prism="http://prismlibrary.com/"
- 设置为自动关联:
prism:ViewModelLocator.AutoWireViewModel="True"
注意事项:
- 必须是Views和ViewModels目录
- 需要保证命名规范的正确性
- View可以以View结尾,也可以不写。
- ViewModel必须以View的名称+”ViewModel”进行命名
自定义ViewModel文件后缀规则
App.cs 中重写ConfigureViewModelLocator方法
- View的名字
- 在那个名称空间
- ViewModel的名字
protected override void ConfigureViewModelLocator()
{
base.ConfigureViewModelLocator();
//自定义ViewModel文件后缀,自动匹配
//第一种方式
//ViewModelLocationProvider.SetDefaultViewTypeToViewModelTypeResolver((viewType) =>
//{
//两个参数表示在那个名称空间
// var viewName = viewType.FullName.Replace("YtViews","YtViewmodels");
// var viewAssemblyName = viewType.GetType().Assembly.FullName;
// var viewModelName = $"{viewName}VM,{viewAssemblyName}";
// return Type.GetType(viewModelName);
//});
//一对一注册的多种方式 01
//ViewModelLocationProvider.Register(typeof(MainWindow).ToString(),typeof(MainWindowViewModel));
//02
//ViewModelLocationProvider.Register(typeof(MainWindow).ToString(),
// () =>Container.Resolve<MainWindowViewModel>()
//);
//03
//ViewModeegisterlLocationProvider.R<MainWindow,M>();ainWindowViewModel
}
一对一注册
三种代码注册方式:第三种方式最为简洁,使用1对1注册的时候,就可以取消掉prism:ViewModelLocator.AutoWireViewModel="True"
。
protected override void ConfigureViewModelLocator()
{
base.ConfigureViewModelLocator();
//01 第一种方式
ViewModelLocationProvider.Register(typeof(FirstWindow).ToString(), typeof(FirstWindowVM));
//02
//ViewModelLocationProvider.Register(typeof(MainWindow).ToString(),
// () =>Container.Resolve<MainWindowViewModel>()
//);
//03
//ViewModelLocationProvider.Register<FirstWindow, FirstWindowVM>();
}
Xaml注册方式
- 引入命名空间:xmlns:vm="clr-namespace:Zhaoxi.PrismLesson.ZxViewModels"
- 设置关联
<Window.DataContext>
<prism:ContainerProvider Type="{x:Type vm:FirstWindowVM}"/>
</Window.DataContext>
MVVM数据属性的多种方式
- 继承BindableBase
- 实现数据绑定
第一种方式:RaisePropertyChanged()
public string MyProperty
{
get { return myVar; }
set
{
myVar = value;
//this.RaisePropertyChanged();
//改变的时候通知其他属性
this.RaisePropertyChanged("其他属性名称");
}
}
第二种方式:SetProperty
写了SetProperty,就可以删除 myVar = value,因为ref.
//思考:为什么不直接在这里写值改变后的处理,SetProperty方法内的回调对值是否改变做了判断,只有真正改变了才会执行回调,直接写SetProperty后面,则每次无论是否改变成功都会执行。
public string MyProperty
{
get { return myVar; }
set
{
// 第二种方式
//this.SetProperty(ref myVar, value, "MyProperty");
//多了一个回调,变化之后调用
this.SetProperty<string>(ref myVar, value,
() =>
{
// onChanged
});
//思考:为什么不直接在这里写值改变后的处理,SetProperty方法内的回调对值是否改变做了判断,只有真正改变了才会执行回调,直接写SetProperty后面,则每次无论是否改变成功都会执行。
}
}
MVVM数据验证
- 继承INotifyDataErrorInfo接口,实现接口:
public class FirstWindowVM : BindableBase, INotifyDataErrorInfo
{
/// <summary>
/// INotifyDataErrorInfo 接口方法
/// </summary>
//
//错误变化 错误属性中调用这个事件
public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;
//检查是否有错误
public bool HasErrors => ErrorsContainer.HasErrors;
//获取错误
public IEnumerable GetErrors(string propertyName) => ErrorsContainer.GetErrors(propertyName);
}
- 实现一个ErrorsContainer属性
//OnPropertyChanged;
// CanExecuteChanged;
private ErrorsContainer<string> errorsContainer;
public ErrorsContainer<string> ErrorsContainer
{
get
{
if (errorsContainer == null)
errorsContainer = new ErrorsContainer<string>((propName) =>
{
// 异常信息的处理
ErrorsChanged?.Invoke(this, new DataErrorsChangedEventArgs(propName));
});
return errorsContainer;
}
set { errorsContainer = value; }
}
- 对数据进行错误处理
public string MyProperty
{
get { return myVar; }
set
{
this.SetProperty(ref myVar, value, "MyProperty");
if (value == "1231")
{
// 异常消息
ErrorsContainer.SetErrors("MyProperty", new string[] { "输入值无效1231231" });
}
else
{
ErrorsContainer.ClearErrors("MyProperty");
}
}
}
- UI错误提示样式
<Window.Resources>
<ControlTemplate TargetType="{x:Type TextBox}" x:Key="ct">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition Height="auto"/>
</Grid.RowDefinitions>
<Border x:Name="border" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}"
Background="{TemplateBinding Background}" SnapsToDevicePixels="True"
CornerRadius="5">
<ScrollViewer x:Name="PART_ContentHost" Focusable="false" HorizontalScrollBarVisibility="Hidden" VerticalScrollBarVisibility="Hidden"
VerticalContentAlignment="Center" Margin="3,5" BorderThickness="0"/>
</Border>
<TextBlock Grid.Row="1" Text="{Binding (Validation.Errors)[0].ErrorContent,RelativeSource={RelativeSource AncestorType=TextBox,Mode=FindAncestor}}"
Foreground="Red" Margin="10,5"
Name="txtError"/>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="Validation.HasError" Value="True">
<Setter Property="Visibility" Value="Visible" TargetName="txtError"/>
<Setter Property="ToolTip"
Value="{Binding RelativeSource={RelativeSource Self}, Path=(Validation.Errors)[0].ErrorContent}"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Window.Resources>
<Grid>
<StackPanel Margin="30">
<TextBox Text="{Binding MyProperty,UpdateSourceTrigger=PropertyChanged}" FontSize="30"
Template="{StaticResource ct}"/>
<TextBlock Text="{Binding MyProperty}" FontSize="30"/>
</StackPanel>
</Grid>
MVVM命令的多种方式
语法糖
- cmd 基本使用
private DelegateCommand _fieldName;
public DelegateCommand CommandName =>
_fieldName ?? (_fieldName = new DelegateCommand(ExecuteCommandName));
void ExecuteCommandName()
{
}
- cmdfull
private DelegateCommand _fieldName;
public DelegateCommand CommandName =>
_fieldName ?? (_fieldName = new DelegateCommand(ExecuteCommandName, CanExecuteCommandName));
void ExecuteCommandName()
{
}
bool CanExecuteCommandName()
{
return true;
}
- cmdg
private DelegateCommand<string> _fieldName;
public DelegateCommand<string> CommandName =>
_fieldName ?? (_fieldName = new DelegateCommand<string>(ExecuteCommandName));
void ExecuteCommandName(string parameter)
{
}
- cmdgfull
private DelegateCommand<string> _fieldName;
public DelegateCommand<string> CommandName =>
_fieldName ?? (_fieldName = new DelegateCommand<string>(ExecuteCommandName, CanExecuteCommandName));
void ExecuteCommandName(string parameter)
{
}
bool CanExecuteCommandName(string parameter)
{
return true;
}
基础使用
传统方式挂载执行委托、检查可执行委托。
默认直接执行cmd
- 继承基类DelegateCommand
- 实现命令属性
private DelegateCommand _changeValue;
public DelegateCommand ChangeValue
{
get
{
if (_changeValue == null)
_changeValue = new DelegateCommand(DoChangeValue);
return _changeValue;
}
set{_changeValue = value;}
}
private void DoChangeValue()
{
this.Value == "1231";
}
默认判断是否执行cmd
IsCan为true,才可以执行命令。
private DelegateCommand _changeValue;
public DelegateCommand ChangeValue
{
get
{
if (_changeValue == null)
_changeValue = new DelegateCommand(DoChangeValue, DoCanChangeValue);
return _changeValue;
}
set{_changeValue = value;}
}
// 判断命令相关按钮是否可执行
private bool DoCanChangeValue()
{
return IsCan;
}
// 命令执行体
private void DoChangeValue(string param)
{
this.Value = "456";
}
private bool _isCan;
public bool IsCan
{
get { return _isCan; }
set
{
SetProperty(ref _isCan, value);
ChangeValue.RaiseCanExecuteChanged();
}
}
监控属性变化,执行cmd
-
监控某个属性是否变化,如果变化出发cmd。
-
使用DelegateCommand对象的ObservesProperty方法,不需要ChangeValue.RaiseCanExecuteChanged()。
ObservesCanExecute监控的是某个属性的变化,而不是某个值。属性变化的时候Prism内部触发了
ChangeValue.RaiseCanExecuteChanged()
精简版
if (_changeValue == null)
{
_changeValue = new DelegateCommand(DoChangeValue, DoCanChangeValue);
//观察一个属性,当这个属性变化的时候触发DoCanChangeValue
_changeValue.ObservesProperty(() => Value);
}
详细版
private string _value;
public string Value
{
get { return _value; }
set { SetProperty<string>(ref _value, value); }
}
private DelegateCommand _changeValue;
public DelegateCommand ChangeValue
{
get
{
if (_changeValue == null)
{
_changeValue = new DelegateCommand(DoChangeValue,DoCanChangeValue).ObservesCanExecute(() => Value);
}
return _changeValue;
}
}
//判断命令相关按钮是否可执行
private bool DoCanChangeValue()
{
return IsCan;
}
//命令执行体
private void DoChangeValue()
{
this.Value = "456";
}
private string _value;
public string Value
{
get { return _value; }
set { SetProperty<string>(ref _value, value); }
}
监控属性是否为true,执行cmd,可以省略大量代码,但有局限性。
通过ObservesCanExecute观察属性是否为true,如果为true则执行cmd.可以删除DoCanChangeValue回调。
简化了DoCanChangeValue,但是功能性少了多了局限性。
精简版
if (_changeValue == null)
{
_changeValue = new DelegateCommand(DoChangeValue);
_changeValue.ObservesCanExecute(() => IsCan);
}
详细版
private string _value;
public string Value
{
get { return _value; }
set { SetProperty<string>(ref _value, value); }
}
private DelegateCommand _changeValue;
public DelegateCommand ChangeValue
{
get
{
if (_changeValue == null)
{
_changeValue = new DelegateCommand(DoChangeValue).ObservesCanExecute(() => IsCan);
}
return _changeValue;
}
}
//命令执行体
private void DoChangeValue()
{
this.Value = "456";
}
private bool _isCan;
public bool IsCan
{
get { return _isCan; }
set
{
SetProperty(ref _isCan, value);
}
}
带参数的cmd
int型,Prism会有错误提示,MvvmLight没有提示。
简化版
private DelegateCommand<string> _changeValue;
public DelegateCommand<string> ChangeValue
{
get
{
if (_changeValue == null)
{
_changeValue = new DelegateCommand<string>(DoChangeValue);
_changeValue.ObservesCanExecute(() => IsCan);
}
return _changeValue;
}
}
//普通
private void DoChangeValue(string param)
{
this.Value = "456";
}
if (_changeValue == null)
{
_changeValue = new DelegateCommand<string>(DoChangeValue);
_changeValue.ObservesCanExecute(() => IsCan);
}
<Button Content="Button" FontSize="30" Command="{Binding ChangeValue}" CommandParameter="123"/>
普通详细版
private string _value;
public string Value
{
get { return _value; }
set { SetProperty<string>(ref _value, value); }
}
private DelegateCommand<string> _changeValue;
public DelegateCommand<string> ChangeValue
{
get
{
if (_changeValue == null)
{
_changeValue = new DelegateCommand<string> (DoChangeValue).ObservesCanExecute(() => IsCan);
return _changeValue;
}
}
//命令执行体
private void DoChangeValue(string param)
{
this.Value = "param";
}
private bool _isCan;
public bool IsCan
{
get { return _isCan; }
set
{
SetProperty(ref _isCan, value);
}
}
异步命令
简化版
//异步
if (_changeValue == null)
{
_changeValue = new DelegateCommand<string>(async (o) => await DoChangeValue(o));
_changeValue.ObservesCanExecute(() => IsCan);
}
private async Task DoChangeValue(string param)
{
await Task.Delay(1000);
this.Value = "456";
}
详细版
private string _value;
public string Value
{
get { return _value; }
set { SetProperty<string>(ref _value, value); }
}
private DelegateCommand<string> _changeValue;
public DelegateCommand<string> ChangeValue
{
get
{
if (_changeValue == null)
{
_changeValue = new DelegateCommand<string>(async (param) => await DoChangeValue(param)).ObservesCanExecute(() => IsCan);
return _changeValue;
}
}
//命令执行体
private async Task DoChangeValue(string param)
{
await Task.Delay(1000);
this.Value = "param";
}
private bool _isCan;
public bool IsCan
{
get { return _isCan; }
set
{
SetProperty(ref _isCan, value);
}
}
事件绑定Command,事件参数传值
-
引用Interactivity\Interaction两个库中的一个,Prism默认继承Interactivity\Interaction库,
xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
-
一般不会在Button中使用,实际是向没有Command属性的对象事件进行绑定,例如TextBox。
-
参数类型:如果是Source就是把事件源对象传进来,还有多种参数类型可以F12跟进去看函数定义。
-
传递事件常见的e参数:删除TriggerParameterPath,默认传递的就是e参数
<TextBox FontSize="30" Text="{Binding Value, UpdateSourceTrigger=PropertyChanged}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="TextChanged">
<!-- 如果需要传EventArgs参数的话,可以将TriggerParameterPath移除,不指定 -->
<prism:InvokeCommandAction Command="{Binding EventCommand}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</TextBox>
xaml
xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
<Button Content="Button - Event" FontSize="30">
<!--利用Button的Click事件做演示,实际是向没有Command属性的对象事件进行绑定-->
<!--Interactivity\Interaction-->
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<!--如果需要传EventArgs参数的话,可以将TriggerParameterPath移除,不指定-->
<prism:InvokeCommandAction Command="{Binding EventCommand}"/>
<!--<prism:InvokeCommandAction Command="{Binding EventCommand}" TriggerParameterPath="Source"/>-->
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>
private DelegateCommand<object> _eventCommand;
public DelegateCommand<object> EventCommand
{
get
{
if (_eventCommand == null)
{
_eventCommand = new DelegateCommand<object>((o) =>
{
//接收到的事件参数
});
}
return _eventCommand;
}
}
事件聚合器、弹出自定义窗口
精简版
事件订阅、发布的一个过程。
- 定义一个基本消息类型MessageEvent,继承PubSubEvent
- 声明IEventAggregator 类型字段
- 构造函数中注入一个IEventAggregator
- 订阅消息事件
执行命令以后,事件执行。
主要是通过 _ea.GetEvent<PubSubEvent
>().Subscribe(DoMessage);中的PubSubEvent 判断的,所以才需要创建单独的类,甚至是空类,主要是为了方便区分。
public class MessageEvent : PubSubEvent<string>
{
}
IEventAggregator _ea;
public FirstWindowViewModel(IEventAggregator ea)
{
this.Value = "123";
_ea = ea;
// 订阅一个消息事件
_ea.GetEvent<PubSubEvent<string>>().Subscribe(DoMessage);
}
private void DoMessage(string msg)
{
}
private DelegateCommand _eventCommand;
public DelegateCommand EventCommand
{
get
{
if (_eventCommand == null)
{
_eventCommand = new DelegateCommand<object>(() =>
{
//弹出窗口
//自定义窗口
//发布
_ea.GetEvent<PubSubEvent<string>>().Publish("123");
});
}
return _eventCommand;
}
}
keepSubscriberReferenceAlive参数控制是否为弱引用
keepSubscriberReferenceAlive:订阅事件属于一个弱引用。
- 如果是false,不需要管取消订阅,默认为false.
- 如果是true,手动处理取消订阅
ea.GetEvent<MessageEvent1>().Subscribe(DoMessage1,keepSubscriberReferenceAlive: false);
// 取消订立,如果 keepSubscriberReferenceAlive 为False或默认值 不需要取消订阅
//ea.GetEvent<MessageEvent1>().Unsubscribe(DoMessage1);
ThreadOption.PublisherThread参数控制线程的
-
PublisherThread = 0, 默认是PublisherThread
-
UIThread = 1,
-
BackgroundThread = 2
ea.GetEvent<MessageEvent1>().Subscribe(DoMessage1, ThreadOption.PublisherThread, keepSubscriberReferenceAlive: false);
Predicate 对消息进行过滤Filter
对发布的事件传过来的参数进行过滤,满足要求的才会出发事件。
ea.GetEvent<MessageEvent1>().Subscribe(DoMessage1, ThreadOption.PublisherThread, keepSubscriberReferenceAlive: false, DoFilter);
private bool DoFilter(string msg)
{
return msg.Contains("12");
}
详细版
xaml代码
<Button
Command="{Binding Command}"
CommandParameter="123"
Content="Button 事件聚合触发"
FontSize="30" />
<ItemsControl FontSize="30" ItemsSource="{Binding ValueList}" />
01 继承PubSubEvent
public class MessageEvent : PubSubEvent<string>
{
public int MyProperty { get; set; }
}
public class MessageEvent1 : PubSubEvent<string>
{
}
02 订阅
private ObservableCollection<string> _valueList;
public ObservableCollection<string> ValueList
{
get { return _valueList ?? (_valueList = new ObservableCollection<string>()); }
set { _valueList = value; }
}
IEventAggregator _ea;
public FirstWindowViewModel(IEventAggregator ea, IContainerExtension containerRegistry)
{
this.Value = "123";
_ea = ea;
// 订阅一个消息事件
//ea.GetEvent<PubSubEvent<string>>().Subscribe(DoMessage);
var me = ea.GetEvent<MessageEvent>();
me.Subscribe(DoMessage);
me.MyProperty = 123;
/// ThreadOption,,默认PublisherThread,管理订立消息的执行线程
/// keepSubscriberReferenceAlive:订阅事件属于一个弱引用
/// Filter 消息过滤 ,如果回调返回True,才执行消息体,否则不处理此消息
//ThreadOption.PublisherThread控制线程的,本案例中配合ValueList和ItemsControl
ea.GetEvent<MessageEvent1>().Subscribe(DoMessage1, ThreadOption.PublisherThread, keepSubscriberReferenceAlive: false, DoFilter);
//指定在UI线程执行
//ea.GetEvent<MessageEvent1>().Subscribe(DoMessage1, ThreadOption.UIThread, keepSubscriberReferenceAlive: false, DoFilter);
// 取消订立,如果 keepSubscriberReferenceAlive 为False或默认值 不需要取消订阅
//ea.GetEvent<MessageEvent1>().Unsubscribe(DoMessage1);
}
private void DoMessage(string msg)
{
}
//UI线程执行
//private void DoMessage(string msg)
//{
//ValueList.Add("123");
//}
private bool DoFilter(string msg)
{
return msg.Contains("12");
}
03 发布
private DelegateCommand _command;
public DelegateCommand Command
{
get
{
if (_command == null)
{
_command = new DelegateCommand(() =>
{
// 弹出窗口
// 通过事件聚合器弹出自定义窗口
// 而不是普通的类似MvvmLight中的 Messenger Token Type
//_ea.GetEvent<PubSubEvent<string>>().Publish("123");
//_ea.GetEvent<MessageEvent>().Publish("123");
var value = _ea.GetEvent<MessageEvent>().MyProperty;
_ea.GetEvent<MessageEvent1>().Publish("123");
_ea.GetEvent<MessageEvent1>().Publish("456");
_ea.GetEvent<MessageEvent1>().Publish("123");
//Task.Run(() =>
//{
// _ea.GetEvent<MessageEvent1>().Publish("123");
//});
});
}
return _command;
}
}
复合命令
一个按钮,把所有相同的复合命令执行一遍。比如:保存按钮,把所有内容保存。
每个页面出发单独的保存,还有个隐藏功能,所有的命令都有个是否可执行的方法,如果设置了,那么只有所有按钮都可以执行以后,它才可以执行。
- 添加一个程序集,需要我们自定义一个接口,把它改成类库。这样复合命令就实现好了
public interface ICompositeCommands
{
CompositeCommand DoCommand { get; }
}
public class CompositeCommands : ICompositeCommands
{
private CompositeCommand _doCommand = new CompositeCommand();
public CompositeCommand DoCommand
{
get { return _doCommand; }
}
}
- App.xaml.cs中的RegisterTypes 注入
protected override void RegisterTypes(IContainerRegistry containerRegistry)
{
// 注册复合命令
containerRegistry.RegisterSingleton<ICompositeCommands, CompositeCommands>();
}
- 创建一个按钮,xaml
<Grid>
<StackPanel>
<TextBlock Text="HeaderView" FontSize="30"/>
<Button Content="Button" FontSize="30" Command="{Binding CompositeCommands.DoCommand}"/>
</StackPanel>
</Grid>
- 创建对应的ViewModule
public class HeaderViewModel : BindableBase
{
private ICompositeCommands _compositeCommand;
public ICompositeCommands CompositeCommands
{
get { return _compositeCommand; }
set { SetProperty(ref _compositeCommand, value); }
}
public HeaderViewModel(ICompositeCommands compositeCommands)
{
CompositeCommands = compositeCommands;
}
}
- 构造函数注入ICompositeCommands,将command注册到复合命令集合中。
public DelegateCommand SaveCommand { get; private set; }
public MenuManagementViewModel(ICompositeCommands compositeCommands)
{
SaveCommand = new DelegateCommand(() =>
{
// 菜单保存
});
compositeCommands.DoCommand.RegisterCommand(SaveCommand);
}