这是一篇朝夕Jovan老师讲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
- 添加启动类: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(); } }
两者类似,只不过把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) { } }
xmlns:prism="http://prismlibrary.com/"
prism:ViewModelLocator.AutoWireViewModel="True"
注意事项:
- 必须是Views和ViewModels目录
- 需要保证命名规范的正确性
- View可以以View结尾,也可以不写。
- ViewModel必须以View的名称+”ViewModel”进行命名
App.cs 中重写ConfigureViewModelLocator方法
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注册方式
<Window.DataContext> <prism:ContainerProvider Type="{x:Type vm:FirstWindowVM}"/> </Window.DataContext>
public string MyProperty { get { return myVar; } set { myVar = value; //this.RaisePropertyChanged(); //改变的时候通知其他属性 this.RaisePropertyChanged("其他属性名称"); } }
写了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后面,则每次无论是否改变成功都会执行。 } }
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); }
//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"); } } }
<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>
private DelegateCommand _fieldName; public DelegateCommand CommandName => _fieldName ?? (_fieldName = new DelegateCommand(ExecuteCommandName)); void ExecuteCommandName() { }
private DelegateCommand _fieldName; public DelegateCommand CommandName => _fieldName ?? (_fieldName = new DelegateCommand(ExecuteCommandName, CanExecuteCommandName)); void ExecuteCommandName() { } bool CanExecuteCommandName() { return true; }
private DelegateCommand<string> _fieldName; public DelegateCommand<string> CommandName => _fieldName ?? (_fieldName = new DelegateCommand<string>(ExecuteCommandName)); void ExecuteCommandName(string parameter) { }
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; }
传统方式挂载执行委托、检查可执行委托。
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"; }
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。
使用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); } }
通过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); } }
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); } }
引用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; } }
事件订阅、发布的一个过程。
执行命令以后,事件执行。
主要是通过 _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:订阅事件属于一个弱引用。
ea.GetEvent<MessageEvent1>().Subscribe(DoMessage1,keepSubscriberReferenceAlive: false); // 取消订立,如果 keepSubscriberReferenceAlive 为False或默认值 不需要取消订阅 //ea.GetEvent<MessageEvent1>().Unsubscribe(DoMessage1);
PublisherThread = 0, 默认是PublisherThread
UIThread = 1,
BackgroundThread = 2
ea.GetEvent<MessageEvent1>().Subscribe(DoMessage1, ThreadOption.PublisherThread, keepSubscriberReferenceAlive: false);
对发布的事件传过来的参数进行过滤,满足要求的才会出发事件。
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; } } }
protected override void RegisterTypes(IContainerRegistry containerRegistry) { // 注册复合命令 containerRegistry.RegisterSingleton<ICompositeCommands, CompositeCommands>(); }
<Grid> <StackPanel> <TextBlock Text="HeaderView" FontSize="30"/> <Button Content="Button" FontSize="30" Command="{Binding CompositeCommands.DoCommand}"/> </StackPanel> </Grid>
public class HeaderViewModel : BindableBase { private ICompositeCommands _compositeCommand; public ICompositeCommands CompositeCommands { get { return _compositeCommand; } set { SetProperty(ref _compositeCommand, value); } } public HeaderViewModel(ICompositeCommands compositeCommands) { CompositeCommands = compositeCommands; } }
public DelegateCommand SaveCommand { get; private set; } public MenuManagementViewModel(ICompositeCommands compositeCommands) { SaveCommand = new DelegateCommand(() => { // 菜单保存 }); compositeCommands.DoCommand.RegisterCommand(SaveCommand); }