.Net Maui CommunityToolkit.MVVM not cascading updated properties

63 Views Asked by At

I have View Model defined as below:

using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;

namespace OPProblems.VM
{
    public partial class MyViewModel : ObservableObject 

    {
        [ObservableProperty]
        List<MyData> dataList = new List<MyData>();

        [ObservableProperty]
        MyData selectedData = new MyData();

        public MyViewModel()
        {

            DataList = new List<MyData>()
            {
                new MyData() {Name="Name 1", State="ME", PostalCode="11111" },
                new MyData() {Name="Name 2", State="MS", PostalCode="22222" },
                new MyData() {Name="Name 3", State="PA", PostalCode="33333" },
                new MyData() {Name="Name 4", State="DE", PostalCode="44444" },
            };

            SelectedData = DataList[0];
        }


        [RelayCommand]
        public void SelectionChanged(object selectedData)
        {
            SelectedData = (MyData)selectedData;
        }
    }

    public partial class MyData: ObservableObject
    {
        [ObservableProperty]
        string name = "";
        [ObservableProperty]
        string state = "";
        [ObservableProperty]
        string postalCode = "";
    }

}

The premise is I don't want to replicate all the values in MyData as fields on the base view model. I want to be able to bind lables on the page to the currently selected MyData entry from the DataList and have the fields on the page automatically update.

The XAML for the page is below:

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:ViewModel="clr-namespace:OPProblems.VM"
             x:DataType="ViewModel:MyViewModel"
             x:Class="OPProblems.MainPage">

    <ScrollView>
        <Grid HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand" RowDefinitions="auto, *" Padding="30,0">
            <Label Text="Select Data" HorizontalOptions="Center" VerticalOptions="Start" FontSize="Large"/>
            <CollectionView ItemsSource="{Binding DataList}" HorizontalOptions="CenterAndExpand" VerticalOptions="Start" Grid.Row="0" BackgroundColor="LightGrey">
                <CollectionView.ItemTemplate>
                    <DataTemplate x:DataType="ViewModel:MyData">
                        <StackLayout Orientation="Horizontal" VerticalOptions="Start" HorizontalOptions="CenterAndExpand">
                            <Label Text="{Binding Name}" Style="{StaticResource SubHeadline}" SemanticProperties.HeadingLevel="Level2"/>
                            <Label Text="{Binding State}" Style="{StaticResource SubHeadline}" SemanticProperties.HeadingLevel="Level2"/>
                            <Label Text="{Binding PostalCode}" Style="{StaticResource SubHeadline}" SemanticProperties.HeadingLevel="Level2"/>
                            <StackLayout.GestureRecognizers>
                                <TapGestureRecognizer Tapped="TapGestureRecognizer_Tapped" CommandParameter="{Binding .}" />
                            </StackLayout.GestureRecognizers>
                        </StackLayout>
                    </DataTemplate>
                </CollectionView.ItemTemplate>
            </CollectionView>

            <StackLayout Orientation="Vertical" HorizontalOptions="FillAndExpand" VerticalOptions="Start" x:DataType="ViewModel:MyData" Grid.Row="1">
                <Label Text="Name: " Style="{StaticResource Headline}" SemanticProperties.HeadingLevel="Level1" />
                <Label x:Name="lblName" Text="{Binding Name}" Style="{StaticResource SubHeadline}" SemanticProperties.HeadingLevel="Level2"/>

                <Label Text="State: " Style="{StaticResource Headline}" SemanticProperties.HeadingLevel="Level1" />
                <Label x:Name="lblState" Text="{Binding State}" Style="{StaticResource SubHeadline}" SemanticProperties.HeadingLevel="Level2"/>

                <Label Text="Postal Code: " Style="{StaticResource Headline}" SemanticProperties.HeadingLevel="Level1" />
                <Label x:Name="lblPostalCode"  Text="{Binding PostalCode}" Style="{StaticResource SubHeadline}" SemanticProperties.HeadingLevel="Level2"/>
            </StackLayout>

        </Grid>
    </ScrollView>

</ContentPage>

Here is the code for the codebehind:

using OPProblems.VM;

namespace OPProblems
{
    public partial class MainPage : ContentPage
    {
        int count = 0;
        MyViewModel vm;


        public MainPage(MyViewModel viewModel)
        {
            InitializeComponent();
            vm = viewModel;
            BindingContext = vm;
            lblName.BindingContext = vm.SelectedData;
            lblState.BindingContext = vm.SelectedData;
            lblPostalCode.BindingContext = vm.SelectedData;
        }

        private void TapGestureRecognizer_Tapped(object sender, TappedEventArgs e)
        {
            DisplayAlert("Data Selected", $"Data for: {((MyData)e.Parameter).Name} selected.", "Ok");
            vm.SelectionChanged(e.Parameter);
        }
    }

}

So the challenge is -- when I select an entry from the collection view, the VM updates the SelectedData by setting it equal to the MyData entry passed in as the selectedData. However, the fields on the screen (Name, State, PostalCode) don't change to represent the values in the newly assigned SelectedData entry.

Any ideas how to make the fields on the screen correctly update to the values in the SelectedData object without manually copying them all to new observable properties in the view model and binding to those properties?

1

There are 1 best solutions below

2
On BEST ANSWER

don't set the BindingContext of each individual control. Just define the binding expression like this

Text="{Binding SelectedData.PostalCode}"