.net MAUI Picker SelectedItem does not cause item to display when inside collection

347 Views Asked by At

the picker is inside a collection and is rendered per row, the select ation works and triggers the command but it doesnt show the selected item when form is restored, it shows empty item instead of address proprety which is bound to selectedItem inside xaml.

here is the xaml code:

<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:sys="clr-namespace:System;assembly=mscorlib"
             xmlns:toolkit="http://schemas.microsoft.com/dotnet/2022/maui/toolkit"
             xmlns:appCtrl="clr-namespace:WS.ORD.Apps.POS.Controls"
             xmlns:appVM="clr-namespace:WS.ORD.Apps.POS.ViewModels"
             xmlns:appModel="clr-namespace:WS.ORD.Apps.POS.Models"
             x:Class="WS.ORD.Apps.POS.Views.PrinterSettingsPage"
             x:Name="page">

    <ContentPage.BindingContext>
        <appVM:PrinterSettingsVM />
    </ContentPage.BindingContext>

    <Shell.BackButtonBehavior>
        <BackButtonBehavior IsVisible="False" />
    </Shell.BackButtonBehavior>

    <ContentPage.Content>
        <VerticalStackLayout BindableLayout.ItemsSource="{Binding Printers}" Spacing="30" Margin="100,50">
            <BindableLayout.ItemTemplate>
                <DataTemplate x:DataType="appModel:PrinterModel">
                    <Grid>
                        <Grid.RowDefinitions>
                            <RowDefinition Height="Auto" />
                        </Grid.RowDefinitions>

                        <Grid.ColumnDefinitions>
                            <ColumnDefinition Width="*" />
                            <ColumnDefinition Width="*" />
                        </Grid.ColumnDefinitions>

                        <Label Grid.Row="0" Grid.Column="0" Text="{Binding Name}" VerticalOptions="Center" />

                        <Picker Grid.Row="0" Grid.Column="1"
                         ItemsSource="{Binding Source={RelativeSource AncestorType={x:Type appVM:PrinterSettingsVM}},Path=Devices}"
                         ItemDisplayBinding="{Binding .}"
                         HorizontalOptions="Fill"
                         VerticalOptions="Center"
                         SelectedItem="{Binding Address, Mode=TwoWay}"
                         Margin="0,0,0,10">
                            <Picker.Behaviors>
                                <toolkit:EventToCommandBehavior EventName="SelectedIndexChanged" Command="{Binding Source={x:Reference page}, Path=BindingContext.PrinterSelectCommand}" CommandParameter="{Binding .}" />
                            </Picker.Behaviors>
                        </Picker>
                    </Grid>
                </DataTemplate>
            </BindableLayout.ItemTemplate>
        </VerticalStackLayout>
    </ContentPage.Content>
</ContentPage>

here is codebehind:

  public partial class PrinterSettingsPage : ContentPage
  {
      public PrinterSettingsPage()
      {
          InitializeComponent();
      }
  }

here is the viewmodel:

namespace WS.ORD.Apps.POS.ViewModels
{
    internal class PrinterSettingsVM : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        public ICommand PrinterSelectCommand { get; private set; }


        public PrinterSettingsVM()
        {
            this.Devices[0] = "device1";
            this.Devices[1] = "device2";
            this.Devices[2] = "device3";


            this.Printers = new List<PrinterModel>()
            {
                new PrinterModel { Id=Guid.NewGuid(), Name="Printer 2", Address="device1" },
                new PrinterModel { Id=Guid.NewGuid(), Name="Printer 2", Address="device2" }
            };
        }


        private string[] _devices = new string[3];
        public string[] Devices
        {
            get { return this._devices; }
            set
            {
                this._devices = value;

                if (this.PropertyChanged != null)
                {
                    this.PropertyChanged(this, new PropertyChangedEventArgs(nameof(this.Devices)));
                }
            }
        }


        private IEnumerable<PrinterModel> _printers;
        public IEnumerable<PrinterModel> Printers
        {
            get { return this._printers; }
            set
            {
                this._printers = value;

                if (this.PropertyChanged != null)
                {
                    this.PropertyChanged(this, new PropertyChangedEventArgs(nameof(this.Printers)));
                }
            }
        }
    }
}

finally the model:

namespace WS.ORD.Apps.POS.Models
{
    public class PrinterModel : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        public Guid Id { get; set; }
        public string Name { get; set; }


        private string _address;
        public string Address
        {
            get { return this._address; }
            set
            {
                this._address = value;

                if (this.PropertyChanged != null)
                {
                    this.PropertyChanged(this, new PropertyChangedEventArgs(nameof(this.Address)));
                }
            }
        }
    }
}
1

There are 1 best solutions below

7
Jessie Zhang -MSFT On

i cant figure out the why binding here dont work

You can try to add a variable SelectedAddress to save the selected item and initialize it in the constructor of your viewmodel.

Please refer to the following code:

public class PrinterSettingsVM: INotifyPropertyChanged
   {
       // add SelectedAddress to save the selected item
       private string _selectedAddress;
       public string SelectedAddress
       {
           get { return this._selectedAddress; }
           set
           {
               this._selectedAddress = value;

               if (this.PropertyChanged != null)
               {
                   this.PropertyChanged(this, new PropertyChangedEventArgs(nameof(this.SelectedAddress)));
               }
           }
       }


       public PrinterSettingsVM()
       {
           this.Devices[0] = "device1";
           this.Devices[1] = "device2";
           this.Devices[2] = "device3";

           //initlize variable SelectedAddress
           SelectedAddress = Devices[1];


          //For the sake of brevity, omit other codes
       }



     //For the sake of brevity, omit other codes


   }

Usage example:

           <Picker Grid.Row="0" Grid.Column="1"
                     ItemsSource="{Binding Source={RelativeSource AncestorType={x:Type appVM:PrinterSettingsVM}},Path=Devices}"
                     ItemDisplayBinding="{Binding .}"
                     HorizontalOptions="Fill"
                     VerticalOptions="Center"
                     SelectedItem="{Binding Source={RelativeSource AncestorType={x:Type appVM:PrinterSettingsVM}},Path=SelectedAddress}"
                     Margin="0,0,0,10">

Update:

but since my pickers are per row generated inside a BindableLayout.ItemTemplate it is impossible to use a single property for all pickers, also list items are dynamically added so i have no idea how many selectedAddress will be necessary.

You can put the Devices and SelectedAddress inside of PrinterModel.

1.you can add property Devices and SelectedAddress into model PrinterModel.cs

public class PrinterModel: INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    public Guid Id { get; set; }
    public string Name { get; set; }

    //add the Devices inside of PrinterModel
    public List<string> Devices { get; set; }


    //add a property SelectedAddress
    private string _selectedAddress;
    public string SelectedAddress
    {
        get { return this._selectedAddress; }
        set
        {
            this._selectedAddress = value;

            if (this.PropertyChanged != null)
            {
                this.PropertyChanged(this, new PropertyChangedEventArgs(nameof(this.SelectedAddress)));
            }
        }
    }



    private string _address;
    public string Address
    {
        get { return this._address; }
        set
        {
            this._address = value;

            if (this.PropertyChanged != null)
            {
                this.PropertyChanged(this, new PropertyChangedEventArgs(nameof(this.Address)));
            }
        }
    }
}

2.And initialize property Devices and SelectedAddress in the constructor of PrinterSettingsVM.cs,just as follows:

public class PrinterSettingsVM: INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    public ICommand PrinterSelectCommand { get; private set; }

    List<string> devices;


    public PrinterSettingsVM()
    {
        devices = new List<string>();
        devices.Add("device1");
        devices.Add("device2");
        devices.Add("device3");


        this.Printers = new List<PrinterModel>()
        {
            //new PrinterModel { Id=Guid.NewGuid(), Name="Printer 2", Address="device1" },
            //new PrinterModel { Id=Guid.NewGuid(), Name="Printer 2", Address="device2" }

            //set the SelectedAddress
            new PrinterModel { Id=Guid.NewGuid(), Name="Printer 2", Address="device1" ,Devices=devices,SelectedAddress=devices[0] },
            new PrinterModel { Id=Guid.NewGuid(), Name="Printer 2", Address="device2",Devices=devices, SelectedAddress=devices[2]}
        };


        PrinterSelectCommand = new Command(DoSomething);
    }

    private void DoSomething(object obj)
    {

    }

    private IEnumerable<PrinterModel> _printers;
    public IEnumerable<PrinterModel> Printers
    {
        get { return this._printers; }
        set
        {
            this._printers = value;

            if (this.PropertyChanged != null)
            {
                this.PropertyChanged(this, new PropertyChangedEventArgs(nameof(this.Printers)));
            }
        }
    }

}

3.modify the data bind as follows:

    <VerticalStackLayout BindableLayout.ItemsSource="{Binding Printers}" Spacing="30" Margin="100,50" Grid.Row="1">
        <BindableLayout.ItemTemplate>
            <DataTemplate x:DataType="appModel:PrinterModel">
                <Grid>
                    <Grid.RowDefinitions>
                        <RowDefinition Height="Auto" />
                    </Grid.RowDefinitions>

                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="60"  />
                        <ColumnDefinition Width="*" />
                    </Grid.ColumnDefinitions>

                    <Label Grid.Row="0" Grid.Column="0" Text="{Binding Name}" VerticalOptions="Center"  BackgroundColor="Pink"/>

                    <Picker Grid.Row="0" Grid.Column="1"
                     ItemsSource="{Binding Devices}"
                     ItemDisplayBinding="{Binding .}"
                     HorizontalOptions="Fill"
                     VerticalOptions="Center"
                     SelectedItem="{Binding SelectedAddress}"
                     Margin="0,0,0,10">

                    </Picker>
                </Grid>
            </DataTemplate>
        </BindableLayout.ItemTemplate>
    </VerticalStackLayout>