.NET WPF CommunityToolkit.Mvvm框架

news2024/11/8 6:50:19

文章目录

  • .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.coloradmin.cn/o/2235629.html

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

相关文章

【go从零单排】Strings and Runes 字符串和字符

Don’t worry , just coding! 内耗与overthinking只会削弱你的精力&#xff0c;虚度你的光阴&#xff0c;每天迈出一小步&#xff0c;回头时发现已经走了很远。 概念 在Go语言中&#xff0c;rune 是一个内置的数据类型&#xff0c;用于表示一个Unicode字符。它实际上是一个别名…

JDK1.5 java代码打包jar HmacSha256

文章目录 demo地址背景实现编写代码编译class文件打包 JAR 文件执行生成的 JAR 文件辅助验证方式 常见问题和解决方法常规生成jar方案maven插件idea工具 demo地址 https://github.com/xiangge-zx/HmacSha256 背景 最近接到一个需求,做一个可以用来HmacSha256加密的小工具&am…

履带机器人(一、STM32控制部分--标准库)

一、履带机器人整体逻辑框架 通过在PC端搭建上位机,使得在PC端可以给STM32发送控制指令并且接受STM32的状态信息。 通过RS485通信,使得STM32可以和电机进行通信,STM32发送启动、停止、转速、方向等指令,并接受电机返回的状态信息。 二、STM32逻辑框架 整体逻辑: 1、先…

yakit中的规则详细解释

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

从0开始学习机器学习--Day14--如何优化神经网络的代价函数

在上一篇文章中&#xff0c;解析了神经网络处理分类问题的过程&#xff0c;类似的&#xff0c;在处理多元分类问题时&#xff0c;神经网络会按照类型分成多个输出层的神经元来表示&#xff0c;如下&#xff1a; 处理4个分类问题时的神经网络 我们可以看到&#xff0c;相较于之…

LINUX下的Myql:库的操作

目录 1.库的创建 2.字符集和校验集 1.查看db1默认的字符集合校验集 2.设置字符集和校验集 3.库的查看 4.数据库的修改 5.数据库的删除 6.数据库的备份 1.库的创建 create database db1; 创建一个名为db1的数据库&#xff0c;LINUX会创建一个名为db1的文件夹。 show data…

Java多线程详解⑤(全程干货!!!)线程安全问题 || 锁 || synchronized

这里是Themberfue 在上一节的最后&#xff0c;我们讨论两个线程同时对一个变量累加所产生的现象 在这一节中&#xff0c;我们将更加详细地解释这个现象背后发生的原因以及该如何解决这样类似的现象 线程安全问题 public class Demo15 {private static int count 0;public …

【taro react】 ---- 常用自定义 React Hooks 的实现【六】之类渐入动画效果的轮播

1. 效果 2. 场景 css 效果实现:可以看到效果图中就是一个图片从小到大的切换动画效果,这个效果很简单,使用 css 的 transform 的 scale 来实现图片的从小到大的效果,切换就更加简单了,不管是 opacity 还是 visibility 都可以实现图片的隐藏和显示的切换。React.Children.m…

杨辉三角,洗牌算法

杨辉三角 给定一个非负整数numRows&#xff0c;生成杨辉三角的前numRows行。 在杨辉三角中&#xff0c;每个数是它的左上方和右上方的数的和。 public List<List<Integer>> generate(int numRows){List<List<Integer>> ret new ArrayList<>();…

ssm068海鲜自助餐厅系统+vue(论文+源码)_kaic

设计题目&#xff1a;海鲜自助餐厅系统的设计与实现 摘 要 网络技术和计算机技术发展至今&#xff0c;已经拥有了深厚的理论基础&#xff0c;并在现实中进行了充分运用&#xff0c;尤其是基于计算机运行的软件更是受到各界的关注。加上现在人们已经步入信息时代&#xff0c;所…

ENSP ISOLATE隔离区域

如果用户想进行二层隔离&#xff0c;用户可以将不同的端口加入不同的VLAN&#xff0c;但这样会浪费有限的VLAN资源。采用端口隔离功能&#xff0c;可以实现同一VLAN内端口之间的隔离。用户只需要将端口加入到隔离组中&#xff0c;就可以实现隔离组内端口之间二层数据的隔离。端…

自攻螺钉的世纪演变:探索关键设计与应用

自攻螺钉作为现代工业和建筑中的不可或缺的标准部件&#xff0c;经过了超过100年的发展和创新。从1914年最早的铁螺钉设计到今天的自钻自攻螺钉&#xff0c;自攻螺钉的设计不断优化&#xff0c;以适应更复杂的应用需求。本文将回顾自攻螺钉的演变历程&#xff0c;分析其设计原理…

KTHREAD结构-->ApcState

1. ApcListHead[2] 2. KernelApcInProgress

深入浅出 Spring Boot 与 Shiro:构建安全认证与权限管理框架

一、Shiro框架概念 &#xff08;一&#xff09;Shiro框架概念 1.概念&#xff1a; Shiro是apache旗下一个开源安全框架&#xff0c;它对软件系统中的安全认证相关功能进行了封装&#xff0c;实现了用户身份认证&#xff0c;权限授权、加密、会话管理等功能&#xff0c;组成一…

魅力标签云,奇幻词云图 —— 数据可视化新境界

目录 目的原理详解建议 标签云&#xff1a;用于汇总生成的标签&#xff0c;一般是独立词汇运行前的准备代码示例 词云&#xff1a;对本文中出现频率较高的词&#xff0c;视觉上突出显示总结 目的 掌握文本与文档可视化&#xff1a;使用特定软件或编程语言&#xff08;如Python…

正则表达式在Kotlin中的应用:提取图片链接

在现代的Web开发中&#xff0c;经常需要从网页内容中提取特定的数据&#xff0c;例如图片链接。Kotlin作为一种现代的编程语言&#xff0c;提供了强大的网络请求和文本处理能力。本文将介绍如何使用Kotlin结合正则表达式来提取网页中的图片链接。 正则表达式基础 正则表达式是…

鉴源实验室·加密技术在汽车系统中的应用

随着汽车技术的快速发展&#xff0c;现代汽车已经不再是简单的交通工具&#xff0c;而是融合了多种智能功能的移动终端。无论是自动驾驶、车联网&#xff08;V2X&#xff09;&#xff0c;还是车内娱乐系统&#xff0c;数据传输和存储已经成为汽车生态系统中的关键环节。然而&am…

UE5.1 控制台设置帧率

仅个人记录&#xff0c;未经过严格验证。 也可通过控制台命令蓝图节点&#xff0c;在运行时执行 锁帧&#xff1a; 0->120帧 1-》60帧

SpringCloud Sentinel 服务治理详解

雪崩问题 微服务调用链路中的某个服务故障&#xff0c;引起整个链路中的所有微服务都不可用&#xff0c;这就是雪崩。 雪崩问题产生的原因&#xff1a; 微服务相互调用&#xff0c;服务提供者出现故障或阻塞。服务调用者没有做好异常处理&#xff0c;导致自身故障。调用链中的…

前端基础-html-注册界面

&#xff08;200粉啦&#xff0c;感谢大家的关注~ 一起加油吧~&#xff09; 浅浅分享下作业&#xff0c;大佬轻喷~ 网页最终效果&#xff1a; 详细代码&#xff1a; ​ <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"…