Share via

Reusable component

Eduardo Gomez 4,316 Reputation points
2024-01-12T22:46:27.1066667+00:00

I have 4 pages, that they almost do the same thing. Schedule test schedule lecture new test new lecture My NewLecturePage looks like this.


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

    <Grid
        Margin="10"
        RowDefinitions="Auto,80,*">

        <Label
            FontAttributes="Bold"
            FontSize="Header"
            HorizontalTextAlignment="Center"
            Text="Create a new lecture" />

        <inputLayout:SfTextInputLayout
            Grid.Row="1"
            Hint="Name of the leture"
            HorizontalOptions="StartAndExpand"
            OutlineCornerRadius="8">
            <Entry Text="{Binding RoomName}" />
        </inputLayout:SfTextInputLayout>

        <expander:SfExpander
            Grid.Row="2"
            HorizontalOptions="Start">
            <expander:SfExpander.Header>
                <Grid
                    ColumnDefinitions="50,60"
                    ColumnSpacing="10"
                    RowDefinitions="60">
                    <Label
                        FontFamily="Mat"
                        FontSize="Header"
                        HorizontalTextAlignment="Center"
                        Text="{Static helpers:IconFont.Group}"
                        VerticalTextAlignment="Center" />
                    <Label
                        Grid.Column="1"
                        Text="Students"
                        VerticalTextAlignment="Center" />
                </Grid>
            </expander:SfExpander.Header>
            <expander:SfExpander.Content>
                <CollectionView
                    ItemsSource="{Binding Users}"
                    SelectionMode="None">
                    <CollectionView.ItemTemplate>
                        <DataTemplate x:DataType="model:User">
                            <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 Source={RelativeSource AncestorType={Type vm:NewLecturePageViewModel}}, Path=HandleCheckBoxCommand}"
                                                CommandParameter="{Binding .}" />
                                        </chechbox:SfCheckBox.GestureRecognizers>
                                    </chechbox:SfCheckBox>
                                </Grid>
                            </Border>
                        </DataTemplate>
                    </CollectionView.ItemTemplate>
                </CollectionView>
            </expander:SfExpander.Content>
        </expander:SfExpander>

        <Button
            Grid.Row="3"
            Command="{Binding StartMeetingCommand}"
            Text="Start meeting"
            VerticalOptions="EndAndExpand" />

    </Grid>

And my view Model looks like this.

public partial class NewLecturePageViewModel(IAppService appService, IDataService<User> dataService, IMeetingService meetingService,
    IAuthenticationService authenticationService) : BaseViewModel {

    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", "You cannot create a Lecture without a name", "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
            Console.WriteLine($"Error creating meeting: {ex.Message}");
        }
    }
}

This work perfect, I can put a roomName, invite users (I use syncfusion checkbox, because I don't want to handle the check for each platform) and create a meeting. So, if everything does the exact same thig why don't we create a reusable component

    <StackLayout>
        <inputLayout:SfTextInputLayout
            Hint="{Binding Hint, Source={x:Reference UserExpander}}"
            HorizontalOptions="StartAndExpand"
            OutlineCornerRadius="8">
            <Entry Text="{Binding RomeName, 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 Source={x:Reference UserExpander}, Path=HandleCheckBox}"
                                                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 RomeNameProperty = BindableProperty.Create(
        nameof(RomeName), typeof(string), typeof(UserDetailsExpander));

    public string RomeName {
        get => (string)GetValue(RomeNameProperty);
        set => SetValue(RomeNameProperty, 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);
    }
}


Now why we don't go even further and inherit from the NewLecturePage I did that NewTestviewModel public partial class NewTestPageViewMode(IAppService appService, IDataService<Models.User> dataService, IMeetingService meetingService, IAuthenticationService authenticationService) : NewLecturePageViewModel(appService, dataService, meetingService, authenticationService) { } and the page

xmlns:vm="clr-namespace:DemyAI.ViewModels"
Title="NewTest"
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"
        RomeName="{Binding RoomName}"
        Users="{Binding Users}" />

    <Button Command="{Binding StartMeetingCommand}"/>
</VerticalStackLayout>

everything seems to work

I get the students and everything but when I press the button everything Is empty.

I do not understand, if I am inherited from NewLecture, and have everything, why is not working

Developer technologies | .NET | .NET Multi-platform App UI

Answer accepted by question author

Yonglun Liu (Shanghai Wicresoft Co,.Ltd.) 50,166 Reputation points Microsoft External Staff
2024-01-15T08:05:10.5633333+00:00

Hello,

There are no issues with your UI reuse layer, which is fully in line with the use cases of the official documentation.

This issue is more likely to be a binding issue at the MVVM level.

For the BindingContext of the ContentView, please refer to the example in
Define the UI for data binding.

Best Regards,

Alec Liu.


If the answer is the right solution, please click "Accept Answer" and kindly upvote it. If you have extra questions about this answer, please click "Comment".

Note: Please follow the steps in our documentation to enable e-mail notifications if you want to receive the related email notification for this thread.

Was this answer helpful?

1 person found this answer helpful.
0 comments No comments

0 additional answers

Sort by: Most helpful

Your answer

Answers can be marked as 'Accepted' by the question author and 'Recommended' by moderators, which helps users know the answer solved the author's problem.