3

In a control I'm currently working on, when an item is focused, the default focus rectangle spans the entire row and all of its visible child items. I know how to hide that. But I still want such a focus indicator when the item has the keyboard focus. I've read about the IsKeyboardFocused property but that is true also when the item was clicked by the mouse. So I guess I need to use the FocusVisualStyle somehow. But I can't figure out how.

Here's what the default focus looks like:

enter image description here

And this is what it should like:

enter image description here

Here's my XAML code for the control template:

<Border ...>
    <ContentPresenter FocusManager.IsFocusScope="True"
        Content="{TemplateBinding HeaderedContentControl.Header}"
        ContentTemplate="{TemplateBinding HeaderedContentControl.HeaderTemplate}"
        ContentStringFormat="{TemplateBinding HeaderedItemsControl.HeaderStringFormat}"
        ContentSource="Header"
        Name="PART_Header" .../>
</Border>
<!-- Additional border glare inside the item -->
<Border BorderThickness="1" BorderBrush="#80ffffff" Margin="1"
    SnapsToDevicePixels="True" CornerRadius="2"/>
<!-- Focus rectangle inside the item -->
<Rectangle StrokeDashArray="1 2" StrokeThickness="1" Stroke="Black"
    SnapsToDevicePixels="True" Margin="2"
    Visibility="Hidden" Name="FocusRectangle"
    FocusVisualStyle="{StaticResource FocusStyle}"/>

There is already a focus rectangle in my XAML that is invisible by default. With FocusVisualStyle or anything, it shall be made visible. But I didn't manage to do that. It's either visible at any focus, or never.

ygoe
  • 18,655
  • 23
  • 113
  • 210

1 Answers1

0

I have found a workaround for the issue. It looks the same to me, but I'm not completely sure whether it's the correct way to do it. I'm using the FocusRectangle from above and care for showing and hiding it myself.

Here's the trigger that manages the focus rectangle visibility:

<!-- Show the focus rectangle when the item is focused -->
<MultiTrigger>
  <MultiTrigger.Conditions>
    <Condition Property="Controls:TreeViewExItem.IsKeyboardMode" Value="True"/>
    <Condition Property="Controls:TreeViewExItem.IsFocused" Value="True"/>
  </MultiTrigger.Conditions>
  <Setter TargetName="FocusRectangle" Property="Visibility" Value="Visible"/>
</MultiTrigger>

Then I have added a new property to the TreeViewExItem that indicates whether the last input came from the mouse or the keyboard. This could possibly be extended to touch or stylus, but I don't have such devices to test.

public static DependencyProperty IsKeyboardModeProperty =
    DependencyProperty.Register(
        "IsKeyboardMode",
        typeof(bool),
        typeof(TreeViewExItem),
        new FrameworkPropertyMetadata(false, null));

public bool IsKeyboardMode
{
    get
    {
        return (bool) GetValue(IsKeyboardModeProperty);
    }
    set
    {
        SetValue(IsKeyboardModeProperty, value);
    }
}

This property is passed to each item from the parent control through binding:

<!-- Pass on the TreeViewEx' IsKeyboardMode value to each item because
  we couldn't access it otherwise in the triggers -->
<Setter Property="IsKeyboardMode"
  Value="{Binding (Controls:TreeViewEx.IsKeyboardMode),
    RelativeSource={RelativeSource
      AncestorType={x:Type Controls:TreeViewEx}}, Mode=OneWay}" />

The same IsKeyboardMode property is added to the TreeViewEx parent control and here comes my magic:

protected override void OnPreviewKeyDown(KeyEventArgs e)
{
    base.OnPreviewKeyDown(e);
    if (!IsKeyboardMode)
    {
        IsKeyboardMode = true;
        //Debug.WriteLine("Changing to keyboard mode from PreviewKeyDown");
    }
}

protected override void OnPreviewKeyUp(KeyEventArgs e)
{
    base.OnPreviewKeyDown(e);
    if (!IsKeyboardMode)
    {
        IsKeyboardMode = true;
        //Debug.WriteLine("Changing to keyboard mode from PreviewKeyUp");
    }
}

protected override void OnPreviewMouseDown(MouseButtonEventArgs e)
{
    base.OnPreviewMouseDown(e);
    if (IsKeyboardMode)
    {
        IsKeyboardMode = false;
        //Debug.WriteLine("Changing to mouse mode");
    }
}

This reacts on the preview events of keyboard and mouse to set the appropriate input mode. Only if the last input came from a keyboard, the focus rectangle is visible.

ygoe
  • 18,655
  • 23
  • 113
  • 210