Denis
Denis

Reputation: 164

Binding ViewModel to BindingContext

There is a template for displaying data with a ViewModel.

BaseTabView.xaml

<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="Oss.Maui.Views.Tabs.BaseTabView"
             xmlns:vm="clr-namespace:Oss.ViewModels.Tabs;assembly=Oss.ViewModels"
             xmlns:controls="clr-namespace:Oss.Maui.Controls;assembly=Oss.Maui.Controls"
             x:DataType="vm:BaseTabViewModel">
    <ContentPage.Content>
        <controls:HybridWebView x:Name="HybridView" Url="{Binding Url}" Source="{Binding Url}" LocalStoragesValue="{Binding LocalStorage}" />
    </ContentPage.Content>
</ContentPage>

BaseTabView.xaml.cs

public partial class BaseTabView : ContentPage
{
    public BaseTabView()
    {
        InitializeComponent();
        
#if ANDROID
        HybridView.JsBridge = ServiceLocator.GetService<IJsBridge<IOssAction>>();
#endif
    }

    protected override void OnBindingContextChanged()
    {
        base.OnBindingContextChanged();
    }
}

Basic ViewModel for displaying

public class BaseTabViewModel : ViewModel
    {
        protected readonly IMauiApplicationViewModel MauiApplicationViewModel;
        public BaseTabViewModel(IApplication application, IMauiApplicationViewModel mauiApplicationViewModel)
            : base(application)
        {
            MauiApplicationViewModel = mauiApplicationViewModel;
            LocalStorage = new Dictionary<string, string>()
            {
                {"oss.locale", $"\"{mauiApplicationViewModel.PersonalSettings.CurrentCulture.TwoLetterISOLanguageName}\""},
                {"hideNavbar", "true" },
                {"user_account_info", "{\"displayName\":\""+mauiApplicationViewModel.UserAccount.DisplayName+"\"," +
                "\"employeeId\":"+mauiApplicationViewModel.UserAccount.EmployeeId+"," +
                "\"employeeName\":\""+mauiApplicationViewModel.UserAccount.EmployeeName+"\"," +
                "\"expires\":\""+mauiApplicationViewModel.UserAccount.TokenDateExpiration.ToString("o")+"\"," +
                "\"token\":\""+mauiApplicationViewModel.UserAccount.AccessToken+"\"," +
                "\"settings\":"+mauiApplicationViewModel.UserAccount.Settings+"}"},
            };
        }

        public string? Url { get; protected set; }
        public Dictionary<string, string> LocalStorage { get; }
    }
protected override void OnConnected(EventArgs e)
        {
            base.OnConnected(e);
            UserAccount = _request.Result;
            
            TaskTabViewModel = ScopedServices.GetRequiredService<TaskTabViewModel>();
            SearchTaskTabViewModel = ScopedServices.GetRequiredService<SearchTaskTabViewModel>();

            //TODO: add other tabs

            Application.Messenger.Send(NavigationMessangerPage.Home);
        }

After authorization, I create ViewModels instances. When the MainPage is first rendered, these ViewModel==null. Next, notification occurs and works out OnPropertyChange(nameof(TaskTabViewModel)). But the binding to a non-empty element does not change after notification. base.OnBindingContextChanged(); triggered only on the first render.

<Shell xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:Oss.Maui"
             xmlns:sys="clr-namespace:System;assembly=netstandard"
             x:Class="Oss.Maui.MainPage"
             xmlns:res="clr-namespace:Oss.Localization;assembly=Oss.Localization"
             xmlns:controls="clr-namespace:Oss.Maui.Controls;assembly=Oss.Maui.Controls"
             xmlns:loginView="clr-namespace:Oss.Maui.Views.Logins;assembly=Oss.Maui.Views"
             xmlns:templateView="clr-namespace:Oss.Maui.Views.Tabs;assembly=Oss.Maui.Views"
             xmlns:vm="clr-namespace:Oss.ViewModels;assembly=Oss.ViewModels"
             Shell.NavBarIsVisible="True"
             Shell.TabBarIsVisible="False"
             Shell.TitleColor="Transparent"
             Shell.NavBarHasShadow="False"
             x:DataType="vm:MauApplicationViewModel"
             BackgroundColor="{DynamicResource PageBackgroundColor}">

    <ShellItem Route="login" FlyoutItemIsVisible="False">
        <ShellContent ContentTemplate="{DataTemplate loginView:LoginView}"/>
    </ShellItem>

    <FlyoutItem Route="main" FlyoutDisplayOptions="AsMultipleItems">
        <ShellContent Title="{res:Translate TaskTabTitle}" Icon="home.png">
            <templateView:BaseTabView BindingContext="{Binding TaskTabViewModel, Mode=TwoWay}"></templateView:BaseTabView>
        </ShellContent>
        <ShellContent Title="{res:Translate SearchTaskTabTitle}" Icon="list.png">
            <templateView:BaseTabView BindingContext="{Binding SearchTaskTabViewModel, Mode=TwoWay}"></templateView:BaseTabView>
        </ShellContent>
    </FlyoutItem>
</Shell>

How can I show tabs only after authorization, or update the binding?

UPDATE:

Ad TaskTabViewModel

private TaskTabViewModel _taskTabViewModel;
        public TaskTabViewModel TaskTabViewModel
        {
            get => _taskTabViewModel;
            set
            {
                SetValue(ref _taskTabViewModel, value);
            }
        }

ViewModel.SetValue

protected bool SetValue<T>(ref T property, T value, [CallerMemberName] string propertyName = null)
        {
            return SetValue(ref property, value, propertyName, null);
        }

        protected bool SetValue<T>(ref T property, T value, string propertyName, Action action)
        {
            if (Equals(property, value))
                return false;

            property = value;
            NotifyPropertyChanged(propertyName);
            if (action != null)
                action();

            return true;
        }

Upvotes: 2

Views: 862

Answers (1)

Liyun Zhang - MSFT
Liyun Zhang - MSFT

Reputation: 14594

I can't ensure the problem is the PropertyChangedEventHandler can work for the binding context of the content page or not. But I have checked the source code and found that there is only one BindableProperty named ContentProperty in the page. So the binding context is not a bindable property.

If you want to set the BaseTabView's binding context after getting the instance of the viewmodel. You can try the following code.

TaskTabViewModel = ScopedServices.GetRequiredService<TaskTabViewModel>();
Shell.Current.Items[1].Items[0].BindingContext = TaskTabViewModel;
// the first Items is flyoutitem which route is main and the second items is the BaseTabView

Upvotes: 1

Related Questions