[Caliburn Micro] How to use the same view for multiple view-models

Topics: Feature Requests, UI Architecture
Jul 18, 2011 at 6:42 AM

Hi,

I want to use single view for multiple viewmodel. I tried using View attribute but I couldnt get it in Caliburn.Micro. Is there any other way to achieve the same.

 

Regards,

- Jey -

Jul 18, 2011 at 7:05 AM
Edited Jul 18, 2011 at 7:09 AM

You need to modify the ViewLocator to achieve this.

Firt of all you need to define an attribute used to store the relevant data used to discover the view:

 

    [AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = false)]
    public class ViewAttribute : Attribute
    {
        public object Context { get; set; }

        public Type ViewType { get; private set; }

        public ViewAttribute(Type viewType)
        {
            ViewType = viewType;
        }
    }

I allowed multiple View attributes to be defined to handle multiple contextes. Moreover, I decided to avoid to inherit such attribute, so that every view-model has to specify it's own.

 

Then, you need to change the LocateTypeForModelType to something like this:

 

ViewLocator.LocateTypeForModelType = (modelType, displayLocation, context) =>
{
            var attribute = modelType.GetCustomAttributes(typeof(ViewAttribute), false).OfType<ViewAttribute>().Where(x => x.Context == context).FirstOrDefault();
            return attribute != null ? attribute.ViewType : null;
};

 

If you want, you could even decide to use the current view-location strategy as a fallback:

 

 
void Initialize()
{
    var baseLocate = ViewLocator.LocateTypeForModelType;

    ViewLocator.LocateTypeForModelType = (modelType,    displayLocation, context) =>
    {
            var attribute = modelType.GetCustomAttributes(typeof(ViewAttribute), false).OfType<ViewAttribute>().Where(x => x.Context == context).FirstOrDefault();
            return attribute != null ? attribute.ViewType : baseLocate(modelType, displayLocation, context);
    };
}

Note that I have not tested this code... :)

 

Edit:

Note that this is not the only way to achieve what you need... another approach would be to allow the ViewLocator to evaluate base classes of the view-model, so that if a proper view matching the view-model is not found, the ancestor type is evaluated. I use this approach as a default strastegy.

All in all, it is up to you how you decide to implement this feature. I just wanted to point out that customizing the ViewLocator is the way to go. :) 

Jul 19, 2011 at 8:52 AM

Hi Wise,

 

Thanks for your reply. Should I write this Initialize() method in every ViewModel & I should use it only on base ViewModel and call it base ViewModel constructor?

Jul 19, 2011 at 10:11 AM

You need to call it just once, either in a static constructor (for the Bootstrapper, for example) or in the Bootstrapper configure method.

Sep 23, 2011 at 3:39 PM

I am using your sample to show multiple views for same viewmodel. I am using following code for view attributes on my view model-

 [View(typeof(StewPlanGenInfoView), Context = "PlanGenInfo")]
    [View(typeof(StewPlanDetailsView), Context = "PlanDetails")]

I am also binding the View.Context to ActionType property on view model as follows-

//shellviewmodel
public void CreatePlan()
        {
            try
            {

                CurrentViewModel = ObjectFactory.GetInstance();
                CurrentViewModel.ActionType = "PlanDetails";
                ActivateItem(CurrentViewModel);

            }
            catch (Exception e)
            {

            }
          


        }

However the view locator always receives null context and my views never get displayed.

Am I missing something? Highly appreciate your help.

Thanks

Sep 23, 2011 at 8:19 PM

Replied here: http://caliburnmicro.codeplex.com/discussions/273505