7

In Xamarin.Forms, how do you designate a button as the default button for a page?

For example, on UWP the click handler for a DefaultButton should fire when the user presses the Enter key while the page has focus.

Edward Brey
  • 40,302
  • 20
  • 199
  • 253
  • 2
    Since a platform-agnostic mechanism for specifying the default button doesn't seem to exist out-of-the-box in Xamarin.Forms, I submitted a [suggestion](https://xamarin.uservoice.com/forums/144858-xamarin-platform-suggestions/suggestions/19109530-specify-a-default-button-in-xamarin-forms) to that effect. – Edward Brey May 01 '17 at 18:29
  • If you're using an entry control, the `Completed` event handler will fire when pressing enter. Note that I've been using this for iOS and Android. Not sure about other uses. – Timothy James May 02 '17 at 19:43

4 Answers4

5

Assuming you are trying to replicate the behavior as noted in your first comment to Alessandro, you want to declare the Completed field of your Entry control to use the same event handler as your button.

For example, in XAML:

<Entry Placeholder="Enter PIN Here"
       Completed="DefaultButton_Clicked"/>

<Button Text="OK"
        Clicked="DefaultButton_Clicked"/>

Then in your code behind:

void DefaultButton_Clicked(object sender, EventArgs e)
{
    //Do stuff
}

If you are looking to do this all in code behind like you have answered, I suggest doing it this way so you are able to unsubscribe your events. You'll find working with anonymous functions to be more of a pain. You should subscribe and unsubscribe your events in OnAppearing/OnDisappearing.

void DefaultButton_Clicked(object sender, EventArgs e)
{
    if (model.MyCommand.CanExecute(null))
            model.MyCommand.Execute(null);
}

protected override void OnAppearing()
{
    base.OnAppearing();
    foreach (var child in ((StackLayout)Content).Children)
    {
        if (child is Entry entry)
            entry.Completed += DefaultButton_Clicked;
    }
}

protected override void OnDisappearing()
{
    base.OnDisappearing();
    foreach (var child in ((StackLayout)Content).Children)
    {
        if (child is Entry entry)
            entry.Completed -= DefaultButton_Clicked;
    }
}
Timothy James
  • 1,062
  • 13
  • 25
  • Is there a similar workaround for when the default button uses a Command with a CanExecute filter? – Edward Brey May 03 '17 at 17:26
  • Not directly I don't think. Check this out: http://stackoverflow.com/questions/4897775/wpf-binding-ui-events-to-commands-in-viewmodel – Timothy James May 03 '17 at 18:50
0

I ended up writing code that wires up each Entry to conditionally invoke the default button's command. In my case, each Entry is in a single StackLayout at the page root, so the page's constructor has code like this:

foreach (var child in ((StackLayout)Content).Children)
    if (child is Entry entry)
        entry.Completed += delegate {
            if (model.MyCommand.CanExecute(null))
                model.MyCommand.Execute(null);
        };
Edward Brey
  • 40,302
  • 20
  • 199
  • 253
0

We ended up implementing an entry behavior for the completed event. We wanted to bind it from .xaml and to be able to both navigate between fields and submit on the last field. It took some research, so I'm sharing here in the hopes it will be useful for someone else, too:

            <Entry TabIndex="0" Placeholder="{ui:Resource ID_STR_USERNAME}" Text="{Binding Username, Mode=TwoWay}" Style="{StaticResource EntryText}">
                <Entry.Behaviors>
                    <behaviors:EntryCompletedBehavior />
                </Entry.Behaviors>
            </Entry>
            <Entry TabIndex="1" Placeholder="{ui:Resource ID_STR_PASSWORD_HINT}" IsPassword="True" Text="{Binding Password.Value, Mode=TwoWay}" Style="{StaticResource EntryText}">
                <Entry.Behaviors>
                    <behaviors:EntryTextChangeCommandBehavior Command="{Binding Password.ValidateCommand}" />
                    <behaviors:EntryCompletedBehavior Command="{Binding LoginCommand, Mode=TwoWay}" CommandParameter="{Binding Password}" />
                </Entry.Behaviors>
            </Entry>

and the behavior:

public class EntryCompletedBehavior : Behavior<Entry>
{
    public static readonly BindableProperty CommandProperty = BindableProperty.Create(nameof(Command), typeof(ICommand), typeof(EntryCompletedBehavior), null);
    public ICommand Command
    {
        get { return (ICommand)GetValue(CommandProperty); }
        set { SetValue(CommandProperty, value); }
    }

    public static readonly BindableProperty CommandParameterProperty = BindableProperty.Create(nameof(CommandParameter), typeof(object), typeof(EntryCompletedBehavior), null);
    public object CommandParameter
    {
        get { return GetValue(CommandParameterProperty); }
        set { SetValue(CommandParameterProperty, value); }
    }

    protected override void OnAttachedTo(Entry bindable)
    {
        base.OnAttachedTo(bindable);

        if(bindable.BindingContext != null)
            BindingContext = bindable.BindingContext;

        bindable.BindingContextChanged += Bindable_BindingContextChanged;

        bindable.Completed += Bindable_Completed;
    }
    protected override void OnDetachingFrom(Entry bindable)
    {
        base.OnDetachingFrom(bindable);

        bindable.BindingContextChanged -= Bindable_BindingContextChanged;

        bindable.Completed -= Bindable_Completed;
    }

    private void Bindable_Completed(object sender, System.EventArgs e)
    {
        if(sender is Entry entry)
        {
            var ix = entry.TabIndex;
            int allIndexesOnPage;
            var coll = entry.GetTabIndexesOnParentPage(out allIndexesOnPage);
            foreach(var item in coll)
            {
                if(ix < item.Key)
                {
                    item.Value.First().Focus();
                    return;
                }
            }
        }

        if(Command == null)
            return;

        if(Command.CanExecute(CommandParameter))
        {
            Command.Execute(CommandParameter);
        }
    }

    void Bindable_BindingContextChanged(object sender, EventArgs e)
    {
        base.OnBindingContextChanged();

        if(!(sender is BindableObject bindable))
            return;

        BindingContext = bindable.BindingContext;
    }
}

As an added bonus, the behavior can be used on its own, just to navigate between tab-indexed fields.

Based on this post: https://forums.xamarin.com/discussion/124714/how-to-fire-a-complete-event-from-viewmodel

-1

You have not to say "In Xamarin.Forms, how do you designate a button as the default button for a page?" because Xamarin.Forms visualize only Platform Specific controls.

You should ask "In Android (or iOS) how do you designate a button as the default button for a page?".

I think it is the focused button, so you can try something like

Button b = new Button {Text = "This is the focused button"};
b.Focus();

But I am not sure of this.

Take a look here in Xamarin forum

Alessandro Caliaro
  • 5,623
  • 7
  • 27
  • 52
  • This question does not relate to automatically changing focus. For example, consider the UWP change PIN dialog in Windows 10. The focus rules are all standard (a clicked control gets focus, Tab advances focus, etc.). From any of the three text boxes, pressing Enter causes OK's click handler to be fired, since it is the default button, but focus remains unchanged. Ideally, Xamarin.Forms would provide a platform agnostic way to designate a default button. Since it doesn't appear to, I'd like to use custom renders or something else that would provide the same effect. – Edward Brey May 01 '17 at 18:26
  • @EdwardBrey, you ask a question, answer to your question, down vote an answer and comment the answer. Excellent – Alessandro Caliaro May 01 '17 at 20:56
  • If I had an answer to my question, I'd post it as an answer. Unfortunately, I don't. Setting focus on the button isn't the answer, because the point of a default button is that it works when it *doesn't* have focus. A custom renderer may be part of a solution, but I couldn't get it to work: `PageRenderer.Control` was always null. – Edward Brey May 01 '17 at 21:58