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);
}