Avalonia 的命令基类和通知基类备份

备份一下,下次懒得写了,MVVM模式,顺带一个按钮激活的使用例子。有更好的建议欢迎提出。

1.CommandBase.cs

cs 复制代码
/// <summary>
/// 命令鸡肋(执行状态控制)
/// 空参 方法
/// </summary>
public class CommandBase : ICommand
{
    // 命令状态变更事件
    public event EventHandler CanExecuteChanged;

    // 存储命令执行逻辑(有参版本)
    private readonly Action<object> _executeWithParam;


    // 存储执行条件判断逻辑
    private readonly Func<object, bool> _canExecute;

    /// <summary>
    /// 构造函数:接收执行逻辑和执行条件
    /// </summary>
    public CommandBase(Action<object> execute, Func<object, bool> canExecute = null)
    {
        _executeWithParam = execute ?? throw new ArgumentNullException(nameof(execute));
        _canExecute = canExecute;
    }

    /// <summary>
    /// 构造函数:接收无参执行逻辑和执行条件
    /// </summary>
    public CommandBase(Action execute, Func<object, bool> canExecute = null)
    {
        _executeWithParam = _ => execute();
        _canExecute = canExecute;
        if (execute == null)
            throw new ArgumentNullException(nameof(execute));
    }

    /// <summary>
    /// 判断命令是否可执行
    /// </summary>
    public bool CanExecute(object parameter)
    {
        // 如果有执行条件则使用条件判断,否则默认可执行
        return _canExecute == null ? true : _canExecute(parameter);
    }

    /// <summary>
    /// 执行命令逻辑
    /// </summary>
    public void Execute(object parameter)
    {
        if (CanExecute(parameter))
        {
            _executeWithParam(parameter);
        }
    }

    /// <summary>
    /// 手动触发CanExecuteChanged事件,强制 UI 更新状态。
    /// </summary>
    public void RaiseCanExecuteChanged()
    {
        CanExecuteChanged?.Invoke(this, EventArgs.Empty);
    }
}

2.NotifyBase.cs

cs 复制代码
public class NotifyBase : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    /// <summary>
    /// 属性改变通知方法
    /// </summary>
    /// <param name="propName"></param>
    public void NotifyChanged([CallerMemberName] string propName = "")
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propName));
    }
}

3.按钮激活示例

在ViewModel中

cs 复制代码
//数据表格的选中项SelectedItem数据源  
private ConfProduct _selectedConfProduct;

    public ConfProduct SelectedConfProduct
    {
        get => _selectedConfProduct;
        set
        {
            if (value == null)
            {
                _selectedConfProduct = null;
                NotifyChanged();
                //激按钮状态控制
                UpdateBtnCommand.RaiseCanExecuteChanged();
                DeleteBtnCommand.RaiseCanExecuteChanged();
                //Dgv2数据初始化
                ResetDgv2ToInitialState();
                return;
            }

            _selectedConfProduct = value;
            NotifyChanged();
            UpdateBtnCommand.RaiseCanExecuteChanged();
            DeleteBtnCommand.RaiseCanExecuteChanged();
            ShowExistingProductDetails(); // Dgv2加载选中项
        }
    }


    #region 2. 命令声明

    public CommandBase UpdateBtnCommand { get; }
    public CommandBase DeleteBtnCommand { get; }

    #endregion



    #region 4. 构造函数(初始化命令,数据)

    public ProductSettingViewModel()
    {
        AddBtnCommmand = new CommandBase(DoAdd);
        UpdateBtnCommand = new CommandBase(DoUpdate, CanUpdateOrDelete);
        DeleteBtnCommand = new CommandBase(DoDelete, CanUpdateOrDelete);

        // 异步加载数据,避免UI阻塞
        _ = LoadDataAsync();
    }

    #endregion


    #region 按钮能否执行判断

    // 按钮是否可执行(选中产品才可用)
    private bool CanUpdateOrDelete(object parameter) => SelectedConfProduct != null;   //选中项不为空,返回true

    #endregion

激活和不激活的实例图片

未选中一行

选中一行

PS:添加我没有使用这套逻辑,默认一直处于能激活的状态。

4.解析

出自Ai的总结

结合CommandBase命令基类和SelectedConfProduct选中项属性,按钮激活 / 取消激活的逻辑形成了 **"条件绑定→状态判断→事件触发→UI 响应"** 的完整闭环,每个环节都有明确的代码支撑,具体拆解如下:

一、第一步:命令初始化 ------ 绑定 "执行条件" 到命令

在 ViewModel 的构造函数中,UpdateBtnCommandDeleteBtnCommand初始化时,会将 "按钮可执行条件"(CanUpdateOrDelete)通过CommandBase的构造函数传入,完成 "条件与命令" 的绑定:

csharp

复制代码
// ViewModel中命令初始化
UpdateBtnCommand = new CommandBase(DoUpdate, CanUpdateOrDelete);
DeleteBtnCommand = new CommandBase(DoDelete, CanUpdateOrDelete);
关键关联:CommandBase构造函数的作用

CommandBase的第二个构造函数(接收无参执行逻辑 + 执行条件)会将传入的CanUpdateOrDeleteFunc<object, bool>类型)赋值给私有字段_canExecute,为后续的 "可执行判断" 埋下依据:

csharp

复制代码
// CommandBase构造函数(接收无参执行逻辑+执行条件)
public CommandBase(Action execute, Func<object, bool> canExecute = null)
{
    _executeWithParam = _ => execute(); // 包装无参执行逻辑(如DoUpdate、DoDelete)
    _canExecute = canExecute; // 保存执行条件(即ViewModel中的CanUpdateOrDelete)
    if (execute == null)
        throw new ArgumentNullException(nameof(execute));
}

二、第二步:可执行判断 ------CanExecute方法决定按钮状态

Avalonia 的按钮在绑定命令后,会自动调用命令的CanExecute方法 判断自身是否激活(可点击),这个判断逻辑由CommandBaseCanExecute方法实现,且直接依赖第一步绑定的_canExecute(即CanUpdateOrDelete):

1. CommandBase.CanExecute的核心逻辑

csharp

复制代码
public bool CanExecute(object parameter)
{
    // 如果有执行条件(_canExecute不为null),则执行条件判断;否则默认"可执行"
    return _canExecute == null ? true : _canExecute(parameter);
}
2. 最终的判断依据:CanUpdateOrDelete

ViewModel 中的CanUpdateOrDelete是实际的 "条件判断函数",它直接关联SelectedConfProduct的状态 ------只有选中项不为 null 时,才返回 true(按钮激活)

csharp

复制代码
private bool CanUpdateOrDelete(object parameter) => SelectedConfProduct != null;
场景举例
  • SelectedConfProduct = null(未选中 DGV1 行):CanUpdateOrDelete返回falseCommandBase.CanExecute返回false→按钮禁用(灰色,不可点击)。
  • SelectedConfProduct = 某产品实体(选中 DGV1 行):CanUpdateOrDelete返回trueCommandBase.CanExecute返回true→按钮激活(正常颜色,可点击)。

三、第三步:状态更新 ------SelectedConfProduct变化时触发事件

CanExecute方法只会在按钮初始化或 "命令状态变更事件" 触发时重新执行。因此,当SelectedConfProduct变化(选中 / 取消选中)时,必须通过CommandBase.RaiseCanExecuteChanged手动触发事件,强制 UI 重新检查CanExecute,更新按钮状态:

1. SelectedConfProduct变化时的触发逻辑

SelectedConfProductset访问器中,无论值是 "从 null 变为实体" 还是 "从实体变为 null",都会调用RaiseCanExecuteChanged

csharp

复制代码
public ConfProduct SelectedConfProduct
{
    get => _selectedConfProduct;
    set
    {
        if (value == null)
        {
            _selectedConfProduct = null;
            NotifyChanged();
            // 关键:触发命令状态更新
            UpdateBtnCommand.RaiseCanExecuteChanged();
            DeleteBtnCommand.RaiseCanExecuteChanged();
            ResetDgv2ToInitialState();
            return;
        }

        _selectedConfProduct = value;
        NotifyChanged();
        // 关键:触发命令状态更新
        UpdateBtnCommand.RaiseCanExecuteChanged();
        DeleteBtnCommand.RaiseCanExecuteChanged();
        ShowExistingProductDetails();
    }
}
2. CommandBase.RaiseCanExecuteChanged的作用

该方法会主动触发CommandBaseCanExecuteChanged事件(ICommand接口的核心事件),而 Avalonia 的按钮会监听这个事件 ------ 事件触发时,按钮会立即重新调用CanExecute方法,更新自身的激活状态:

csharp

复制代码
public void RaiseCanExecuteChanged()
{
    CanExecuteChanged?.Invoke(this, EventArgs.Empty); // 触发状态变更事件
}

四、第四步:UI 响应 ------ 按钮根据状态更新显示

Avalonia 的Button控件在绑定Command后,会自动完成以下逻辑,无需额外代码:

  1. 初始化时,调用Command.CanExecute(null)获取初始状态,设置按钮是否激活。
  2. Command.CanExecuteChanged事件触发时,再次调用Command.CanExecute(null),重新获取状态,并更新按钮的IsEnabled属性(激活 / 禁用)。

总结:完整逻辑链路(以 "删除按钮" 为例)

操作场景 代码执行流程 按钮状态结果
程序启动,未选中任何行 1. SelectedConfProduct = nullCanUpdateOrDelete返回false;2. 按钮初始化调用DeleteBtnCommand.CanExecute(null)→返回false;3. 按钮禁用。 禁用
用户点击 DGV1 某行 1. SelectedConfProduct = 选中的产品实体;2. 调用DeleteBtnCommand.RaiseCanExecuteChanged()→触发CanExecuteChanged事件;3. 按钮监听事件,调用CanExecute(null)CanUpdateOrDelete返回true;4. 按钮激活。 激活
用户点击 DGV1 空白区域 1. SelectedConfProduct = null;2. 调用DeleteBtnCommand.RaiseCanExecuteChanged()→触发事件;3. 按钮调用CanExecute(null)→返回false;4. 按钮禁用。 禁用

整个逻辑完全遵循 MVVM 模式:数据(SelectedConfProduct)的变化驱动命令状态,命令状态驱动 UI(按钮)的显示 ,无需在代码后置中直接操作按钮的IsEnabled属性,实现了 "数据与 UI 的解耦"。

相关推荐
Larry_Yanan7 小时前
QML学习笔记(四十五)QML与C++交互:信号槽的双向实现
c++·笔记·qt·学习·ui·交互
低调小一9 小时前
KuiklyUI 科普:UI 如何映射到 Android View 并完成渲染
android·windows·ui
张人玉11 小时前
C#WPF如何实现登录页面跳转
ui·c#·wpf
张人玉12 小时前
C#WPF如何跳转页面
笔记·ui·c#·wpf
猫林老师14 小时前
HarmonyOS测试与上架:单元测试、UI测试与App Gallery Connect发布实战
ui·单元测试·harmonyos
爱学习的大牛1231 天前
MVVM 架构 android
android·mvvm
专注VB编程开发20年1 天前
B.NET编写不阻塞UI线程的同步延时
ui·.net·vb.net·doevents
渡我白衣2 天前
未来的 AI 操作系统(三)——智能的中枢:从模型到系统的统一
人工智能·深度学习·ui·语言模型·人机交互
Element_南笙2 天前
吴恩达新课程:Agentic AI(笔记2)
数据库·人工智能·笔记·python·深度学习·ui·自然语言处理