4

Okay, I know this title looks like it has answers all around SO, but none of the answers found here worked for me. So, here goes.

I have this layout:

<Window>
  <Grid>
    <DockPanel>
      <TabControl>
        <TabItem>
          <Page x:Name="p">
            <Grid x:Name="g2">
              <TabControl x:Name="tc">
                <TabItem x:Name="ti1">
                  <StackPanel x:Name="sp">
                    <C:TextBox x:Name="txt"/>
                  </StackPanel>
                </TabItem>
                <TabItem x:Name="ti2">
                  <C:DataGrid x:Name="dg"/>
                </TabItem>
              </TabControl>
            </Grid>
          </Page>
        </TabItem>
      </TabControl>
    </DockPanel>
  </Grid>
</Window>

Now, my goal is to put focus on txt TextBox when ti1 TabItem gets selected and on dg DataGrid when ti2 TabItem gets selected. Also, I would really love to set this in XAML.

Note: I can only use controls that are named here, so up until Page control.

What have I tried so far:

  • setting FocusManager.FocusedElement="{Binding ElementName=txt}" on all of the parent controls in the parent tree of the txt Control (up until Page).
  • setting FocusManager.FocusedElement="{Binding RelativeSource={RelativeSource Self}}" on the txt and dg controls.
  • setting focus in code through TabControl's SelectionChanged event:

    • if ( ti1.IsSelected ) { tc.UpdateLayout(); FocusManager.SetFocusedElement( sp, txt ); }
    • and
    • if ( ti1.IsSelected ) { tc.UpdateLayout(); txt.Focus(); }

TextBox and DataGrid controls are created like UserControls, but are actually classes that inherit TextBox and DataGrid, like this:

<TextBox ... </TextBox>

and

public partial class TextBox : System.Windows.Controls.TextBox

As I said, XAML solution is desired, but I will also settle for a code one, if former is not possible.

Adder
  • 165
  • 1
  • 14
  • Would be nice to see outcome of each attempt (aka what happens). Since you tried what is the most obvious, it would be nice to have [mcve](https://stackoverflow.com/help/mcve) to just copy and play. – Sinatr Oct 09 '17 at 08:03
  • @Sinatr I'll try later, when there is more time, but for now I can say that all solutions, except `SetFocusedElement` one, had no effect, whatsoever. But using `FocusManager` resulted in visible `Caret` within `TextBox`, but focus was not there, I haven't been able to type. And that worked only for the initial opening of the page, when I selected some other tab, and than went back to the first one, even the `Caret` wasn't visible. – Adder Oct 09 '17 at 08:08
  • @Adder, please do not substantially edit other people's answers to include additional information. In your specific case I would recommend that you up-vote the answer for being helpful (if you feel like it) and post your own answer with additional information and accept it. See: https://stackoverflow.com/help/self-answer – Alex Oct 09 '17 at 11:43
  • @Alex, if I do that, people will see that his answer is the accepted one and use it, only to find it is not working. Why would I want that? I want people to see how to solve the problem, but also give credit to Keyur PATEL for helping me. – Adder Oct 09 '17 at 11:55
  • 1
    @Alex, sorry, I've read 'up-vote' but it got translated in my head as 'accept it' :). Never mind, I will do what you said. – Adder Oct 09 '17 at 11:57

3 Answers3

3

Okay, the Dispatcher part from Keyur PATEL's answer was the solution for me, although not the complete one. The answer for me was to update TabControl layout with the Dispatcher and than invoke the Focus Dispatcher. So, the complete answer for me was:

Dispatcher.BeginInvoke( (Action) (() => tc.UpdateLayout()) );
Dispatcher.BeginInvoke( (Action) (() => txt.Focus() ) );

or you can use just Invoke instead, for the UI thread to wait for your Action.

And for the reason why I had to use a Dispatcher, it is because I used it to change the selected tab in the first place. That is my best guess, at least.

Adder
  • 165
  • 1
  • 14
1

You could try something similar to your code-behind solution but with this variation:

<TabControl x:Name="tc" SelectionChanged="tc_selectionChanged">

and in code behind:

InitializeComponent();

//if you know which control to focus by default when page is first loaded
Dispatcher.BeginInvoke(new Action(() => { txt.Focus(); })); 

and

private void tc_selectionChanged(object sender, SelectionChangedEventArgs e)
{
    if (ti1.IsSelected)
    {
        txt.Focus();
    }
    else if (ti2.IsSelected)
    {
        dg.Focus();
    }
}

I tried this exact setup on my own WPF application so I know it works.

Helpful links:

WPF TabControl On SelectionChanged, set focus to a text field

and

How to focus control in the tabItem ın WPF (although for me it worked without UpdateLayout())

Keyur PATEL
  • 2,299
  • 1
  • 15
  • 41
  • This is not working for me. The `Caret` is visible, but the focus is not on the `TextBox`. [image of the textbox](https://postimg.org/image/41j8drx9vv/) – Adder Oct 09 '17 at 09:06
  • @Adder Interesting, I had the exact same problem (frozen Caret is visible, but typing something shows that the focus isn't on the textbox) but I got it working with the line `Dispatcher.BeginInvoke(new Action(() => { txt.Focus(); }));`. Can you verify you're calling it right after `InitializeComponent();` in code behind? – Keyur PATEL Oct 09 '17 at 09:46
  • Possibly try `FocusManager.SetFocusedElement(txt, txt)` instead of `txt.Focus()` – grek40 Oct 09 '17 at 11:11
0

Found solution using extension attribute

public static class TabItemExtention
{
    public static bool GetIsSelected(DependencyObject obj)
    {
        return (bool)obj.GetValue(IsSelectedProperty);
    }

    public static void SetIsSelected(DependencyObject obj, bool value)
    {
        obj.SetValue(IsSelectedProperty, value);
    }

    public static readonly DependencyProperty IsSelectedProperty =
        DependencyProperty.RegisterAttached(
            "IsSelected", typeof(bool), typeof(TabItemExtention),
            new UIPropertyMetadata(false, OnIsSelectedPropertyChanged));

    private static void OnIsSelectedPropertyChanged(
        DependencyObject d,
        DependencyPropertyChangedEventArgs e)
    {
        var uie = (UIElement)d;
        var tabItem = uie as TabItem;
        if (tabItem != null)
            tabItem.IsSelected = (bool)e.NewValue;
        if ((bool)e.NewValue)
        {
            uie.UpdateLayout();
        }
    }
}

XAML:

ctrl:TabItemExtention.IsSelected="{Binding IsTabNewCustomsDelaySelected}"