8
votes

How can I in code define the logical focus of a container, or focused child, WITHOUT giving it keyboard focus ?

I just want to control which child will get the focus when the control will get focus through Tab key or clicking on part of the container that does not hit a child, but not give it (or steal) actual focus if it doesn't already have it.

And I also want that selecting a specific child through a keyboard gesture or clicking on it with the mouse is still possible.

I understand the difference in WPF between Keyboard Focus and Logical Focus, and the fact that for an element, having Keyboard Focus implies also having logical focus, but having logical focus does not imply that the element have keyboard focus.

I also understand attached property FocusManager.FocusedElement defines which element has logical focus in the visual tree starting at the element defining this property.

I’ve realized that this property is not used only when FocusManager.IsFocusScope is set to true, but also for containers such as GroupBox.

I’ve made many attempts, tons of searches and reading on WPF focus topic, but with no success so far, I don't understand what I'm missing:

  • Calling FocusManager.SetFocusedElement also give keyboard focus, and it I temporarily change the property Focusable of my child element to false just before, it only works the very first time when no child had focus before, but not after a child got focus
  • Handling events GotKeyboardFocus and PreviewGotKeyboardFocus at element or container to override initial focused element doesn’t work either, since I cannot tell whether the focus was obtained through the mouse or the keyboard, and whether focus got directly set to a child element or indirectly through the container.
  • An example to illustrate what I’m trying to achieve: I’ve a simple group of RadioButtons, and I want to control dynamically in code which option will get focus when user will “tab” to move focus to this GroupBox (typically the option that has isChecked=true).

    <GroupBox Header="Options" Name="myGroupBox"
              KeyboardNavigation.TabNavigation="Once" 
              KeyboardNavigation. DirectionalNavigation="Cycle" >
        <StackPanel>
            <RadioButton GroupName="g1" Name="opt1" Content="Option _1"/>
            <RadioButton GroupName="g1" Name="opt2" Content="Option _2"/>
            <RadioButton GroupName="g1" Name="opt3" Content="Option _3"/>
        </StackPanel>
    </GroupBox>
    

    A final comment, I know how to implement a dynamic list of options using a ListBox, binding selectedItem of the list to a property of the data context, and then through styles and templates on ListBoxItem bind IsChecked property of the RadioButton in item template to IsSelcted property of its parent ListBoxItem, and it works, but for my specific case, I need to keep my RadioButtons directly bound to properties of my data context, I can’t bind them to IsSelected property of the list at the same time.

    2
    try to set FocusManager.IsFocusScope = true for GroupBoxPavel Voronin
    Great question, I'm searching for this answer myself...Doug

    2 Answers

    1
    votes

    I know this is an old question but hopefully this answer will help others with a similar problem.

    If I understand the problem correctly, you can achieve the desired behaviour by setting FocusManager.IsFocusScope="True" on the GroupBox, and hook up an event handler for the RadioButton.Checked event that sets the Logical Focus to the sender of the event:

    Xaml:

            <GroupBox Header="Options" Name="myGroupBox"
                  FocusManager.IsFocusScope="True"
                  RadioButton.Checked="MyGroupBox_OnChecked"
                  KeyboardNavigation.TabNavigation="Once" 
                  KeyboardNavigation.DirectionalNavigation="Cycle">
            <StackPanel>
                <RadioButton GroupName="g1" Name="opt1" Content="Option _1"/>
                <RadioButton GroupName="g1" Name="opt2" Content="Option _2"/>
                <RadioButton GroupName="g1" Name="opt3" Content="Option _3"/>
            </StackPanel>
        </GroupBox>
    

    Code behind:

        private void MyGroupBox_OnChecked(object sender, RoutedEventArgs e)
        {
            var radioB = sender as RadioButton;
            if (radioB != null)
                FocusManager.SetFocusedElement(myGroupBox, radioB);
        }
    
    0
    votes

    As the Document remarked, FocusManager.SetFocusedElement will give the specified element logical focus in the specified focus scope and will attempt to give the element keyboard focus.

    I encountered the same problem, and I feel this attempt to give the element keyboard focus is rather not predictable. In some cases, the element which gets the logical focus will also get the keyboard focus. But in some other cases, it won't. I have not yet figured out the exact mechanism behind.

    If the official documents say so, I reckon there may not be an "official" way to implement what you want. For now I am doing this by just invoking Keyboard.Focus upon the element you would like to maintain the keyboard focus after setting the logical focus.

    An illustration of what I am explaining is as follows:

    private void SomeMember(){
      FocusManager.SetFocusedElement(focusScope, logicalFocus);
      Keyboard.Focus(controlMaintainingKeyboardFocus);
    }