Search code examples
c#wpffocustreeviewitem

How can I programatically get keyboard focus on a WPF TreeViewItem?


I'm trying to programatically set keyboard focus to a tree view item (under certain conditions). I've tried 2 methods of setting focus, both of which successfully obtain focus on the TreeViewItem, but lose keyboard focus.

The tree view is bound to a view model:

<TreeView Name="solutionsModel" TreeViewItem.Selected="solutionsModel_Selected"
          ItemsSource="{Binding Items, Mode=OneWay}" />

I'm trying to set focus via the TreeViewItem Selected routed event:

private void solutionsModel_Selected(object sender, RoutedEventArgs e)
{
    if (solutionsModel.SelectedItem != null && solutionsModel.SelectedItem is SolutionViewModel)
    {
        if (e.OriginalSource != null && e.OriginalSource is TreeViewItem)
        {
            FocusManager.SetFocusedElement(solutionsModel, e.OriginalSource as TreeViewItem);
        }
    }
}

I'm trying to set focus on the TreeViewItem in the ControlTemplate:

<Style d:IsControlPart="True" TargetType="{x:Type TreeViewItem}">
    <Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type TreeViewItem}">
                <ControlTemplate.Triggers>
                    <Trigger Property="IsSelected" Value="true">
                        <Trigger.Setters>
                            <Setter Property="FocusManager.FocusedElement" Value="{Binding RelativeSource={RelativeSource Self}}"></Setter>
                        </Trigger.Setters>
                    </Trigger>
                    <MultiTrigger>
                        <MultiTrigger.Conditions>
                            <Condition Property="IsSelected" Value="true" />
                            <Condition Property="IsSelectionActive" Value="false" />
                        </MultiTrigger.Conditions>
                        <!--
                        <MultiTrigger.Setters>
                            <Setter Property="FocusManager.FocusedElement" Value="{Binding RelativeSource={RelativeSource Self}}"></Setter>
                        </MultiTrigger.Setters>
                        -->
                    </MultiTrigger>
                </ControlTemplate.Triggers>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

Both of these methods get focus, but lose keyboard focus (TreeViewItem.IsSelectionActive is false). No other element in the window has focus or keyboard focus that I can tell (in a test, I only have one read only textbox on another panel that could get focus). Interestingly, I can get keyboard focus on the (commented out) MultiTrigger where IsSelectionActive is false, but of course that forces keyboard focus on the TreeViewItem at all times.

Is there another way to have a better chance of getting keyboard focus, and what are some conditions where keyboard focus cannot be obtained?


Solution

  • There are probably better ways, but I found a way to do this by extending TreeView and TreeViewItem, to have a separate NeedsFocus property to trigger when to set focus.

    The tree view:

        <local:ModelTreeView x:Name="solutionsModel" ItemsSource="{Binding Items, Mode=OneWay}">
        </local:ModelTreeView>
    

    The updated (partial) control template:

    <Style d:IsControlPart="True" TargetType="{x:Type local:ModelTreeViewItem}">
        <Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
        <Setter Property="NeedsFocus" Value="{Binding NeedsFocus, Mode=TwoWay}" />
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type local:ModelTreeViewItem}">
                    <ControlTemplate.Triggers>
                        <Trigger Property="NeedsFocus" Value="true">
                            <Trigger.Setters>
                                <Setter Property="FocusManager.FocusedElement" Value="{Binding RelativeSource={RelativeSource Self}}"></Setter>
                            </Trigger.Setters>
                        </Trigger>
                    </ControlTemplate.Triggers>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
    

    The extended classes:

    public class ModelTreeView : TreeView
    {
        protected override DependencyObject GetContainerForItemOverride()
        {
            return new ModelTreeViewItem();
        }
    
        protected override bool IsItemItsOwnContainerOverride(object item)
        {
            return item is ModelTreeViewItem;
        }
    }
    
    public class ModelTreeViewItem : TreeViewItem
    {
        ///--------------------------------------------------------------------------------
        /// <summary>This property gets or sets whether the item needs focus.</summary>
        ///--------------------------------------------------------------------------------
        public static readonly DependencyProperty NeedsFocusProperty = DependencyProperty.Register("NeedsFocus", typeof(bool), typeof(ModelTreeViewItem));
        public bool NeedsFocus
        {
            get
            {
                return (bool)GetValue(NeedsFocusProperty);
            }
            set
            {
                SetValue(NeedsFocusProperty, value);
            }
        }
    
        protected override DependencyObject GetContainerForItemOverride()
        {
            return new ModelTreeViewItem();
        }
    
        protected override bool IsItemItsOwnContainerOverride(object item)
        {
            return item is ModelTreeViewItem;
        }
    }
    

    In the view model, NeedsFocus is set to false whenever IsSelected is set.