Avalonia 的命令基类和通知基类备份
备份一下,下次懒得写了,MVVM模式,顺带一个按钮激活的使用例子。有更好的建议欢迎提出。
1.CommandBase.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
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中
//数据表格的选中项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 的构造函数中,UpdateBtnCommand
和DeleteBtnCommand
初始化时,会将 “按钮可执行条件”(CanUpdateOrDelete
)通过CommandBase
的构造函数传入,完成 “条件与命令” 的绑定:
csharp
// ViewModel中命令初始化
UpdateBtnCommand = new CommandBase(DoUpdate, CanUpdateOrDelete);
DeleteBtnCommand = new CommandBase(DoDelete, CanUpdateOrDelete);
关键关联:CommandBase
构造函数的作用
CommandBase
的第二个构造函数(接收无参执行逻辑 + 执行条件)会将传入的CanUpdateOrDelete
(Func<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
方法判断自身是否激活(可点击),这个判断逻辑由CommandBase
的CanExecute
方法实现,且直接依赖第一步绑定的_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
返回false
→CommandBase.CanExecute
返回false
→按钮禁用(灰色,不可点击)。 - 当
SelectedConfProduct = 某产品实体
(选中 DGV1 行):CanUpdateOrDelete
返回true
→CommandBase.CanExecute
返回true
→按钮激活(正常颜色,可点击)。
三、第三步:状态更新 ——SelectedConfProduct
变化时触发事件
CanExecute
方法只会在按钮初始化或 “命令状态变更事件” 触发时重新执行。因此,当SelectedConfProduct
变化(选中 / 取消选中)时,必须通过CommandBase.RaiseCanExecuteChanged
手动触发事件,强制 UI 重新检查CanExecute
,更新按钮状态:
1. SelectedConfProduct
变化时的触发逻辑
在SelectedConfProduct
的set
访问器中,无论值是 “从 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
的作用
该方法会主动触发CommandBase
的CanExecuteChanged
事件(ICommand
接口的核心事件),而 Avalonia 的按钮会监听这个事件 —— 事件触发时,按钮会立即重新调用CanExecute
方法,更新自身的激活状态:
csharp
public void RaiseCanExecuteChanged()
{CanExecuteChanged?.Invoke(this, EventArgs.Empty); // 触发状态变更事件
}
四、第四步:UI 响应 —— 按钮根据状态更新显示
Avalonia 的Button
控件在绑定Command
后,会自动完成以下逻辑,无需额外代码:
- 初始化时,调用
Command.CanExecute(null)
获取初始状态,设置按钮是否激活。 - 当
Command.CanExecuteChanged
事件触发时,再次调用Command.CanExecute(null)
,重新获取状态,并更新按钮的IsEnabled
属性(激活 / 禁用)。
总结:完整逻辑链路(以 “删除按钮” 为例)
操作场景 | 代码执行流程 | 按钮状态结果 |
---|---|---|
程序启动,未选中任何行 | 1. SelectedConfProduct = null →CanUpdateOrDelete 返回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 的解耦”。