L'intestazione GroupBox in WPF ingoia clic del mouse?
Domanda
Dai un'occhiata a questo semplicissimo programma WPF di esempio:
<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>
Quindi ho un GroupBox la cui intestazione è un CheckBox. Tutti abbiamo fatto qualcosa del genere: in genere si lega il contenuto di GroupBox in modo tale che sia disabilitato quando CheckBox è deselezionato.
Tuttavia, quando eseguo questa applicazione e faccio clic su CheckBox, ho scoperto che a volte i clic del mouse vengono ingoiati e lo stato di CheckBox non cambia. Se ho ragione, è quando faccio clic sulla riga esatta di pixel su cui si trova il bordo superiore del GroupBox.
Qualcuno può duplicare questo? Perché dovrebbe accadere e c'è un modo per aggirarlo?
Modifica: l'impostazione di BorderThickness di GroupBox su 0 risolve il problema, ma ovviamente rimuove il bordo, quindi non sembra più un GroupBox.
Soluzione
Sembra essere un bug sottile nel modello di controllo per GroupBox. Ho scoperto modificando il modello predefinito per GroupBox e spostando il bordo denominato "Intestazione" sull'ultimo elemento nell'elemento Griglia dei modelli di controllo, il problema si risolve da solo.
Il motivo è che uno degli altri elementi Border con un TemplateBinding di BorderBrush era più in basso nella struttura ad albero visivo e stava catturando il clic del mouse, ecco perché l'impostazione di BorderBrush su null ha permesso a CheckBox di ricevere correttamente il clic del mouse.
Di seguito è riportato lo stile risultante per GroupBox. È quasi identico al modello predefinito per il controllo, ad eccezione dell'elemento Border denominato "Header", che è ora l'ultimo figlio della griglia, anziché il secondo.
<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>
Altri suggerimenti
La risposta di Ian Oakes riempie l'ordine delle schede in modo che l'intestazione arrivi dopo il contenuto. È possibile modificare il modello di controllo in modo tale che il bordo non possa ricevere lo stato attivo.
Per fare ciò, modifica il modello in modo che il 2o e 3o bordo (entrambi nella Grid Row 1) abbiano IsHitTestVisible=false
Modello completo di seguito
<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>
Una soluzione alternativa che ho realizzato è l'implementazione di OnApplyTemplate in un GroupBox derivato:
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;
}
}
}
Se cambi BorderBrush di GroupBox, funziona!
<GroupBox BorderBrush="{x:Null}">
So che questo sconfigge l'obiettivo, ma dimostra dove si trova il problema!