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.
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;
}
}