參考文檔
共四篇入門介紹 MvvmLight框架使用入門(一) - 樓上那個蜀黍 - 博客園 (cnblogs.com)
官方使用文檔 Introduction to the MVVM Toolkit - Windows Community Toolkit | Microsoft Docs
DLL介紹
- Microsoft.Toolkit.Mvvm.ComponentModel
- ObservableObject 該類實現了INotifyPropertyChanged接口,定義了一個可通知的對象基類,供ViewModelBase繼承 摘要: 一個對象的基類,其屬性必須是可觀察的
- ObservableRecipient 可觀察對象的基類,它也充當消息的接收者 這個類是ObservableObject的擴展,它也提供了使用Microsoft.Toolkit.Mvvm.Messaging.IMessenger類型的內置支持。
- 是一個基類,實現了INotifyDataErrorInfo接口,為驗證向其他應用程序模塊公開的屬性提供了支持。 它也繼承了ObservableObject,所以它也實現了INotifyPropertyChanged和INotifyPropertyChanging。 它可以作為所有需要支持屬性更改通知和屬性驗證的對象的起點
- Microsoft.Toolkit.Mvvm.DependencyInjection
- Ioc一種便於系統使用的類型。 IServiceProvider類型。 ioc提供了在單例、線程安全的服務提供者實例中配置服務的能力,然后可以使用該實例來解析服務實例。 使用此特性的第一步是聲明一些服務
- Microsoft.Toolkit.Mvvm.Input
- RelayCommand 一種命令,其唯一目的是通過調用委托將其功能傳遞給其他對象;RelayCommand.CanExecute(System.Object)方法的默認返回值為true。 這種類型不允許你在.Execute(System.Object)和CanExecute(System.Object)回調方法中接受命令參數
- RelayCommand<T>
- AsyncRelayCommand
- AsyncRelayCommand<T>
- IRelayCommand
- IRelayCommand<in T>
- IAsyncRelayCommand
- IAsyncRelayCommand<in T>
- Microsoft.Toolkit.Mvvm.Messaging
- IMessenger 提供在不同對象之間交換消息能力的類型的接口 , 還可以將消息發送到由令牌唯一標識的特定通道,並在應用程序的不同部分中使用不同的信使
- WeakReferenceMessenger
- 消息接口的實現 備注:imessenger實現使用弱引用來跟蹤已注冊的收件人,所以當不再需要收件人時,
- 不需要手動注銷他們該類型將自動執行內部調整時完整GC調用集合,所以手動調用Cleanup沒有必要確保平均內部數據結構盡可能的削減和緊湊。 注意:在。net Framework中運行時,由於應用程序域卸載問題,不支持此功能。
- StrongReferenceMessenger
- 消息接口的實現 備注:imessenger實現使用強引用來跟蹤已注冊的收件人,所以當他們不再需要時,必須手動注銷他們。
- IRecipient<TMessage> 用於聲明特定消息類型注冊的接收方的接口。
- MessageHandler<TRecipient, TMessage>
- Microsoft.Toolkit.Mvvm.Messaging.Messages
- PropertyChangedMessage<T> 用於廣播可觀察對象屬性更改的消息
- RequestMessage<T>
- AsyncRequestMessage<T>
- CollectionRequestMessage<T>
- AsyncCollectionRequestMessage<T> 用於接收多個響應的請求消息的類,這些響應可以直接使用,也可以通過派生類使用
- ValueChangedMessage<T> 一種基消息,每當特定值發生更改時發出信號;類型參數:改變的值的類型
MVVM簡單使用方法
安裝包
添加視圖模型
public class MainWindowViewModel : ObservableObject { private string title; public string Title { get { return title; } set { SetProperty(ref title, value); } } public ICommand ChangeTitleCommand { get; set; } private void ChangeTitle() { Title = "Hello MvvmLight"; } public MainWindowViewModel() { Title = "Hello World"; ChangeTitleCommand = new RelayCommand(ChangeTitle); } }
添加視圖模型Locator
public class ViewModelLocator { /// <summary> /// Gets the <see cref="IServiceProvider"/> instance to resolve application services. /// </summary> internal static IServiceProvider Services { get; private set; } public ViewModelLocator() { ConfigureServices(); } /// <summary> /// Configures the services for the application. /// </summary> private static IServiceProvider ConfigureServices() { var services = new ServiceCollection(); services.AddSingleton<MainWindowViewModel>(); Services = services.BuildServiceProvider(); Ioc.Default.ConfigureServices(Services); return Services; } public MainWindowViewModel MainVM=> Services.GetService<MainWindowViewModel>(); }
XAML配置
主窗體xaml
<Window.DataContext> <Binding Path="MainVM" Source="{StaticResource Locator}"/> </Window.DataContext> <Grid> <Grid.RowDefinitions> <RowDefinition Height="*"></RowDefinition> <RowDefinition Height="*"></RowDefinition> </Grid.RowDefinitions> <TextBlock Height="50" Text="{Binding Title}"></TextBlock> <Button Height="50" Grid.Row="1" Command="{Binding ChangeTitleCommand}"></Button> </Grid>
數據綁定的一個備注 利刃 MVVMLight 4:綁定和綁定的各種使用場景 - Hello-Brand - 博客園 (cnblogs.com)
值驗證ObservableValidator
//參考鏈接 https://www.cnblogs.com/AtTheMoment/p/15676984.html public class NumberAttribute : ValidationAttribute { public NumberAttribute() { } public override string FormatErrorMessage(string name) { return base.FormatErrorMessage(name); } protected override ValidationResult IsValid(object value, ValidationContext validationContext) { if (value == null || string.IsNullOrWhiteSpace(value.ToString())) return new("內容為空!"); if (!int.TryParse(value.ToString(), out var number)) return new("年齡非法"); else if (number <= 0 || number > 100) return new($"{number}是非法年齡"); return ValidationResult.Success; } } public class ValidatorVM : ObservableValidator { private string name; [Required(AllowEmptyStrings =false,ErrorMessage ="名字不可為空")] [MaxLength(6,ErrorMessage ="長度不能大於6")] public string Name { get { return name; } set { SetProperty(ref name, value, true); } } private int age; [Number] public int Age { get { return age;} set { SetProperty(ref age, value, true); } } }
前台代碼
<TextBox Height="50" Text="{Binding VM.Age}"> <TextBox.Style> <Style> <Setter Property="Validation.ErrorTemplate"> <Setter.Value> <ControlTemplate> <StackPanel> <AdornedElementPlaceholder/> <ItemsControl ItemsSource="{Binding}"> <ItemsControl.ItemTemplate> <DataTemplate> <TextBlock Foreground="Red" Text="{Binding ErrorContent}"/> </DataTemplate> </ItemsControl.ItemTemplate> </ItemsControl> </StackPanel> </ControlTemplate> </Setter.Value> </Setter> </Style> </TextBox.Style> </TextBox>
或者參考 https://www.cnblogs.com/wzh2010/p/6518834.html
命令綁定修改為
ChangeTitleCommand = new RelayCommand(ChangeTitle, CanExecute);
private bool CanExecute() { return !VM.HasErrors; }
完整的模型代碼如下
public class MainWindowViewModel : ObservableObject { private string title; public string Title { get { return title; } set { SetProperty(ref title, value); } } public ICommand ChangeTitleCommand { get; set; } private ValidatorVM _VM; public ValidatorVM VM { get { return _VM; } set { _VM = value; } } private void ChangeTitle() { Title = "Hello MvvmLight"; } public MainWindowViewModel(IServiceProvider service) { _VM = service.GetRequiredService<ValidatorVM>(); Title = "Hello World"; //ChangeTitleCommand = new RelayCommand(ChangeTitle); ChangeTitleCommand = new RelayCommand(ChangeTitle, CanExecute); } private bool CanExecute() { return !VM.HasErrors; } }
帶參數命令
RelayCommand = new RelayCommand<string>(obj => Title = "帶參數的命令" + obj);
將參數包裝成依賴屬性,這種方式不推薦,太麻煩
/// <summary> /// 一種方式就是將 UserParam類 改成 支持具有依賴屬性的對象 /// </summary> public class UserParam:FrameworkElement { public int Age { get { return (int)GetValue(AgeProperty); } set { SetValue(AgeProperty, value); } } // Using a DependencyProperty as the backing store for Age. This enables animation, styling, binding, etc... public static readonly DependencyProperty AgeProperty = DependencyProperty.Register("Age", typeof(int), typeof(UserParam), new PropertyMetadata(0, CustomProperty, CustomValidateValueCallback)); /// <summary> /// 屬性值驗證回調方法 /// </summary> /// <param name="d"></param> /// <param name="e"></param> private static object CustomValidateValueCallback(DependencyObject d, object baseValue) { return baseValue; } /// <summary> /// 屬性值更改回調方法 /// </summary> /// <param name="d"></param> /// <param name="e"></param> private static void CustomProperty(DependencyObject d, DependencyPropertyChangedEventArgs e) { } }
綁定示例 https://www.cnblogs.com/wzh2010/p/6607702.html
<StackPanel DockPanel.Dock="Left" Width="240"> <Button Command="{Binding PassArgObjCmd}" Content="傳遞多個參數" Height="23" HorizontalAlignment="Left" Width="100"> <Button.CommandParameter> <model:UserParam UserName="{Binding ElementName=ArgStrFrom,Path=Text}" UserPhone="88888888" UserAdd="地址" UserSex="男" ></model:UserParam> </Button.CommandParameter> </Button> </StackPanel>
轉換器傳遞參數
模型定義如下
/// <summary> /// 一種方式就是將 UserParam類 改成 支持具有依賴屬性的對象 /// </summary> public class UserParam { public string UserName { get; internal set; } public string UserSex { get; internal set; } public string UserPhone { get; internal set; } public string UserAdd { get; internal set; } } public class UserInfoConvert : IMultiValueConverter { /// <summary> /// 對象轉換 /// </summary> /// <param name="values">所綁定的源的值</param> /// <param name="targetType">目標的類型</param> /// <param name="parameter">綁定時所傳遞的參數</param> /// <param name="culture"><系統語言等信息</param> /// <returns></returns> public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture) { if (!values.Cast<string>().Any(text => string.IsNullOrEmpty(text)) && values.Count() == 4) { UserParam up = new UserParam() { UserName = values[0].ToString(), UserSex = values[1].ToString(), UserPhone = values[2].ToString(), UserAdd = values[3].ToString() }; return up; } return null; } public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture) { throw new NotImplementedException(); } }
ViewModel中添加屬性
private void ExcuteParam(UserParam obj) { userParam = obj; } public IRelayCommand<UserParam> RelayCommand { get; } private UserParam userParam; public UserParam UserParam { get { return userParam; } set { SetProperty(ref userParam, value); } }
RelayCommand = new RelayCommand<UserParam>(ExcuteParam);
前台頁面代碼
名稱空間引入
xmlns:convert="clr-namespace:MvvmSample.Core.ViewModels"
<StackPanel Margin="10,0,0,50"> <TextBlock Text="動態參數傳遞" FontWeight="Bold" FontSize="12" Margin="0,5,0,5" ></TextBlock> <StackPanel Orientation="Horizontal" > <StackPanel Orientation="Vertical" Margin="0,0,10,0" > <StackPanel Orientation="Horizontal" Margin="0,0,0,5" > <TextBlock Text="姓名" Width="80" ></TextBlock> <TextBox x:Name="txtUName" Width="200" /> </StackPanel> <StackPanel Orientation="Horizontal" Margin="0,0,0,5" > <TextBlock Text="電話" Width="80" ></TextBlock> <TextBox x:Name="txtUPhone" Width="200" /> </StackPanel> <StackPanel Orientation="Horizontal" Margin="0,0,0,5" > <TextBlock Text="地址" Width="80"></TextBlock> <TextBox x:Name="txtUAdd" Width="200"/> </StackPanel> <StackPanel Orientation="Horizontal" Margin="0,0,0,5" > <TextBlock Text="性別" Width="80" ></TextBlock> <TextBox x:Name="txtUSex" Width="200" /> </StackPanel> </StackPanel> <StackPanel> <StackPanel.Resources> <convert:UserInfoConvert x:Key="uic"/> </StackPanel.Resources> <Button Content="點擊傳遞" Command="{Binding RelayCommand}"> <Button.CommandParameter> <MultiBinding Converter="{StaticResource uic}"> <Binding ElementName="txtUName" Path="Text"/> <Binding ElementName="txtUSex" Path="Text"/> <Binding ElementName="txtUPhone" Path="Text"/> <Binding ElementName="txtUAdd" Path="Text"/> </MultiBinding> </Button.CommandParameter> </Button> </StackPanel> <StackPanel Width="240" Orientation="Vertical" Margin="10,0,0,0" > <TextBlock Text="{Binding UserParam.UserName,StringFormat='姓名:\{0\}'}" ></TextBlock> <TextBlock Text="{Binding UserParam.UserPhone,StringFormat='電話:\{0\}'}" ></TextBlock> <TextBlock Text="{Binding UserParam.UserAdd,StringFormat='地址:\{0\}'}" ></TextBlock> <TextBlock Text="{Binding UserParam.UserSex,StringFormat='性別:\{0\}'}" ></TextBlock> </StackPanel> </StackPanel> </StackPanel>
傳遞事件參數--拖拽上傳
添加包
名稱空間引用
xmlns:behav="http://schemas.microsoft.com/xaml/behaviors"
<StackPanel Margin="10,0,0,50"> <TextBlock Text="傳遞原事件參數" FontWeight="Bold" FontSize="12" Margin="0,5,0,5" ></TextBlock> <DockPanel x:Name="PassEventArg" > <StackPanel DockPanel.Dock="Left" Width="240" Orientation="Horizontal" > <Border BorderBrush="Red" BorderThickness="1" > <TextBlock Width="100" Height="50" Text="拖拽上傳" TextAlignment="Center" FontSize="18" AllowDrop="True" > <behav:Interaction.Triggers> <behav:EventTrigger EventName="Drop"> <behav:InvokeCommandAction Command="{Binding DragCommand}" PassEventArgsToCommand="True"/> </behav:EventTrigger> </behav:Interaction.Triggers> </TextBlock> </Border> </StackPanel> <StackPanel DockPanel.Dock="Right" Width="240" Orientation="Horizontal"> <TextBlock Text="{Binding FileAdd,StringFormat='獲取地址:\{0\}'}" ></TextBlock> </StackPanel> </DockPanel> </StackPanel>
后台代碼
public IRelayCommand<DragEventArgs> DragCommand { get; set; } private string _FileAdd; public string FileAdd { get { return _FileAdd; } set { SetProperty(ref _FileAdd, value); } } private void ExecuteDrop(DragEventArgs obj) { FileAdd = ((System.Array)obj.Data.GetData(System.Windows.DataFormats.FileDrop)).GetValue(0).ToString(); } DragCommand = new RelayCommand<DragEventArgs>(ExecuteDrop);
控件的事件轉換為命令
<StackPanel Margin="10,0,0,50"> <TextBlock Text="事件轉命令執行" FontWeight="Bold" FontSize="12" Margin="0,5,0,5" ></TextBlock> <DockPanel x:Name="EventToCommand" > <StackPanel DockPanel.Dock="Left" Width="240" Orientation="Horizontal" > <ComboBox Name="combox" Width="130" ItemsSource="{Binding UserParamList}" DisplayMemberPath="UserName" SelectedValuePath="UserSex"> <behav:Interaction.Triggers> <behav:EventTrigger EventName="SelectionChanged"> <behav:InvokeCommandAction Command="{Binding SelectCommand}"/> </behav:EventTrigger> </behav:Interaction.Triggers> </ComboBox> </StackPanel> <StackPanel DockPanel.Dock="Right" Width="240" Orientation="Horizontal"> <TextBlock Text="{Binding ElementName=combox, Path=SelectedIndex}" ></TextBlock> </StackPanel> </DockPanel> </StackPanel>
后台代碼
public ObservableCollection<UserParam> UserParamList { get; set; } public IRelayCommand SelectCommand { get; } private void OnSelectCommand() { }
數據初始化
UserParamList = new() { new() { UserName = "張三", UserSex = "1" }, new() { UserName = "李四", UserSex = "2" }, new() { UserName = "王五", UserSex = "3" }, new() { UserName = "趙六", UserSex = "4" }, }; SelectCommand=new RelayCommand(OnSelectCommand);
在多線程和調度中使用
異步調用不卡UI界面
public IAsyncRelayCommand AsyncRelayCommand { get; } AsyncRelayCommand = new AsyncRelayCommand(() => Task.Run(() => { Thread.Sleep(8000); Title = "我執行完了"; }));
Messenger
注冊消息的簡單模式
參數說明:
<string, string> 消息類型,令牌類型
this為消息的接收者實例,“AAA”為消息令牌,
MessageHandler:(sender,message)==>sender為接收消息的收件人,message接收到的消息
WeakReferenceMessenger.Default.Register<string, string>(this, "AAA", (recipient, message) => { MessageBox.Show($"MessagePage 接收到了消息: {message};\n消息的收件人類型為{recipient.GetType()};"); });
不帶令牌的消息注冊
WeakReferenceMessenger.Default.Register<string>(this, (recipient, message) => { if (recipient is MessagePage)//如果是發送給我的 { MessageBox.Show($"MessagePage 接收到了消息: {message};\n消息的收件人類型為{recipient.GetType()};"); } });
備注:同一收件人只能注冊一種消息類型
發送字符串消息的兩種形式
WeakReferenceMessenger.Default.Send( DateTime.Now.ToString("HH:mm:ss")+"發送了字符串消息"); WeakReferenceMessenger.Default.Send( DateTime.Now.ToString("HH:mm:ss") +"發送了令牌消息,標識符為AAA", "AAA");
備注:不帶令牌的形式的消息不會發送給注冊了令牌的接收者,帶令牌的形式,不會發送給沒有注冊令牌的接收者
自動注冊接收消息
備注:需要將isactive設置為true;以下實例將接收到所有字符串類型的消息
public class MessagePageViewModel : ObservableRecipient, IRecipient<string> { public MessagePageViewModel() { this.IsActive = true; } public void Receive(string message) { MessageBox.Show($"MessagePage 接收到了消息: {message};消息令牌為:AAA;\n消息的收件人類型為{this.GetType()};"); } }
ObservableRecipient的方法說明:發送屬性改變的消息,如果想要使用自定義的令牌,可以重寫該方法
protected virtual void Broadcast<T>(T oldValue, T newValue, string? propertyName)
注冊接收屬性改變的消息
WeakReferenceMessenger.Default.Register<PropertyChangedMessage<string>>(this, (recipient, message) => { if (recipient is MessagePage) { MessageBox.Show($"發件人: {message.Sender};屬性名:{message.PropertyName}"); } });
請求消息
注冊請求消息
WeakReferenceMessenger.Default.Register<RequestMessage<string>>(this, (recipient, message) => { if (recipient is MessagePage) { message.Reply("我已經接收到了你的請求消息,這是我的答復"); } });
發送請求,並接收答復
var name = WeakReferenceMessenger.Default.Send<RequestMessage<string>>();