Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Custom mouse hit region for CheckBox in WPF

I have set a custom style for my checkbox, which looks like this (unchecked state):

Image of an expander with a border around it and dog-ear signifying a checkbox

When the checkbox is "checked", it should look like this:

Image of an expander with a border around it and dog-ear highlighted green.

What I want to happen is that the user should only be able to toggle it by clicking the checkmark on the top-right corner. Right now, the checkbox will toggle anywhere the user clicks that's inside of the box and not inside of the expander.

Is there a way I can set the mouse-click region for toggling the checkbox to be only around the checkmark on the upper right?

Here is my code as it stands:

<SolidColorBrush x:Key="SelectedCheckboxColor" Color="Green"/>
<SolidColorBrush x:Key="UnselectedCheckboxColor" Color="Gray"/>
<Style x:Key="SelectBoxCheckBoxStyle" TargetType="{x:Type CheckBox}">
    <Setter Property="BorderThickness" Value="1"/>
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type CheckBox}">
                <Border x:Name="border" BorderThickness="2" BorderBrush="#FF666666">
                    <Grid>
                        <Grid.ColumnDefinitions>
                            <ColumnDefinition Width="*"/>
                            <ColumnDefinition Width="Auto"/>
                        </Grid.ColumnDefinitions>
                        <ContentPresenter Grid.Column="0" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" Margin="{TemplateBinding Padding}" RecognizesAccessKey="True" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
                        <Image x:Name="image" Width="24" Height="24" Grid.Column="1" VerticalAlignment="Top" Source="Resources/checkbox-dogear-unchecked.png"/>
                    </Grid>

                    <!--<BulletDecorator Background="Transparent" SnapsToDevicePixels="true">
                        <BulletDecorator.Bullet>
                            <Themes:BulletChrome BorderBrush="{TemplateBinding BorderBrush}" Background="{TemplateBinding Background}" IsChecked="{TemplateBinding IsChecked}" RenderMouseOver="{TemplateBinding IsMouseOver}" RenderPressed="{TemplateBinding IsPressed}"/>
                        </BulletDecorator.Bullet>
                    </BulletDecorator>-->
                </Border>
                <ControlTemplate.Triggers>
                    <Trigger Property="HasContent" Value="true">
                        <Setter Property="Padding" Value="4,0,0,0"/>
                    </Trigger>
                    <Trigger Property="IsEnabled" Value="false">
                        <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}"/>
                    </Trigger>
                    <Trigger Property="IsChecked" Value="True">
                        <Setter Property="Source" TargetName="image" Value="Resources/checkbox-dogear-checked.png"/>
                        <Setter Property="BorderBrush" TargetName="border" Value="#FF00D212"/>
                    </Trigger>
                    <Trigger Property="IsMouseCaptureWithin" Value="True">
                        <Setter Property="BorderBrush" TargetName="border" Value="#FFA200FF"/>
                    </Trigger>
                </ControlTemplate.Triggers>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>
like image 264
Spen-ZAR Avatar asked Nov 11 '22 08:11

Spen-ZAR


1 Answers

I think Rachel's answer is the right way to go, and since it's not something I've quite tried before, I thought I'd have a crack and knocking up a quick attempt (criticism welcome).

I didn't have the images you were using, so I used a Polygon and a Path to mockup the tickbox instead - so the style became this:

        <SolidColorBrush x:Key="SelectedCheckboxColor" Color="#FF00D212"/>
        <SolidColorBrush x:Key="UnselectedCheckboxColor" Color="Gray"/>
        <Style TargetType="{x:Type CheckBox}">
            <Setter Property="BorderThickness" Value="1"/>
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="{x:Type CheckBox}">
                        <Border x:Name="border" BorderThickness="2" BorderBrush="{StaticResource ResourceKey=UnselectedCheckboxColor}">
                            <Grid>
                                <Grid.ColumnDefinitions>
                                    <ColumnDefinition Width="*"/>
                                    <ColumnDefinition Width="Auto"/>
                                </Grid.ColumnDefinitions>
                                <ContentPresenter Grid.Column="0" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" Margin="{TemplateBinding Padding}" RecognizesAccessKey="True" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>

                                <!-- Tickbox Mockup -->
                                <Polygon x:Name="checkPoly" Points="0,0 24,24 24,0 0,0" Stroke="Purple" StrokeThickness="0" Width="24" Height="24" 
                                         VerticalAlignment="Top" HorizontalAlignment="Right" Fill="{StaticResource ResourceKey=UnselectedCheckboxColor}" />
                                <Path Stroke="White" Data="M 6,6 L 9, 9 L 16,2" StrokeThickness="2" Margin="0, 1, 1, 0"
                                      VerticalAlignment="Top" HorizontalAlignment="Right" />
                            </Grid>
                        </Border>
                        <ControlTemplate.Triggers>
                            <Trigger Property="HasContent" Value="true">
                                <Setter Property="Padding" Value="4,0,0,0"/>
                            </Trigger>
                            <Trigger Property="IsEnabled" Value="false">
                                <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}"/>
                            </Trigger>
                            <Trigger Property="IsChecked" Value="True">
                                <Setter Property="Fill" TargetName="checkPoly" Value="{StaticResource ResourceKey=SelectedCheckboxColor}"/>
                                <Setter Property="BorderBrush" TargetName="border" Value="{StaticResource ResourceKey=SelectedCheckboxColor}" />
                            </Trigger>
                            <Trigger Property="IsMouseCaptureWithin" Value="True">
                                <Setter Property="BorderBrush" TargetName="border" Value="#FFA200FF"/>
                            </Trigger>
                        </ControlTemplate.Triggers>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>

Which was then hosted, along with an Expander (with some test content), inside a ContentControl:

<ContentControl Width="100" Height="100">
        <Grid>
            <!-- Make sure the Checkbox is the foremost element -->
            <CheckBox Grid.ZIndex="99" />
            <Expander Header="Test" >
                <StackPanel>
                    <TextBlock Text="Test" />
                    <TextBlock Text="Test" />
                    <TextBlock Text="Test" />
                </StackPanel>
            </Expander>
        </Grid>
</ContentControl>

Which looks like this:

enter image description here

Your mileage may vary depending where you intend to use the Control. One thing to note, the checkbox will still toggle if you click the border - if that's a problem, you could remove the border width from the CheckBox, stick it on the ContentControl, and use some binding to hook up the Border brush color from the CheckBox.

like image 172
Chris Avatar answered Nov 15 '22 10:11

Chris