-2

I have to migrate an old Delphi application to WPF with the MVVM Light framework, in my main screen I have about 50 MenuItems... (no comment).

Currently, each MenuItem has a RelayCommand that executes a generic method based on a precise model:

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

and in ViewModel

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

(where OrderModel interface is IBaseModel) calling this method definition:

ShowGridType<T>() where T : IBaseModel

(note: generic type must be kept because of DI resolution further in code).

I would like to remove these hundreds of lines of RelayCommand<OrderModel, PriceModel, ...> instantiations by a single command that can pass the type of model via a command parameter (or other) with a generic method like ShowCommand = RelayCommand<IBaseModel> or RelayCommand<TModel>.

I thought I had found the solution simply through this:

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

But when I click on my MenuItem I have a conversion error:

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

I have also tried to receive an 'object' instead of a IBaseModel, but can't find how to use this with the generic definition ShowGridType<T> .

Any idea that will help?

thatguy
  • 21,059
  • 6
  • 30
  • 40
PhilSE
  • 9
  • 5
  • how did you set CommandParameter before changing to RelayCommand? – ASh Jan 28 '21 at 08:57
  • Not sure to understand. But the initialisation of the relaycommand is: RelayCommand EditCommand {get;set;} and in the constructor EditCommand = RelayCommand(ShowGridType); – PhilSE Jan 28 '21 at 09:09
  • what was in CommandParameter, before it became `CommandParameter="{x:Type models:OrderModel}"`? I don't think you need to change it at all – ASh Jan 28 '21 at 09:15
  • Nothing before this, because the type was hard-coded in each relaycommand instantiations in the constructor of viewmodel i.e.:ShowOrderCommand = RelayCommand(ShowGridType, CanShowGridType) – PhilSE Jan 28 '21 at 09:21
  • @PhilSE Is the point of your question how to expose a **single** command that calls the generic method with the concrete type of the object? This also means, just using a method with a `IBaseModel` parameter is **not possible**, because you need to access methods, fields or properties **on the concrete type**? Apart from that, you do not pass the object as command parameter, it is available as a field or property in your view model, right? – thatguy Jan 28 '21 at 09:35
  • @PhilSE Another question is which container you use. Do you need the type only to resolve an instance via a container? Then there might not even be a need for a generic method. – thatguy Jan 28 '21 at 09:45
  • @thatguy OMG, I've completely missunderstood the use of CommandParameter, I understand now that I send a Type object and I want to catch an Interface of my models. I'm so sorry :/ I use Unity as container and I only need the type to resolve many things that are generic like my GenericGrid that will load views and viewmodels for the 50 models, and the start point is the MenuItem... – PhilSE Jan 28 '21 at 10:09

1 Answers1

0

I use Unity as container and I only need the type to resolve many things [...]

If it is only about resolution, you could expose the command with Type as parameter.

public RelayCommand<Type> ShowOrderCommand { get; }

Create methods for the execute and can execute delegates.

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.
}

As you can see, Unity does not only provide the generic extension methods for resolving, there are others that work with Type, see below. That means you do not need a generic method in your view model.

Initialize the ShowOrderCommand command using both methods.

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

In XAML, bind the model as you did in the question as command parameter for each MenuItem.

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

Now, if you really need to call your own generic method with a specific type, there are two options.

  1. Check the type explicitly using an if or switch statement to call you generic ShowGridType<T> method.

    private void ShowOrder(Type type)
    {
       if (type == typeof(OrderModel))
          ShowGridType<OrderModel>();
    
       // ...check other derivatives.
    }
    
  2. Reflect the method and type parameter and call it. Here, you get the method info using GetMethod and use the concrete Type that you have as type parameter for it. Then you invoke it on this, because the method is defined in the containing class, and without parameters (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);
    }
    

    Please be aware that you there are some pitfalls, so this is just a proof-of-concept.

Both methods involve reflection, as you need to check the runtime type. Passing an IBaseModel as a command parameter will not magically call a generic method with a concrete type at runtime. This also means that there is a certain overhead and errors can occur at runtime, while the code compiles just fine, so you need to be sure what you are doing and check the code carefully.

thatguy
  • 21,059
  • 6
  • 30
  • 40
  • Thanks a lot for your time and your explanations ! It's clear for me now. I will probably do the job with reflection methods. May the tech god bless you ! – PhilSE Jan 28 '21 at 11:20
  • this is terrible. if generics cannot be impelemented, stick to using `object` (like `ICommand ` declares). – ASh Jan 28 '21 at 11:56
  • @ASh Actually, I agree with you. However, @PhilSE does not pass an instance, he only needs the type and even if he did, what would you do with `object` if you need to operate on a concrete type? Or did I misunderstand you? How does `object` solve the problem? – thatguy Jan 28 '21 at 12:15
  • "what would you do with object if you need to operate on a concrete type" - I would do a type cast. "he only needs the type" - somehow without [mcve] I'm not conviced it should be implemented that way. – ASh Jan 28 '21 at 12:22
  • @ASh Yes, a type cast, but to which type _at runtime_? The original solution of OP is exactly that, one `RelayCommand` with a distinct type parameter for each model type, not even a need for a cast there, the relay command does it implicitly. The question of OP is if you only use a single command, but you need a concrete type, what would you do? I do not think there is an alternative for that specific question. Of course, you could change the design completely and in that case the example is definitely not enough. – thatguy Jan 28 '21 at 13:37