tags:

views:

169

answers:

2

I have a UserControl that incorporates a textbox. I want to set the keyboardfocus to this textbox programmatically when the user clicks a button. I tried this:

private void Button_Click(object sender,EventArgs e)
{
  Keyboard.Focus(MyUserControl);
}

no luck. Then I exposed the Textbox in the UserControl though a property of type TextBox

private void Button_Click(object sender,EventArgs e)
{
  Keyboard.Focus(MyUserControl.TextBox);
}

again no luck. Finally I made an eventhandler in the UserControl to handle the GotKeyboardFocus event, calling the Keyboard.Focus method on the textbox inside it.

Again no luck?!

How to do this??

EDIT: The problem is not related to UserControls. It is an issue when you try to pass focus to an other UIElement in a Click or MouseDownHandler. The XAML en code behind below tell their own story: the focus does pass to the textbox but is stolen back by the listbox.

<Window x:Class="FocusSpike.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="Window1" Height="300" Width="300">
    <StackPanel>
        <TextBox Name="FocusedTextBox" Height="30">
            <TextBox.Style>
                <Style TargetType="TextBox">
                    <Setter Property="Text" Value="I am unfocused..."/>
                    <Setter Property="Opacity" Value=".3"/>
                    <Style.Triggers>
                        <Trigger Property="IsKeyboardFocused" Value="True">
                            <Trigger.EnterActions>
                                <BeginStoryboard>
                                    <Storyboard AutoReverse="True">
                                        <DoubleAnimation Storyboard.TargetProperty="FontSize" To="20"/>
                                    </Storyboard>
                                </BeginStoryboard>
                            </Trigger.EnterActions>
                            <Setter Property="Text" Value="I am focused!"/>
                            <Setter Property="Opacity" Value="1"/>
                        </Trigger>
                    </Style.Triggers>
                </Style>
            </TextBox.Style>
        </TextBox>
        <Button>Click to steal focus.</Button>
        <ListBox>
            <ListBoxItem GotFocus="Listbox_GotFocus">
                <Label MouseDown="ListBoxItem_MouseDown">
                    Click to restore focus
                </Label>
            </ListBoxItem>
        </ListBox>
    </StackPanel>
</Window>

using System.Windows;
using System.Windows.Input;
namespace FocusSpike
{
    public partial class Window1 : Window
    {
        public Window1()
        {
            InitializeComponent();
            FocusedTextBox.Focus();
        }

        private void ListBoxItem_MouseDown(object sender, MouseButtonEventArgs e)
        {
            Keyboard.Focus(FocusedTextBox);//This does not work, remove it!
        }

        private void Listbox_GotFocus(object sender, RoutedEventArgs e)
        {
            //Keyboard.Focus(FocusedTextBox);//uncomment to restore focus!
        }
    }
}
+3  A: 
MyUserControl.FindControl("TextBox1").Focus();

Failing that...

(TextBox)(MyUserControl.FindControl("TextBox1")).Focus();
tsilb
or MyUserControl.TextBox.Focus()
Zenuka
Yeah, just that sometimes it doesn't see them...
tsilb
Thanks for the reply. It does not work though;-) I am beginning to suspect that the focus is set on the textbox but immediately stolen from it. I will investigate further.
Dabblernl
You could throw in a thread.sleep or put the focus in an event that fires later on in the lifecycle. Failing that, a Timer would be a (ghetto but effective) solution.
tsilb
`Thread.Sleep` and `Timer` would both be unreliable because they would depend on the speed of the computer and what else was going on at the time. Dispatcher.BeginInvoke does not have this problem. See my answer and the comments thereunto for more details.
Ray Burns
+2  A: 

You can get your .Focus() call to stick by using Dispatcher.BeginInvoke to delay the actual Focus() call until the Click or MouseDown event handler has completed and the calling code has finished working.

Here is how to do it:

private void ListBoxItem_MouseDown(object sender, MouseButtonEventArgs e)
{
  //Keyboard.Focus(FocusedTextBox);  // May be necessary to uncomment this in some scenarios

  Dispatcher.BeginInvoke(DispatcherPriority.Input, new Action(() =>
  {
    Keyboard.Focus(FocusedTextBox);
  });
}

Here is the sequence of events:

  1. WPF calls your MouseDown handler
  2. Your MouseDown handler asks the WPF Dispatcher to call it back when all DispatcherPriority.Input (and higher) invocations have been completed
  3. Your MouseDown handler returns and the calling code completes its processing
  4. Any other input events in the queue are processed
  5. Your Action is called, and your code calls Keyboard.Focus(FocusedTextBox) to set the focus.

The key point here is that KeyboardFocus.Focus() is not called until after your handler has returned and WPF has completed all pending input processing.

It is possible that the KeyboardFocus.Focus() call itself may trigger ListBox to steal focus from you. I haven't checked this. If that is the case, the solution would be to call it twice: Once from the MouseDown handler and again from the Action scheduled by the MouseDown handler. So if it doesn't work with the first line commented out, uncomment it and try again.

Ray Burns
I don't understand: it is an asynchronous call and even though it starts AFTER the method that invokes it has completed. Do you mean by "delay" buying time and hoping to get the timing right (getting different results between computers based on their speed), or do you mean that the BeginInvoked method will reliably not be executed untill after the calling method has finished? That would be strange: what if I in a malignant mood put in a thread.sleep right after the BeginInvoke?
Dabblernl
This is not related to timing or computer speed. The BeginInvoke call I gave executes the KeyBoard.Focus() call in a deterministic sequence. Specifically, it executes it after: 1. The current invocation has been completed, 2. All invocations above DispatcherPriority.Input have been completed and 2. All *currently scheduled* invocations at DispatcherPriority.Input have been completed. At the precise time this condition is satisfied (whenever it is), your `KeyboardFocus(FocusedTextBox)` code will be executed.
Ray Burns
`BeginInvoke` will really not be executed until after the calling method has finished. If you put a `Thread.Sleep()` right after the `BeginInvoke`, the focus will not change until the end of the sleep interval. Of course `Thread.Sleep()` would lock up the rest of the UI as well.
Ray Burns