首页 > 其他分享 >CommunityToolkit.Mvvm框架

CommunityToolkit.Mvvm框架

时间:2024-11-08 10:58:06浏览次数:6  
标签:CommunityToolkit Mvvm 框架 global System value Diagnostics new property

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

标签:CommunityToolkit,Mvvm,框架,global,System,value,Diagnostics,new,property
From: https://www.cnblogs.com/winemonk/p/18534682

相关文章

  • Mybatis框架入门
    IDEA使用Maven部署第一个MyBatis项目,两种方法导入,一个是Jar包的形式,此形式用于初学者学习并完成学校实训作业,第二种则是通过pom.xml文件引入依赖从而避免了从官网下载jar包等问题。一、创建一个Maven工程:File-->New-->projcet-->Maven二、导入以下Jar包:New-->ProjcetStruc......
  • Next.js 实战开发入门教程敏捷开发框架
    在上一篇文章中,我们已经成功实现了网站的导航栏。接下来,我们将继续开发网页的主体部分,用于展示我们的网站业务情况。主页内容展示首先,我们需要创建一个名为 /app/components/Main.tsx 的文件,作为主页内容的入口。然后在根目录的 /app/page.tsx 中引入这个组件,这样在访问主页......
  • PowerShell DSC(Desired State Configuration)是一种配置管理框架,旨在通过声明性的方式
    PowerShellDSC(DesiredStateConfiguration)是一种配置管理框架,旨在通过声明性的方式自动化和管理计算机的配置。它是WindowsPowerShell的一部分,允许管理员定义和维护计算机系统的目标配置状态,而不是手动进行逐个更改。1. 什么是PowerShellDSC?PowerShellDSC是一种基于声......
  • Keras框架——卷积神经CNN神经网络~MINST手写数字识别
    一.原理说明卷积神经网络(ConvolutionalNeuralNetworks)是一种深度学习模型或类似于人工神经网络的多层感知器,常用来分析视觉图像。卷积神经网络的创始人是着名的计算机科学家YannLeCun,目前在Facebook工作,他是第一个通过卷积神经网络在MNIST数据集上解决手写数字问题的......
  • PHP框架选择:如何根据项目需求选择最合适的框架PHP框架选择:如何根据项目需求选择最合适
    在开发PHP项目时,框架的选择至关重要。一个合适的PHP框架不仅能提高开发效率,还能增强代码的可维护性和可扩展性。如何根据项目需求选择合适的框架呢?以下是一些关键的考虑因素。项目的规模和复杂度是选择框架时重要的考量之一。如果是一个小型项目,可能不需要一个复杂的框架,像Slim或......
  • AI助力论文框架设计
    AI助力论文框架设计在论文写作的过程中,设计出一个清晰且富有逻辑的框架通常是最为关键且最具挑战性的部分之一。AI工具的兴起为学术写作带来了全新的可能性,尤其是在构建论文框架时,AI可以充当一个智能的辅助工具,帮助研究者理清思路、高效规划内容。通过AI的辅助,不仅可以加快......
  • dotnet core微服务框架Jimu ~ 会员注册微服务
     提供会员注册服务,用户必须注册成会员才能享受应用提供的服务,如浏览和发布新闻,但有些服务又需要指定角色的会员才能操作,如所有会员都可以浏览新闻,只有管理员(admin)角色的会员才可以发布新闻。有2个公开的api:CheckName:判断用户名是否可用;Register:根据用户名注册......
  • 大模型-大模型训练框架-07
    目录1.训练框架概述2.重点Deepspeed框架介绍3.DeepSpeed框架实践4.debug5.扩展1.训练框架概述100亿10^1010B参数量是模型具备涌现能力的基本门槛如何充分的利用显卡的能力充分的使用显存分布式训练框架对比MegatronandDeepSpeed是目前主流的训练加速框架训......
  • 适合才最美:Shiro安全框架使用心得
    大家好,我是V哥。ApacheShiro是一个强大且灵活的Java安全框架,专注于提供认证、授权、会话管理和加密功能。它常用于保护Java应用的访问控制,特别是在Web应用中。相比于SpringSecurity,Shiro的设计更简洁,适合轻量级应用,并且在许多方面具有更好的易用性和扩展性,今......
  • 基于springboot框架在线生鲜商城推荐系统 java实现个性化生鲜/农产品购物商城推荐网站
    基于springboot框架在线生鲜商城推荐系统java实现个性化生鲜/农产品购物商城推荐网站爬虫、数据分析、排行榜基于协同过滤算法推荐、基于流行度热点推荐、平均加权混合推荐机器学习、大数据、深度学习OnlineShopRecommendEx一、项目简介1、开发工具和使用技术IDEA,jdk......