Reputation: 301
HI I have a Maui app using ContentTemplates where I'm trying to bind the Unfocused event of an Editor to a RelayCommand on the Viewmodel.
I've managed to bind a button click to a RelayCommand but the Editor Unfocused command won't trigger.
FYI I've implemented the following solution to solve the issue with Editors not unfocusing on clicking outside of the control:
https://github.com/dotnet/maui/issues/21053
Sample repo here where the functionality is in MainPage:
Any help much appreciated.
Upvotes: 0
Views: 217
Reputation: 8260
You want to use TemplateBinding for the ControlTemplate. But the tricky thing is that we cannot use RelativeBinding or TemplateBinding in Behavoirs. You may use x:reference Binding expression instead, see this similar thread, How to use UserStoppedTypingBehavior from ControlTemplate?.
As a workaround, I made some changes to your code, in ControlTemplates,
<Editor x:Name="myeditor" BindingContext="{Binding Source={RelativeSource TemplatedParent}}"
Grid.Row="1"
AutoSize="TextChanges"
Text="{Binding BindingContext.Response,Source={RelativeSource TemplatedParent}}">
<Editor.Behaviors>
<!--<toolkit:EventToCommandBehavior Command="{Binding Source={RelativeSource TemplatedParent}, Path=EditorUnfocusedRelayCommand}" EventName="Unfocused" />-->
<!--<toolkit:EventToCommandBehavior
Command="{Binding Source={RelativeSource AncestorType={x:Type viewModel:MainPageViewModel}}, Path=EditorUnfocusedRelayCommand}"
CommandParameter="{Binding .}"
EventName="Unfocused" />-->
<toolkit:EventToCommandBehavior
Command="{Binding Source={x:Reference myeditor}, Path=BindingContext.EditorUnfocusedCommand}"
CommandParameter="{Binding BindingContext.BindingContext,Source={x:Reference myeditor}}"
EventName="Unfocused" />
</Editor.Behaviors>
</Editor>
Please note that you have both EditorUnfocusedCommand
and EditorUnfocusedRelayCommand
in viewmodel. Here I use EditorUnfocusedCommand
for debugging.
It seems complex though works on my side. Also, the tricky thing is that you may use different Binding Source in an Editor
control.
First is the Text
Property, we want to bind it to the ContentTemplateDto
Item which is set as the BindingContext for the EditorContentView
.
Second is the Command
property, we want to set it to the value of EditorUnfocusedCommand
in EditorContentView
.
To achieve this, we set the BinidngContext of the Editor
control to the TemplatedParent
which is EditorContentView
. For the Command
Property, we bind it to the EditorUnfocusedCommand
in EditorContentView
while for the CommandParameter
Property, we should bind it to the the EditorContentView
's BindingContext, which is set as the ContentTemplateDto
Item in MainPage.cs.
Upvotes: 0
Reputation: 26274
When working with ContentView
, it's best to focus on Commands or Properties. If you want to map an Event to a Command, there's extra work involved, but it is doable. However, since the Editor
has an IsFocused
property, we can declare a BindableProperty
called IsEditorFocused
and use a OneWayBindingToSource
to propagate this event from the Editor
through the ContentView
to the Page
.
<!-- CustomEditor.xaml -->
<ContentView
x:Class="Maui.StackOverflow.CustomEditor"
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml">
<ContentView.ControlTemplate>
<ControlTemplate>
<Border>
<Editor IsFocused="{TemplateBinding IsEditorFocused, Mode=OneWayToSource}" Placeholder="CustomEntry" />
</Border>
</ControlTemplate>
</ContentView.ControlTemplate>
</ContentView>
// CustomEditor.xaml.cs
namespace Maui.StackOverflow;
public partial class CustomEditor : ContentView
{
public static readonly BindableProperty IsEditorFocusedProperty = BindableProperty.Create(nameof(IsEditorFocused), typeof(bool), typeof(CustomEditor), false);
public bool IsEditorFocused
{
get => (bool)GetValue(IsFocusedProperty);
set => SetValue(IsFocusedProperty, value);
}
public CustomEditor()
{
InitializeComponent();
}
}
And we can consume it on our page as follows:
<!-- EditorPage.xaml -->
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage
x:Class="Maui.StackOverflow.EditorPage"
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:Maui.StackOverflow"
x:Name="thisPage"
Title="EditorPage"
x:DataType="local:EditorPage">
<VerticalStackLayout>
<Entry Placeholder="Entry1" />
<local:CustomEditor IsEditorFocused="{Binding IsEditorFocused, Mode=OneWayToSource, Source={Reference thisPage}}" />
<Entry Placeholder="Entry2" />
</VerticalStackLayout>
</ContentPage>
// EditorPage.xaml.cs
using System.Diagnostics;
namespace Maui.StackOverflow;
public partial class EditorPage : ContentPage
{
public static readonly BindableProperty IsEditorFocusedProperty = BindableProperty.Create(nameof(IsEditorFocused), typeof(bool), typeof(EditorPage), false);
public bool IsEditorFocused
{
get => (bool)GetValue(IsEditorFocusedProperty);
set => SetValue(IsEditorFocusedProperty, value);
}
public EditorPage()
{
InitializeComponent();
BindingContext = this;
PropertyChanged += (s, e) =>
{
switch (e.PropertyName)
{
case nameof(IsEditorFocused):
Debug.WriteLine($"IsEditorFocused changed to {IsEditorFocused}");
break;
}
};
}
}
Now, I mentioned it was possible to convert Events to Commands. The pattern I follow is I declare a thin wrapper to the thing that I need commands on. In this case, I will declare CustomEditorInner
which is basically a wrapper on Editor
where Focused and Unfocused events are converted to ICommands
. Then, I can rewrite CustomEditor
and my ContentPage
to use it. On the ContentPage
is where I declare the RelayCommand
.
// CustomEditorInner.cs
using System.Windows.Input;
namespace Maui.StackOverflow;
class CustomEditorInner : Editor
{
public static readonly BindableProperty FocusedCommandProperty = BindableProperty.Create(nameof(FocusedCommand), typeof(ICommand), typeof(CustomEditorInner), null);
public ICommand FocusedCommand
{
get => (ICommand)GetValue(FocusedCommandProperty);
set => SetValue(FocusedCommandProperty, value);
}
public static readonly BindableProperty UnfocusedCommandProperty = BindableProperty.Create(nameof(UnfocusedCommand), typeof(ICommand), typeof(CustomEditorInner), null);
public ICommand UnfocusedCommand
{
get => (ICommand)GetValue(UnfocusedCommandProperty);
set => SetValue(UnfocusedCommandProperty, value);
}
public static readonly BindableProperty CommandParameterProperty = BindableProperty.Create(nameof(CommandParameter), typeof(object), typeof(CustomEditorInner), null);
public object CommandParameter
{
get => GetValue(CommandParameterProperty);
set => SetValue(CommandParameterProperty, value);
}
public CustomEditorInner()
{
this.Focused += (s,e) => FocusedCommand?.Execute(CommandParameter);
this.Unfocused += (s,e) => UnfocusedCommand?.Execute(CommandParameter);
}
}
<!-- CustomEditor.xaml -->
<?xml version="1.0" encoding="utf-8" ?>
<ContentView
x:Class="Maui.StackOverflow.CustomEditor"
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:Maui.StackOverflow">
<ContentView.ControlTemplate>
<ControlTemplate>
<Border>
<local:CustomEditorInner
CommandParameter="{TemplateBinding CommandParameter}"
FocusedCommand="{TemplateBinding FocusedCommand}"
Placeholder="CustomEditor"
UnfocusedCommand="{TemplateBinding UnfocusedCommand}" />
</Border>
</ControlTemplate>
</ContentView.ControlTemplate>
</ContentView>
// CustomEditor.xaml.cs
using System.Windows.Input;
namespace Maui.StackOverflow;
public partial class CustomEditor : ContentView
{
public static readonly BindableProperty FocusedCommandProperty = BindableProperty.Create(nameof(FocusedCommand), typeof(ICommand), typeof(CustomEditor), null);
public ICommand FocusedCommand
{
get => (ICommand)GetValue(FocusedCommandProperty);
set => SetValue(FocusedCommandProperty, value);
}
public static readonly BindableProperty UnfocusedCommandProperty = BindableProperty.Create(nameof(UnfocusedCommand), typeof(ICommand), typeof(CustomEditor), null);
public ICommand UnfocusedCommand
{
get => (ICommand)GetValue(UnfocusedCommandProperty);
set => SetValue(UnfocusedCommandProperty, value);
}
public static readonly BindableProperty CommandParameterProperty = BindableProperty.Create(nameof(CommandParameter), typeof(object), typeof(CustomEditor), null);
public object CommandParameter
{
get => GetValue(CommandParameterProperty);
set => SetValue(CommandParameterProperty, value);
}
public CustomEditor()
{
InitializeComponent();
}
}
<!-- EditorPage.xaml -->
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage
x:Class="Maui.StackOverflow.EditorPage"
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:Maui.StackOverflow"
x:Name="thisPage"
Title="EditorPage"
x:DataType="local:EditorPage">
<VerticalStackLayout Spacing="20">
<Entry Placeholder="Entry1" />
<local:CustomEditor
CommandParameter="{Binding Magic}"
FocusedCommand="{Binding MyFocusedCommand}"
UnfocusedCommand="{Binding MyUnfocusedCommand}" />
<Entry Placeholder="Entry2" />
</VerticalStackLayout>
</ContentPage>
// EditorPage.xaml.cs
using System.Diagnostics;
using CommunityToolkit.Mvvm.Input;
namespace Maui.StackOverflow;
public partial class EditorPage : ContentPage
{
[RelayCommand]
void MyFocused(int magic)
{
Debug.WriteLine($"OnFocused magic:{magic}");
}
[RelayCommand]
void MyUnfocused(int magic)
{
Debug.WriteLine($"OnUnfocused magic:{magic}");
}
public int Magic { get; set; } = 42;
public EditorPage()
{
InitializeComponent();
BindingContext = this;
}
}
Upvotes: 1