使用鼠标和键盘聚焦实现FocusVisualStyle

时间:2017-07-27 17:37:18

标签: c# wpf xaml focus

我想要实现的目标

为简单起见,我有一个单元格网格,我试图在聚焦时将FocusVisualStyle虚线矩形添加到每个单元格。但是,只有在元素按照其设计导航到使用键盘时,样式才有效。我希望达到相同的效果,但让它适用于键盘焦点鼠标焦点(单击单元格时)。

我尝试过什么

我研究过使用像Border这样的容器,但是它没有虚线,试图使用虚线矩形并将其放在元素上,但这提供了不一致的结果。我还尝试将触发器附加到单元格的IsFocused属性,但这仅适用于键盘焦点。

当前代码

现在我将事件设置为单元格(StackPanel),允许我通过网格进行自定义导航。我目前的视觉风格"是将背景更改为颜色,适用于鼠标和键盘焦点。我正在寻找替换单元格周围虚线矩形的背景更改,但是我尝试的XAML并不起作用,因为FocusVisualStyle仅适用于键盘焦点。

以下是我尝试过的XAML和C#的简化版本

XAML

<!-- The "cell" I'm trying to acheive a dashed border around -->
<StackPanel x:Key="ContactCell" 
            Focusable="True" 
            GotFocus="StackPanel_GotFocus" 
            LostFocus="StackPanel_LostFocus" 
            PreviewMouseDown="Contact_Select" 
            Style="{DynamicResource ContactFocusStyle}">
    <!-- other children in here -->
</StackPanel>

<Style x:Key="ContactFocusStyle" TargetType="StackPanel">
        <Style.Triggers>
            <Trigger Property="IsFocused" Value="True">
                <Setter Property="FocusVisualStyle" 
                        Value="{DynamicResource MyFocusVisualStyle}"/>
            </Trigger>
        </Style.Triggers>
</Style>

<Style x:Key="MyFocusVisualStyle">
        <Setter Property="Control.Template">
            <Setter.Value>
                <ControlTemplate>
                    <Rectangle Stroke="Black" 
                               StrokeDashArray="2 3" 
                               Fill="Transparent" 
                               StrokeDashCap="Round" 
                               RadiusX="3" 
                               RadiusY="3"/>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
</Style>

C#

    private void Contact_Select(object send, MouseButtonEventArgs e)
    {
        StackPanel sender = (StackPanel)send;
        sender.Focus();
    }

    private void StackPanel_GotFocus(object sender, RoutedEventArgs e)
    {
        StackPanel s = (StackPanel)sender;
        s.Background = Brushes.Red;
    }

    private void StackPanel_LostFocus(object sender, RoutedEventArgs e)
    {
        StackPanel s = (StackPanel)sender;
        s.Background = Brushes.Transparent;
    }

2 个答案:

答案 0 :(得分:1)

我在实施中看到两个问题。首先是its hard to capture mouse-events in a StackPanel with transparent background。其次,FocusVisualStyle only works when the last input is from the keyboard

要获得类似的视觉效果 - 您可以使用装饰器来实现焦点风格。例如 - 您可以像这样定义一个装饰器:

public class FocusAdorner : Adorner
{
    // Be sure to call the base class constructor.
    public FocusAdorner(UIElement adornedElement)
      : base(adornedElement)
    {
        IsHitTestVisible = false;
    }

    // A common way to implement an adorner's rendering behavior is to override the OnRender
    // method, which is called by the layout system as part of a rendering pass.
    protected override void OnRender(DrawingContext drawingContext)
    {
        var drawRect = LayoutInformation.GetLayoutSlot((FrameworkElement)this.AdornedElement);
        drawRect = new Rect(1, 1, drawRect.Width - 2, drawRect.Height - 2);


        // Some arbitrary drawing implements.
        SolidColorBrush renderBrush = new SolidColorBrush(Colors.Transparent);
        Pen renderPen = new Pen(new SolidColorBrush(Colors.Black), 2);
        renderPen.DashStyle = new DashStyle(new double[] { 2, 3 }, 0);

        drawingContext.DrawRoundedRectangle(renderBrush, renderPen, drawRect, 3, 3);
    }
}

示例使用 - XAML

<Window.Resources>
    <!-- The "cell" I'm trying to acheive a dashed border around -->

    <Style x:Key="ContactFocusStyle" TargetType="StackPanel">
        <Setter Property="Background" Value="White" />
        <Setter Property="FocusVisualStyle" Value="{x:Null}" />
        <Style.Triggers>
            <Trigger Property="IsFocused" Value="True">
                <Setter Property="Background" Value="Red" />
            </Trigger>
        </Style.Triggers>
    </Style>
</Window.Resources>
<Grid Margin="15">
    <Grid.RowDefinitions>
        <RowDefinition />
        <RowDefinition />
    </Grid.RowDefinitions>
    <Grid.ColumnDefinitions>
        <ColumnDefinition />
        <ColumnDefinition />
    </Grid.ColumnDefinitions>
    <StackPanel
        Grid.Row="0"
        Grid.Column="0"
        Focusable="True" 
        GotFocus="Got_Focus"
        LostFocus="Lost_Focus"
        PreviewMouseDown="Contact_Select"
        Style="{DynamicResource ContactFocusStyle}">
        <!-- other children in here -->
    </StackPanel>
    <StackPanel
        Grid.Row="0"
        Grid.Column="1"
        Focusable="True" 
        GotFocus="Got_Focus"
        LostFocus="Lost_Focus"
        PreviewMouseDown="Contact_Select"
        Style="{DynamicResource ContactFocusStyle}">
        <!-- other children in here -->
    </StackPanel>
    <StackPanel
        Grid.Row="1"
        Grid.Column="0"
        Focusable="True" 
        GotFocus="Got_Focus"
        LostFocus="Lost_Focus"
        PreviewMouseDown="Contact_Select"
        Style="{DynamicResource ContactFocusStyle}">

        <!-- other children in here -->
    </StackPanel>
    <StackPanel
        Grid.Row="1"
        Grid.Column="1"
        Focusable="True" 
        GotFocus="Got_Focus"
        LostFocus="Lost_Focus"
        PreviewMouseDown="Contact_Select"
        Style="{DynamicResource ContactFocusStyle}">

        <!-- other children in here -->
    </StackPanel>
</Grid>

和代码隐藏

private void Contact_Select(object send, MouseButtonEventArgs e)
{
    var sender = (StackPanel)send;
    Keyboard.Focus(sender);
}

private void Got_Focus(object send, RoutedEventArgs e)
{
    var sender = (StackPanel)send;
    AdornerLayer.GetAdornerLayer(sender).Add(new FocusAdorner(sender));
}

private void Lost_Focus(object send, RoutedEventArgs e)
{
    var sender = (StackPanel)send;
    var layer = AdornerLayer.GetAdornerLayer(sender);
    foreach (var adorner in layer.GetAdorners(sender))
        layer.Remove(adorner);
}

**输出**

enter image description here

答案 1 :(得分:0)

我认为最好的方法是创建一个可选择的面板来处理鼠标和键盘事件的焦点。使用面板包裹您想要在其周围显示焦点矩形的任何元素。

面板...

 public class SelectablePanel : Panel
{
    public static readonly DependencyProperty FocusBrushProperty = DependencyProperty.Register("FocusBrush", typeof(Brush), typeof(SelectablePanel),
        new PropertyMetadata(Brushes.Red));
    public static readonly DependencyProperty FocusThicknessProperty = DependencyProperty.Register("FocusThickness", typeof(double), typeof(SelectablePanel),
        new PropertyMetadata(1.0));
    public Brush FocusBrush
    {
        get => (Brush)GetValue(FocusBrushProperty);
        set => SetValue(FocusBrushProperty, value);
    }
    public double FocusThickness
    {
        get => (double)GetValue(FocusThicknessProperty);
        set => SetValue(FocusThicknessProperty, value);
    }
    static SelectablePanel()
    {
        DefaultStyleKeyProperty.OverrideMetadata(typeof(SelectablePanel), new FrameworkPropertyMetadata(typeof(SelectablePanel)));
        FocusableProperty.OverrideMetadata(typeof(SelectablePanel), new FrameworkPropertyMetadata(true));
        BackgroundProperty.OverrideMetadata(typeof(SelectablePanel), new FrameworkPropertyMetadata(Brushes.White));
    }
    protected override void OnPreviewMouseLeftButtonDown(MouseButtonEventArgs e)
    {
        Focus();
        e.Handled = true;
    }
    protected override void OnGotFocus(RoutedEventArgs e)
    {
        InvalidateVisual();
        base.OnGotFocus(e);
    }
    protected override void OnLostFocus(RoutedEventArgs e)
    {
        InvalidateVisual();
        base.OnLostFocus(e);
    }
    protected override Size MeasureOverride(Size availableSize)
    {
        if (Background == Brushes.Transparent) throw new InvalidOperationException("Selectable panel cannot be transparent.");
        if (InternalChildren.Count > 1) throw new InvalidOperationException("SelectablePanel takes only a single child control.");
        Size panelDesiredSize = new Size();
        foreach (UIElement child in InternalChildren)
        {
            child.Measure(availableSize);
            panelDesiredSize = child.DesiredSize;
        }
        return panelDesiredSize;
    }
    protected override Size ArrangeOverride(Size finalSize)
    {
        foreach (UIElement child in InternalChildren)
        {
            double x = (finalSize.Width - child.DesiredSize.Width)/ 2;
            double y = (finalSize.Height - child.DesiredSize.Height) / 2;
            child.Arrange(new Rect(new Point(x, y), child.DesiredSize));
        }
        return finalSize;
    }
    protected override void OnRender(DrawingContext dc)
    {
        base.OnRender(dc);
        if (IsFocused)
        {
            var rect = LayoutInformation.GetLayoutSlot(this);
            var r = new Rect(1, 1 + Margin.Top, rect.Width - 2 - Margin.Left - Margin.Right, rect.Height - 2 - Margin.Top - Margin.Bottom);
            SolidColorBrush b = new SolidColorBrush(Colors.Transparent);
            Pen p = new Pen(FocusBrush, FocusThickness);
            dc.DrawRectangle(b, p, r);
        }
    }
}

示例用法...

    <StackPanel>
    <Label Margin="10,0,0,0">Text Bex 1:</Label>
    <TextBox Margin="10,0,10,0" HorizontalAlignment="Stretch" Text="Text Box 1" />
    <Label Margin="10,0,0,0">Image:</Label>
    <local:SelectablePanel Margin="10,0,10,0">
        <Image Source="Images/globe.png" Height="100"/>
    </local:SelectablePanel>
    <Label Margin="10,0,0,0">Text Box 2:</Label>
    <TextBox Margin="10,0,10,0" HorizontalAlignment="Stretch" Text="Text Box 2"/>
</StackPanel>

SelectablePanel with focus

相关问题