【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的同步特性。选择哪种方法取决于你的具体需求和偏好。

相关链接

相关推荐
知识分享小能手5 小时前
React学习教程,从入门到精通, React 属性(Props)语法知识点与案例详解(14)
前端·javascript·vue.js·学习·react.js·vue·react
茯苓gao7 小时前
STM32G4 速度环开环,电流环闭环 IF模式建模
笔记·stm32·单片机·嵌入式硬件·学习
是誰萆微了承諾7 小时前
【golang学习笔记 gin 】1.2 redis 的使用
笔记·学习·golang
DKPT8 小时前
Java内存区域与内存溢出
java·开发语言·jvm·笔记·学习
aaaweiaaaaaa8 小时前
HTML和CSS学习
前端·css·学习·html
看海天一色听风起雨落9 小时前
Python学习之装饰器
开发语言·python·学习
speop10 小时前
llm的一点学习笔记
笔记·学习
非凡ghost10 小时前
FxSound:提升音频体验,让音乐更动听
前端·学习·音视频·生活·软件需求
ue星空11 小时前
月2期学习笔记
学习·游戏·ue5
萧邀人11 小时前
第二课、熟悉Cocos Creator 编辑器界面
学习