Search code examples
mauimvvm-toolkit

ContentView Binding


I have a custom control, that represent the stucture of a meeting room

Meeting room name, Users, and a checkbox to invite users.

The code looks like

    <StackLayout>
        <inputLayout:SfTextInputLayout
            Hint="{Binding Hint, Source={x:Reference UserExpander}}"
            HorizontalOptions="StartAndExpand"
            OutlineCornerRadius="8">
            <Entry Text="{Binding RoomName, Source={x:Reference UserExpander}}" />
        </inputLayout:SfTextInputLayout>

        <expander:SfExpander>
            <expander:SfExpander.Header>
                <Grid
                    ColumnDefinitions="50,60"
                    ColumnSpacing="10">
                    <Label
                        FontFamily="Mat"
                        FontSize="Header"
                        HorizontalTextAlignment="Center"
                        Text="{Static helpers:IconFont.Group}"
                        VerticalTextAlignment="Center" />
                    <Label
                        Grid.Column="1"
                        Text="Students" />
                </Grid>
            </expander:SfExpander.Header>
            <expander:SfExpander.Content>
                <CollectionView
                    ItemsSource="{Binding Users, Source={x:Reference UserExpander}}"
                    SelectionMode="None">
                    <CollectionView.ItemTemplate>
                        <DataTemplate>
                            <Border Margin="2">
                                <Border.StrokeShape>
                                    <RoundRectangle CornerRadius="8" />
                                </Border.StrokeShape>
                                <Grid
                                    Margin="5,5,5,0"
                                    ColumnDefinitions="40,*"
                                    RowDefinitions="*,*">
                                    <Label
                                        Grid.Column="1"
                                        Text="{Binding Name}" />
                                    <Label
                                        Grid.Row="3"
                                        Grid.Column="1"
                                        Text="{Binding Email}" />
                                    <chechbox:SfCheckBox
                                        Grid.RowSpan="3"
                                        HorizontalOptions="End"
                                        IsChecked="{Binding IsParticipant}"
                                        VerticalOptions="End">
                                        <chechbox:SfCheckBox.GestureRecognizers>
                                            <TapGestureRecognizer
                                                Command="{Binding HandleCheckBoxCommand, Source={RelativeSource AncestorType={x:Type ContentView}}}"
                                                CommandParameter="{Binding .}" />
                                        </chechbox:SfCheckBox.GestureRecognizers>
                                    </chechbox:SfCheckBox>
                                </Grid>
                            </Border>
                        </DataTemplate>
                    </CollectionView.ItemTemplate>
                </CollectionView>
            </expander:SfExpander.Content>
        </expander:SfExpander>
    </StackLayout>
</ContentView>`

public partial class UserDetailsExpander : ContentView {
    public UserDetailsExpander() {
        InitializeComponent();
    }


    public static readonly BindableProperty HintProperty = BindableProperty.Create(
        nameof(Hint), typeof(string), typeof(UserDetailsExpander));

    public string Hint {
        get => (string)GetValue(HintProperty);
        set => SetValue(HintProperty, value);
    }

    public static readonly BindableProperty RoomNameProperty = BindableProperty.Create(
        nameof(RoomName), typeof(string), typeof(UserDetailsExpander), string.Empty,
        BindingMode.TwoWay);

    public string RoomName {
        get => (string)GetValue(RoomNameProperty);
        set => SetValue(RoomNameProperty, value);
    }


    public static readonly BindableProperty UsersProperty = BindableProperty.Create(
        nameof(Users), typeof(IEnumerable), typeof(UserDetailsExpander));

    public IEnumerable Users {
        get => (IEnumerable)GetValue(UsersProperty);
        set => SetValue(UsersProperty, value);
    }

    public static readonly BindableProperty HandleCheckBoxCommandProperty = BindableProperty.Create(
        nameof(HandleCheckBoxCommand), typeof(RelayCommand), typeof(UserDetailsExpander));

    public RelayCommand HandleCheckBoxCommand {
        get => (RelayCommand)GetValue(HandleCheckBoxCommandProperty);
        set => SetValue(HandleCheckBoxCommandProperty, value);
    }

for the most part, this is working. I display my list of users, I can put a meeting name, but the HandleCheckBoxCommand is not working.

I am using this control here

<ContentPage
    x:Class="DemyAI.Views.NewTestPage"
    xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
    xmlns:controls="clr-namespace:DemyAI.Controls"
    xmlns:mct="http://schemas.microsoft.com/dotnet/2022/maui/toolkit"
    xmlns:vm="clr-namespace:DemyAI.ViewModels"
    x:DataType="vm:NewTestPageViewMode">

    <ContentPage.Behaviors>
        <mct:EventToCommandBehavior
            Command="{Binding AppearingCommand}"
            EventName="Appearing" />
    </ContentPage.Behaviors>

    <VerticalStackLayout Margin="10">
        <controls:UserDetailsExpander
            HandleCheckBoxCommand="{Binding HandleCheckBoxCommand}"
            Hint="Name of the test"
            RoomName="{Binding RoomName}"
            Users="{Binding Users}" />
        <Button Command="{Binding StartMeetingCommand}" />
    </VerticalStackLayout>
</ContentPage>

and the command comes from this viewModel

     public ObservableCollection<User> Users { get; set; } = [];

    public ObservableCollection<User> Invited { get; set; } = [];

    [ObservableProperty]
    string? roomName;

    [RelayCommand]
    async Task Appearing() {
        await GetStudents();
    }

    private async Task GetStudents() {

        Users.Clear();

        var data = await dataService.GetByRole<User>("Users", Roles.Student.ToString());

        foreach(var filterUser in data) {

            Users.Add(filterUser);
        }
    }

    [RelayCommand]
    void HandleCheckBox(User user) {

        if(user.IsParticipant) {
            Invited.Add(user);
        } else {
            Invited.Remove(user);
        }


    }

    [RelayCommand]
    async Task StartMeeting() {

        if(string.IsNullOrEmpty(RoomName)) {
            await appService.DisplayAlert("Error", "The room name cannot be empty", "OK");
            return;
        }

        var loogedUser = await authenticationService.GetLoggedInUser();

        var databaseUer = await dataService.GetByUidAsync<User>("Users", loogedUser!.Uid);

        var meetingOptions = new MeetingOptions {
            EnableAdvancedChat = true,
            EnableEmojiReactions = true,
            EnableNoiseCancellationUi = true,
            EnableHandRaising = true,
            EnablePrejoinUi = true,
            EnablePipUi = true,
            EnableScreenshare = true,
            EnableVideoProcessingUi = true,
            EnablePeopleUi = false,
            EnableChat = true
            // Set other meeting option properties as needed
        };

        try {
            var roomURL = await meetingService.CreateMeetingAsync(RoomName, meetingOptions, Constants.DAILY);

            foreach(var userInvited in Invited) {
                string userEmail = userInvited.Email;
                await EmailHelper.SendEmail(userEmail, RoomName, databaseUer, roomURL, null);
            }

        } catch(Exception ex) {
            // Handle any exceptions that might occur during the meeting creation
            await appService.DisplayAlert("Error", $"Error creating meeting: {ex.Message}", "OK");
        }
    }
}

I am expecting that the HandleCheckBox trigger.

I tried to use the ancestor type

I named the controller.

I put two mode

I tried BindingContext.HandleCheckBoxCommand


Solution

  • The reason it's probably not working is because you are trying to add a command to a TapGestureRecognizor to a Toggleable control(first responders), what you should be doing instead is using the StateChanged event and triggering the command through it.

    In syncfusions docs you can see it has a StateChanged event use it like this:

      <syncfusion:SfCheckBox StateChanged="CheckBox_StateChanged"/>
    

    In your ContentViews class add the event and trigger your command

     private void CheckBox_StateChanged(object sender, StateChangedEventArgs e)
      {
          //HandleCommand should be triggered here and you can get the relevant state from events 
      }