Home > Software engineering >  Binding the Command property of a Label in nested ObservableCollections
Binding the Command property of a Label in nested ObservableCollections

Time:10-20

I have two nested observable collection, first is the list of week days and the second one is the available hours of this day. I need to have a button to make possible to single remove any of these hours. But can't achieve the Command "RemoveHour" be called properly when the "REMOVE" Label is tapped. I have this so far:

code behind

public static ObservableCollection<Calendar> CalendarDays { get; set; } = new ObservableCollection<Calendar>();

        public AgendaAutoDispon()
        {
            InitializeComponent();
            InitializeCalendar();
            CalendarDays.Add(new Calendar(DateTime.Today));
            CalendarDays.Add(new Calendar(DateTime.Today.AddDays(1)));
            CalendarDays.Add(new Calendar(DateTime.Today.AddDays(2)));
            CollectionViewDatas.BindingContext = CalendarDays;
        }


public class Calendar : INotifyPropertyChanged
        {
            public Picker HourPicker { get; set; } = new Picker();
            public ObservableCollection<string> Hours { get; set; } = new();
            public ObservableCollection<string> HoursReceipt { get; set; } = new();

            public Calendar(DateTime day)
            {
                Day = day;
                for (int i = 6; i <= 22; i  )
                {
                    HoursReceipt.Add((i < 10 ? "0"   i.ToString() : i.ToString())   ":00");
                    HoursReceipt.Add((i < 10 ? "0"   i.ToString() : i.ToString())   ":30");
                }
                NotifyPropertyChanged("HoursReceipt");
            }

            private string _DateShow;
            public string DateShow
            {
                get => _DateShow;
                set
                {
                    _DateShow = value;
                    NotifyPropertyChanged("DateShow");
                }
            }

            private DateTime _Day { get; set; }
            public DateTime Day
            {
                get => _Day;
                set
                {
                    _Day = value;
                    switch (value.DayOfWeek)
                    {
                        case DayOfWeek.Sunday: WeekDay = "Sunday"; break;
                        case DayOfWeek.Monday: WeekDay = "Monday"; break;
                        case DayOfWeek.Tuesday: WeekDay = "Tuesday"; break;
                        case DayOfWeek.Wednesday: WeekDay = "Wednesday"; break;
                        case DayOfWeek.Thursday: WeekDay = "Thursday"; break;
                        case DayOfWeek.Friday: WeekDay = "Friday"; break;
                        case DayOfWeek.Saturday: WeekDay = "Saturday"; break;
                    }
                    DateShow = value.ToString("dd/MM/yyyy");
                    NotifyPropertyChanged("Day");
                }
            }

            private string _WeekDay;
            public string WeekDay
            {
                get => _WeekDay;
                set
                {
                    _WeekDay = value;
                    NotifyPropertyChanged("WeekDay");
                }
            }

            public string HourChangedEvent
            {
                set
                {
                    if (!Hours.Contains(value))
                    {
                        Hours.Add(value);
                        Hours = new ObservableCollection<string>(Hours.OrderBy(i => i));
                        NotifyPropertyChanged("Hours");
                    }
                }
            }

            public Command RemoveHour
            {
                get
                {
                    return new Command((arg) => {
                        Console.WriteLine("I SHOULD REMOVE =====> " (string)arg);
                    });
                }
            }

            public event PropertyChangedEventHandler PropertyChanged;
            void NotifyPropertyChanged(string propChanged)
            {
                if (PropertyChanged != null)
                    PropertyChanged(this, new PropertyChangedEventArgs(propChanged));
            }

        }

xaml

<CollectionView x:Name="CollectionViewDates" BindingContext="{Binding CalendarDays}" ItemsSource="{Binding .}"
    VerticalOptions="Center" Margin="0,10,0,0">

    <CollectionView.ItemsLayout>
        <GridItemsLayout Orientation="Horizontal" HorizontalItemSpacing="10" />
    </CollectionView.ItemsLayout>
    <!-- WEEK DAYS -->
    <CollectionView.ItemTemplate>
        <DataTemplate>
            <Frame CornerRadius="15" Padding="0" Margin="5" HasShadow="False" BorderColor="LightGray">
                <Grid HeightRequest="300" WidthRequest="200" HorizontalOptions="CenterAndExpand">
                    <Grid.RowDefinitions>
                        <RowDefinition Height="30" />
                        <RowDefinition Height="30" />
                        <RowDefinition Height="30" />
                        <RowDefinition Height="*" />

                    </Grid.RowDefinitions>


                    <Label Text="{Binding WeekDay}" Grid.Row="0" TextColor="#4D4D4D" FontSize="20"
                        HorizontalTextAlignment="Center" FontAttributes="Bold" Margin="0,5,0,0" />
                    <Label Text="{Binding DateShow}" Grid.Row="1" Margin="0,3,0,0" TextColor="#4D4D4D" FontSize="18"
                        HorizontalTextAlignment="Center" />


                    <!-- HOURS -->
                    <CollectionView Grid.Row="3" ItemsSource="{Binding Hours}">
                        <CollectionView.ItemsLayout>
                            <GridItemsLayout Orientation="Vertical" VerticalItemSpacing="5" />
                        </CollectionView.ItemsLayout>

                        <CollectionView.ItemTemplate>
                            <DataTemplate>
                                <Grid HeightRequest="30">
                                    <Grid.ColumnDefinitions>
                                        <ColumnDefinition Width="*" />
                                        <ColumnDefinition Width="30" />
                                    </Grid.ColumnDefinitions>

                                    
                                    <Label Grid.Column="0" Text="{Binding .}" HorizontalTextAlignment="Center" />

                                    <Label Grid.Column="1" Text="REMOVE" FontSize="20" TextColor="Red" Padding="0"
                                        HorizontalTextAlignment="Center">
                                        
                                        <Label.GestureRecognizers>
                                            <TapGestureRecognizer
                                                Command="{Binding WHAT SHOULD I HAVE HERE??? }"
                                                CommandParameter="{Binding .}" />
                                        </Label.GestureRecognizers>
                                    </Label>
                                </Grid>

                            </DataTemplate>
                        </CollectionView.ItemTemplate>
                        
                    </CollectionView>

                </Grid>
            </Frame>
        </DataTemplate>
    </CollectionView.ItemTemplate>
</CollectionView>

What I have to do to hit the previous level of the binding, which have the command RemoveHour?

CodePudding user response:

You can use a Relative Binding:

<TapGestureRecognizer
    Command="{Binding RemoveHour, Source={RelativeSource AncestorType={x:Type local:Calendar}}}"
    CommandParameter="{Binding .}"/>

The local:Calendar means you need to import the namespace of your Calendar ViewModel as xmlns:local="..." before you can use the Calendar ViewModel for the binding.

More info: https://learn.microsoft.com/en-us/xamarin/xamarin-forms/app-fundamentals/data-binding/relative-bindings

I would also recommend using common C# and MVVM naming conventions, such as giving a ViewModel the ViewModel postfix, e.g. CalendarViewModel or RemoveHourCommand for a Command.

Update

I've just noticed that you're not applying the MVVM pattern correctly. You should move your Calendar class to a separate file and set it as the BindingContext of your View's code-behind. The code-behind should not hold state or objects and functionality that are part of the Business Logic, it should really just do things concerning the View. My solution only works if the BindingContext of the View is set to the Calendar, which should actually be a ViewModel called CalendarViewModel and contain the CalendarDays and the Command your trying to bind to.

  • Related