Styling the indeterminate state of a WPF checkbox

后端 未结 9 1907
情话喂你
情话喂你 2021-02-04 07:30

I want to style the indeterminate state of a WPF checkbox. We have a treeview control with checkboxes, and we want the indeterminate state to represent that some descendants are

相关标签:
9条回答
  • 2021-02-04 07:54

    Have you already tried to download the Aero Theme xaml and looked into it?
    Where can I download Microsoft's standard WPF themes from?

    0 讨论(0)
  • 2021-02-04 07:56

    Using the template from Blend:

    To make BulletChrome work, you need to add a reference to PresentationFramework.Aero, and add the xml namespace declaration for the "theme" namespace:

    xmlns:theme="clr-namespace:Microsoft.Windows.Themes;assembly=PresentationFramework.Aero"
    

    I didn't try this myself but I believe it should work (I've done it with Luna).

    0 讨论(0)
  • 2021-02-04 08:00

    Call me crazy, but I've actually reimplemented standard Aero checkbox in pure XAML. If you want to customize Aero checkbox, it's a good starting point. You can find other styles in my repository on GitHub (specific commit, in case files are moved).

    BulletCommon.xaml (common resources for CheckBox and RadioButton)

    <ResourceDictionary
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:xx="clr-namespace:Alba.WpfThemeGenerator.Markup">
    
        <!-- Colors -->
    
        <!-- Background overlay -->
        <SolidColorBrush x:Key="Bullet.BackgroundOverlay.Hover" Color="#DEF9FA"/>
        <SolidColorBrush x:Key="Bullet.BackgroundOverlay.Pressed" Color="#C2E4F6"/>
        <SolidColorBrush x:Key="Bullet.BackgroundOverlay.Disabled" Color="#F4F4F4"/>
        <!-- Border overlay -->
        <SolidColorBrush x:Key="Bullet.BorderOverlay.Hover" Color="#3C7FB1"/>
        <SolidColorBrush x:Key="Bullet.BorderOverlay.Pressed" Color="#2C628B"/>
        <SolidColorBrush x:Key="Bullet.BorderOverlay.Disabled" Color="#ADB2B5"/>
        <!-- Inner border -->
        <LinearGradientBrush x:Key="Bullet.InnerBorder.Disabled" StartPoint="0,0" EndPoint="1,1">
            <GradientStop Color="#E1E3E5" Offset="0.25"/>
            <GradientStop Color="#E8E9EA" Offset="0.5"/>
            <GradientStop Color="#F3F3F3" Offset="1"/>
        </LinearGradientBrush>
        <!-- Indeterminate inner border -->
        <LinearGradientBrush x:Key="Bullet.InnerBorder.IndeterminateDisabled" StartPoint="0,0" EndPoint="1,1">
            <GradientStop Color="#BFD0DD" Offset="0"/>
            <GradientStop Color="#BDCBD7" Offset="0.5"/>
            <GradientStop Color="#BAC4CC" Offset="1"/>
        </LinearGradientBrush>
        <!-- Inner fill -->
        <LinearGradientBrush x:Key="Bullet.InnerFill.Normal" StartPoint="0,0" EndPoint="1,1">
            <GradientStop Color="#CBCFD5" Offset="0.2"/>
            <GradientStop Color="#F7F7F7" Offset="0.8"/>
        </LinearGradientBrush>
        <LinearGradientBrush x:Key="Bullet.InnerFill.Hover" StartPoint="0,0" EndPoint="1,1">
            <GradientStop Color="#B1DFFD" Offset="0.2"/>
            <GradientStop Color="#E9F7FE" Offset="0.8"/>
        </LinearGradientBrush>
        <LinearGradientBrush x:Key="Bullet.InnerFill.Pressed" StartPoint="0,0" EndPoint="1,1">
            <GradientStop Color="#7FBADC" Offset="0.2"/>
            <GradientStop Color="#D6EDF9" Offset="0.8"/>
        </LinearGradientBrush>
        <!-- Indeterminate fill -->
        <LinearGradientBrush x:Key="Bullet.Fill.Indeterminate" StartPoint="0,0" EndPoint="1,1">
            <GradientStop Color="#2FA8D5" Offset="0.2"/>
            <GradientStop Color="#25598C" Offset="0.8"/>
        </LinearGradientBrush>
        <LinearGradientBrush x:Key="Bullet.Fill.IndeterminateHover" StartPoint="0,0" EndPoint="1,1">
            <GradientStop Color="#33D7ED" Offset="0.2"/>
            <GradientStop Color="#2094CE" Offset="0.8"/>
        </LinearGradientBrush>
        <LinearGradientBrush x:Key="Bullet.Fill.IndeterminatePressed" StartPoint="0,0" EndPoint="1,1">
            <GradientStop Color="#17447A" Offset="0.2"/>
            <GradientStop Color="#218BC3" Offset="0.8"/>
        </LinearGradientBrush>
        <LinearGradientBrush x:Key="Bullet.Fill.IndeterminateDisabled" StartPoint="0,0" EndPoint="1,1">
            <GradientStop Color="#C0E5F3" Offset="0.2"/>
            <GradientStop Color="#BDCDDC" Offset="0.8"/>
        </LinearGradientBrush>
    
        <!-- Styles -->
    
        <Style x:Key="Bullet.FocusVisual.Normal">
            <Setter Property="Control.Template">
                <Setter.Value>
                    <ControlTemplate>
                        <Rectangle Margin="14,0,0,0" SnapsToDevicePixels="True"
                                StrokeThickness="1" Stroke="{xx:SystemBrush ControlText}" StrokeDashArray="1 2"/>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>
    
        <Style x:Key="Bullet.FocusVisual.Empty">
            <Setter Property="Control.Template">
                <Setter.Value>
                    <ControlTemplate>
                        <Rectangle Margin="1" SnapsToDevicePixels="True"
                                StrokeThickness="1" Stroke="{xx:SystemBrush ControlText}" StrokeDashArray="1 2"/>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>
    
    </ResourceDictionary>
    

    CheckBox.xaml (resources for CheckBox)

    <ResourceDictionary
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:xx="clr-namespace:Alba.WpfThemeGenerator.Markup">
    
        <ResourceDictionary.MergedDictionaries>
            <ResourceDictionary Source="BulletCommon.xaml"/>
        </ResourceDictionary.MergedDictionaries>
    
        <!-- Colors -->
    
        <SolidColorBrush x:Key="CheckBox.Stroke" Color="#8E8F8F"/>
        <SolidColorBrush x:Key="CheckBox.Fill" Color="#F4F4F4"/>
        <!-- Check mark -->
        <SolidColorBrush x:Key="CheckBox.Glyph.Stroke.Normal" Color="#FFFFFF"/>
        <SolidColorBrush x:Key="CheckBox.Glyph.Stroke.Pressed" Color="#B2FFFFFF"/>
        <SolidColorBrush x:Key="CheckBox.Glyph.Fill.Normal" Color="#31347C"/>
        <SolidColorBrush x:Key="CheckBox.Glyph.Fill.Pressed" Color="#B231347C"/>
        <SolidColorBrush x:Key="CheckBox.Glyph.Fill.Disabled" Color="#AEB7CF"/>
        <!-- Inner border -->
        <LinearGradientBrush x:Key="CheckBox.InnerBorder" StartPoint="0,0" EndPoint="1,1">
            <GradientStop Color="#AEB3B9" Offset="0.25"/>
            <GradientStop Color="#C2C4C6" Offset="0.5"/>
            <GradientStop Color="#EAEBEB" Offset="1"/>
        </LinearGradientBrush>
        <LinearGradientBrush x:Key="CheckBox.InnerBorder.Hover" StartPoint="0,0" EndPoint="1,1">
            <GradientStop Color="#79C6F9" Offset="0.3"/>
            <GradientStop Color="#79C6F9" Offset="0.5"/>
            <GradientStop Color="#D2EDFD" Offset="1"/>
        </LinearGradientBrush>
        <LinearGradientBrush x:Key="CheckBox.InnerBorder.Pressed" StartPoint="0,0" EndPoint="1,1">
            <GradientStop Color="#54A6D5" Offset="0.3"/>
            <GradientStop Color="#5EB5E4" Offset="0.5"/>
            <GradientStop Color="#C4E5F6" Offset="1"/>
        </LinearGradientBrush>
        <!-- Indeterminate inner border -->
        <LinearGradientBrush x:Key="CheckBox.InnerBorder.Indeterminate" StartPoint="0,0" EndPoint="1,1">
            <GradientStop Color="#2A628D" Offset="0"/>
            <GradientStop Color="#245479" Offset="0.5"/>
            <GradientStop Color="#193B55" Offset="1"/>
        </LinearGradientBrush>
        <LinearGradientBrush x:Key="CheckBox.InnerBorder.IndeterminateHover" StartPoint="0,0" EndPoint="1,1">
            <GradientStop Color="#29628D" Offset="0"/>
            <GradientStop Color="#245479" Offset="0.5"/>
            <GradientStop Color="#193B55" Offset="1"/>
        </LinearGradientBrush>
        <LinearGradientBrush x:Key="CheckBox.InnerBorder.IndeterminatePressed" StartPoint="0,0" EndPoint="1,1">
            <GradientStop Color="#193B55" Offset="0"/>
            <GradientStop Color="#245479" Offset="0.5"/>
            <GradientStop Color="#29628D" Offset="1"/>
        </LinearGradientBrush>
        <!-- Indeterminate highlight -->
        <LinearGradientBrush x:Key="CheckBox.Highlight.Indeterminate" StartPoint="0,0" EndPoint="1,1">
            <GradientStop Color="#80FFFFFF" Offset="0"/>
            <GradientStop Color="#00FFFFFF" Offset="0.5"/>
            <GradientStop Color="#003333A0" Offset="0.5"/>
            <GradientStop Color="#003333A0" Offset="1"/>
        </LinearGradientBrush>
        <LinearGradientBrush x:Key="CheckBox.Highlight.IndeterminateHover" StartPoint="0,0" EndPoint="1,1">
            <GradientStop Color="#80FFFFFF" Offset="0"/>
            <GradientStop Color="#00FFFFFF" Offset="0.5"/>
            <GradientStop Color="#003333A0" Offset="0.5"/>
            <GradientStop Color="#803333A0" Offset="1"/>
        </LinearGradientBrush>
        <LinearGradientBrush x:Key="CheckBox.Highlight.IndeterminatePressed" StartPoint="0,0" EndPoint="1,1">
            <GradientStop Color="#00FFFFFF" Offset="0.5"/>
            <GradientStop Color="#20FFFFFF" Offset="1"/>
        </LinearGradientBrush>
    
        <!-- Images -->
    
        <PathGeometry x:Key="CheckBox.Glyph.Geometry">
            <PathFigure StartPoint="9.0, 1.833" IsClosed="True">
                <LineSegment Point="10.667, 3.167"/>
                <LineSegment Point="7, 10.667"/>
                <LineSegment Point="5.333, 10.667"/>
                <LineSegment Point="3.333, 8.167"/>
                <LineSegment Point="3.333, 6.833"/>
                <LineSegment Point="4.833, 6.5"/>
                <LineSegment Point="6, 8"/>
            </PathFigure>
        </PathGeometry>
    
        <!-- Styles -->
    
        <Style x:Key="{x:Type CheckBox}" TargetType="{x:Type CheckBox}">
            <Setter Property="FocusVisualStyle" Value="{StaticResource Bullet.FocusVisual.Empty}"/>
            <Setter Property="Foreground" Value="{xx:SystemBrush ControlText}"/>
            <Setter Property="Background" Value="{StaticResource CheckBox.Fill}"/>
            <Setter Property="BorderBrush" Value="{StaticResource CheckBox.Stroke}"/>
            <Setter Property="BorderThickness" Value="1"/>
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="{x:Type CheckBox}">
                        <BulletDecorator Background="Transparent" SnapsToDevicePixels="True">
                            <BulletDecorator.Bullet>
                                <Grid Width="13" Height="13">
                                    <Rectangle x:Name="Background" Margin="0" Fill="{TemplateBinding Background}"/>
                                    <Rectangle x:Name="BackgroundOverlay" Margin="0"/>
                                    <Rectangle x:Name="InnerFill" Margin="3" Fill="{StaticResource Bullet.InnerFill.Normal}"/>
                                    <Rectangle x:Name="InnerBorder" Margin="2" Stroke="{StaticResource CheckBox.InnerBorder}"/>
                                    <Rectangle x:Name="Highlight" Margin="3"/>
                                    <Path x:Name="GlyphStroke" Margin="0" StrokeThickness="1.5" Data="{StaticResource CheckBox.Glyph.Geometry}"/>
                                    <Path x:Name="GlyphFill" Margin="0" Data="{StaticResource CheckBox.Glyph.Geometry}"/>
                                    <Rectangle x:Name="Border" Margin="0" Stroke="{TemplateBinding BorderBrush}"/>
                                    <Rectangle x:Name="BorderOverlay" Margin="0"/>
                                </Grid>
                            </BulletDecorator.Bullet>
                            <ContentPresenter SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" Margin="{TemplateBinding Padding}"
                                    VerticalAlignment="{TemplateBinding VerticalContentAlignment}" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
                                    RecognizesAccessKey="True"/>
                        </BulletDecorator>
                        <ControlTemplate.Triggers>
    
                            <Trigger Property="HasContent" Value="True">
                                <!-- if (HasContent) -->
                                <Setter Property="FocusVisualStyle" Value="{StaticResource Bullet.FocusVisual.Normal}"/>
                                <Setter Property="Padding" Value="4,0,0,0"/>
                            </Trigger>
    
                            <Trigger Property="IsMouseOver" Value="True">
                                <!-- if (IsMouseOver) -->
                                <Setter TargetName="BackgroundOverlay" Property="Fill" Value="{StaticResource Bullet.BackgroundOverlay.Hover}"/>
                                <Setter TargetName="InnerFill" Property="Fill" Value="{StaticResource Bullet.InnerFill.Hover}"/>
                                <Setter TargetName="BorderOverlay" Property="Stroke" Value="{StaticResource Bullet.BorderOverlay.Hover}"/>
                                <Setter TargetName="InnerBorder" Property="Stroke" Value="{StaticResource CheckBox.InnerBorder.Hover}"/>
                            </Trigger>
                            <Trigger Property="IsChecked" Value="{x:Null}">
                                <!-- if (IsChecked == null) -->
                                <Setter TargetName="Highlight" Property="Stroke" Value="{StaticResource CheckBox.Highlight.Indeterminate}"/>
                                <Setter TargetName="InnerFill" Property="Fill" Value="{StaticResource Bullet.Fill.Indeterminate}"/>
                                <Setter TargetName="InnerBorder" Property="Stroke" Value="{StaticResource CheckBox.InnerBorder.Indeterminate}"/>
                            </Trigger>
                            <Trigger Property="IsPressed" Value="True">
                                <!-- if (IsPressed) -->
                                <Setter TargetName="BackgroundOverlay" Property="Fill" Value="{StaticResource Bullet.BackgroundOverlay.Pressed}"/>
                                <Setter TargetName="InnerFill" Property="Fill" Value="{StaticResource Bullet.InnerFill.Pressed}"/>
                                <Setter TargetName="BorderOverlay" Property="Stroke" Value="{StaticResource Bullet.BorderOverlay.Pressed}"/>
                                <Setter TargetName="InnerBorder" Property="Stroke" Value="{StaticResource CheckBox.InnerBorder.Pressed}"/>
                            </Trigger>
                            <MultiTrigger>
                                <!-- if (IsChecked == null && IsMouseOver) -->
                                <MultiTrigger.Conditions>
                                    <Condition Property="IsChecked" Value="{x:Null}"/>
                                    <Condition Property="IsMouseOver" Value="True"/>
                                </MultiTrigger.Conditions>
                                <Setter TargetName="Highlight" Property="Stroke" Value="{StaticResource CheckBox.Highlight.IndeterminateHover}"/>
                                <Setter TargetName="InnerFill" Property="Fill" Value="{StaticResource Bullet.Fill.IndeterminateHover}"/>
                                <Setter TargetName="InnerBorder" Property="Stroke" Value="{StaticResource CheckBox.InnerBorder.IndeterminateHover}"/>
                            </MultiTrigger>
    
                            <MultiTrigger>
                                <!-- if (IsChecked == null && IsPressed) -->
                                <MultiTrigger.Conditions>
                                    <Condition Property="IsChecked" Value="{x:Null}"/>
                                    <Condition Property="IsPressed" Value="True"/>
                                </MultiTrigger.Conditions>
                                <Setter TargetName="Highlight" Property="Stroke" Value="{StaticResource CheckBox.Highlight.IndeterminatePressed}"/>
                                <Setter TargetName="InnerFill" Property="Fill" Value="{StaticResource Bullet.Fill.IndeterminatePressed}"/>
                                <Setter TargetName="InnerBorder" Property="Stroke" Value="{StaticResource CheckBox.InnerBorder.IndeterminatePressed}"/>
                            </MultiTrigger>
                            <MultiTrigger>
                                <!-- if (IsChecked == true && IsPressed) -->
                                <MultiTrigger.Conditions>
                                    <Condition Property="IsChecked" Value="True"/>
                                    <Condition Property="IsPressed" Value="True"/>
                                </MultiTrigger.Conditions>
                                <Setter TargetName="GlyphStroke" Property="Stroke" Value="{StaticResource CheckBox.Glyph.Stroke.Pressed}"/>
                                <Setter TargetName="GlyphFill" Property="Fill" Value="{StaticResource CheckBox.Glyph.Fill.Pressed}"/>
                            </MultiTrigger>
                            <MultiTrigger>
                                <!-- if (IsChecked == true && !IsPressed) -->
                                <MultiTrigger.Conditions>
                                    <Condition Property="IsChecked" Value="True"/>
                                    <Condition Property="IsPressed" Value="False"/>
                                </MultiTrigger.Conditions>
                                <Setter TargetName="GlyphStroke" Property="Stroke" Value="{StaticResource CheckBox.Glyph.Stroke.Normal}"/>
                                <Setter TargetName="GlyphFill" Property="Fill" Value="{StaticResource CheckBox.Glyph.Fill.Normal}"/>
                            </MultiTrigger>
    
                            <Trigger Property="IsEnabled" Value="False">
                                <!-- if (!IsEnabled) -->
                                <Setter Property="Foreground" Value="{xx:SystemBrush GrayText}"/>
                                <Setter TargetName="InnerFill" Property="Fill" Value="{x:Null}"/>
                                <Setter TargetName="GlyphStroke" Property="Stroke" Value="{x:Null}"/>
                                <Setter TargetName="BorderOverlay" Property="Stroke" Value="{StaticResource Bullet.BorderOverlay.Disabled}"/>
                                <Setter TargetName="InnerBorder" Property="Stroke" Value="{StaticResource Bullet.InnerBorder.Disabled}"/>
                            </Trigger>
                            <MultiTrigger>
                                <!-- if (IsChecked == null && !IsEnabled) -->
                                <MultiTrigger.Conditions>
                                    <Condition Property="IsChecked" Value="{x:Null}"/>
                                    <Condition Property="IsEnabled" Value="False"/>
                                </MultiTrigger.Conditions>
                                <Setter TargetName="InnerFill" Property="Fill" Value="{StaticResource Bullet.Fill.IndeterminateDisabled}"/>
                                <Setter TargetName="InnerBorder" Property="Stroke" Value="{StaticResource Bullet.InnerBorder.IndeterminateDisabled}"/>
                            </MultiTrigger>
                            <MultiTrigger>
                                <!-- if (IsChecked == true && !IsEnabled) -->
                                <MultiTrigger.Conditions>
                                    <Condition Property="IsChecked" Value="True"/>
                                    <Condition Property="IsEnabled" Value="False"/>
                                </MultiTrigger.Conditions>
                                <Setter TargetName="GlyphFill" Property="Fill" Value="{StaticResource CheckBox.Glyph.Fill.Disabled}"/>
                            </MultiTrigger>
    
                        </ControlTemplate.Triggers>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>
    
    </ResourceDictionary>
    

    Notes:

    1. {xx:SystemBrush ControlText} is a shortcut for {DynamicResource {x:Static SystemColors.ControlTextBrushKey}}. You can either use that shortcut or just find and replace with regex.

    2. RTL, animations, weird cases are not supported.

    3. This style is slower than using BulletChrome which is heavily optimized.

    0 讨论(0)
提交回复
热议问题