【问题标题】:Pass custom type as command parameter将自定义类型作为命令参数传递
【发布时间】:2021-05-02 03:44:08
【问题描述】:

我必须使用 MVVM Light 框架将旧的 Delphi 应用程序迁移到 WPF,在我的主屏幕中我有大约 50 个 MenuItems...(没有评论)。

目前,每个MenuItem 都有一个RelayCommand,它执行基于精确模型的泛型方法:

<MenuItem Command="{Binding ShowOrderCommand}"/>

在 ViewModel 中

ShowOrderCommand = new RelayCommand(ShowGridType<OrderModel>, CanShowGridType)

(其中OrderModel接口为IBaseModel)调用此方法定义:

ShowGridType<T>() where T : IBaseModel

(注意:必须保留泛型类型,因为代码中需要进一步解析 DI)。

我想通过单个命令删除这数百行 RelayCommand&lt;OrderModel, PriceModel, ...&gt; 实例化,该命令可以通过命令参数(或其他)使用通用方法(如 ShowCommand = RelayCommand&lt;IBaseModel&gt;RelayCommand&lt;TModel&gt;)传递模型类型。

我以为我只是通过这个找到了解决方案:

<MenuItem Command="{Binding ShowCommand}"  CommandParameter="{x:Type models:OrderModel}"/>
ShowCommand = RelayCommand<IBaseModel>(ShowGridType);
ShowGridType<Tmodel>(Tmodel model) where Tmodel : IBaseModel

但是当我点击我的 MenuItem 时,我遇到了转换错误:

Unable to cast object of type 'System.RuntimeType' to type 'XXXXXX.Models.IBaseModel'

我也尝试接收“对象”而不是 IBaseModel,但找不到如何将其与通用定义 ShowGridType&lt;T&gt; 一起使用。

有什么好办法吗?

【问题讨论】:

  • 在更改为 RelayCommand 之前如何设置 CommandParameter?
  • 不明白。但是中继命令的初始化是: RelayCommand EditCommand {get;set;} 并且在构造函数中 EditCommand = RelayCommand(ShowGridType);
  • CommandParameter 在变成CommandParameter="{x:Type models:OrderModel}" 之前是什么?我认为您根本不需要更改它
  • 在此之前没有任何内容,因为类型在 viewmodel 的构造函数中的每个中继命令实例中都是硬编码的,即:ShowOrderCommand = RelayCommand(ShowGridType, CanShowGridType)
  • @PhilSE 您的问题的重点是如何公开一个 single 命令,该命令使用对象的具体类型调用通用方法?这也意味着,仅仅使用带有IBaseModel 参数的方法不可能,因为您需要访问具体类型上的方法、字段或属性?除此之外,您不会将对象作为命令参数传递,它可以作为视图模型中的字段或属性使用,对吧?

标签: c# wpf xaml mvvm command


【解决方案1】:

我使用 Unity 作为容器,我只需要类型来解决很多事情 [...]

如果只是关于分辨率,您可以使用Type 作为参数公开命令。

public RelayCommand<Type> ShowOrderCommand { get; }

为执行创建方法并可以执行委托。

private bool CanShowOrder(Type type)
{
   // ...replace with your code.
   return true;
}

private void ShowOrder(Type type)
{
   // Resolve type using the your container here.
   var obj= _myUnityContainer.Resolve(type);

   // ...do something with the instance.
}

如您所见,Unity 不仅提供用于解析的通用扩展方法,还有其他与Type 一起使用的方法,见下文。这意味着您的视图模型中不需要泛型方法。

使用这两种方法初始化ShowOrderCommand 命令。

ShowOrderCommand = new RelayCommand<Type>(ShowOrder, CanShowOrder);

在 XAML 中,像在问题中那样将模型绑定为每个 MenuItem 的命令参数。

<MenuItem Command="{Binding ShowCommand}"  CommandParameter="{x:Type models:OrderModel}"/>

现在,如果您确实需要使用特定类型调用自己的泛型方法,有两种选择。

  1. 使用ifswitch 语句显式检查类型以调用通用ShowGridType&lt;T&gt; 方法。

    private void ShowOrder(Type type)
    {
       if (type == typeof(OrderModel))
          ShowGridType<OrderModel>();
    
       // ...check other derivatives.
    }
    
  2. Reflect the method and type parameter and call it。在这里,您使用GetMethod 获取方法信息,并使用您拥有的具体Type 作为它的类型参数。然后在 this 上调用它,因为该方法是在包含类中定义的,并且没有参数 (null)。

    private void ShowOrder(Type type)
    {
       var method = GetType().GetMethod(nameof(ShowGridType), BindingFlags.Instance | BindingFlags.NonPublic);
       var genericMethod = method.MakeGenericMethod(type);
    
       genericMethod.Invoke(this, null);
    }
    

    请注意存在一些陷阱,因此这只是一个概念验证。

这两种方法都涉及反射,因为您需要检查运行时类型。将IBaseModel 作为命令参数传递不会在运行时神奇地调用具有具体类型的泛型方法。这也意味着在运行时会有一定的开销和错误,而代码编译得很好,所以你需要确定你在做什么并仔细检查代码。

【讨论】:

  • 非常感谢您的时间和解释!我现在很清楚了。我可能会用反射方法来完成这项工作。愿科技之神保佑你!
  • 这太可怕了。如果无法实现泛型,请坚持使用object(如ICommand 声明)。
  • @ASh 实际上,我同意你的看法。但是,@PhilSE 不传递实例,他只需要类型,即使他这样做了,如果你需要对具体类型进行操作,你会用 object 做什么?还是我误会你了? object如何解决问题?
  • “如果您需要对具体类型进行操作,您将如何处理对象” - 我会进行类型转换。 “他只需要类型” - 不知何故没有minimal reproducible example 我不相信它应该以这种方式实现。
  • @ASh 是的,一个类型转换,但是在运行时是哪个类型? OP 的原始解决方案正是这样,一个 RelayCommand&lt;T&gt; 对每个模型类型都有一个不同的类型参数,甚至不需要在那里进行转换,relay 命令隐式地执行它。 OP的问题是如果你只使用一个命令,但是你需要一个具体的类型,你会怎么做?我认为该特定问题没有其他选择。当然,您可以完全更改设计,在这种情况下,示例肯定是不够的。