Elegantly override style of ComboBox's ToggleButton in WPF

巧了我就是萌 提交于 2019-12-09 14:20:29

问题


I have a question regarding how to elegantly override an arbitrary element deep inside a control's visual tree. I also have attempted to resolve it in a few different ways, but I've run into several problems with each. Usually when I try three different paths and fail at each one I go downstairs, have a coffee, and ask someone smarter than myself. So here I am.

Specifics:

I want to flatten the style of a combo box so that it will not draw attention to itself. I want it to be similar to Windows.Forms.ComboBox's FlatStyle I want it to look the same on Windows 7 and XP.

Mainly, I want to change the look of a ComboBox's ToggleButton.

I could just use Blend and rip the control template's guts out and manually change them. That doesn't sound very appetizing to me.

I tried using a style to override the ToggleButton's background, but it turns out that the whole ComboBox control is actually a front for a ToggleButton.

<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="ComboBoxExpiriment2.MainWindow"
x:Name="Window"
xmlns:Microsoft_Windows_Themes="clr-namespace:Microsoft.Windows.Themes;assembly=PresentationFramework.Classic" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Title="MainWindow"
Width="204" Height="103">
<Grid x:Name="LayoutRoot">
    <ComboBox HorizontalAlignment="Left" Margin="32,26.723,0,0" Width="120" VerticalAlignment="Top" Height="21.277">
    <ComboBox.Style>
      <Style>
        <Setter Property="ToggleButton.Background" Value="Green" />
      </Style>
    </ComboBox.Style>
    </ComboBox>
</Grid>

So I gave up and ripped it using Blend. I found that it's actually a Style called ComboBoxTransparentButtonStyle with a target type of ToggleButton. The style sets a ControlTemplate that uses a DockPanel that has a "Microsoft_Windows_Themes:ClassicBorderDecorator" type set to the right, and that's what we're actually trying to control. (Are you with me so far?)
Here's the pic:

<Style x:Key="ComboBoxTransparentButtonStyle" TargetType="{x:Type ToggleButton}">
                <Setter Property="MinWidth" Value="0"/>
                <Setter Property="MinHeight" Value="0"/>
                <Setter Property="Width" Value="Auto"/>
                <Setter Property="Height" Value="Auto"/>
                <Setter Property="Background" Value="Transparent"/>
                <Setter Property="BorderBrush" Value="{x:Static Microsoft_Windows_Themes:ClassicBorderDecorator.ClassicBorderBrush}"/>
                <Setter Property="BorderThickness" Value="2"/>
                <Setter Property="Template">
                    <Setter.Value>
                        <ControlTemplate TargetType="{x:Type ToggleButton}">
                            <DockPanel SnapsToDevicePixels="true" Background="{TemplateBinding Background}" LastChildFill="false">
                                <Microsoft_Windows_Themes:ClassicBorderDecorator x:Name="Border" Width="{DynamicResource {x:Static SystemParameters.VerticalScrollBarWidthKey}}" DockPanel.Dock="Right" Background="Green" BorderBrush="{TemplateBinding BorderBrush}" BorderStyle="None" BorderThickness="{TemplateBinding BorderThickness}">
                                    <Path Fill="{TemplateBinding Foreground}" HorizontalAlignment="Center" VerticalAlignment="Center" Data="{StaticResource DownArrowGeometry}"/>
                                </Microsoft_Windows_Themes:ClassicBorderDecorator>
                            </DockPanel>
                            <ControlTemplate.Triggers>
                                <Trigger Property="IsChecked" Value="true">
                                    <Setter Property="BorderStyle" TargetName="Border" Value="AltPressed"/>
                                </Trigger>
                            </ControlTemplate.Triggers>
                        </ControlTemplate>
                    </Setter.Value>
                </Setter>
                <Style.Triggers>
                    <Trigger Property="IsEnabled" Value="false">
                        <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.ControlDarkBrushKey}}"/>
                    </Trigger>
                </Style.Triggers>
            </Style>

Arg. Isn't WPF a blast?

So I extracted the style ComboBoxTransparentButtonStyle and dropped it into another project's application.resources. Problem is I can't apply that style to a ComboBox because the style I extracted has a targetType of ToggleButton, so the TargeTypes don't match.

tl;dr how would you guys do it?


回答1:


There isn't an elegant solution for this. The best you can do is override the style for the entire ComboBox so that you can change the style it sets for the ToggleButton.

You can use Blend to get the styles, however that probabliy isn't the easiest way. If you have Blend installed, go to "[Program files or where Blend is installed]\SystemThemes\WPF\areo.normalcolor.xaml".




回答2:


A Working Solution


Hi, this style satisfies your needs, feel free to edit it as you need:

    <SolidColorBrush x:Key="WindowBackgroundBrush" Color="White" />

<SolidColorBrush x:Key="MainColor" Color="DeepSkyBlue"/>
<SolidColorBrush x:Key="MainColorLight" Color="LightSkyBlue"/>
<SolidColorBrush x:Key="MainColorDark" Color="#00A7DF"/>

<SolidColorBrush x:Key="BorderMainBrush" Color="LightGray"/>
<SolidColorBrush x:Key="BorderDarkMainBrush" Color="#C0C0C0"/>

<SolidColorBrush x:Key="BackgroundGrayDark" Color="#FFEFEFEF"/>
<SolidColorBrush x:Key="BackgroundGrayLight" Color="#F5F5F5"/>

<SolidColorBrush x:Key="ForegroundDisabledBrush" Color="DimGray"/>
<SolidColorBrush x:Key="ForegroundBrush" Color="Black"/>


<LinearGradientBrush x:Key="FormBackgroundBrush"
                             EndPoint="0.5,1" StartPoint="0.5,0">
    <GradientStop Color="#FFFFFD" Offset="0.31" />
    <GradientStop Color="#FFF8F8F8" Offset="1" />
</LinearGradientBrush>


<ControlTemplate x:Key="ComboBoxToggleButton" TargetType="ToggleButton">
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition />
            <ColumnDefinition Width="20" />
        </Grid.ColumnDefinitions>
        <VisualStateManager.VisualStateGroups>
            <VisualStateGroup x:Name="CommonStates">
                <VisualState x:Name="Normal"/>
                <VisualState x:Name="MouseOver"/>
                <VisualState x:Name="Pressed"/>
                <VisualState x:Name="Disabled"/>
            </VisualStateGroup>
        </VisualStateManager.VisualStateGroups>
        <Border x:Name="Border" SnapsToDevicePixels="True" Grid.ColumnSpan="2" Background="{DynamicResource BackgroundGrayDark}" BorderBrush="{DynamicResource BorderDarkMainBrush}" BorderThickness="1"  />
        <Border x:Name="Border2" Grid.Column="0" SnapsToDevicePixels="True" Margin="1" Background="{StaticResource WindowBackgroundBrush}" BorderBrush="{DynamicResource BorderDarkMainBrush}" BorderThickness="0,0,1,0" />
        <Path x:Name="Arrow" Grid.Column="1" Data="M 0 0 L 4 4 L 8 0 Z" Fill="DimGray" HorizontalAlignment="Center" VerticalAlignment="Center" />
    </Grid>
    <ControlTemplate.Triggers>
        <Trigger Property="ToggleButton.IsMouseOver" Value="true">
            <Setter Property="Background" TargetName="Border" Value="{DynamicResource MainColor}" />
            <Setter Property="BorderBrush" TargetName="Border" Value="{DynamicResource MainColor}" />
            <Setter Property="BorderBrush" TargetName="Border2" Value="{DynamicResource MainColor}" />
            <Setter Property="Fill" TargetName="Arrow" Value="White" />
        </Trigger>
        <Trigger Property="ToggleButton.IsChecked" Value="true">
            <Setter Property="Background" TargetName="Border" Value="{DynamicResource MainColorDark}" />
            <Setter Property="BorderBrush" TargetName="Border" Value="{DynamicResource MainColorDark}" />
            <Setter Property="BorderBrush" TargetName="Border2" Value="{DynamicResource MainColorDark}" />
            <Setter Property="Fill" TargetName="Arrow" Value="White" />
        </Trigger>
        <Trigger Property="IsEnabled" Value="False">
            <Setter Property="Background" TargetName="Border" Value="{DynamicResource BackgroundGrayLight}" />
            <Setter Property="BorderBrush" TargetName="Border" Value="{StaticResource BorderMainBrush}" />
            <Setter Property="Foreground" Value="{StaticResource ForegroundDisabledBrush}" />
        </Trigger>
        <DataTrigger Binding="{Binding IsKeyboardFocusWithin, RelativeSource={RelativeSource Mode=TemplatedParent}, Mode=OneWay}" Value="True">
            <Setter Property="Background" TargetName="Border" Value="{DynamicResource MainColorLight}" />
            <Setter Property="BorderBrush" TargetName="Border" Value="{DynamicResource MainColorLight}" />
            <Setter Property="BorderBrush" TargetName="Border2" Value="{DynamicResource MainColorLight}" />
            <Setter Property="Fill" TargetName="Arrow" Value="White" />
        </DataTrigger >
    </ControlTemplate.Triggers>
</ControlTemplate>

<ControlTemplate x:Key="ComboBoxTextBox" TargetType="TextBox">
    <Border x:Name="PART_ContentHost" Background="{TemplateBinding Background}" Focusable="False" />
</ControlTemplate>

<Style TargetType="ComboBox">
    <Setter Property="Validation.ErrorTemplate" Value="{x:Null}" />
    <Setter Property="VerticalAlignment" Value="Center" />
    <Setter Property="OverridesDefaultStyle" Value="true" />
    <Setter Property="IsEditable" Value="True"/>
    <Setter Property="SnapsToDevicePixels" Value="true" />
    <Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Auto" />
    <Setter Property="ScrollViewer.VerticalScrollBarVisibility" Value="Auto" />
    <Setter Property="ScrollViewer.CanContentScroll" Value="true" />
    <Setter Property="Margin" Value="2" />
    <Setter Property="MinHeight" Value="20" />
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="ComboBox">
                <Grid>
                    <VisualStateManager.VisualStateGroups>
                        <VisualStateGroup x:Name="CommonStates">
                            <VisualState x:Name="Normal"/>
                            <VisualState x:Name="MouseOver"/>
                            <VisualState x:Name="Disabled"/>
                        </VisualStateGroup>
                    </VisualStateManager.VisualStateGroups>
                    <ToggleButton x:Name="ToggleButton" Grid.Column="2" ClickMode="Press" Focusable="false"
                        IsChecked="{Binding IsDropDownOpen, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}"
                        Template="{StaticResource ComboBoxToggleButton}"/>

                    <ContentPresenter Margin="3,3,23,3" Content="{TemplateBinding SelectionBoxItem}"
                        ContentTemplate="{TemplateBinding SelectionBoxItemTemplate}"
                        ContentTemplateSelector="{TemplateBinding ItemTemplateSelector}"
                        HorizontalAlignment="Left" IsHitTestVisible="False" x:Name="ContentSite"
                        VerticalAlignment="Center" />

                    <TextBox Style="{x:Null}" x:Name="PART_EditableTextBox" Margin="3,3,23,3" Background="Transparent"
                        Focusable="True" HorizontalAlignment="Left" IsReadOnly="{TemplateBinding IsReadOnly}"
                        Template="{StaticResource ComboBoxTextBox}" VerticalAlignment="Center" Visibility="Hidden" />

                    <Popup AllowsTransparency="True" Focusable="False" IsOpen="{TemplateBinding IsDropDownOpen}" x:Name="Popup" Placement="Bottom" PopupAnimation="Fade">
                        <Grid MaxHeight="{TemplateBinding MaxDropDownHeight}" MinWidth="{TemplateBinding ActualWidth}" x:Name="DropDown" SnapsToDevicePixels="True">
                            <Border x:Name="DropDownBorder" Background="White" BorderBrush="{StaticResource BorderDarkMainBrush}" BorderThickness="1" CornerRadius="0" />
                            <ScrollViewer Margin="2" SnapsToDevicePixels="True">
                                <StackPanel KeyboardNavigation.DirectionalNavigation="Contained" IsItemsHost="True" TextBlock.Foreground="Black" />
                            </ScrollViewer>
                        </Grid>
                    </Popup>
                </Grid>
                <ControlTemplate.Triggers>
                    <Trigger Property="HasItems" Value="false">
                        <Setter Property="MinHeight" TargetName="DropDownBorder" Value="95" />
                    </Trigger>
                    <Trigger Property="IsGrouping" Value="true">
                        <Setter Property="ScrollViewer.CanContentScroll" Value="false" />
                    </Trigger>
                    <Trigger Property="IsEditable" Value="true">
                        <Setter Property="IsTabStop" Value="false" />
                        <Setter Property="Visibility" TargetName="PART_EditableTextBox" Value="Visible" />
                        <Setter Property="Visibility" TargetName="ContentSite" Value="Hidden" />
                    </Trigger>
                </ControlTemplate.Triggers>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
    <Style.Triggers>
    </Style.Triggers>
</Style>

Note the DataTrigger part inside the toggle button style, it hooks to its templated parent's IsKeyboardFocusWithin property istead of IsFocused property, because the last one won't work if you set the ComboBox.IsEditable to True as I did in this style.

<DataTrigger Binding="{Binding IsKeyboardFocusWithin, RelativeSource={RelativeSource Mode=TemplatedParent}, Mode=OneWay}" Value="True">
    <Setter Property="Background" TargetName="Border" Value="{DynamicResource MainColorLight}" />
    <Setter Property="BorderBrush" TargetName="Border" Value="{DynamicResource MainColorLight}" />
    <Setter Property="BorderBrush" TargetName="Border2" Value="{DynamicResource MainColorLight}" />
    <Setter Property="Fill" TargetName="Arrow" Value="White" />
</DataTrigger >


来源:https://stackoverflow.com/questions/1799974/elegantly-override-style-of-comboboxs-togglebutton-in-wpf

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!