Search code examples
imageuser-controlswindows-runtimefocusable

Focusable WinRT UserControl with image background


I'm having difficulty developing a WinRT control that can display an image as well as be focusable and receive keyboard input. The first part - displaying an image in a UserControl - is straightforward; using a child Rectangle whose background is an ImageBrush works fine.

However, calling UserControl.Focus(FocusState.Programmatic) (or any other focus state) does not work - it returns false and focus is not set to the user control.

Incidentally, this UserControl is currently being tested inside a ContentControl, not sure if that makes any difference.

How can I make this UserControl focusable and able to receive keyboard input?


Solution

  • You need to set IsTabStop="True" to let it focus. UserControl's default is False.

    Another thing you need to do is to display a focus indicator, which you don't get with UserControl for free. Here's the way you can add the visuals for it - copied from a Button template:

    <Grid>
       <VisualStateManager.VisualStateGroups>
            <VisualStateGroup
                x:Name="FocusStates">
                <VisualState
                    x:Name="Focused">
                    <Storyboard>
                        <DoubleAnimation
                            Duration="0"
                            To="1"
                            Storyboard.TargetProperty="Opacity"
                            Storyboard.TargetName="FocusVisualWhite" />
                        <DoubleAnimation
                            Duration="0"
                            To="1"
                            Storyboard.TargetProperty="Opacity"
                            Storyboard.TargetName="FocusVisualBlack" />
                    </Storyboard>
                </VisualState>
                <VisualState
                    x:Name="Unfocused" />
                <VisualState
                    x:Name="PointerFocused" />
            </VisualStateGroup>
        </VisualStateManager.VisualStateGroups>
        <Rectangle
            x:Name="FocusVisualWhite"
            IsHitTestVisible="False"
            Opacity="0"
            StrokeDashOffset="1.5"
            StrokeEndLineCap="Square"
            Stroke="{ThemeResource FocusVisualWhiteStrokeThemeBrush}"
            StrokeDashArray="1,1" />
        <Rectangle
            x:Name="FocusVisualBlack"
            IsHitTestVisible="False"
            Opacity="0"
            StrokeDashOffset="0.5"
            StrokeEndLineCap="Square"
            Stroke="{ThemeResource FocusVisualBlackStrokeThemeBrush}"
            StrokeDashArray="1,1" />
    </Grid>
    

    You still need to switch the visual state and so you could do something like this:

    protected override void OnGotFocus(RoutedEventArgs e)
    {
        base.OnGotFocus(e);
        this.UpdateVisualState(true);
    }
    
    protected override void OnLostFocus(RoutedEventArgs e)
    {
        base.OnLostFocus(e);
        this.UpdateVisualState(true);
    }
    
    private void UpdateVisualState(bool useTransitions)
    {
        switch (this.FocusState)
        {
            case FocusState.Programmatic:
            case FocusState.Keyboard:
                VisualStateManager.GoToState(this, "Focused", useTransitions);
                break;
            case FocusState.Pointer:
                VisualStateManager.GoToState(this, "PointerFocused", useTransitions);
                break;
            case FocusState.Unfocused:
                VisualStateManager.GoToState(this, "Unfocused", useTransitions);
                break;
        }
    }