Search code examples
c#xamlbindingmauilost-focus

MAUI.NET update binding on lost focus alike


introduction:

Using MAUI.NET with MVVM architecture, but MVVM can be sacrifice (this need is so important for the application). Standard way - I have ViewModel with a property with get and set implementing INotifyPropertyChanged and binded this one to the Text property of the Entry in the Views that have BindingContext of the mentioned ViewModel.

need:

Need binding to be executed (set property called) after the user go out of the Entry (click somewhere outside of the control or push TAB on keyboard). Default binding situation is that the binding is executed every keystroke the user do.

example:

If the user enters Entry and make input 1234 the set funciton on binded property in VM is fired 4 times, after every key pushed. I want it to fire just once after user ends with this Entry control.

tries:

Seems to be so basic... I already tried:

  • find equivalent for UpdateSourceTrigger="LostFocus" (like we did in WPF)
  • trying to use UpdateSourceEventName with Completed or Unfocused
  • trying to manually update this binding - I have managed to catch the moment of the user leaving the Entry by checking second parameter of Unfocused event private void Entry_Unfocused(object sender, FocusEventArgs e) { if(!e.IsFocused) { ... but can't find equivalent to BindingOperations.GetBindingExpression (like we did in WPF), moreover on sender as Entry there is only SetBinding while nothing to get it...
  • googled for almost 2 hours...

EDIT: my workaround:

I have managed to do crazy workaround like that:

  • In my ViewModel I have done an additional property like ValueTemp which is binded to Text property of the Entry. The View is binded to this property.
  • The old Value property from ViewModel is binded to Placeholder (must be binded to something to trigger getter, maybe I would do multibinding with converter or some non visible label) and during its getter I am setting the value to ValueTemp
  • During Entry_Unfocused event I am acquiring ValueTemp and setting it to the old Value property. Somehow Completed does not trigger in my case.
  • There are some tricks for proper PropertyChanged and two way synchronization.
  • In reality my code is much more complex, that is why it is hard to put some code here. But in general I use an additional property in VM and bind it for Views purposes, when user finishes editing it I set its value to my proper property so it can be processed later on.

Solution

  • You can write a behavior for this as described in the MAUI documentation.

    In this behavior you declare an attached property for the binding with your ViewModel and subscribe to the Unfocused event of the entry to update the attached property.

    public class EntryLostFocusBehavior : Behavior<Entry>
    {
        public static readonly BindableProperty TextProperty = BindableProperty.CreateAttached(
            "Text",
            typeof( string ),
            typeof( EntryLostFocusBehavior ),
            null,
            BindingMode.TwoWay,
            propertyChanged: TextPropertyChanged );
    
        public static string GetText( Entry entry ) => (string)entry.GetValue( TextProperty );
        public static void SetText( Entry entry, string value ) => entry.SetValue( TextProperty, value );
    
        private static void TextPropertyChanged( BindableObject bindable, object oldValue, object newValue )
        {
            var entry = (Entry)bindable;
            entry.Placeholder = newValue as string;
        }
    
        protected override void OnAttachedTo( Entry bindable )
        {
            base.OnAttachedTo( bindable );
            bindable.Unfocused += Entry_Unfocused;
        }
    
        protected override void OnDetachingFrom( Entry bindable )
        {
            base.OnDetachingFrom( bindable );
            bindable.Unfocused -= Entry_Unfocused;
        }
    
        private void Entry_Unfocused( object sender, FocusEventArgs e )
        {
            var entry = (Entry)sender;
            SetText( entry, entry.Text );
        }
    }
    

    and use this in your view

    <Page ...
          xmlns:b="clr-namespace:MyMauiApp.Behaviors"
          ...>
    
      <Entry b:EntryLostFocusBehavior.Text="{Binding Value1}">
          <Entry.Behaviors>
              <b:EntryLostFocusBehavior />
          <Entry.Behaviors>
      </Entry>
    
      <Entry b:EntryLostFocusBehavior.Text="{Binding Value2}">
          <Entry.Behaviors>
              <b:EntryLostFocusBehavior />
          <Entry.Behaviors>
      </Entry>
    
    </Page>
    

    And thats it for the view/viewmodel. No code-behind, no additional properties.