【问题标题】:Does the GroupBox Header in WPF swallow mouse-clicks?WPF 中的 GroupBox 标题是否会吞下鼠标单击?
【发布时间】:2010-09-14 04:45:01
【问题描述】:

看看这个非常简单的示例 WPF 程序:

<Window x:Class="WpfApplication1.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="Window1" Height="300" Width="300">

    <GroupBox>
        <GroupBox.Header>
            <CheckBox Content="Click Here"/>
        </GroupBox.Header>
    </GroupBox>
</Window>

所以我有一个 GroupBox,其标题是 CheckBox。我们都做过这样的事情——通常你绑定 GroupBox 的内容,这样当 CheckBox 未选中时它就会被禁用。

但是,当我运行此应用程序并单击 CheckBox 时,我发现有时我的鼠标单击被吞下并且 CheckBox 的状态没有改变。如果我是对的,那就是当我点击 GroupBox 的上边框所在的确切像素行时。

有人可以复制吗?为什么会发生这种情况,有没有办法解决它?

编辑:将 GroupBox 的 BorderThickness 设置为 0 可以解决问题,但显然它会移除边框,因此它看起来不再像 GroupBox。

【问题讨论】:

标签: .net wpf checkbox groupbox


【解决方案1】:

Ian Oakes 的回答填充了 Tab 键顺序,使得标题出现在内容之后。可以修改控件模板,使边框无法获得焦点。

为此,请修改模板,使第 2 和第 3 个边框(都在网格第 1 行中)具有IsHitTestVisible=false

下面的完整模板

<BorderGapMaskConverter x:Key="GroupBoxBorderGapMaskConverter" />

<Style x:Key="{x:Type GroupBox}" TargetType="{x:Type GroupBox}">
    <Setter Property="Control.BorderBrush" Value="#FFD5DFE5" />
    <Setter Property="Control.BorderThickness" Value="1" />
    <Setter Property="Control.Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type GroupBox}">
                <Grid SnapsToDevicePixels="True">
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="6" />
                        <ColumnDefinition Width="Auto" />
                        <ColumnDefinition Width="*" />
                        <ColumnDefinition Width="6" />
                    </Grid.ColumnDefinitions>
                    <Grid.RowDefinitions>
                        <RowDefinition Height="Auto" />
                        <RowDefinition Height="Auto" />
                        <RowDefinition Height="*" />
                        <RowDefinition Height="6" />
                    </Grid.RowDefinitions>
                    <Border Name="Header" Padding="3,1,3,0" Grid.Row="0" Grid.RowSpan="2" Grid.Column="1">
                        <ContentPresenter ContentSource="Header" RecognizesAccessKey="True" SnapsToDevicePixels="{TemplateBinding UIElement.SnapsToDevicePixels}" />
                    </Border>
                    <Border CornerRadius="4" Grid.Row="1" Grid.RowSpan="3" Grid.Column="0" Grid.ColumnSpan="4" BorderThickness="{TemplateBinding Control.BorderThickness}" BorderBrush="#00FFFFFF" Background="{TemplateBinding Control.Background}" IsHitTestVisible="False" />
                    <ContentPresenter Grid.Row="2" Grid.Column="1" Grid.ColumnSpan="2" Margin="{TemplateBinding Control.Padding}" SnapsToDevicePixels="{TemplateBinding UIElement.SnapsToDevicePixels}"/>
                    <Border CornerRadius="4" Grid.Row="1" Grid.RowSpan="3" Grid.ColumnSpan="4" BorderThickness="{TemplateBinding Control.BorderThickness}" BorderBrush="#FFFFFFFF" IsHitTestVisible="False">
                        <UIElement.OpacityMask>
                            <MultiBinding Converter="{StaticResource GroupBoxBorderGapMaskConverter}" ConverterParameter="7">
                                <Binding ElementName="Header" Path="ActualWidth" />
                                <Binding Path="ActualWidth" RelativeSource="{RelativeSource Self}" />
                                <Binding Path="ActualHeight" RelativeSource="{RelativeSource Self}" />
                            </MultiBinding>
                        </UIElement.OpacityMask>
                        <Border BorderThickness="{TemplateBinding Control.BorderThickness}" BorderBrush="{TemplateBinding Control.BorderBrush}" CornerRadius="3">
                            <Border BorderThickness="{TemplateBinding Control.BorderThickness}" BorderBrush="#FFFFFFFF" CornerRadius="2" />
                        </Border>
                    </Border>                        
                </Grid>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

【讨论】:

    【解决方案2】:

    这似乎是 GroupBox 控件模板中的一个细微错误。我发现通过编辑 GroupBox 的默认模板并将名为“Header”的边框移动到控件模板 Grid 元素中的最后一项,问题自行解决。

    原因是带有 TemplateBinding 的 BorderBrush 的其他 Border 元素之一在可视化树中更靠后并且正在捕获鼠标点击,这就是为什么将 BorderBrush 设置为 null 允许 CheckBox 正确接收鼠标点击的原因。

    下面是 GroupBox 的最终样式。它与控件的默认模板几乎相同,除了名为“Header”的 Border 元素,它现在是 Grid 的最后一个子元素,而不是第二个。

    <BorderGapMaskConverter x:Key="BorderGapMaskConverter"/>
    
    <Style x:Key="GroupBoxStyle1" TargetType="{x:Type GroupBox}">
        <Setter Property="BorderBrush" Value="#D5DFE5"/>
        <Setter Property="BorderThickness" Value="1"/>
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type GroupBox}">
                    <Grid SnapsToDevicePixels="true">
                        <Grid.RowDefinitions>
                            <RowDefinition Height="Auto"/>
                            <RowDefinition Height="Auto"/>
                            <RowDefinition Height="*"/>
                            <RowDefinition Height="6"/>
                        </Grid.RowDefinitions>
                        <Grid.ColumnDefinitions>
                            <ColumnDefinition Width="6"/>
                            <ColumnDefinition Width="Auto"/>
                            <ColumnDefinition Width="*"/>
                            <ColumnDefinition Width="6"/>
                        </Grid.ColumnDefinitions>
                        <Border Grid.Column="0" Grid.ColumnSpan="4" Grid.Row="1" Grid.RowSpan="3" Background="{TemplateBinding Background}" BorderBrush="Transparent" BorderThickness="{TemplateBinding BorderThickness}" CornerRadius="4"/>
                        <ContentPresenter Margin="{TemplateBinding Padding}" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" Grid.Column="1" Grid.ColumnSpan="2" Grid.Row="2"/>
                        <Border Grid.ColumnSpan="4" Grid.Row="1" Grid.RowSpan="3" BorderBrush="White" BorderThickness="{TemplateBinding BorderThickness}" CornerRadius="4">
                            <Border.OpacityMask>
                                <MultiBinding Converter="{StaticResource BorderGapMaskConverter}" ConverterParameter="7">
                                    <Binding Path="ActualWidth" ElementName="Header"/>
                                    <Binding Path="ActualWidth" RelativeSource="{RelativeSource Self}"/>
                                    <Binding Path="ActualHeight" RelativeSource="{RelativeSource Self}"/>
                                </MultiBinding>
                            </Border.OpacityMask>
                            <Border BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" CornerRadius="3">
                                <Border BorderBrush="White" BorderThickness="{TemplateBinding BorderThickness}" CornerRadius="2"/>
                            </Border>
                        </Border>
                        <Border x:Name="Header" Grid.Column="1" Grid.Row="0" Grid.RowSpan="2" Padding="3,1,3,0">
                            <ContentPresenter SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" ContentSource="Header" RecognizesAccessKey="True"/>
                        </Border>
                    </Grid>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
    

    【讨论】:

    • Ian,修改后的 ControlTemplate 是不是很复杂?愿意发布吗?
    • 谢谢伊恩!我必须在此样式之外添加一个 BorderGapConverter 资源才能使其工作,但是一旦我这样做了,它就可以很好地完成工作。
    • 注意标签顺序。内容现在将位于标题之前。请参阅我的答案以获得稍微不同的答案(它仍然涉及编辑控件模板)。
    【解决方案3】:

    我提出的另一种解决方案是在派生的 GroupBox 中实现 OnApplyTemplate:

    public override void OnApplyTemplate()
    {
      base.OnApplyTemplate();
      if (Children.Count == 0) return;
    
      var grid = GetVisualChild(0) as Grid;
      if (grid != null && grid.Children.Count > 3)
      {
        var bd = grid.Children[3] as Border;
        if (bd != null)
        {
          bd.IsHitTestVisible = false;
        }
      }
    }
    

    【讨论】:

    • 我的解决方案的好处是您不会更改组框的外观,因此不会影响默认主题。它也非常紧凑。
    • 我使用的是 WPF 4.0,而且 GroupBox.Children 似乎不存在。 WPF 4 的代码应该是什么?应该是 if (VisualChildrenCount == 0) 吗?
    • +1,很好的解决方案,再简单不过了! @Mas:您可以用VisualChildrenCount == 0 替换它,但我认为不需要整个检查(总是有一个视觉孩子)。事实上,我认为不需要所有的检查(加上base 调用),所以整个方法体可以只是一行:((Grid)GetVisualChild(0)).Children[3].IsHitTestVisible = false;。这就是我使用的,到目前为止它有效。
    • 警告:似乎根错误已在 .NET 4.5 中修复,当安装在机器上时,此解决方法会导致 GroupBox 内的所有控件都具有 IsHitTestVisible = false 导致它们不是可点击!
    • 好的。我会在我的机器上安装 4.5 后立即检查,感谢您的警告!
    【解决方案4】:

    如果你改变 GroupBox 的 BorderBrush,它就可以工作!

    <GroupBox BorderBrush="{x:Null}">
    

    我知道这违背了目标,但它确实证明了问题所在!

    【讨论】:

    • 无论如何这可能会有所帮助。我们也许能够以某种方式在 GroupBox 后面放置一个“真实的”边框来伪造它。我会等待,看看我们是否会收到更多回复,但这可能与我得到的答案一样接近。
    • 我也曾在 WPF 门徒群中询问过这个问题:groups.google.com/group/wpf-disciples/browse_thread/thread/…
    • Rudi - 我们只是尝试过,但无济于事。如果您点击边框为复选框的像素,则复选框状态不会改变。即使将 BorderBrush 设置为透明也无济于事。
    • 啊!将 BorderThickness 设置为 0 可以修复它。当然,我们失去了 GroupBox 的边框,这意味着它看起来不再像 GroupBox。
    • {x:Null} 和 Transparent 都不起作用,但 BorderThickness="0" 起作用了。透明有时会影响命中测试,例如在窗口中,所以我认为当 null 失败时值得一试。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-05-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-05-07
    • 2011-06-21
    • 1970-01-01
    相关资源
    最近更新 更多