Xamarin: Set multiple viewmodels by reference from code behind

Issue

I need to set two ViewModels from the code behind in the xaml code. Or if there is better way doing would be great to.

When I do it like this way the application crashes. When I set ProductDetailViewModel in the code behind (BindingContext ViewModel) everything works fine.

update It’s not an good idea to pass viewModels as parameters. I have now one class “ViewModelLocator” which contains all the ViewModels as static properties. Use Google for more info. This way things are way easier.

example ViewModelLocator

public static class ViewModelLocator
{
     public static AddProductViewModel AddProductViewModel { get; set; }  new AddProductViewModel(App.ProductDataStore, App.NavigationService);
}

end update

update 2

As @Waescher stated, it’s better to use FreshMvvm. The static approach is simple and fast but not good for slow devices or larger apps. Thanks.

end update 2

**Xamarin.Forms.Xaml.XamlParseException:** 'Position 9:10. Can not find the object referenced by `ProductDetailViewModel`'

Since I can’t set the ViewModels directly in the xaml I need to do it by reference from code behind.

See < *** First ViewModel *** > and < *** Second ViewModel *** > in the xaml code.

<?xml version"1.0" encoding"utf-8" ?>
    <ContentPage xmlns"http://xamarin.com/schemas/2014/forms"
                 xmlns:x"http://schemas.microsoft.com/winfx/2009/xaml"
                 xmlns:controls"clr-namespace:BoerPlaza.Controls"
                 xmlns:flv"clr-namespace:DLToolkit.Forms.Controls;assemblyDLToolkit.Forms.Controls.FlowListView"
                 xmlns:ffimageloading"clr-namespace:FFImageLoading.Forms;assemblyFFImageLoading.Forms"
                 x:Class"BoerPlaza.Views.Product.ProductCustomerPictures">
        <ContentPage.BindingContext>
            <x:Reference Name"ProductDetailViewModel" /><!-- *** First ViewModel ***!-->
        </ContentPage.BindingContext>
        <ContentPage.Content>
            <StackLayout>
                <!-- Total image count -->
                <Label Text"{Binding Product.UserImages.Total}"
                       Style"{StaticResource H2}" />
                <!-- Title -->
                <Label Text"{Binding Product.Title}"
                       Style"{StaticResource H1}" />
                <!-- reviews -->
                <StackLayout Orientation"Horizontal">
                    <controls:StarDisplayTemplateView x:Name"customRattingBar"
                                                      SelectedStarValue"{Binding Product.RatingTotal}" />
                    <Label  Text"{Binding Product.RatingAmount, StringFormat'{0} reviews | '}" />
                    <Label Text"Schrijf een review" />
                </StackLayout>

                <Label Text"{Binding Product.Title, StringFormat'Heb je een productfoto van {0} die je wilt delen? '}" />

                <Button Text"Foto's toevoegen"
                        Command"{Binding SelectImagesCommand}"
                        BackgroundColor"{StaticResource neutral-color}"
                        BorderColor"{StaticResource alt-color}"
                        BorderWidth"1"
                        TextColor"{StaticResource primary-color}"
                        HorizontalOptions"Start"
                        HeightRequest"40"
                        FontSize"12" />

                <!-- hr -->
                <BoxView Style"{StaticResource separator}" />

                <flv:FlowListView FlowColumnCount"3"
                                  x:Name"listItems"
                                  FlowItemsSource"{Binding Media}"
                                  SeparatorVisibility"None"
                                  HasUnevenRows"false"
                                  RowHeight"100"
                                  HeightRequest"0">
                    <flv:FlowListView.BindingContext>
                        <x:Reference Name"MultiMediaPickerViewModel" /> <!-- *** Second ViewModel ***!-->
                    </flv:FlowListView.BindingContext>
                    <flv:FlowListView.FlowColumnTemplate>
                        <DataTemplate>
                            <Grid>
                                <ffimageloading:CachedImage  DownsampleToViewSize"true"
                                                             HeightRequest"100"
                                                             Source"{Binding PreviewPath}"
                                                             Aspect"AspectFill"
                                                             HorizontalOptions"FillAndExpand">
                                </ffimageloading:CachedImage>
                                <Image Source"play"
                                       IsVisible"false"
                                       HorizontalOptions"End"
                                       VerticalOptions"End">
                                    <Image.Triggers>
                                        <DataTrigger TargetType"Image"
                                                     Binding"{Binding Type}"
                                                     Value"Video">
                                            <Setter Property"IsVisible"
                                                    Value"True" />
                                        </DataTrigger>
                                    </Image.Triggers>
                                </Image>
                            </Grid>
                        </DataTemplate>
                    </flv:FlowListView.FlowColumnTemplate>
                </flv:FlowListView>

            </StackLayout>
        </ContentPage.Content>
    </ContentPage>

Code behind:

  [XamlCompilation(XamlCompilationOptions.Compile)]
    public partial class ProductCustomerPictures : ContentPage
    {

        public ProductDetailViewModel ProductDetailViewModel
        {
            get { return _productDetailViewModel; }
            set { _productDetailViewModel  value; }
        }

        public MultiMediaPickerViewModel MultiMediaPickerViewModel
        {
            get { return _multiMediaPickerViewModel; }
            set { _multiMediaPickerViewModel  value; }
        }


        private ProductDetailViewModel _productDetailViewModel;
        private MultiMediaPickerViewModel _multiMediaPickerViewModel;

        public ProductCustomerPictures(ProductDetailViewModel viewModel)
        {
            InitializeComponent();
            ProductDetailViewModel  viewModel;
            MultiMediaPickerViewModel  new MultiMediaPickerViewModel(MultiMediaPickerServiceStaticVariableHolder.MultiMediaPickerService);
        }
    }

Solution

If I understood this correctly and if you want to keep the pattern to pass in the view model as constructor argument …

    public ProductCustomerPictures(ProductDetailViewModel viewModel)
    {
        InitializeComponent();
        ProductDetailViewModel  viewModel;
        MultiMediaPickerViewModel  new MultiMediaPickerViewModel(MultiMediaPickerServiceStaticVariableHolder.MultiMediaPickerService);
    }

… then you can remove this completely …

    <ContentPage.BindingContext>
        ...
    </ContentPage.BindingContext>

… and this property …

    public ProductDetailViewModel ProductDetailViewModel
    {
        get { return _productDetailViewModel; }
        set { _productDetailViewModel  value; }
    }

Instead, just set the BindingContext directly in the constructor.

    public ProductCustomerPictures(ProductDetailViewModel viewModel)
    {
        InitializeComponent();
        BindingContext  viewModel;    // <-- here
        MultiMediaPickerViewModel  new MultiMediaPickerViewModel(MultiMediaPickerServiceStaticVariableHolder.MultiMediaPickerService);
    }

Now, each and every control in the XAML is binding to the ProductDetailViewModel.

But you still have the FlowListView which should bind to the MultiMediaPickerViewModel. Instead of setting its binding context directly in XAML, it is common to use the binding with a reference, but first you have to give the whole page a name with which we can refer in the binding:

    <ContentPage xmlns"http://xamarin.com/schemas/2014/forms"
        ...
        ...
        x:Name"thisPage"     <--- here
        x:Class"BoerPlaza.Views.Product.ProductCustomerPictures">

Now, you can use the name as reference in the binding expression:

    <flv:FlowListView FlowColumnCount"3"
        x:Name"listItems"
        FlowItemsSource"{Binding Source{x:Reference thisPage}, PathMultiMediaPickerViewModel.Media}"
        SeparatorVisibility"None"
        HasUnevenRows"false"
        RowHeight"100"
        HeightRequest"0">

"{Binding Source{x:Reference thisPage}, PathMultiMediaPickerViewModel.Media}" uses the page itself (by name thisPage) and binds to the property Media of the property MultiMediaPickerViewModel of the page.

With that, you can safely remove this code as well:

    <flv:FlowListView.BindingContext>
        ...
    </flv:FlowListView.BindingContext>

By the way, you can condense the properties in the code behind:

    public MultiMediaPickerViewModel MultiMediaPickerViewModel&nbsp;{ get; private set; }

    public ProductCustomerPictures(ProductDetailViewModel viewModel)
    {
        InitializeComponent();
        BindingContext  viewModel;
        MultiMediaPickerViewModel  new MultiMediaPickerViewModel(MultiMediaPickerServiceStaticVariableHolder.MultiMediaPickerService);
    }

Answered By – Waescher

Leave a Comment