26

Is there a way to set Focus from one control to another using WPF Triggers?

Like the following example:

<Page
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
  <Grid>  
    <Grid.RowDefinitions>
      <RowDefinition/>
      <RowDefinition/>
      <RowDefinition/>
    </Grid.RowDefinitions>

    <TextBox Name="txtName"></TextBox>    
    <TextBox Grid.Row="1" Name="txtAddress"></TextBox>
    <Button Grid.Row="2" Content="Finish">
        <Button.Triggers>
            <EventTrigger RoutedEvent="Button.Click">

                <!-- Insert cool code here-->  

            </EventTrigger>
        </Button.Triggers>
    </Button>
  </Grid>
</Page>

Is there a way for this EventTrigger to put to focus on the textBox "txtName"?

I am trying to find the way to do something like this using strict MVVM. If this is something that should not be done via the XAML (in MVVM) then that is fine. But I would like to see some kind of documentation as to how it fit in the MVVM pattern doing it outside the XAML.

Dave Clemmer
  • 3,741
  • 12
  • 49
  • 72
Vaccano
  • 78,325
  • 149
  • 468
  • 850

7 Answers7

36

Have you considered using an attached behaviour. They are simple to implement and use AttachedProperty's. Although it still requires code, this code is abstracted away in a class and be reused. They can eliminate the need 'code behind' and are often used with the MVVM pattern.

Try this one and see if it works for you.

public class EventFocusAttachment
{
    public static Control GetElementToFocus(Button button)
    {
        return (Control)button.GetValue(ElementToFocusProperty);
    }

    public static void SetElementToFocus(Button button, Control value)
    {
        button.SetValue(ElementToFocusProperty, value);
    }

    public static readonly DependencyProperty ElementToFocusProperty =
        DependencyProperty.RegisterAttached("ElementToFocus", typeof(Control), 
        typeof(EventFocusAttachment), new UIPropertyMetadata(null, ElementToFocusPropertyChanged));

    public static void ElementToFocusPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
    {
        var button = sender as Button;
        if (button != null)
        {
            button.Click += (s, args) =>
                {
                    Control control = GetElementToFocus(button);
                    if (control != null)
                    {
                        control.Focus();
                    }
                };
        }
    }
}

And then in your XAML do something like...

<Button 
    Content="Click Me!" 
    local:EventFocusAttachment.ElementToFocus="{Binding ElementName=textBox}" 
    />
<TextBox x:Name="textBox" />
Ian Oakes
  • 10,153
  • 6
  • 36
  • 47
  • I like this. I am going to try it out. – Vaccano Feb 06 '10 at 20:02
  • That worked great. While it uses code, it is not the code behind the window so that is ok by me. Thanks! – Vaccano Feb 06 '10 at 23:50
  • This is the only working solution that I was able to use with DataTemplate and to focus a textbox from ViewModel. Thank you. – Alexandru Dicu May 14 '13 at 13:05
  • This doesn't seem to work if my TextBox was initially hidden and my DataTemplate makes it visible through a Trigger and then tries to set focus to it. – dotNET Nov 13 '16 at 09:37
17

I'm not near visual studio so I can't actually try this right now, but off the top of my head, you should be able to do something like this:

FocusManager.FocusedElement="{Binding ElementName=txtName}">

Edit:

There is a followup question (asked more recently) about this here: How to set autofocus only in xaml? which contains this method, and a few different ideas on how to use it.

caesay
  • 16,932
  • 15
  • 95
  • 160
  • I like this, but I need a bit more code to see how it could work. I could not find a way to set this using an EventTrigger. (A storyboard can't do that from what I can tell.) – Vaccano Feb 05 '10 at 16:33
  • 1
    @Vaccano - I'm a lot late to the conversation, but if you're strickly looking to change focus after the on click event as you had originally posted, you don't need the EventTrigger at all, you can just set the focused element property that caesay pointed out...once the button is clicked it'll change focus to the bound element. – Aaj Apr 30 '12 at 14:14
  • It works here exactly as OP asked. Nice job, caesay! – Click Ok Apr 18 '16 at 22:57
  • This apparently does work inside a DataTemplate where I first set TextBox's Visibility to True and then try setting focus to it with the above method. – dotNET Nov 13 '16 at 09:30
10

You could also use a WPF Behavior...

    public class FocusElementAfterClickBehavior : Behavior<ButtonBase>
{
    private ButtonBase _AssociatedButton;

    protected override void OnAttached()
    {
        _AssociatedButton = AssociatedObject;

        _AssociatedButton.Click += AssociatedButtonClick;
    }

    protected override void OnDetaching()
    {
        _AssociatedButton.Click -= AssociatedButtonClick;
    }

    void AssociatedButtonClick(object sender, RoutedEventArgs e)
    {
        Keyboard.Focus(FocusElement);
    }

    public Control FocusElement
    {
        get { return (Control)GetValue(FocusElementProperty); }
        set { SetValue(FocusElementProperty, value); }
    }

    // Using a DependencyProperty as the backing store for FocusElement.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty FocusElementProperty =
        DependencyProperty.Register("FocusElement", typeof(Control), typeof(FocusElementAfterClickBehavior), new UIPropertyMetadata());
}

Here is the XAML to use the behavior.

Include namespaces:

xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity" 
xmlns:local="clr-namespace:WpfApplication1"

Attach WPF Behavior to button and bind element you want to set focus to:

<Button Content="Focus" Width="75">
    <i:Interaction.Behaviors>
        <local:FocusElementAfterClickBehavior FocusElement="{Binding ElementName=CheckBoxComboBox, Mode=OneWay}"/>
    </i:Interaction.Behaviors>
</Button>
<ComboBox x:Name="CheckBoxComboBox" HorizontalAlignment="Center" VerticalAlignment="Center" Width="120" Grid.Row="1"/>

So this way you have no code behind and it is reusable on any control that inherits from ButtonBase.

Hope this helps someone.

Dmitrii Dovgopolyi
  • 6,231
  • 2
  • 27
  • 44
droidalmatter
  • 324
  • 4
  • 10
  • this is better than attached property in the sense that it has better Blend support. If however you're not using blend then this would just require a helluva lot more xaml code. – Elad Katz Mar 02 '11 at 03:30
  • Don't forget to add a reference to System.Windows.Interactivity too. – Lee Oades Apr 21 '11 at 10:15
4

you need a TriggerAction to invoke the Focus() method on the desired control.

public class SetFocusTrigger : TargetedTriggerAction<Control>
{
 protected override void Invoke(object parameter)
 {
    if (Target == null) return;

    Target.Focus();
 }
}

To have the focus set to a Control , you place a Triggers collection after your LayoutRoot (or any control really), select the event as the trigger, and select the SetFocusTrigger as the class to run. In the SetFocusTrigger declaration, you put the name of the control that you want to receive the focus by using the TargetName property.

<Button x:Name="LayoutRoot" >
<i:Interaction.Triggers>
    <i:EventTrigger EventName="Clicked">
        <local:SetFocusTrigger TargetName="StartHere"/>
    </i:EventTrigger>
</i:Interaction.Triggers>

<TextBox x:Name="StartHere"/>
</Button>
ar.gorgin
  • 4,765
  • 12
  • 61
  • 100
  • This is a great solution - thank you! Just one minor error in the XAML... the `EventName` should be "Click" instead of "Clicked" – Mash Oct 29 '18 at 21:44
  • Nowadays one could use a null propagation operator to turn the Invoke method into a one-liner – maets Oct 02 '19 at 18:37
1

Is this what you want?

    <TextBox Name="txtName"></TextBox>
    <TextBox Grid.Row="1" Name="txtAddress"></TextBox>
    <Button Grid.Row="2" Content="Finish">
        <Button.Style>
            <Style TargetType="{x:Type Button}">
                <EventSetter Event="Click" Handler="MoveFocusOnClick" />
            </Style>
        </Button.Style>
        <!--<Button.Triggers>
            <EventTrigger RoutedEvent="Button.Click">
            </EventTrigger>
        </Button.Triggers>-->
    </Button>

c#:

    public void MoveFocusOnClick(object sender, RoutedEventArgs e)
    {
        Keyboard.Focus(txtName); // Or your own logic
    }
mg007
  • 2,888
  • 24
  • 29
  • Yeah, if I am willing to use the code behind then there are several ways to accomplish this. I was wondering if there is a way to do it without code behind. – Vaccano Feb 05 '10 at 16:34
1

This is the same as Ian Oakes' solution, but I made a couple minor changes.

  1. The button type can be more general, namely ButtonBase, to handle more cases, such as ToggleButton.
  2. The target type can also be more general, namely UIElement. Technically, this could be IInputElement, I suppose.
  3. Made the event handler static so that it won't generate a runtime closure every time this is used.
  4. [edit: 2019] Updated to use null-conditional and C#7 expression body syntax.

Many thanks to Ian.


public sealed class EventFocusAttachment
{
    public static UIElement GetTarget(ButtonBase b) => (UIElement)b.GetValue(TargetProperty);

    public static void SetTarget(ButtonBase b, UIElement tgt) => b.SetValue(TargetProperty, tgt);

    public static readonly DependencyProperty TargetProperty = DependencyProperty.RegisterAttached(
            "Target",
            typeof(UIElement),
            typeof(EventFocusAttachment),
            new UIPropertyMetadata(null, (b, _) => (b as ButtonBase)?.AddHandler(
                ButtonBase.ClickEvent,
                new RoutedEventHandler((bb, __) => GetTarget((ButtonBase)bb)?.Focus()))));
};

Usage is basically the same as above:

<ToggleButton z:EventFocusAttachment.Target="{Binding RelativeSource={RelativeSource Self}}" />

Note that the event can target/focus the originating button itself.

Glenn Slayden
  • 17,543
  • 3
  • 114
  • 108
0

Look if you're using any Dispatcher then it would be helpful but this is a short trick I used in my code. Just use the Loaded event in your XAML and make a new handler. In that handler paste this code and bingo! you're ready to go

Your loaded event with some arguments here...

{
    Dispatcher.CurrentDispatcher.BeginInvoke(DispatcherPriority.Normal, new System.Action(() => {
        The element to be focused goes here......
    }));
}

PS: It requires Windows. Threading if you didn't know ;)

Barry
  • 3,303
  • 7
  • 23
  • 42