Using a generic ViewModel

Topics: Questions
Sep 11, 2012 at 8:44 AM

Hi,

Is it possible to use a generic ViewModel?

Lets say I have a View Model PresenterViewModel<MyType> and a non-generic View PresenterWindow.

If I instanciate the viewmodel and try to open the window via the GetService<IUIVisualizerService>().ShowDialog() method I get the following error:

Catel.MVVM.Services.WindowNotRegisteredException was unhandled Message=There is no window registered as ...

Is there a way to fix this or some known workaround?

Thanks, G.

Coordinator
Sep 11, 2012 at 8:46 AM

This should be possible. However you will need to make sure that the IViewLocator and IViewModelLocator are aware of these generic contraints as well. You can register your custom view models manually in there and it will work.

Sep 11, 2012 at 9:42 AM

Thanks for the info, but this will only work if I register e.g. PresenterViewModel<Company> and PresenterViewModel<Customer> explicitely as I cannot register the generic type "as is" within the viewLocator.Register method (well I can tried registering PresenterViewModel<> but this does not help even if it does not throw an error).

Coordinator
Sep 11, 2012 at 9:44 AM

Yes, but that shouldn't be any problem.

1) Just derive from UIVisualizerService

2) If the VM is generic, register it automatically by overriding the right methods

3) register your own version in the ServiceLocator

Sep 11, 2012 at 9:47 AM

That makes sense, I'll try this right away.

Sep 11, 2012 at 11:49 AM

I got it running :)

I wrote my own ViewLocator that supports generic resolutions. For reference, this is it:

 

public class GenericViewLocator : ViewLocator
{
    public override Type ResolveView(Type viewModelType)
    {
        Condition.Requires(viewModelType, "viewModelType").IsNotNull();

        if (viewModelType.IsGenericType)
        {
            string typeNameWithAssembly = viewModelType.FullName + ", " + viewModelType.Assembly.GetName().Name;
            string baseTypeName = Regex.Replace(typeNameWithAssembly, @"`\d+\[.*\]", "");
            string fullTypeName = this.Resolve(baseTypeName);
            return LocatorBase.GetTypeFromString(fullTypeName);
        }
        else
            return base.ResolveView(viewModelType);
    }
}

Additinally I had to add the following constructor to my view:

public MySuperWindow(Catel.MVVM.IViewModel viewModel) : base(viewModel, DataWindowMode.Custom) { InitializeComponent(); }

I'm not really sure why I need the constructor. Should I always implement it in all my views?

Just one note, using the Catel helper method TypeHelper.GetTypeNameWithAssembly(viewModelType.AssemblyQualifiedName) did not return a sensible string. Seems like the function does not expect a generic input. I therefor had to construct the typeNameWIthAssembly myself - it seems to be correct.

Thanks again, Gorroux

 

Coordinator
Sep 11, 2012 at 12:37 PM

You don't need the ctor. The UIVisualzierService first tries to inject it. If that doesn't work, it will just set the data context if I am not mistaken.

If you can write a unit test for the 2nd case, then we can fix it.

Sep 11, 2012 at 12:42 PM

Well, in this case it was necessary. Otherwise catel compained that it cannot find viewmodel for the view.