Arvind Chourasiya
Arvind Chourasiya

Reputation: 17432

How to link View to Viewmodel without using BindingContext in xaml.cs page in MAUI

I am using Commnunity.Toolkit.Mvvm to use MVVM in my project. I have seen many article, YouTube videos almost everyone at the end link View to Viewmodel using BindingContext. I want to connect it without BindingContext.

How can I avoid this code

public partial class ItemEntryPage : ContentPage
{
    public ItemEntryPage()
    {
        InitializeComponent();
        BindingContext = new ItemEntryPageModel();
    }
}

Where do I need to make that connection in MauiProgram.cs or in App.xaml.cs file or do I need to use different Mvvm nuget package ?

There are few approaches where to not need to worry about BindingContext just follow the naming convention of View and Viewmodel, it works, in few cases I have seen in Xamarin.Forms we need to keep View and Viewmodel name in dependency injection in App.xaml.cs file and it works.

I am following this thread mvvm in .net maui.

Upvotes: 1

Views: 1346

Answers (2)

Stephen Quan
Stephen Quan

Reputation: 26169

If you have numerous pages, but, they all have the same view model, the recommendation is to use dependency injection and configure that view model as a singleton in your MauiProgram.cs:

// MauiProgram.cs ...
builder.Services.AddSingleton<GlobalViewModel>();
builder.Services.AddTransient<MainPage>();
builder.Services.AddTransient<SecondPage>();
// ...
// MainPage.xaml.cs
public class MainPage : ContentPage
{
    public MainPage(GlobalViewModel VM)
    {
        InitializeComponent();
        BindingContext = VM;
    }
}
// SecondPage.xaml.cs
public class SecondPage : ContentPage
{
    public SecondPage(GlobalViewModel VM)
    {
        InitializeComponent();
        BindingContext = VM;
    }
}

Alternatively, if you want to give each page its own transient ViewModel, but, also, within each ViewModel, have access to the global singleton ViewModel, you can do it like this:

// MauiProgram.cs ...
builder.Services.AddSingleton<GlobalViewModel>();
builder.Services.AddTransient<MainPage>();
builder.Services.AddTransient<MainViewModel>();
builder.Services.AddTransient<SecondPage>();
builder.Services.AddTransient<SecondViewModel>();
// ...
// MainPage.xaml.cs
public class MainPage : ContentPage
{
    public MainPage(MainViewModel VM)
    {
        InitializeComponent();
        BindingContext = VM;
    }
}
// MainViewModel.cs
public class MainViewModel
{
    public GlobalViewModel Global { get; }
    public MainViewModel(GlobalViewModel Global)
    {
        this.Global = Global;
    }
}
// SecondPage.xaml.cs
public class SecondPage : ContentPage
{
    public SecondPage(SecondViewModel VM)
    {
        InitializeComponent();
        BindingContext = VM;
    }
}
// SecondViewModel.cs
public class SecondViewModel
{
    public GlobalViewModel Global { get; }
    public SecondViewModel(GlobalViewModel Global)
    {
        this.Global = Global;
    }
}

If your question is not able global or singleton, but, about avoiding setting BindingContext in the c# code behind, you can actually set BindingContext in XAML instead, e.g.

<!-- MainPage.xaml -->
<ContentPage BindingContext="{local:MainViewModel}"/>
</ContentPage>
<!-- SecondPage.xaml -->
<ContentPage BindingContext="{local:SecondViewModel}"/>
</ContentPage>

If you want to declare a singleton global GlobalViewModel but just in XAML, one way you could place it in a ResourceDictionary in your App.xaml:

<!-- App.xaml ... -->
<Application>
    <Application.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary Source="Resources/Styles/Colors.xaml" />
                <ResourceDictionary Source="Resources/Styles/Styles.xaml" />
                <ResourceDictionary>
                    <local:GlobalViewModel x:Key="globalViewModel"/>
                </ResourceDictionary>
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
    </Application.Resources>
</Application>
<!-- MainPage.xaml -->
<ContentPage BindingContext="{x:StaticResource globalViewModel}">
</ContentPage>
<!-- SecondPage.xaml -->
<ContentPage BindingContext="{x:StaticResource globalViewModel}">
</ContentPage>

Or, even, just declare it in the BindingContext in App.xaml:

<!-- App.xaml ... -->
<Application>
    <Application.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary Source="Resources/Styles/Colors.xaml" />
                <ResourceDictionary Source="Resources/Styles/Styles.xaml" />
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
    </Application.Resources>
    <Application.BindingContext>
        <local:GlobalViewModel/>
    </Application.BindingContext>
</Application>

Upvotes: 3

H.A.H.
H.A.H.

Reputation: 3907

First we follow the official documentation: https://learn.microsoft.com/en-us/dotnet/communitytoolkit/mvvm/

Instead of random links, that may be not-available (or turn out to contain banana shake recipe) after a month or so.

Second, we do not do this:

BindingContext = new ItemEntryPageModel();

But we use the Dependency Injection, we have in MAUI by:

  1. Register the ViewModel in your MauiProgram.cs.
  2. We modify our Page constructor to set the injected ViewModel as BindingContext.

In your case:

public ItemEntryPage(ItemEntryViewModel vm)
{
    InitializeComponent();
    BindingContext = vm;
}

That is the right way to do it. Nothing else is required.

Anyway, if you want to save some lines of codes using reflection and feel better, this is what I do:

Extension:

public static void AddTransientNamespace(this MauiAppBuilder builder, string nameSpace)
        {
            foreach (Type t in Utils.GetTypesInNamespace(Assembly.GetExecutingAssembly(), nameSpace))
            {
                builder.Services.AddTransient(t);
            }
        }

How to use:

builder.AddTransientNamespace("MyProject.ViewModelsNamespace");

Upvotes: 1

Related Questions