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 的解耦"。

相关推荐
I'm Jie1 天前
Swagger UI 本地化部署,解决 FastAPI Swagger UI 依赖外部 CDN 加载失败问题
python·ui·fastapi·swagger·swagger ui
爱学习的程序媛1 天前
【Web前端】优化Core Web Vitals提升用户体验
前端·ui·web·ux·用户体验
爱学习的程序媛1 天前
【Web前端】前端用户体验优化全攻略
前端·ui·交互·web·ux·用户体验
紫丁香1 天前
Selenium自动化测试详解1
python·selenium·测试工具·ui
GISer_Jing1 天前
前端组件库——shadcn/ui:轻量、自由、可拥有,解锁前端组件库的AI时代未来
前端·人工智能·ui
rjc_lihui2 天前
IntelliSense: 无法打开 源 文件 “ui_mainwindow.h“ demo\qtdemosrc\mainwindow
ui
老星*2 天前
Lucide Icons:开源、轻量、设计师友好的现代图标库
ui·开源·github
Swift社区2 天前
AI 驱动 UI:鸿蒙 ArkUI 的新可能
人工智能·ui·harmonyos
Feng-licong3 天前
告别手写 UI:当 Google Stitch 遇上 Flutter,2026 年的“Vibe Coding”开发流
flutter·ui
一字白首3 天前
微信小程序进阶实战:从 UI 组件库到全局状态管理全解DAY05
ui·微信小程序·小程序