詳解WPF中MVVM架構中的多種數據綁定方式
WPF基礎框架
通過繼承接口INotifyPropertyChanged和ICommand 來實現。
數據綁定部分
INotifyPropertyChanged原代碼如下,WPF框架會自動注冊DataContext對象中的PropertyChanged事件(若事件存在)。然后根據該事件修改前端屬性。
namespace System.ComponentModel
{
// Notifies clients that a property value has changed.
public interface INotifyPropertyChanged
{
// Occurs when a property value changes.
event PropertyChangedEventHandler? PropertyChanged;
}
}
注意:INotifyPropertyChanged接口并非是強制要求的,當不需要通過設置屬性自動修改頁面數據時,也就是不需要執(zhí)行set方法時,不需要繼承該接口。若使用了通知集合ObservableCollection,也是不需要專門通過事件通知頁面的。
public class NativeViewModel : INotifyPropertyChanged
{
private string _name;
public string Name
{
get => _name;
set
{
if (_name != value)
{
_name = value;
OnPropertyChanged();
}
}
}
protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
<StackPanel>
<TextBox Text="{Binding Name}" />
</StackPanel>
數據綁定中的命令綁定
ICommand 原代碼如下,可以看到其中有按鈕操作常用的幾種屬性:是否可用、可用性變化、觸發(fā)事件。與前端通過Click屬性指定事件相比:一個是前端指定要執(zhí)行的邏輯,一個是由vm來確定最終的執(zhí)行邏輯,相當于是反轉了控制方??梢愿鶕枰褂?、并非強制要求。
#nullable enable
using System.ComponentModel;
using System.Windows.Markup;
namespace System.Windows.Input
{
//
// 摘要:
// Defines a command.
[TypeConverter("System.Windows.Input.CommandConverter, PresentationFramework, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, Custom=null")]
[ValueSerializer("System.Windows.Input.CommandValueSerializer, PresentationFramework, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, Custom=null")]
public interface ICommand
{
//
// 摘要:
// Occurs when changes occur that affect whether or not the command should execute.
event EventHandler? CanExecuteChanged;
//
// 摘要:
// Defines the method that determines whether the command can execute in its current
// state.
//
// 參數:
// parameter:
// Data used by the command. If the command does not require data to be passed,
// this object can be set to null.
//
// 返回結果:
// true if this command can be executed; otherwise, false.
bool CanExecute(object? parameter);
//
// 摘要:
// Defines the method to be called when the command is invoked.
//
// 參數:
// parameter:
// Data used by the command. If the command does not require data to be passed,
// this object can be set to null.
void Execute(object? parameter);
}
}
對于ICommand屬性,推薦使用方式是不要讓其觸發(fā)PropertyChanged,不應該被動態(tài)設置,而應該初始定好。不過如果使用場景確實需要,應該也是能生效的。
public class NativeViewModel
{
public ICommand GreetCommand { get; }
public NativeViewModel()
{
// 使用自定義的 RelayCommand 需要自己實現
GreetCommand = new RelayCommand();
}
}
// 需要實現的簡單命令類
public class RelayCommand : ICommand
{
// 略
}
<StackPanel>
<Button Content="Say Hello" Command="{Binding GreetCommand}" />
</StackPanel>
CommunityToolkit.Mvvm 方式
CommunityToolkit.Mvvm 利用 C# 的源碼生成器,在編譯時自動生成INotifyPropertyChanged和ICommand的樣板代碼。需要nuget包CommunityToolkit.Mvvm。
其僅在vm上有區(qū)別,在實際綁定方式上沒有區(qū)別。
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
// 3. 使用 [ObservableObject] 特性或繼承 ObservableObject
[ObservableObject]
public partial class ToolkitViewModel
{
// 4. 使用 [ObservableProperty] 標記字段,自動生成名為 "Name" 的屬性
[ObservableProperty]
[NotifyCanExecuteChangedFor(nameof(GreetCommand))] // 當 Name 改變時,通知 GreetCommand 重新驗證
private string _name;
[ObservableProperty]
private string _greeting;
// 5. 使用 [RelayCommand] 自動生成名為 GreetCommand 的 ICommand 屬性
[RelayCommand(CanExecute = nameof(CanGreet))]
private void Greet()
{
Greeting = $"Hello from Toolkit, {Name}!";
}
private bool CanGreet() => !string.IsNullOrWhiteSpace(Name);
}
ReactiveUI
ReactiveUI 將響應式編程理念引入 MVVM,核心是使用ReactiveObject和WhenAnyValue等來聲明數據流和反應關系。需要nuget包ReactiveUI.WPF。
其僅在vm上有區(qū)別,在實際綁定方式上沒有區(qū)別。
using ReactiveUI;
using System.Reactive.Linq;
// 6. 繼承 ReactiveObject
public class ReactiveViewModel : ReactiveObject
{
// 7. 使用 [Reactive] 特性或 WhenAnyValue
private string _name;
public string Name
{
get => _name;
set => this.RaiseAndSetIfChanged(ref _name, value);
}
private readonly ObservableAsPropertyHelper<string> _greeting;
public string Greeting => _greeting.Value;
// 8. 使用 ReactiveCommand 創(chuàng)建命令
public ReactiveCommand<Unit, Unit> GreetCommand { get; }
public ReactiveViewModel()
{
// 判斷命令何時可執(zhí)行:當 Name 不為空時
var canGreet = this.WhenAnyValue(x => x.Name, name => !string.IsNullOrWhiteSpace(name));
// 創(chuàng)建命令
GreetCommand = ReactiveCommand.CreateFromTask(
execute: async () => { /* 可以執(zhí)行異步操作 */ return $"Hello from ReactiveUI, {Name}!"; },
canExecute: canGreet // 綁定可執(zhí)行條件
);
// 9. 將命令的執(zhí)行結果(一個IObservable<string>)訂閱到 Greeting 屬性
_greeting = GreetCommand.ToProperty(this, x => x.Greeting, initialValue: "Waiting...");
// 另一種更直接的寫法(不通過命令結果):
// GreetCommand = ReactiveCommand.Create(() => { Greeting = $"Hello from ReactiveUI, {Name}!"; }, canGreet);
// 但上面那種方式展示了將 IObservable 流轉換為屬性的強大能力。
}
}
到此這篇關于詳解WPF中MVVM架構中的多種數據綁定方式的文章就介紹到這了,更多相關WPF MVVM架構的數據綁定內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
c#斐波那契數列(Fibonacci)(遞歸,非遞歸)實現代碼
c#斐波那契數列(Fibonacci)(遞歸,非遞歸)實現代碼,需要的朋友可以參考一下2013-05-05

