.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;}// ...}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.xdnf.cn/news/6623.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

linux 安装anaconda3

1.下载 使用repo镜像网址下载对应安装包 右击获取下载地址&#xff0c;使用终端下载 wget https://repo.anaconda.com/archive/Anaconda3-2024.02-1-Linux-x86_64.sh2.安装 使用以下命令可直接指定位置 bash Anaconda3-2024.02-1-Linux-x86_64.sh -b -p /home/anaconda3也…

JavaScript。—关于语法基础的理解—

一、程序控制语句 JavaScript 提供了 if 、if else 和 switch 3种条件语句&#xff0c;条件语句也可以嵌套。 &#xff08;一&#xff09;、条件语句 1、单向判断 &#xff1a; if... &#xff08;1&#xff09;概述 < if >元素用于在判断该语句是否满足特定条…

DDD学习笔记

DDD学习笔记 1. 什么是 DDD&#xff1f; 领域驱动设计&#xff08;Domain-Driven Design, DDD&#xff09;是一种复杂软件系统设计的方法&#xff0c;强调以业务领域为核心进行设计与开发。它通过将业务逻辑与代码组织紧密结合&#xff0c;帮助开发团队更好地理解和实现业务需…

c语言简单编程练习8

1、递归函数&#xff1a; 通过调用自身来解决问题的函数&#xff0c;递归也就是传递和回归&#xff1b; 递归函数的两个条件&#xff1a; 1&#xff09;函数调用函数本身 2&#xff09;一定要有结束条件 循环与递归的区别&#xff1a; 每调用一次递归函数&#xff0c;都会…

如何将MySQL彻底卸载干净

目录 背景&#xff1a; MySQL的卸载 步骤1&#xff1a;停止MySQL服务 步骤2&#xff1a;软件的卸载 步骤3&#xff1a;残余文件的清理 步骤4&#xff1a;清理注册表 步骤五:删除环境变量配置 总结&#xff1a; 背景&#xff1a; MySQL卸载不彻底往往会导致重新安装失败…

linux-环境变量

环境变量是系统提供的一组 name value 的变量&#xff0c;不同的变量有不同的用途&#xff0c;通常都具有全局属性 env 查看环境变量 PATH PATH是一个保存着系统指令路径的一个环境变量&#xff0c;系统提供的指令不需要路径&#xff0c;直接就可以使用就是因为指令的路径…

IDEA修改生成jar包名字的两种方法实现

IDEA修改生成jar包名字的两种方法实现 更新时间&#xff1a;2023年08月18日 11:45:36 作者&#xff1a;白白白鲤鱼 本文主要介绍了IDEA修改生成jar包名字的两种方法实现,通过简单的步骤,您可以修改项目名称并在打包时使用新的名称,具有一定的参考价值,感兴趣的可以了解下 …

【Java Web】JSP实现数据传递和保存(中)中文乱码 转发与重定向

文章目录 中文乱码转发与重定向转发重定向区别 升级示例1 中文乱码 JSP 中默认使用的字符编码方式&#xff1a;iso-8859-1&#xff0c;不支持中文。常见的支持中文的编码方式及其收录的字符&#xff1a; gb2312&#xff1a;常用简体汉字gbk&#xff1a;简体和繁体汉字utf-8&a…

ROS话题通信机制理论模型的学习

话题通信是ROS&#xff08;Robot Operating System&#xff0c;机器人操作系统&#xff09;中使用频率最高的一种通信模式&#xff0c;其实现模型主要基于发布/订阅模式。 一、基本概念 话题通信模型中涉及三个主要角色&#xff1a; ROS Master&#xff08;管理者&#xff0…

【Ai教程】Ollma安装 | 0代码本地运行Qwen大模型,保姆级教程来了!

我们平时使用的ChatGPT、kimi、豆包等Ai对话工具&#xff0c;其服务器都是部署在各家公司的机房里&#xff0c;如果我们有一些隐私数据发到对话中&#xff0c;很难保证信息是否安全等问题&#xff0c;如何在保证数据安全的情况下&#xff0c;又可以使用大预言模型&#xff0c;O…

从工作原理上解释为什么MPLS比传统IP方式高效?

多协议标签交换&#xff08;Multiprotocol Label Switching, MPLS&#xff09;是一种用于高速数据包转发的技术。它通过在网络的入口点对数据包进行标签操作&#xff0c;然后在核心网络内部基于这些标签来快速转发数据包&#xff0c;从而提高了数据传输效率。以下是几个方面解释…

以命令行形式执行Postman脚本(使用Newman)

一、背景 ​ Postman的操作离不开客户端。但是在一些情况下可能无法使用客户端去进行脚本执行。比如在服务端进行接口测试。由此我们引入了Newman。Newman基于Node.js开发&#xff0c;它使您可以直接从命令行轻松运行和测试Postman测试集。它在构建时考虑了可扩展性&#xff0c…

国内手机号Google账号(gmail)注册教程

注意&#xff01;&#xff01;本篇只适用于未注册过或未修改过的萌新用户&#xff01;&#xff01;&#xff01;&#xff01;&#xff08;我注册第二个账号时就通过不了了&#xff09; 国内手机号码如何创建Google&#xff08;谷歌&#xff09;账号&#xff0c;我们会发现&…

性能测试|docker容器下搭建JMeter+Grafana+Influxdb监控可视化平台

前言 在当前激烈的市场竞争中&#xff0c;创新和效率成为企业发展的核心要素之一。在这种背景下&#xff0c;如何保证产品和服务的稳定性、可靠性以及高效性就显得尤为重要。 而在软件开发过程中&#xff0c;性能测试是一项不可或缺的环节&#xff0c;它可以有效的评估一个系…

大语言模型训练的全过程:预训练、微调、RLHF

一、 大语言模型的训练过程 预训练阶段&#xff1a;PT&#xff08;Pre training&#xff09;。使用公开数据经过预训练得到预训练模型&#xff0c;预训练模型具备语言的初步理解&#xff1b;训练周期比较长&#xff1b;微调阶段1&#xff1a;SFT&#xff08;指令微调/有监督微调…

【LeetCode】【算法】142. 环形链表II

142环形链表II 题目描述 给定一个链表的头节点 head &#xff0c;返回链表开始入环的第一个节点。 如果链表无环&#xff0c;则返回 null。 如果链表中有某个节点&#xff0c;可以通过连续跟踪 next 指针再次到达&#xff0c;则链表中存在环。 为了表示给定链表中的环&#x…

白话文讲解大模型| Attention is all you need

本文档旨在详细阐述当前主流的大模型技术架构如Transformer架构。我们将从技术概述、架构介绍到具体模型实现等多个角度进行讲解。通过本文档&#xff0c;我们期望为读者提供一个全面的理解&#xff0c;帮助大家掌握大模型的工作原理&#xff0c;增强与客户沟通的技术基础。本文…

解析IO零拷贝技术

背景介绍 从字面上我们很容易理解出&#xff0c;零拷贝包含两个意思&#xff1a; 拷贝&#xff1a;就是指数据从一个存储区域转移到另一个存储区域。零&#xff1a;它表示拷贝数据的次数为 0。 合起来理解&#xff0c;零拷贝就是不需要将数据从一个存储区域复制到另一个存储…

练习LabVIEW第三十六题

学习目标&#xff1a; 刚学了LabVIEW&#xff0c;在网上找了些题&#xff0c;练习一下LabVIEW&#xff0c;有不对不好不足的地方欢迎指正&#xff01; 第三十六题&#xff1a; 使用labview模拟温度采集系统&#xff0c;要求有停止键 开始编写&#xff1a; 前面板放一个温度…

day-81 打家劫舍 II

思路 与LCR 089. 打家劫舍相比&#xff0c;本题所有房屋围成了一圈&#xff0c;那么第一间房子和最后一间房子不能同时打劫&#xff0c;那么就可以分为两种情况&#xff1a;1.选第一间房打劫&#xff1b;2.选最后一间房打劫 解题过程 然后依次计算出以上两种情况的最大金额&am…