【问题标题】:Achieving FocusVisualStyle with both Mouse and Keyboard Focus使用鼠标和键盘焦点实现 FocusVisualStyle
【发布时间】:2017-07-27 17:37:18
【问题描述】:

我正在努力实现的目标

为简单起见,我有一个单元格网格,我尝试在每个单元格获得焦点时将 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;
    }

【问题讨论】:

    标签: c# wpf xaml focus


    【解决方案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);
    }
    

    ** 输出**

    【讨论】:

    • 谢谢!这是一个很好的答案!
    【解决方案2】:

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

    面板...

     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>
    

    【讨论】:

      猜你喜欢
      • 2021-11-05
      • 1970-01-01
      • 1970-01-01
      • 2013-08-02
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-12-11
      相关资源
      最近更新 更多