Search code examples
c#xamlmaui

Why does a picker in a CollectionView set the SelectedItem binding to null?


I have a picker inside a CollectionView that should select a predefined item ColorNav but when it starts the item is not selected.

XAML:

        <StackLayout>
            <Label Text="Players" />
            <CollectionView ItemsSource="{Binding PlayerList}">
                <CollectionView.ItemTemplate>
                    <DataTemplate x:DataType="model:Player">
                        <SwipeView>
                            <Border>
                                <Grid ColumnDefinitions="*, *, *" ColumnSpacing="1" >
                                    <Label Grid.Column="0" Text="{Binding Name}" />
                                    <Picker Grid.Column="1" ItemsSource="{Binding ColorsPicker, Source={RelativeSource AncestorType={x:Type viewmodel:MainViewModel}}}" ItemDisplayBinding="{Binding Name}" SelectedItem="{Binding ColorNav}"/>
                                    <Label Grid.Column="2" Text="{Binding ColorNav.Name}"/>
                                </Grid>
                            </Border>
                        </SwipeView>
                    </DataTemplate>
                </CollectionView.ItemTemplate>
            </CollectionView>
            <Button Text="Save" Command="{Binding SaveCommand}"/>
        </StackLayout>

C#

    public class Player
    {
        public string Name { get; set; } = default!;
        public int ColorId { get; set; } = default!;
        public Colour ColorNav { get; set; } = default!;
    }

    public class Colour
    {
        public int ColorId { get; set; }
        public string Name { get; set; } = default!;
    }

    public MainViewModel() {
            ColorsPicker = [
                new Colour {
                    ColorId = 1,
                    Name = "Red"
                },
                new Colour {
                    ColorId = 2,
                    Name = "Blue"
                },
                new Colour {
                    ColorId = 3,
                    Name = "Yellow"
                },
            ];

            PlayerList = [
                new Player {
                    Name = "John",
                    ColorId = 1,
                    ColorNav = ColorsPicker[0],
                },
                new Player {
                    Name = "Carla",
                    ColorId = 3,
                    ColorNav = ColorsPicker[2],
                },
            ];
    }

While debugging, I can see that ColorNav is loaded in the BindingContext, but somehow it becomes null when the page starts. When I go to 'Save, ColorNav is null for every player. If I remove SelectedItem="{Binding ColorNav}" from the Picker, ColorNav remains as expected.Every player in the BindingContext has ColorNav initialized


Solution

  • You can put variable ColorsPicker and variable ColorNav inside of model Player.cs and implement interface INotifyPropertyChanged for model Player.cs.

    public class Player: INotifyPropertyChanged
    {
        public string Name { get; set; } = default!;
        public int ColorId { get; set; } = default!;
        // public Colour ColorNav { get; set; } = default!;
    
        public ObservableCollection<Colour> ColorsPicker { get; set; } = new ObservableCollection<Colour>();
    
    
        //add a property for the SelectedItem property of picker
        private Colour _colorNav;
        public Colour ColorNav
        {
            set
            {
                SetProperty(ref _colorNav, value);
            }
            get { return _colorNav; }
        }
    
        bool SetProperty<T>(ref T storage, T value, [CallerMemberName] string propertyName = null)
        {
            if (Object.Equals(storage, value))
                return false;
            storage = value;
            OnPropertyChanged(propertyName);
            return true;
        }
        protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
        public event PropertyChangedEventHandler PropertyChanged;
    }
    

    Besides, if we want UI update(<Label Grid.Column="2" Text="{Binding ColorNav.Name}") while changing the selected Item of current Picker, we also need to implement interface INotifyPropertyChanged for model Colour.cs

    public class Colour: INotifyPropertyChanged
    {
        public int ColorId { get; set; }
        //public string Name { get; set; } = default!;
    
        private string _name;
        public string Name
        {
            set
            {
                SetProperty(ref _name, value);
            }
            get { return _name; }
        }
        bool SetProperty<T>(ref T storage, T value, [CallerMemberName] string propertyName = null)
        {
            if (Object.Equals(storage, value))
                return false;
            storage = value;
            OnPropertyChanged(propertyName);
            return true;
        }
        protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
        public event PropertyChangedEventHandler PropertyChanged;
    
    }
    

    Then we can initialize MainViewModel.cs as follows:

    public class MainViewModel
    {
        public ObservableCollection<Colour> ColorsPicker { get; set; } = new ObservableCollection<Colour>();
        public ObservableCollection<Player> PlayerList { get; set; } = new ObservableCollection<Player>();
    
        public MainViewModel() {
    
            ColorsPicker = [
         new Colour {
                    ColorId = 1,
                    Name = "Red"
                },
                new Colour {
                    ColorId = 2,
                    Name = "Blue"
                },
                new Colour {
                    ColorId = 3,
                    Name = "Yellow"
                },
            ];
    
            PlayerList = [
                new Player {
                    Name = "John",
                    ColorId = 1,
                    ColorNav = ColorsPicker[0],
                    ColorsPicker = ColorsPicker // initialize ColorsPicker
                },
                new Player {
                    Name = "Carla",
                    ColorId = 3,
                    ColorNav = ColorsPicker[2],
                    ColorsPicker = ColorsPicker // initialize ColorsPicker
    
                },
            ];
    
        }
    }
    

    And then, we can bind data as follows:

        <CollectionView ItemsSource="{Binding PlayerList}">
            <CollectionView.ItemTemplate>
                <DataTemplate x:DataType="model:Player">
                    <SwipeView>
                        <Border>
                            <Grid ColumnDefinitions="*, *, *" ColumnSpacing="1" >
                                <Label Grid.Column="0" Text="{Binding Name}" />
                                <!--<Picker Grid.Column="1" ItemsSource="{Binding ColorsPicker, Source={RelativeSource AncestorType={x:Type viewmodel:MainViewModel}}}" ItemDisplayBinding="{Binding Name}" SelectedItem="{Binding ColorNav}"/>-->
                                <Picker Grid.Column="1" ItemsSource="{Binding ColorsPicker}" ItemDisplayBinding="{Binding Name}" SelectedItem="{Binding ColorNav ,Mode=TwoWay}"/>
    
                                <Label Grid.Column="2" Text="{Binding ColorNav.Name}"/>
                            </Grid>
                        </Border>
                    </SwipeView>
                </DataTemplate>
            </CollectionView.ItemTemplate>
        </CollectionView>