当前位置: 首页 > news >正文

.NET WPF CommunityToolkit.Mvvm框架

文章目录

  • .NET WPF CommunityToolkit.Mvvm框架
    • 1 源生成器
      • 1.1 ObservablePropertyAttribute & RelayCommandAttribute
      • 1.2 INotifyPropertyChangedAttribute
    • 2 可观测对象
      • 2.1 ObservableValidator
      • 2.2 ObservableRecipient

.NET WPF CommunityToolkit.Mvvm框架

1 源生成器

1.1 ObservablePropertyAttribute & RelayCommandAttribute

using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using System.Windows;namespace TestCommunityToolkit._1_Attribute
{public partial class BlogVO : ObservableObject{[property: JsonIgnore][ObservableProperty]private string _name;// [ObservableProperty] : ObservableProperty 类型是一种允许从带批注字段生成可观察属性的特性。 其用途是显著减少定义可观察属性所需的样本量。它将生成如下所示的可观察属性:////         ↓//// [JsonIgnore]// public string? Name// {//     get => name;//     set//     {//         if (!EqualityComparer<string?>.Default.Equals(name, value))//         {//             string? oldValue = name;//             OnNameChanging(value);//             OnNameChanging(oldValue, value);//             OnPropertyChanging();//             name = value;//             OnNameChanged(value);//             OnNameChanged(oldValue, value);//             OnPropertyChanged();//         }//     }// }[ObservableProperty]private string _description;[ObservableProperty]private string _url;[RelayCommand]private void BlogInfo(){MessageBox.Show($"Name: {Name}\nUrl: {Url}\nDescription: {Description}");}// [RelayCommand] : RelayCommand 类型是一个特性,允许为带批注的方法生成中继命令属性。 其目的是完全消除在 viewmodel 中定义命令包装私有方法所需的模板。它将生成如下所示的命令:////       ↓//// private ICommand blogInfoCommand;// public ICommand BlogInfoCommand => blogInfoCommand ??= new RelayCommand(BlogInfo);// 将基于方法名称创建生成的命令的名称。 生成器将使用方法名称并在末尾追加“Command”,并且去除“On”前缀(如果存在)。 此外,对于异步方法,“Async”后缀也会在追加“Command”之前去除。[RelayCommand]private async Task AddPost(){await Task.Delay(1000);// Add a new post to the list...}// [RelayCommand] : RelayCommand 类型是一个特性,允许为带批注的方法生成中继命令属性。 其目的是完全消除在 viewmodel 中定义命令包装私有方法所需的模板。它将生成如下所示的命令:////       ↓//// private ICommand addPostCommand;// public IAsyncRelayCommand AddPostCommand => addPostCommand ??= new AsyncRelayCommand(new Func<Task>(AddPost));// 将基于方法名称创建生成的命令的名称。 生成器将使用方法名称并在末尾追加“Command”,并且去除“On”前缀(如果存在)。 此外,对于异步方法,“Async”后缀也会在追加“Command”之前去除。}
}

1.2 INotifyPropertyChangedAttribute

INotifyPropertyChanged 类型是一个允许将 MVVM 支持代码插入现有类型的属性,其目的是在需要这些类型的相同功能,但已经从另一种类型中实现目标类型的情况下,为开发人员提供支持。 由于 C# 不允许多重继承,因此可以转而使用这些属性让 MVVM 工具包生成器将相同的代码直接添加到这些类型中,从而避开此限制。

为了正常工作,带批注的类型需要位于分部类中。 如果对类型进行嵌套,则必须也将声明语法树中的所有类型批注为分部。 否则将导致编译错误,因为生成器无法使用请求的其他代码生成该类型的其他分部声明。

这些属性仅适用于目标类型不能仅从等效类型(例如从 ObservableObject)继承的情况。 如果可能,推荐的方法是继承,因为它将通过避免在最终程序集中创建重复的代码来减小二进制文件大小。

using CommunityToolkit.Mvvm.ComponentModel;namespace TestCommunityToolkit._1_Attribute
{public class BaseObject{// Some common properties and methods...}[INotifyPropertyChanged]public partial class BlogViewModel : BaseObject{// Some properties and methods...}
}

这将在 MyViewModel 类型中生成一个完整的 INotifyPropertyChanged 实现,并附带可用于降低详细程度的其他帮助程序(如 SetProperty)。 以下是各种属性的简要总结:

INotifyPropertyChanged:实现接口,并添加帮助程序方法来设置属性和引发事件。
ObservableObject:添加 ObservableObject 类型中的所有代码。 它在概念上等同于 INotifyPropertyChanged,主要区别在于它还实现了 INotifyPropertyChanging。
ObservableRecipient:添加 ObservableRecipient 类型中的所有代码。 特别是,可以将其添加到从 ObservableValidator 继承的类型,以合并两者。

// <auto-generated/>
#pragma warning disable
#nullable enable
namespace TestCommunityToolkit._1_Attribute
{/// <inheritdoc/>partial class BlogViewModel : global::System.ComponentModel.INotifyPropertyChanged{/// <inheritdoc cref = "global::System.ComponentModel.INotifyPropertyChanged.PropertyChanged"/>[global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.INotifyPropertyChangedGenerator", "8.3.0.0")][global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]public event global::System.ComponentModel.PropertyChangedEventHandler? PropertyChanged;/// <summary>/// Raises the <see cref = "PropertyChanged"/> event./// </summary>/// <param name = "e">The input <see cref = "global::System.ComponentModel.PropertyChangedEventArgs"/> instance.</param>[global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.INotifyPropertyChangedGenerator", "8.3.0.0")][global::System.Diagnostics.DebuggerNonUserCode][global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]protected virtual void OnPropertyChanged(global::System.ComponentModel.PropertyChangedEventArgs e){PropertyChanged?.Invoke(this, e);}/// <summary>/// Raises the <see cref = "PropertyChanged"/> event./// </summary>/// <param name = "propertyName">(optional) The name of the property that changed.</param>[global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.INotifyPropertyChangedGenerator", "8.3.0.0")][global::System.Diagnostics.DebuggerNonUserCode][global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]protected void OnPropertyChanged([global::System.Runtime.CompilerServices.CallerMemberName] string? propertyName = null){OnPropertyChanged(new global::System.ComponentModel.PropertyChangedEventArgs(propertyName));}/// <summary>/// Compares the current and new values for a given property. If the value has changed, updates/// the property with the new value, then raises the <see cref = "PropertyChanged"/> event./// </summary>/// <typeparam name = "T">The type of the property that changed.</typeparam>/// <param name = "field">The field storing the property's value.</param>/// <param name = "newValue">The property's value after the change occurred.</param>/// <param name = "propertyName">(optional) The name of the property that changed.</param>/// <returns><see langword="true"/> if the property was changed, <see langword="false"/> otherwise.</returns>/// <remarks>/// The <see cref = "PropertyChanged"/> event is not raised if the current and new value for the target property are the same./// </remarks>[global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.INotifyPropertyChangedGenerator", "8.3.0.0")][global::System.Diagnostics.DebuggerNonUserCode][global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]protected bool SetProperty<T>([global::System.Diagnostics.CodeAnalysis.NotNullIfNotNull("newValue")] ref T field, T newValue, [global::System.Runtime.CompilerServices.CallerMemberName] string? propertyName = null){if (global::System.Collections.Generic.EqualityComparer<T>.Default.Equals(field, newValue)){return false;}field = newValue;OnPropertyChanged(propertyName);return true;}/// <summary>/// Compares the current and new values for a given property. If the value has changed, updates/// the property with the new value, then raises the <see cref = "PropertyChanged"/> event./// See additional notes about this overload in <see cref = "SetProperty{T}(ref T, T, string)"/>./// </summary>/// <typeparam name = "T">The type of the property that changed.</typeparam>/// <param name = "field">The field storing the property's value.</param>/// <param name = "newValue">The property's value after the change occurred.</param>/// <param name = "comparer">The <see cref = "global::System.Collections.Generic.IEqualityComparer{T}"/> instance to use to compare the input values.</param>/// <param name = "propertyName">(optional) The name of the property that changed.</param>/// <returns><see langword="true"/> if the property was changed, <see langword="false"/> otherwise.</returns>[global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.INotifyPropertyChangedGenerator", "8.3.0.0")][global::System.Diagnostics.DebuggerNonUserCode][global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]protected bool SetProperty<T>([global::System.Diagnostics.CodeAnalysis.NotNullIfNotNull("newValue")] ref T field, T newValue, global::System.Collections.Generic.IEqualityComparer<T> comparer, [global::System.Runtime.CompilerServices.CallerMemberName] string? propertyName = null){if (comparer.Equals(field, newValue)){return false;}field = newValue;OnPropertyChanged(propertyName);return true;}/// <summary>/// Compares the current and new values for a given property. If the value has changed, updates/// the property with the new value, then raises the <see cref = "PropertyChanged"/> event./// This overload is much less efficient than <see cref = "SetProperty{T}(ref T, T, string)"/> and it/// should only be used when the former is not viable (eg. when the target property being/// updated does not directly expose a backing field that can be passed by reference)./// For performance reasons, it is recommended to use a stateful callback if possible through/// the <see cref = "SetProperty{TModel, T}(T, T, TModel, global::System.Action{TModel, T}, string? )"/> whenever possible/// instead of this overload, as that will allow the C# compiler to cache the input callback and/// reduce the memory allocations. More info on that overload are available in the related XML/// docs. This overload is here for completeness and in cases where that is not applicable./// </summary>/// <typeparam name = "T">The type of the property that changed.</typeparam>/// <param name = "oldValue">The current property value.</param>/// <param name = "newValue">The property's value after the change occurred.</param>/// <param name = "callback">A callback to invoke to update the property value.</param>/// <param name = "propertyName">(optional) The name of the property that changed.</param>/// <returns><see langword="true"/> if the property was changed, <see langword="false"/> otherwise.</returns>/// <remarks>/// The <see cref = "PropertyChanged"/> event is not raised if the current and new value for the target property are the same./// </remarks>[global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.INotifyPropertyChangedGenerator", "8.3.0.0")][global::System.Diagnostics.DebuggerNonUserCode][global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]protected bool SetProperty<T>(T oldValue, T newValue, global::System.Action<T> callback, [global::System.Runtime.CompilerServices.CallerMemberName] string? propertyName = null){if (global::System.Collections.Generic.EqualityComparer<T>.Default.Equals(oldValue, newValue)){return false;}callback(newValue);OnPropertyChanged(propertyName);return true;}/// <summary>/// Compares the current and new values for a given property. If the value has changed, updates/// the property with the new value, then raises the <see cref = "PropertyChanged"/> event./// See additional notes about this overload in <see cref = "SetProperty{T}(T, T, global::System.Action{T}, string)"/>./// </summary>/// <typeparam name = "T">The type of the property that changed.</typeparam>/// <param name = "oldValue">The current property value.</param>/// <param name = "newValue">The property's value after the change occurred.</param>/// <param name = "comparer">The <see cref = "global::System.Collections.Generic.IEqualityComparer{T}"/> instance to use to compare the input values.</param>/// <param name = "callback">A callback to invoke to update the property value.</param>/// <param name = "propertyName">(optional) The name of the property that changed.</param>/// <returns><see langword="true"/> if the property was changed, <see langword="false"/> otherwise.</returns>[global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.INotifyPropertyChangedGenerator", "8.3.0.0")][global::System.Diagnostics.DebuggerNonUserCode][global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]protected bool SetProperty<T>(T oldValue, T newValue, global::System.Collections.Generic.IEqualityComparer<T> comparer, global::System.Action<T> callback, [global::System.Runtime.CompilerServices.CallerMemberName] string? propertyName = null){if (comparer.Equals(oldValue, newValue)){return false;}callback(newValue);OnPropertyChanged(propertyName);return true;}/// <summary>/// Compares the current and new values for a given nested property. If the value has changed,/// updates the property and then raises the <see cref = "PropertyChanged"/> event./// The behavior mirrors that of <see cref = "SetProperty{T}(ref T, T, string)"/>,/// with the difference being that this method is used to relay properties from a wrapped model in the/// current instance. This type is useful when creating wrapping, bindable objects that operate over/// models that lack support for notification (eg. for CRUD operations)./// Suppose we have this model (eg. for a database row in a table):/// <code>/// public class Person/// {///     public string Name { get; set; }/// }/// </code>/// We can then use a property to wrap instances of this type into our observable model (which supports/// notifications), injecting the notification to the properties of that model, like so:/// <code>/// [INotifyPropertyChanged]/// public partial class BindablePerson/// {///     public Model { get; }//////     public BindablePerson(Person model)///     {///         Model = model;///     }//////     public string Name///     {///         get => Model.Name;///         set => Set(Model.Name, value, Model, (model, name) => model.Name = name);///     }/// }/// </code>/// This way we can then use the wrapping object in our application, and all those "proxy" properties will/// also raise notifications when changed. Note that this method is not meant to be a replacement for/// <see cref = "SetProperty{T}(ref T, T, string)"/>, and it should only be used when relaying properties to a model that/// doesn't support notifications, and only if you can't implement notifications to that model directly (eg. by having/// it implement <see cref = "global::System.ComponentModel.INotifyPropertyChanged"/>). The syntax relies on passing the target model and a stateless callback/// to allow the C# compiler to cache the function, which results in much better performance and no memory usage./// </summary>/// <typeparam name = "TModel">The type of model whose property (or field) to set.</typeparam>/// <typeparam name = "T">The type of property (or field) to set.</typeparam>/// <param name = "oldValue">The current property value.</param>/// <param name = "newValue">The property's value after the change occurred.</param>/// <param name = "model">The model containing the property being updated.</param>/// <param name = "callback">The callback to invoke to set the target property value, if a change has occurred.</param>/// <param name = "propertyName">(optional) The name of the property that changed.</param>/// <returns><see langword="true"/> if the property was changed, <see langword="false"/> otherwise.</returns>/// <remarks>/// The <see cref = "PropertyChanged"/> event is not raised if the current and new value for the target property are the same./// </remarks>[global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.INotifyPropertyChangedGenerator", "8.3.0.0")][global::System.Diagnostics.DebuggerNonUserCode][global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]protected bool SetProperty<TModel, T>(T oldValue, T newValue, TModel model, global::System.Action<TModel, T> callback, [global::System.Runtime.CompilerServices.CallerMemberName] string? propertyName = null)where TModel : class{if (global::System.Collections.Generic.EqualityComparer<T>.Default.Equals(oldValue, newValue)){return false;}callback(model, newValue);OnPropertyChanged(propertyName);return true;}/// <summary>/// Compares the current and new values for a given nested property. If the value has changed,/// updates the property and then raises the <see cref = "PropertyChanged"/> event./// The behavior mirrors that of <see cref = "SetProperty{T}(ref T, T, string)"/>,/// with the difference being that this method is used to relay properties from a wrapped model in the/// current instance. See additional notes about this overload in <see cref = "SetProperty{TModel, T}(T, T, TModel, global::System.Action{TModel, T}, string)"/>./// </summary>/// <typeparam name = "TModel">The type of model whose property (or field) to set.</typeparam>/// <typeparam name = "T">The type of property (or field) to set.</typeparam>/// <param name = "oldValue">The current property value.</param>/// <param name = "newValue">The property's value after the change occurred.</param>/// <param name = "comparer">The <see cref = "global::System.Collections.Generic.IEqualityComparer{T}"/> instance to use to compare the input values.</param>/// <param name = "model">The model containing the property being updated.</param>/// <param name = "callback">The callback to invoke to set the target property value, if a change has occurred.</param>/// <param name = "propertyName">(optional) The name of the property that changed.</param>/// <returns><see langword="true"/> if the property was changed, <see langword="false"/> otherwise.</returns>[global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.INotifyPropertyChangedGenerator", "8.3.0.0")][global::System.Diagnostics.DebuggerNonUserCode][global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]protected bool SetProperty<TModel, T>(T oldValue, T newValue, global::System.Collections.Generic.IEqualityComparer<T> comparer, TModel model, global::System.Action<TModel, T> callback, [global::System.Runtime.CompilerServices.CallerMemberName] string? propertyName = null)where TModel : class{if (comparer.Equals(oldValue, newValue)){return false;}callback(model, newValue);OnPropertyChanged(propertyName);return true;}/// <summary>/// Compares the current and new values for a given field (which should be the backing field for a property)./// If the value has changed, updates the field and then raises the <see cref = "PropertyChanged"/> event./// The behavior mirrors that of <see cref = "SetProperty{T}(ref T, T, string)"/>, with the difference being that/// this method will also monitor the new value of the property (a generic <see cref = "global::System.Threading.Tasks.Task"/>) and will also/// raise the <see cref = "PropertyChanged"/> again for the target property when it completes./// This can be used to update bindings observing that <see cref = "global::System.Threading.Tasks.Task"/> or any of its properties./// This method and its overload specifically rely on the <see cref = "TaskNotifier"/> type, which needs/// to be used in the backing field for the target <see cref = "global::System.Threading.Tasks.Task"/> property. The field doesn't need to be/// initialized, as this method will take care of doing that automatically. The <see cref = "TaskNotifier"/>/// type also includes an implicit operator, so it can be assigned to any <see cref = "global::System.Threading.Tasks.Task"/> instance directly./// Here is a sample property declaration using this method:/// <code>/// private TaskNotifier myTask;////// public Task MyTask/// {///     get => myTask;///     private set => SetAndNotifyOnCompletion(ref myTask, value);/// }/// </code>/// </summary>/// <param name = "taskNotifier">The field notifier to modify.</param>/// <param name = "newValue">The property's value after the change occurred.</param>/// <param name = "propertyName">(optional) The name of the property that changed.</param>/// <returns><see langword="true"/> if the property was changed, <see langword="false"/> otherwise.</returns>/// <remarks>/// The <see cref = "PropertyChanged"/> event is not raised if the current and new value for the target property are/// the same. The return value being <see langword="true"/> only indicates that the new value being assigned to/// <paramref name = "taskNotifier"/> is different than the previous one, and it does not mean the new/// <see cref = "global::System.Threading.Tasks.Task"/> instance passed as argument is in any particular state./// </remarks>[global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.INotifyPropertyChangedGenerator", "8.3.0.0")][global::System.Diagnostics.DebuggerNonUserCode][global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]protected bool SetPropertyAndNotifyOnCompletion([global::System.Diagnostics.CodeAnalysis.NotNull] ref TaskNotifier? taskNotifier, global::System.Threading.Tasks.Task? newValue, [global::System.Runtime.CompilerServices.CallerMemberName] string? propertyName = null){return SetPropertyAndNotifyOnCompletion(taskNotifier ??= new TaskNotifier(), newValue, null, propertyName);}/// <summary>/// Compares the current and new values for a given field (which should be the backing field for a property)./// If the value has changed, updates the field and then raises the <see cref = "PropertyChanged"/> event./// This method is just like <see cref = "SetPropertyAndNotifyOnCompletion(ref TaskNotifier, global::System.Threading.Tasks.Task, string)"/>,/// with the difference being an extra <see cref = "global::System.Action{T}"/> parameter with a callback being invoked/// either immediately, if the new task has already completed or is <see langword="null"/>, or upon completion./// </summary>/// <param name = "taskNotifier">The field notifier to modify.</param>/// <param name = "newValue">The property's value after the change occurred.</param>/// <param name = "callback">A callback to invoke to update the property value.</param>/// <param name = "propertyName">(optional) The name of the property that changed.</param>/// <returns><see langword="true"/> if the property was changed, <see langword="false"/> otherwise.</returns>/// <remarks>/// The <see cref = "PropertyChanged"/> event is not raised if the current and new value for the target property are the same./// </remarks>[global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.INotifyPropertyChangedGenerator", "8.3.0.0")][global::System.Diagnostics.DebuggerNonUserCode][global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]protected bool SetPropertyAndNotifyOnCompletion([global::System.Diagnostics.CodeAnalysis.NotNull] ref TaskNotifier? taskNotifier, global::System.Threading.Tasks.Task? newValue, global::System.Action<global::System.Threading.Tasks.Task?> callback, [global::System.Runtime.CompilerServices.CallerMemberName] string? propertyName = null){return SetPropertyAndNotifyOnCompletion(taskNotifier ??= new TaskNotifier(), newValue, callback, propertyName);}/// <summary>/// Compares the current and new values for a given field (which should be the backing field for a property)./// If the value has changed, updates the field and then raises the <see cref = "PropertyChanged"/> event./// The behavior mirrors that of <see cref = "SetProperty{T}(ref T, T, string)"/>, with the difference being that/// this method will also monitor the new value of the property (a generic <see cref = "global::System.Threading.Tasks.Task"/>) and will also/// raise the <see cref = "PropertyChanged"/> again for the target property when it completes./// This can be used to update bindings observing that <see cref = "global::System.Threading.Tasks.Task"/> or any of its properties./// This method and its overload specifically rely on the <see cref = "TaskNotifier{T}"/> type, which needs/// to be used in the backing field for the target <see cref = "global::System.Threading.Tasks.Task"/> property. The field doesn't need to be/// initialized, as this method will take care of doing that automatically. The <see cref = "TaskNotifier{T}"/>/// type also includes an implicit operator, so it can be assigned to any <see cref = "global::System.Threading.Tasks.Task"/> instance directly./// Here is a sample property declaration using this method:/// <code>/// private TaskNotifier&lt;int&gt; myTask;////// public Task&lt;int&gt; MyTask/// {///     get => myTask;///     private set => SetAndNotifyOnCompletion(ref myTask, value);/// }/// </code>/// </summary>/// <typeparam name = "T">The type of result for the <see cref = "global::System.Threading.Tasks.Task{TResult}"/> to set and monitor.</typeparam>/// <param name = "taskNotifier">The field notifier to modify.</param>/// <param name = "newValue">The property's value after the change occurred.</param>/// <param name = "propertyName">(optional) The name of the property that changed.</param>/// <returns><see langword="true"/> if the property was changed, <see langword="false"/> otherwise.</returns>/// <remarks>/// The <see cref = "PropertyChanged"/> event is not raised if the current and new value for the target property are/// the same. The return value being <see langword="true"/> only indicates that the new value being assigned to/// <paramref name = "taskNotifier"/> is different than the previous one, and it does not mean the new/// <see cref = "global::System.Threading.Tasks.Task{TResult}"/> instance passed as argument is in any particular state./// </remarks>[global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.INotifyPropertyChangedGenerator", "8.3.0.0")][global::System.Diagnostics.DebuggerNonUserCode][global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]protected bool SetPropertyAndNotifyOnCompletion<T>([global::System.Diagnostics.CodeAnalysis.NotNull] ref TaskNotifier<T>? taskNotifier, global::System.Threading.Tasks.Task<T>? newValue, [global::System.Runtime.CompilerServices.CallerMemberName] string? propertyName = null){return SetPropertyAndNotifyOnCompletion(taskNotifier ??= new TaskNotifier<T>(), newValue, null, propertyName);}/// <summary>/// Compares the current and new values for a given field (which should be the backing field for a property)./// If the value has changed, updates the field and then raises the <see cref = "PropertyChanged"/> event./// This method is just like <see cref = "SetPropertyAndNotifyOnCompletion{T}(ref TaskNotifier{T}, global::System.Threading.Tasks.Task{T}, string)"/>,/// with the difference being an extra <see cref = "global::System.Action{T}"/> parameter with a callback being invoked/// either immediately, if the new task has already completed or is <see langword="null"/>, or upon completion./// </summary>/// <typeparam name = "T">The type of result for the <see cref = "global::System.Threading.Tasks.Task{TResult}"/> to set and monitor.</typeparam>/// <param name = "taskNotifier">The field notifier to modify.</param>/// <param name = "newValue">The property's value after the change occurred.</param>/// <param name = "callback">A callback to invoke to update the property value.</param>/// <param name = "propertyName">(optional) The name of the property that changed.</param>/// <returns><see langword="true"/> if the property was changed, <see langword="false"/> otherwise.</returns>/// <remarks>/// The <see cref = "PropertyChanged"/> event is not raised if the current and new value for the target property are the same./// </remarks>[global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.INotifyPropertyChangedGenerator", "8.3.0.0")][global::System.Diagnostics.DebuggerNonUserCode][global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]protected bool SetPropertyAndNotifyOnCompletion<T>([global::System.Diagnostics.CodeAnalysis.NotNull] ref TaskNotifier<T>? taskNotifier, global::System.Threading.Tasks.Task<T>? newValue, global::System.Action<global::System.Threading.Tasks.Task<T>?> callback, [global::System.Runtime.CompilerServices.CallerMemberName] string? propertyName = null){return SetPropertyAndNotifyOnCompletion(taskNotifier ??= new TaskNotifier<T>(), newValue, callback, propertyName);}/// <summary>/// Implements the notification logic for the related methods./// </summary>/// <typeparam name = "TTask">The type of <see cref = "global::System.Threading.Tasks.Task"/> to set and monitor.</typeparam>/// <param name = "taskNotifier">The field notifier.</param>/// <param name = "newValue">The property's value after the change occurred.</param>/// <param name = "callback">(optional) A callback to invoke to update the property value.</param>/// <param name = "propertyName">(optional) The name of the property that changed.</param>/// <returns><see langword="true"/> if the property was changed, <see langword="false"/> otherwise.</returns>[global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.INotifyPropertyChangedGenerator", "8.3.0.0")][global::System.Diagnostics.DebuggerNonUserCode][global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]private bool SetPropertyAndNotifyOnCompletion<TTask>(ITaskNotifier<TTask> taskNotifier, TTask? newValue, global::System.Action<TTask?>? callback, [global::System.Runtime.CompilerServices.CallerMemberName] string? propertyName = null)where TTask : global::System.Threading.Tasks.Task{if (ReferenceEquals(taskNotifier.Task, newValue)){return false;}bool isAlreadyCompletedOrNull = newValue?.IsCompleted ?? true;taskNotifier.Task = newValue;OnPropertyChanged(propertyName);if (isAlreadyCompletedOrNull){if (callback != null){callback(newValue);}return true;}async void MonitorTask(){await global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__TaskExtensions.GetAwaitableWithoutEndValidation(newValue!);if (ReferenceEquals(taskNotifier.Task, newValue)){OnPropertyChanged(propertyName);}if (callback != null){callback(newValue);}}MonitorTask();return true;}/// <summary>/// An interface for task notifiers of a specified type./// </summary>/// <typeparam name = "TTask">The type of value to store.</typeparam>[global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.INotifyPropertyChangedGenerator", "8.3.0.0")]private interface ITaskNotifier<TTask>where TTask : global::System.Threading.Tasks.Task{/// <summary>/// Gets or sets the wrapped <typeparamref name = "TTask"/> value./// </summary>TTask? Task { get; set; }}/// <summary>/// A wrapping class that can hold a <see cref = "global::System.Threading.Tasks.Task"/> value./// </summary>[global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.INotifyPropertyChangedGenerator", "8.3.0.0")][global::System.Diagnostics.DebuggerNonUserCode][global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]protected sealed class TaskNotifier : ITaskNotifier<global::System.Threading.Tasks.Task>{/// <summary>/// Initializes a new instance of the <see cref = "TaskNotifier"/> class./// </summary>internal TaskNotifier(){}private global::System.Threading.Tasks.Task? task;/// <inheritdoc/>global::System.Threading.Tasks.Task? ITaskNotifier<global::System.Threading.Tasks.Task>.Task { get => this.task; set => this.task = value; }/// <summary>/// Unwraps the <see cref = "global::System.Threading.Tasks.Task"/> value stored in the current instance./// </summary>/// <param name = "notifier">The input <see cref = "TaskNotifier{TTask}"/> instance.</param>public static implicit operator global::System.Threading.Tasks.Task? (TaskNotifier? notifier){return notifier?.task;}}/// <summary>/// A wrapping class that can hold a <see cref = "global::System.Threading.Tasks.Task{T}"/> value./// </summary>/// <typeparam name = "T">The type of value for the wrapped <see cref = "global::System.Threading.Tasks.Task{T}"/> instance.</typeparam>[global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.INotifyPropertyChangedGenerator", "8.3.0.0")][global::System.Diagnostics.DebuggerNonUserCode][global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]protected sealed class TaskNotifier<T> : ITaskNotifier<global::System.Threading.Tasks.Task<T>>{/// <summary>/// Initializes a new instance of the <see cref = "TaskNotifier{TTask}"/> class./// </summary>internal TaskNotifier(){}private global::System.Threading.Tasks.Task<T>? task;/// <inheritdoc/>global::System.Threading.Tasks.Task<T>? ITaskNotifier<global::System.Threading.Tasks.Task<T>>.Task { get => this.task; set => this.task = value; }/// <summary>/// Unwraps the <see cref = "global::System.Threading.Tasks.Task{T}"/> value stored in the current instance./// </summary>/// <param name = "notifier">The input <see cref = "TaskNotifier{TTask}"/> instance.</param>public static implicit operator global::System.Threading.Tasks.Task<T>? (TaskNotifier<T>? notifier){return notifier?.task;}}}
}

2 可观测对象

2.1 ObservableValidator

对象类

public partial class CommentVO : ObservableValidator
{[ObservableProperty][NotifyDataErrorInfo][Required][MinLength(2)][MaxLength(8)]private string _author;[ObservableProperty][NotifyDataErrorInfo][MinLength(1)][CustomValidation(typeof(CommentVO), nameof(ValidateText))]private string _text;public static ValidationResult ValidateText(string value, ValidationContext context){if (value?.Contains("error") == true){return new ValidationResult("Text cannot contain the word 'error'");}return ValidationResult.Success;}[RelayCommand]private void SubmitComment(){this.ValidateAllProperties();if (this.HasErrors){string errors = string.Join(Environment.NewLine, this.GetErrors().Select(vr => vr.ErrorMessage));MessageBox.Show(errors, "Validation errors", MessageBoxButton.OK, MessageBoxImage.Error);return;}// ...}
}

自动生成类:

partial class CommentVO
{/// <inheritdoc cref="_author"/>[global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", "8.3.0.0")][global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage][global::System.ComponentModel.DataAnnotations.RequiredAttribute()][global::System.ComponentModel.DataAnnotations.MinLengthAttribute(2)][global::System.ComponentModel.DataAnnotations.MaxLengthAttribute(8)]public string Author{get => _author;[global::System.Diagnostics.CodeAnalysis.MemberNotNull("_author")][global::System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("The type of the current instance cannot be statically discovered.")]set{if (!global::System.Collections.Generic.EqualityComparer<string>.Default.Equals(_author, value)){OnAuthorChanging(value);OnAuthorChanging(default, value);OnPropertyChanging(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangingArgs.Author);_author = value;ValidateProperty(value, "Author");OnAuthorChanged(value);OnAuthorChanged(default, value);OnPropertyChanged(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangedArgs.Author);}}}/// <inheritdoc cref="_text"/>[global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", "8.3.0.0")][global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage][global::System.ComponentModel.DataAnnotations.MinLengthAttribute(1)][global::System.ComponentModel.DataAnnotations.CustomValidationAttribute(typeof(global::TestCommunityToolkit._2_Observable.CommentVO), "ValidateText")]public string Text{get => _text;[global::System.Diagnostics.CodeAnalysis.MemberNotNull("_text")][global::System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("The type of the current instance cannot be statically discovered.")]set{if (!global::System.Collections.Generic.EqualityComparer<string>.Default.Equals(_text, value)){OnTextChanging(value);OnTextChanging(default, value);OnPropertyChanging(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangingArgs.Text);_text = value;ValidateProperty(value, "Text");OnTextChanged(value);OnTextChanged(default, value);OnPropertyChanged(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangedArgs.Text);}}}
}

2.2 ObservableRecipient

public class CommentMessage : RequestMessage<(bool success, string message)>
{public CommentVO Comment { get; set; }public CommentMessage(CommentVO comment){Comment = comment;}
}public partial class PostVO : ObservableRecipient, IRecipient<CommentMessage>
{[ObservableProperty]private string _title;[ObservableProperty]private string _content;[ObservableProperty]private IList<CommentVO> _comments;[RelayCommand]private void AddComment(){IsActive = true;try{CommentWindow window = new CommentWindow();window.Owner = Application.Current.MainWindow;window.DataContext = new CommentVO();window.ShowDialog();}finally{IsActive = false;}}public void Receive(CommentMessage message){this.Comments.Add(message.Comment);message.Reply(new() { message = "Comment added successfully!", success = true });}
}public partial class CommentVO : ObservableValidator
{// Some common properties and methods...[RelayCommand]private void SubmitComment(){// ...CommentMessage commentMessage = WeakReferenceMessenger.Default.Send(new CommentMessage(this));bool success = commentMessage.Response.success;if (!success){MessageBox.Show(commentMessage.Response.message, "Error", MessageBoxButton.OK, MessageBoxImage.Error);return;}// ...}
}

相关文章:

.NET WPF CommunityToolkit.Mvvm框架

文章目录 .NET WPF CommunityToolkit.Mvvm框架1 源生成器1.1 ObservablePropertyAttribute & RelayCommandAttribute1.2 INotifyPropertyChangedAttribute 2 可观测对象2.1 ObservableValidator2.2 ObservableRecipient .NET WPF CommunityToolkit.Mvvm框架 1 源生成器 1…...

微信小程序使用阿里巴巴矢量图标库正确姿势

1、打开官网&#xff1a;https://www.iconfont.cn/&#xff0c;把整理好的图标下载解压。 2、由于微信小程序不支持直接在wxss中引入.ttf/.woff/.woff2&#xff08;在开发工具生效&#xff0c;手机不生效&#xff09;。我们需要对下载的文件进一步处理。 eot&#xff1a;IE系列…...

【K8S问题系列 |1 】Kubernetes 中 NodePort 类型的 Service 无法访问【已解决】

在 Kubernetes 中&#xff0c;NodePort 类型的 Service 允许用户通过每个节点的 IP 地址和指定的端口访问应用程序。如果 NodePort 类型的 Service 无法通过节点的 IP 地址和指定端口进行访问&#xff0c;可能会导致用户无法访问应用。本文将详细分析该问题的常见原因及其解决方…...

Java基础Day-Thirteen

Java字符串 String类 创建String对象的方法 方法一&#xff1a;创建一个字符串对象imooc&#xff0c;名为s1 String s1"imooc"; 方法二&#xff1a;创建一个空字符串对象&#xff0c;名为s2 String s2new String(); 方法三&#xff1a;创建一个字符串对象imooc&a…...

LangChain实际应用

1、LangChain与RAG检索增强生成技术 LangChain是个开源框架&#xff0c;可以将大语言模型与本地数据源相结合&#xff0c;该框架目前以Python或JavaScript包的形式提供&#xff1b; 大语言模型&#xff1a;可以是GPT-4或HuggingFace的模型&#xff1b;本地数据源&#xff1a;…...

【数据结构】哈希/散列表

目录 一、哈希表的概念二、哈希冲突2.1 冲突概念2.2 冲突避免2.2.1 方式一哈希函数设计2.2.2 方式二负载因子调节 2.3 冲突解决2.3.1 闭散列2.3.2 开散列&#xff08;哈希桶&#xff09; 2.4 性能分析 三、实现简单hash桶3.1 内部类与成员变量3.2 插入3.3 获取value值3.4 总代码…...

flutter 项目初建碰到的控制台报错无法启动问题

在第一次运行flutter时&#xff0c;会碰见一直卡在Runing Gradle task assembleDebug的问题。其实出现这个问题的原因有两个。 一&#xff1a;如果你flutter -doctor 检测都很ok&#xff0c;而且环境配置都很正确&#xff0c;那么大概率就是需要多等一会&#xff0c;少则几十分…...

Java字符串深度解析:String的实现、常量池与性能优化

引言 在Java编程中&#xff0c;字符串操作是最常见的任务之一。String 类在 Java 中有着独特的实现和特性&#xff0c;理解其背后的原理对于编写高效、安全的代码至关重要。本文将深入探讨 String 的实现机制、字符串常量池、不可变性的优点&#xff0c;以及 String、StringBu…...

leetcode 2043.简易银行系统

1.题目要求: 示例: 输入&#xff1a; ["Bank", "withdraw", "transfer", "deposit", "transfer", "withdraw"] [[[10, 100, 20, 50, 30]], [3, 10], [5, 1, 20], [5, 20], [3, 4, 15], [10, 50]] 输出&#xff…...

基于SSM(Spring + Spring MVC + MyBatis)框架的文物管理系统

基于SSM&#xff08;Spring Spring MVC MyBatis&#xff09;框架的文物管理系统是一个综合性的Web应用程序&#xff0c;用于管理和保护文物资源。下面我将提供一个详细的案例程序概述&#xff0c;包括主要的功能模块和技术栈介绍。 项目概述 功能需求 用户管理&#xff1a…...

yakit中的规则详细解释

官方文档 序列前置知识之高级配置 | Yak Program Language 本文章多以编写yaml模版的视角来解释 规则一览 匹配器 在编写yaml中会使用到这里两个东西 点击添加会在返回包的右下角出现匹配器 上面有三个过滤器模式&#xff0c;官方解释 丢弃&#xff1a;丢弃模式会在符合匹配…...

[c语言]strcmp函数的使用和模拟实现

1.strcmp函数的使用 int strcmp ( const char * str1, const char * str2 ); 如果 str1 小于 str2&#xff0c;返回一个负值。如果 str1 等于 str2&#xff0c;返回 0。如果 str1 大于 str2&#xff0c;返回一个正值。 实例&#xff1a; #include <stdio.h> #include &…...

如何把子组件的v-model修改数据,进行接收然后定义数据格式,子传父的实现

在 Vue 中&#xff0c;实现子组件通过 v-model 向父组件传递数据并接收后进行格式化&#xff0c;可以按照以下步骤来封装和实现&#xff1a; 步骤 1: 子组件实现 v-model 子组件需要定义一个 props 来接收 v-model 的值&#xff0c;并通过 emit 方法发出更新事件。 <!-- …...

linux dpkg 查看 安装 卸载 .deb

1、安装 sudo dpkg -i google-chrome-stable.deb # 如果您在安装过程中或安装和启动程序后遇到任何依赖项错误&#xff0c; # 您可以使用以下apt 命令使用-f标志解析​​和安装依赖项&#xff0c;该标志告诉程序修复损坏的依赖项。 # -y 表示自动回答“yes”&#xff0c;在安装…...

【算法】递归+深搜:105.从前序与中序遍历序列构造二叉树

目录 1、题目链接 2、题目介绍 ​​3、解法 函数头-----找出重复子问题 函数体---解决子问题 4、代码 1、题目链接 105.从前序与中序遍历序列构造二叉树. - 力扣&#xff08;LeetCode&#xff09; 2、题目介绍 ​ 3、解法 前序遍历性质&#xff1a; 节点按照 [ 根节点 …...

ESP32 gptimer通用定时器初始化报错:assert failed: timer_ll_set_clock_prescale

背景&#xff1a;IDF版本V5.1.2 &#xff0c;配置ESP32 通用定时器&#xff0c;实现100HZ&#xff0c;占空比50% 的PWM波形。 根据乐鑫官方的IDF指导文档设置内部计数器的分辨率&#xff0c;计数器每滴答一次相当于 1 / resolution_hz 秒。 &#xff08;ESP-IDF编程指导文档&a…...

基于Python的旅游景点推荐系统

作者&#xff1a;计算机学姐 开发技术&#xff1a;SpringBoot、SSM、Vue、MySQL、JSP、ElementUI、Python、小程序等&#xff0c;“文末源码”。 专栏推荐&#xff1a;前后端分离项目源码、SpringBoot项目源码、Vue项目源码、SSM项目源码、微信小程序源码 精品专栏&#xff1a;…...

【开源社区】ELK 磁盘异常占用解决及优化实践

1、问题及场景描述 本文主要讨论在 CentOS环境下基于 rpm 包部署 ELK 系统磁盘异常占用的问题解析和解决方案。 生产问题描述&#xff1a;以下问题现实场景基于ELK体系下&#xff0c;ES服务的磁盘占用问题解析。默认情况下&#xff0c;基于 RPM 安装的 Elasticsearch 服务的安…...

达梦数据守护集群_动态增加实时备库

目录 1、概述 2、实验环境 2.1环境信息 2.2配置信息 2.3 查看初始化参数 3、动态增加实时备库 3.1数据准备 3.2配置新备库 3.3动态增加MAL配置 3.4 关闭守护进程及监视器 3.5修改归档&#xff08;方法1&#xff1a;动态添加归档配置&#xff09; 3.6 修改归档&…...

计算机基础:Ping、Telnet和SSH

文章目录 PingTelnetSSLSSH隧道 Ping Ping和Telnet是两种常见的网络工具&#xff0c;它们分别用于测试网络连接和检查服务端口的连通性。 Ping是一种网络工具&#xff0c;用于测试主机之间的连通性。它通过发送ICMP&#xff08;Internet Control Message Protocol&#xff09…...

[特殊字符] 智能合约中的数据是如何在区块链中保持一致的?

&#x1f9e0; 智能合约中的数据是如何在区块链中保持一致的&#xff1f; 为什么所有区块链节点都能得出相同结果&#xff1f;合约调用这么复杂&#xff0c;状态真能保持一致吗&#xff1f;本篇带你从底层视角理解“状态一致性”的真相。 一、智能合约的数据存储在哪里&#xf…...

大数据学习栈记——Neo4j的安装与使用

本文介绍图数据库Neofj的安装与使用&#xff0c;操作系统&#xff1a;Ubuntu24.04&#xff0c;Neofj版本&#xff1a;2025.04.0。 Apt安装 Neofj可以进行官网安装&#xff1a;Neo4j Deployment Center - Graph Database & Analytics 我这里安装是添加软件源的方法 最新版…...

论文解读:交大港大上海AI Lab开源论文 | 宇树机器人多姿态起立控制强化学习框架(二)

HoST框架核心实现方法详解 - 论文深度解读(第二部分) 《Learning Humanoid Standing-up Control across Diverse Postures》 系列文章: 论文深度解读 + 算法与代码分析(二) 作者机构: 上海AI Lab, 上海交通大学, 香港大学, 浙江大学, 香港中文大学 论文主题: 人形机器人…...

Python:操作 Excel 折叠

💖亲爱的技术爱好者们,热烈欢迎来到 Kant2048 的博客!我是 Thomas Kant,很开心能在CSDN上与你们相遇~💖 本博客的精华专栏: 【自动化测试】 【测试经验】 【人工智能】 【Python】 Python 操作 Excel 系列 读取单元格数据按行写入设置行高和列宽自动调整行高和列宽水平…...

家政维修平台实战20:权限设计

目录 1 获取工人信息2 搭建工人入口3 权限判断总结 目前我们已经搭建好了基础的用户体系&#xff0c;主要是分成几个表&#xff0c;用户表我们是记录用户的基础信息&#xff0c;包括手机、昵称、头像。而工人和员工各有各的表。那么就有一个问题&#xff0c;不同的角色&#xf…...

使用van-uploader 的UI组件,结合vue2如何实现图片上传组件的封装

以下是基于 vant-ui&#xff08;适配 Vue2 版本 &#xff09;实现截图中照片上传预览、删除功能&#xff0c;并封装成可复用组件的完整代码&#xff0c;包含样式和逻辑实现&#xff0c;可直接在 Vue2 项目中使用&#xff1a; 1. 封装的图片上传组件 ImageUploader.vue <te…...

MODBUS TCP转CANopen 技术赋能高效协同作业

在现代工业自动化领域&#xff0c;MODBUS TCP和CANopen两种通讯协议因其稳定性和高效性被广泛应用于各种设备和系统中。而随着科技的不断进步&#xff0c;这两种通讯协议也正在被逐步融合&#xff0c;形成了一种新型的通讯方式——开疆智能MODBUS TCP转CANopen网关KJ-TCPC-CANP…...

基于Docker Compose部署Java微服务项目

一. 创建根项目 根项目&#xff08;父项目&#xff09;主要用于依赖管理 一些需要注意的点&#xff1a; 打包方式需要为 pom<modules>里需要注册子模块不要引入maven的打包插件&#xff0c;否则打包时会出问题 <?xml version"1.0" encoding"UTF-8…...

大模型多显卡多服务器并行计算方法与实践指南

一、分布式训练概述 大规模语言模型的训练通常需要分布式计算技术,以解决单机资源不足的问题。分布式训练主要分为两种模式: 数据并行:将数据分片到不同设备,每个设备拥有完整的模型副本 模型并行:将模型分割到不同设备,每个设备处理部分模型计算 现代大模型训练通常结合…...

CMake控制VS2022项目文件分组

我们可以通过 CMake 控制源文件的组织结构,使它们在 VS 解决方案资源管理器中以“组”(Filter)的形式进行分类展示。 🎯 目标 通过 CMake 脚本将 .cpp、.h 等源文件分组显示在 Visual Studio 2022 的解决方案资源管理器中。 ✅ 支持的方法汇总(共4种) 方法描述是否推荐…...