canton7 / Stylet

A very lightweight but powerful ViewModel-First MVVM framework for WPF for .NET Framework and .NET Core, inspired by Caliburn.Micro.
MIT License
988 stars 143 forks source link

ViewModel <-> View mapping feature #145

Closed SuperBrain closed 3 years ago

SuperBrain commented 4 years ago

I would like to be able to tell Stylet which View should be created for which ViewModel.

I have a specific use-case where I would like to create multiple VMs but have them all use a single View.

I understand this could be done by subclassing the ViewManager. However, having a simple dictionary that can tell Stylet to create MyView for both MyViewModel and MyOtherViewModel would be a nice feature.

EDIT: Someone pointed this from samples, which seems to be what I need, but I don't know how I would use that?

EDIT 2: I see the Attribute in there but there's a couple of issues with it

  1. You have to apply it to a View (rather than a ViewModel)
  2. AllowMultiple = false suggests that only 1 ViewModel can be mapped to a single View

Wouldn't it be better if this attribute was applied to VMs, specifying the View that goes with them?

canton7 commented 4 years ago

Have you read this page from the documentation? Define your own ViewManager which implements whatever strategy you want.

SuperBrain commented 4 years ago

Thanks for pointing that out, but I'm well aware of it (hence mentioning the sample). My question here is, would it be possible to provide one of the following solutions:

  1. An easy, publicly accessible ViewModel <-> View mapper that supports n:1 mapping
  2. An Attribute introduced in mentioned sample that is applied to VMs, pointing to View that needs to be created

1. would mean adding a new feature while 2. would be providing another sample. Either way, this would add more value to already great library.

canton7 commented 4 years ago

You can write whatever mapper you need.

vgist1 commented 4 years ago

Would be really nice if Stylet had such feature without having to create your own ViewManager. I've faced a similar situation and ended up making multiple views due to the complexity of the viewmanager, to just add a few mappings. I would definitively use a feature like this if available, could be as simple as a dictionary that Stylet lookup before mapping things by itself.

canton7 commented 4 years ago

due to the complexity of the viewmanager

The ViewManager is simple: for your case, you can override LocateViewForModel or ViewTypeNameForModelTypeName.

Stylet makes it easy to replace bits of the framework precisely so that you can do what you're asking, and so that I don't need to ship an infinitely-customisable ViewManager.

Stylet uses a convention-based approach, where the name of the view type is derived from the name of the ViewModel type. That, unfortunately, means you can't easily have multiple ViewModel types for a single view type: in order to do this, you need to introduce some other mechanism. There are many such mechanisms you could choose: you could use an attribute (and I've written a ViewManager which does this, see the samples), or you could add some extra conventions onto the name (SomeViewModel1 -> SomeView, etc), or some manual mapping, or... I don't think there's a best approach here, but it really is very easy to do something yourself.

One of the aims of Stylet is to be lightweight. That means not shipping the kitchen sink, and instead letting people customise the behaviour using a small amount of their own code.

Here's an example which maps ViewModel1 and ViewModel2, etc, to View. Note how it's almost fewer lines than setting up some configuration on an infinitely-configurable ViewManager would be:

public class MyViewManager : ViewManager
{
    protected override string ViewTypeNameForModelTypeName(string modelTypeName)
    {
        return base.ViewTypeNameForModelTypeName(Regex.Replace(modelTypeName, @"\d+$", ""));
    }
}
vgist1 commented 4 years ago

@canton7 excuse my ignorance are you saying I could technically do something like this:

public class MyViewManager : ViewManager
{
    public Dictionary<string, string> Map { get; set; }

    protected override string ViewTypeNameForModelTypeName(string modelTypeName)
    {
        if (Map.ContainsKey(modelTypeName))
        {
            return base.ViewTypeNameForModelTypeName(Map[modelTypeName]);
        }
        return base.ViewTypeNameForModelTypeName(modelTypeName);
    }
}

Where map would be say

Map = new Dictionary<string, string>
{
    { "CustomerReportViewModel", "ReportViewModel" },
    { "CompanyReportViewModel", "ReportViewModel" },
    { "AccountReportViewModel", "ReportViewModel" },
};

Is that not what this technically does if I understood it right? https://github.com/canton7/Stylet/blob/master/Stylet/ViewManager.cs#L107

canton7 commented 4 years ago

Yes, you could do exactly that, although it's mapping from ViewModel -> View of course, not ViewModel -> ViewModel (I'd use TryGetValue instead of doing two lookups, but the result is the same). You'll need the fully-qualified names (including namespace).

NamespaceTransformations are only for transforming namespaces -- they map e.g. SomeLibrary.SomeViewModel to SomeApplication.SomeView. You can see that they match the start of the ViewModel's type name, and end at a .. This can be needed for the convention-based mapping that Stylet does out of the box.

canton7 commented 3 years ago

Closing as no response