Search code examples
commandmauidatatemplatebehavioreventtocommand

MAUI event to command behavior command binding to viewmodel


So i got ContentPage.Resources in which i define DataTemplate for BindableLayout. I need to bind Unfocused editor event from this DataTemplate to my ViewModel relay command. But i cant just do it like this Command="{Binding UnfocusedCommand} because i define data type for this DataTemplate. So i try to do it in that way Command="{Binding UnfocusedCommand, Source={RelativeSource AncestorType={x:Type viewModels:TestDetailsViewModel}}} and also this way Command="{Binding Source={RelativeSource AncestorType={x:Type viewModel:TestDetailsViewModel}}, Path=UnfocusedCommand}" it also doesnt work and giving me error "Operation is not valid due to the current state of the object". Without this behaviors all working fine, so tell me please how to do this right and what am i doing wrong.

MyView:

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:toolkit="http://schemas.microsoft.com/dotnet/2022/maui/toolkit"
             x:Class="Bibliomatic_MAUI_App.Views.TestDetailsView"
             xmlns:models="clr-namespace:Bibliomatic_MAUI_App.Models"
             xmlns:viewModel="clr-namespace:Bibliomatic_MAUI_App.ViewModels" 
             xmlns:selectors="clr-namespace:Bibliomatic_MAUI_App.Selectors"               
             x:DataType="viewModel:TestDetailsViewModel"
             Title="TestDetailsView">   

    <ContentPage.Resources>    
        <!-- Problem with that data template -->
        <DataTemplate x:Key="OneAnswerQuestion" x:DataType="models:TestAnswerResponce">

            <Grid>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="Auto"/>
                    <ColumnDefinition Width="*"/>
                </Grid.ColumnDefinitions>
                <RadioButton GroupName="TestAnswersGroup" Grid.Column="0"/>
                <Editor AutoSize="TextChanges" Placeholder="Enter qustion answer" Grid.Column="1">
                    
                    <Editor.Behaviors>
                        <toolkit:EventToCommandBehavior                            
                            EventName="TextChanged"
                            Command="{Binding Source={RelativeSource AncestorType={x:Type viewModel:TestDetailsViewModel}}, Path=UnfocusedCommand}"/>
                    </Editor.Behaviors>
                    
                </Editor>
            </Grid>

        </DataTemplate>

        <DataTemplate x:Key="MultipleAnswerQuestion" x:DataType="models:TestAnswerResponce">

            <Grid>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="Auto"/>
                    <ColumnDefinition Width="*"/>
                </Grid.ColumnDefinitions>
                <CheckBox Grid.Column="0"/>
                <Editor Grid.Column="1" AutoSize="TextChanges"  Placeholder="Enter qustion answer"/>
            </Grid>

        </DataTemplate>

        <DataTemplate x:Key="PairsQuestion" x:DataType="models:TestAnswerResponce">

            <Grid>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="*"/>
                    <ColumnDefinition Width="*"/>
                </Grid.ColumnDefinitions>
                <Editor Grid.Column="0" AutoSize="TextChanges" Placeholder="Enter qustion"/>
                <Editor Grid.Column="1" AutoSize="TextChanges" Placeholder="Enter answer"/>
            </Grid>

        </DataTemplate>

        <DataTemplate x:Key="OpenEndedQuestion" x:DataType="models:TestQuestionResponce">

            <Editor AutoSize="TextChanges"  Placeholder="Enter qustion answer"/>

        </DataTemplate>

        <selectors:TestQuestionsDataTemplateSelector x:Key="TestAnswerTemplate" 
                                                         OneAnswerQuestion="{x:StaticResource OneAnswerQuestion}"
                                                         MultipleAnswerQuestion="{x:StaticResource MultipleAnswerQuestion}"
                                                         PairsQuestion="{x:StaticResource PairsQuestion}"
                                                         OpenEndedQuestion="{x:StaticResource OpenEndedQuestion}"/>

    </ContentPage.Resources>
    
    <StackLayout>

        <Grid>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="*"/>
                <ColumnDefinition Width="Auto"/>
            </Grid.ColumnDefinitions>
            <Border StrokeThickness="3.5">

                <Border.StrokeShape>
                    <RoundRectangle CornerRadius="10"></RoundRectangle>
                </Border.StrokeShape>

                <Picker Grid.Column="0" SelectedItem="{Binding SelectedQuestionType}" ItemsSource="{Binding TestQuestionTypes}"/>
            </Border>            
            <Button Grid.Column="1" Margin="0,20" Text="Add test question" Command="{Binding AddTestQuestionCommand}"/>
        </Grid>        
        

        <CollectionView ItemsSource ="{Binding TestQuestions}"                          
                        VerticalOptions="FillAndExpand"                    
                        SelectionMode="None"                     
                        x:Name="TestsCollectionView">

            <CollectionView.ItemTemplate>

                <DataTemplate x:DataType="models:TestQuestionResponce">
                    
                    <StackLayout Margin="0,20">
                        
                        <Entry Placeholder="Enter your question"/>

                        <StackLayout BindableLayout.ItemsSource="{Binding TestAnswers}" BindableLayout.ItemTemplateSelector="{StaticResource TestAnswerTemplate}"/>






                        <Button Text="Add test answer" Command="{Binding Source={RelativeSource AncestorType={x:Type viewModel:TestDetailsViewModel}}, Path=AddTestAnswerCommand}"
                                                       CommandParameter="{Binding .}"/>
                        
                    </StackLayout>                   

                </DataTemplate>

            </CollectionView.ItemTemplate>
            
        </CollectionView>      
        
        
    </StackLayout>
    
</ContentPage>

MyViewModel:

    using Bibliomatic_MAUI_App.Models;
    using Bibliomatic_MAUI_App.Services;
    using CommunityToolkit.Mvvm.ComponentModel;
    using CommunityToolkit.Mvvm.Input;
    using Syncfusion.Maui.DataSource.Extensions;
    using System.Collections.ObjectModel;


   namespace Bibliomatic_MAUI_App.ViewModels
   {
    [QueryProperty(nameof(Test), "Test")]
    public partial class TestDetailsViewModel : ObservableObject
    {
        public ObservableCollection<TestQuestionResponce> TestQuestions { get; set; }        
        public List<TestQuestionType> TestQuestionTypes { get; set; }

        [ObservableProperty]
        public TestResponce test;

        [ObservableProperty]
        public TestQuestionType selectedQuestionType;

        private readonly TestService testSerice;

        public TestDetailsViewModel(TestService testService)
        {
            this.testSerice = testService;

            TestQuestions = new ObservableCollection<TestQuestionResponce>();
            TestQuestionTypes = Enum.GetValues(typeof(TestQuestionType))
                                     .Cast<TestQuestionType>()
                                     .ToList();            
        }

        [RelayCommand] 
        public void AddTestQuestion()
        {
            TestQuestions.Add(new TestQuestionResponce
            {
                Question = string.Empty,
                TestId = Test.Id,
                Test = Test, 
                TestQuestionType = SelectedQuestionType,
                TestAnswers = new ObservableCollection<TestAnswerResponce>(),                
            });
        }

        [RelayCommand]
        public void AddTestAnswer(object parameter)
        {
            var selectedQuestion = parameter as TestQuestionResponce;

            selectedQuestion.TestAnswers.Add(new TestAnswerResponce
            {                
                Answer = string.Empty,
                TestQuestionId = selectedQuestion.Id,
                TestQuestion = selectedQuestion,                
            });
        }

        [RelayCommand]
        public async void Unfocused()
        {
            //Some work
        }
    }
}

Solution

  • You could try the following way.

    1.Set the x:Name of the ContentPage:

    <ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             ...
             x:Name="this">
    
    1. Set the Binding for Command. Then the Command binds to the UnFocusedCommand in ViewModel

       <Editor.Behaviors>
           <toolkit:EventToCommandBehavior                            
               EventName="TextChanged"
               Command="{Binding Source={x:Reference this}, Path=BindingContext.UnFocusedCommand}">
           </toolkit:EventToCommandBehavior>               
       </Editor.Behaviors>
      

    Hope it works for you.