James Lavery
James Lavery

Reputation: 949

Prism with Xamarin.Forms - how to autowire ContentView which is inside another ContentView

We are successfully wiring ContentViews to their ViewModels when the ContentView is directly contained in a Page, using XAML like:

<local:AwesomeView mvvm:ViewModelLocator.AutowirePartialView=”{x:Reference self}” />

Where self is the parent page.

However, we have ContentViews which contain ContentViews, and using AutoWirePartialView as above for the nested view does not work. The ContentViews don't get wired up to their ViewModels.

Looking at the Prism code:

  1. AutoWirePartialView has the comment "This API is Obsolete and will be removed during the 8.0 previews"
  2. The property changed handler for AutoWirePartialView explicitly checks for the parent being a Page, so won't work with a parent ContentView

So it's fairly clear from the Prism code why this won't work!

Is there a way to achieve this with Prism?

Versions: Xamarin.Forms - 4.4.0.991265

Prism - 7.1.0.431

Upvotes: 1

Views: 911

Answers (2)

Dan Siegel
Dan Siegel

Reputation: 5799

Partial Views are indeed being made Obsolete. The reason it's obsolete is that we are bringing Region support to Prism 8. Partial Views were always meant as a quick and temporary solution to help bridge the gap until we got to implementing Regions for Prism.Forms. Regions are far more powerful and will let you do a lot more like nesting, and lazily loading views.

Realistically the concept of nested Regions where you have:

ComponentViewA which has its own ViewModel.

Then you have ComponentViewB which has it's own ViewModel and has ComponentViewA as a child

And ComponentViewA is itself a child of AwesomePage

It sounds like this is the general concept that you're looking to support. So the short answer is that in Prism 7 there is no good way of doing this. There are certainly some hacks like you could for instance pass the page as a parameter and set the property in the code behind like:

public class ComponentViewB : ContentView
{
    public static readonly BindableProperty ParentPageProperty =
        BindableProperty.Create(nameof(ParentPage), typeof(Page), typeof(ComponentViewB), null, propertyChanged: OnParentPageChanged);

    private static void OnParentPageChanged(BindableObject bindable, object oldValue, object newValue)
    {
        // This guards the action from being performed more than once.
        if(oldValue is null && newValue != null && bindable is ComponentViewB view)
        {
            // This assumes you've set the property x:Name="componentViewA"
            // for your ComponentViewA in XAML
            ViewModelLocator.SetAutowirePartialView(view.componentViewA, (view.ParentPage);
        }
    }

    public Page ParentPage
    {
        get => (Page)GetValue(ParentPageProperty);
        set => SetValue(ParentPageProperty, value);
    }
}

To be honest if I had to make something work today this is how I would recommend doing it. Once we merge the PR I referenced above I would suggest that you update to the previews and migrate to use Regions.

Upvotes: 2

xerx
xerx

Reputation: 119

My opinion, in order to keep things clean, is to use the default prism approach and bind a viewmodel to the respective page only and not to any subviews.

Any binding to a component inside the page should be made through properties on the page's viewmodel so you can achieve bindings no matter the depth of the content view in the display hierarchy.

For instance:

Your page's view model

public class PageAViewModel : ViewModelBase
{
   public ContentViewAViewModel ContentViewViewModel
   {
      get { return _contentViewViewModel; }
      set { SetProperty(ref _contentViewViewModel, value); }
   }
}

Your page's view

<?xml version="1.0" encoding="UTF-8"?>
<views:BasePage xmlns="http://xamarin.com/schemas/2014/forms"
                xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
                xmlns:components="clr-YourProject.Components"
                xmlns:views="clr-YourProject.Views"
                x:Class="YourProject.Views.PageA">
     <Grid RowSpacing="0">
          <Grid RowSpacing="0">
                <components:ContentViewA BindingContext="{Binding ContentViewViewModel}"/>
          </Grid>
    </Grid>
</views:BasePage>

Upvotes: 1

Related Questions