Prism程序入口、View ViewModel关联、数据绑定、数据校验、cmd
2021/9/2 1:06:32
本文主要是介绍Prism程序入口、View ViewModel关联、数据绑定、数据校验、cmd,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
目录- Prism程序入口、View ViewModel关联、数据绑定、数据校验、cmd
- 关于Prism框架
- 创建启动程序
- 第一种初始化方式:8.0版本以前只能使用PrismBootstrapper
- 第二种初始化方式:8.0版本开始提供一种新的方式PrismApplication【全局资源管控,项目启动】
- View与ViewModel的多种关联方法
- 使用方法
- 自定义ViewModel文件后缀规则
- 一对一注册
- MVVM数据属性的多种方式
- 第一种方式:RaisePropertyChanged()
- 第二种方式:SetProperty
- MVVM数据验证
- MVVM命令的多种方式
- 语法糖
- 基础使用
- 默认直接执行cmd
- 默认判断是否执行cmd
- 监控属性变化,执行cmd
- 监控属性是否为true,执行cmd,可以省略大量代码,但有局限性。
- 带参数的cmd
- 异步命令
- 事件绑定Command,事件参数传值
- 事件聚合器、弹出自定义窗口
- 精简版
- keepSubscriberReferenceAlive参数控制是否为弱引用
- ThreadOption.PublisherThread参数控制线程的
- Predicate 对消息进行过滤Filter
- 详细版
- 精简版
- 复合命令
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); }
这篇关于Prism程序入口、View ViewModel关联、数据绑定、数据校验、cmd的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2025-01-10Rakuten 乐天积分系统从 Cassandra 到 TiDB 的选型与实战
- 2025-01-09CMS内容管理系统是什么?如何选择适合你的平台?
- 2025-01-08CCPM如何缩短项目周期并降低风险?
- 2025-01-08Omnivore 替代品 Readeck 安装与使用教程
- 2025-01-07Cursor 收费太贵?3分钟教你接入超低价 DeepSeek-V3,代码质量逼近 Claude 3.5
- 2025-01-06PingCAP 连续两年入选 Gartner 云数据库管理系统魔力象限“荣誉提及”
- 2025-01-05Easysearch 可搜索快照功能,看这篇就够了
- 2025-01-04BOT+EPC模式在基础设施项目中的应用与优势
- 2025-01-03用LangChain构建会检索和搜索的智能聊天机器人指南
- 2025-01-03图像文字理解,OCR、大模型还是多模态模型?PalliGema2在QLoRA技术上的微调与应用