【WPF】Prism学习(二)

Prism Commands

1.命令(Commanding)

1.1. ViewModel的作用

  • ViewModel不仅提供在视图中显示或编辑的数据,还可能定义一个或多个用户可以执行的动作或操作。
  • 这些用户可以通过用户界面(UI)执行的动作或操作通常被定义为命令(Commands)。

1.2. 命令(Commands)的作用

  • 命令提供了一种方便的方式来表示可以轻松绑定到UI控件的动作或操作。
  • 它们封装了实现动作或操作的实际代码,并有助于将其与视图中的实际视觉表示分离。

1.3. 命令的触发方式

  • 用户与视图交互时,可以通过多种方式视觉上表示和调用命令。
  • 大多数情况下,命令是由鼠标点击触发的,但它们也可以由快捷键按下、触摸手势或其他输入事件触发。
  • 视图中的控件与ViewModel的命令数据绑定,因此用户可以使用控件定义的任何输入事件或手势来调用它们。
  • 视图中的UI控件与命令之间的交互可以是双向的,即命令可以在用户与UI交互时被调用,UI也可以根据底层命令的启用或禁用状态自动启用或禁用。

1.4. 命令对象的实现

  • ViewModel可以将命令实现为命令对象(一个实现了ICommand接口的对象)。
  • 视图与命令的交互可以声明性地定义,而不需要在视图的代码后台文件中编写复杂的事件处理代码。
  • 例如,某些控件天生支持命令,并提供了一个可以与ViewModel提供的ICommand对象数据绑定的Command属性。
  • 在其他情况下,可以使用命令行为将控件与ViewModel提供的命令方法或命令对象关联起来。

1.5. ICommand接口的实现

  • 实现ICommand接口是直接的。
  • Prism框架提供了DelegateCommand这个ICommand接口的实现,你可以在你的应用程序中直接使用它。

DelegateCommand可以在Prism.Core Nuget包的Prism.Commands命名空间中找到。

2.创建一个委托命令对象

如何在MVVM模式中使用DelegateCommand来实现命令,以及如何将这些命令绑定到UI控件上。

2.1. DelegateCommand类封装了两个委托,这两个委托分别对应于ViewModel中的ExecuteCanExecute方法。这意味着,当你在UI中触发一个命令时,DelegateCommand会调用ViewModel中相应的方法。

2.2. 你可以通过DelegateCommand类的构造函数来指定这些委托。例如,下面的代码示例展示了如何通过指定委托到ViewModel中的SubmitCanSubmit方法来构造一个代表提交命令的DelegateCommand实例。然后,这个命令通过一个只读属性暴露给视图,该属性返回对DelegateCommand的引用。

csharp 复制代码
public class ArticleViewModel
{
    public DelegateCommand SubmitCommand { get; private set; }

    public ArticleViewModel()
    {
        SubmitCommand = new DelegateCommand<object>(Submit, CanSubmit);
    }

    void Submit(object parameter)
    {
        // 实现逻辑
    }

    bool CanSubmit(object parameter)
    {
        return true;
    }
}

2.3. 当DelegateCommand对象的Execute方法被调用时,它会通过你在构造函数中指定的委托将调用转发到ViewModel类中的方法。同样,当CanExecute方法被调用时,会调用ViewModel类中的对应方法。CanExecute方法的委托在构造函数中是可选的。如果没有指定,DelegateCommand将始终返回true作为CanExecute的结果。

2.4. DelegateCommand类是一个泛型类型。类型参数指定了传递给ExecuteCanExecute方法的命令参数的类型。在上面的例子中,命令参数是object类型。Prism还提供了一个非泛型的DelegateCommand类,用于不需要命令参数的情况,定义如下:

csharp 复制代码
public class ArticleViewModel
{
    public DelegateCommand SubmitCommand { get; private set; }

    public ArticleViewModel()
    {
        SubmitCommand = new DelegateCommand(Submit, CanSubmit);
    }

    void Submit()
    {
        // 实现逻辑
    }

    bool CanSubmit()
    {
        return true;
    }
}

DelegateCommand避免使用值类型作为参数,如果需要使用值类型参数时,应该如何处理以避免潜在的问题:

  1. DelegateCommand避免使用值类型DelegateCommand这个类在设计时故意不使用值类型(如intdoublebool等)作为参数类型。这是因为ICommand接口接受的是一个对象类型的参数。
  2. 值类型参数可能导致意外行为 :如果在XAML初始化时,命令绑定调用了CanExecute(null),并且T是一个值类型,那么可能会导致一些意料之外的行为。这是因为值类型在默认情况下会被初始化为它们的默认值(如int的默认值是0),这可能会导致命令的执行逻辑出现问题。
  3. 默认值问题 :考虑过使用default(T)作为解决方案,但最终被拒绝了。原因是实现者将无法区分一个有效的值和一个默认值。例如,如果Tint,那么default(int)是0,但实现者可能无法确定0是一个有效的参数值还是一个默认值。
  4. 使用可空类型 :如果你确实需要使用值类型作为参数,那么必须将其转换为可空类型(nullable)。这可以通过使用DelegateCommand<Nullable<int>>或者使用简写形式?语法(DelegateCommand<int?>)来实现。这样,就可以区分一个有效的值和一个未赋值(null)的情况。

3.在视图中调用 ViewModel 提供的 DelegateCommands

3.1. 视图与命令对象的关联:在视图中,可以通过 Command 属性将控件与 ViewModel 提供的命令对象关联起来。这适用于 WPF、Xamarin.Forms 和 UWP 控件,它们可以通过数据绑定轻松地与命令对象关联。

3.2. 数据绑定示例:提供了一个按钮的示例代码,展示了如何将按钮的 Command 属性绑定到 ViewModel 中的 SubmitCommand 命令上。代码如下:

xml 复制代码
<Button Command="{Binding SubmitCommand}" CommandParameter="OrderId"/>

这里,{Binding SubmitCommand} 表示将按钮的 Command 属性绑定到 ViewModel 的 SubmitCommand 属性上,CommandParameter="OrderId" 表示传递一个参数 "OrderId" 给命令的 Execute 方法。

3.3. 命令参数:可以通过 CommandParameter 属性可选地定义一个命令参数。预期的参数类型在 DelegateCommand 的泛型声明中指定。当用户与控件交互时,控件会自动调用目标命令,如果提供了命令参数,它将作为参数传递给命令的 Execute 方法。

3.4. 自动调用命令:在上述示例中,当按钮被点击时,它会自动调用 ViewModel 中的 SubmitCommand。此外,如果指定了 CanExecute 委托,按钮会在 CanExecute 返回 false 时自动禁用,在返回 true 时启用。

4.向UI发送变化通知

4.1. RaiseCanExecuteChanged方法

当你需要手动更新绑定到UI元素的状态时,可以使用这个方法。例如,当IsEnabled属性的值发生变化时,在属性的setter中调用RaiseCanExecuteChanged来通知UI状态变化。

csharp 复制代码
private bool _isEnabled;
public bool IsEnabled
{
    get { return _isEnabled; }
    set
    {
        SetProperty(ref _isEnabled, value);
        SubmitCommand.RaiseCanExecuteChanged();
    }
}

4.2. ObservesProperty方法

当命令需要在属性值变化时发送通知时,可以使用这个方法。使用ObservesProperty方法时,每当提供的属性值变化,DelegateCommand会自动调用RaiseCanExecuteChanged来通知UI状态变化。

csharp 复制代码
public class ArticleViewModel : BindableBase
{
    private bool _isEnabled;
    public bool IsEnabled
    {
        get { return _isEnabled; }
        set { SetProperty(ref _isEnabled, value); }
    }

    public DelegateCommand SubmitCommand { get; private set; }

    public ArticleViewModel()
    {
        SubmitCommand = new DelegateCommand(Submit, CanSubmit).ObservesProperty(() => IsEnabled);
    }

    void Submit()
    {
        // 实现逻辑
    }

    bool CanSubmit()
    {
        return IsEnabled;
    }
}

注意:使用ObservesProperty方法时,可以链式注册多个属性进行观察。例如:ObservesProperty(() => IsEnabled).ObservesProperty(() => CanSave)

4.3. ObservesCanExecute方法

如果你的CanExecute是简单布尔属性的结果,你可以消除声明CanExecute委托的需要,改用ObservesCanExecute方法。ObservesCanExecute不仅会在注册的属性值变化时向UI发送通知,还会使用同一个属性作为实际的CanExecute委托。

csharp 复制代码
public class ArticleViewModel : BindableBase
{
    private bool _isEnabled;
    public bool IsEnabled
    {
        get { return _isEnabled; }
        set { SetProperty(ref _isEnabled, value); }
    }

    public DelegateCommand SubmitCommand { get; private set; }

    public ArticleViewModel()
    {
        SubmitCommand = new DelegateCommand(Submit).ObservesCanExecute(() => IsEnabled);
    }

    void Submit()
    {
        // implement logic
    }
}

警告:不要尝试链式注册ObservesCanExecute方法。只能为CanExecute委托观察一个属性。

5.在命令中使用异步方法

在现代编程中,使用asyncawait调用异步方法是很常见的需求。虽然许多人的第一反应是需要一个AsyncCommand,但实际上这种假设是错误的。ICommand本质上是同步的,ExecuteCanExecute委托应该被视为事件。这意味着使用async void作为命令的语法是完全有效的。文中提供了两种使用DelegateCommand与异步方法的方法。

选项 1:

csharp 复制代码
public class ArticleViewModel
{
    public DelegateCommand SubmitCommand { get; private set; }

    public ArticleViewModel()
    {
        SubmitCommand = new DelegateCommand(Submit);
    }

    async void Submit()
    {
        await SomeAsyncMethod();
    }
}

在这个选项中,Submit方法被声明为async void,这意味着它可以包含await语句来调用异步方法。DelegateCommand在执行时会调用这个Submit方法。

选项 2:

csharp 复制代码
public class ArticleViewModel
{
    public DelegateCommand SubmitCommand { get; private set; }

    public ArticleViewModel()
    {
        SubmitCommand = new DelegateCommand(async () => await Submit());
    }

    Task Submit()
    {
        return SomeAsyncMethod();
    }
}

在这个选项中,Submit方法被声明为返回Task的普通方法,而DelegateCommand的构造函数中使用了lambda表达式async () => await Submit()来包装这个方法调用。这样,当DelegateCommand被执行时,它会调用这个lambda表达式,该表达式会异步地调用Submit方法。

这两种方法都允许你在DelegateCommand中使用异步方法,同时保持ICommand的同步特性。选择哪种方法取决于你的具体需求和偏好。

相关链接

相关推荐
Scout-leaf9 天前
WPF新手村教程(三)—— 路由事件
c#·wpf
西岸行者11 天前
学习笔记:SKILLS 能帮助更好的vibe coding
笔记·学习
悠哉悠哉愿意11 天前
【单片机学习笔记】串口、超声波、NE555的同时使用
笔记·单片机·学习
别催小唐敲代码11 天前
嵌入式学习路线
学习
毛小茛11 天前
计算机系统概论——校验码
学习
babe小鑫11 天前
大专经济信息管理专业学习数据分析的必要性
学习·数据挖掘·数据分析
winfreedoms11 天前
ROS2知识大白话
笔记·学习·ros2
在这habit之下11 天前
Linux Virtual Server(LVS)学习总结
linux·学习·lvs
我想我不够好。11 天前
2026.2.25监控学习
学习
im_AMBER11 天前
Leetcode 127 删除有序数组中的重复项 | 删除有序数组中的重复项 II
数据结构·学习·算法·leetcode