3

I would like to trigger a change in a Button's template to complement the FocusVisualStyle effects. Basically I want the text 'foo' in the snippet below to turn red if and only if FocusVisualStyle is visible:

<Style x:Key="ButtonStyle1" TargetType="{x:Type Button}">
    <Setter Property="FocusVisualStyle" Value="{StaticResource ButtonFocusVisual}"/>
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type Button}">
                <Grid>
                    <ContentPresenter/>
                    <TextBlock x:Name="TxtFoo" Text="foo" Foreground="Black"/>
                </Grid>
                <ControlTemplate.Triggers>
                    <Trigger Property="IsKeyboardFocused" Value="true">
                        <Setter Property="Foreground" TargetName="TxtFoo" Value="Red"/>
                    </Trigger>
                </ControlTemplate.Triggers>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

Please ignore the fact that the example is idiotic, the actual code is more useful (it modifies a Path).

This works on keyboard navigation, but has an issue: the text turns red also when the button is pressed (FocusVisualStyle is not displayed in that case). Using an eventTrigger on GotKeyboardFocus/LostKeyboardFocus yields the same result.

Looking at the framework's source code I don't see anything special: KeyboardNavigation.ShowFocusVisual() is called by FrameworkElement.OnGotKeyboardFocus() as expected. However, obviously there must be something else going on because not every gotKeyboardFocus events cause FocusVisualStyle to show.

What property/event should I target if I want to be "synchronized" with FocusVisualStyle?

Francesco De Vittori
  • 9,100
  • 6
  • 33
  • 43

3 Answers3

3

Found by rummaging through .net core sources for Wpf, but it works brilliantly for WPF 4.7.2 as well....

For a normal control, the FocusVisualStyle is applied only if the keyboard was the most recent input device. Unfortunately, there is no way to make the FocusVisualStyle dependent on the state of the control to which it is applied; and there is no out-of-the-box property, event or attached property which can be used to detect FocusVisualStyle state.

The solution is to provide a derived dependency property on your control, and then emulate FocusVisualStyle in the control template using triggers.

Include the following code in your Control, and trigger off the IsVisualFocus property in your control template, which will be true only when FocusVisualStyle would have been applied.

protected override void OnGotFocus(RoutedEventArgs e)
{
    base.OnGotFocus(e);
    IsVisualFocus = IsKeyboardMostRecentInputDevice();
}

protected override void OnLostFocus(RoutedEventArgs e)
{
    base.OnLostFocus(e);
    IsVisualFocus = false;
}

private bool IsKeyboardMostRecentInputDevice()
{
    return InputManager.Current.MostRecentInputDevice is KeyboardDevice;
}

public bool IsVisualFocus
{
    get { return (bool)GetValue(IsVisualFocusProperty); }
    set { SetValue(IsVisualFocusProperty, value); }
}

And then in the Control template...

     <Style x:Key="{x:Type local:FlatButton}" TargetType="local:FlatButton">
          <Setter Property="FocusVisualStyle" Value={x:Null}/> <!-- disable system FocusVisualStyle -->
          <Setter Property="Template">
                <Setter.Value>
                        <ControlTempalte.Triggers>
                              <Trigger Property="IsVisualFocus" Value="True">
                                       .... your setters here ....
                                       <Setter TargetName="T_Border" Property="Background" Value="#10FF0000"/>
                              </Trigger>
                        </ControlTemplate.Triggers>
                    </ControlTemplate>          
                </Setter.Value>
          </Setter>
   </Style>
Robin Davies
  • 7,547
  • 1
  • 35
  • 50
1

Silly question maybe, but why don't you simply copy the FocusVisualStyle (in Blend) and edit it to display what you want? This is the way we normally do these things.

Cheers, Laurent

LBugnion
  • 6,672
  • 2
  • 24
  • 28
  • 1
    I cannot use FocusVisualStyle because that puts an additional layer over the button (default template is just the dotted border). All setters in FocusVisualStyle target that additional layer. However, In this case I must interact with the control itself. In the example 'foo' is hardwired, in reality I change the actual button's content. Does this make sense? – Francesco De Vittori Mar 02 '12 at 14:32
  • 1
    I see. I cannot try right now, but open the Button control template in Blend and check the Visual States or the Triggers. Find the Focused one, and check what they do in the template. Best way IMHO is to have two layers, and when Focused to hide the Content one and show the Focus one. Makes sense? – LBugnion Mar 02 '12 at 14:37
  • Makes sense. The wpf template is still not Visual State Manager based, but it won't be difficult to make it that way (maybe starting from the Silverlight template). Thanks for the tip! – Francesco De Vittori Mar 02 '12 at 14:43
  • Attempt failed: a VSM-based template (like the default one in Silverlight) has the same problem: it enters the Focused state also when you press the button (without keyboard navigation). – Francesco De Vittori Mar 02 '12 at 15:39
0

Instead of using IsKeyboardFocused tryIsFocused.

Nitin Joshi
  • 1,638
  • 1
  • 14
  • 17
  • Neither IsFocused nor IsKeyboardFocused track the state of the Focus visual. They both return true when you click on the control, whereas the Focus visual only shows when you navigate to the control using the keyboard, but not when you click on the control with a mouse. (Visual States have the same problem) – Robin Davies May 17 '20 at 02:24