在 WPF 中优雅地覆盖 ComboBox 的 ToggleButton 样式

Posted

技术标签:

【中文标题】在 WPF 中优雅地覆盖 ComboBox 的 ToggleButton 样式【英文标题】:Elegantly override style of ComboBox's ToggleButton in WPF 【发布时间】:2010-12-20 11:24:35 【问题描述】:

我有一个关于如何优雅地覆盖控件可视化树深处的任意元素的问题。我也尝试过用几种不同的方式来解决它,但是每种方式我都遇到了几个问题。通常,当我尝试三种不同的路径并且每一种都失败时,我会下楼,喝杯咖啡,然后问比我更聪明的人。所以我来了。

具体说明:

我想扁平化组合框的样式,这样它就不会引起对自身的注意。我希望它类似于 Windows.Forms.ComboBox 的 FlatStyle 我希望它在 Windows 7 和 XP 上看起来相同。

主要是,我想改变 ComboBox 的 ToggleButton 的外观。

我可以只使用 Blend 并撕掉控制模板的内容并手动更改它们。这听起来对我来说不是很开胃。

我尝试使用一种样式来覆盖 ToggleButton 的背景,但事实证明,整个 ComboBox 控件实际上是 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>

所以我放弃并使用 Blend 将其撕掉。我发现它实际上是一个名为 ComboBoxTransparentButtonStyle 的样式,其目标类型为 ToggleButton。该样式设置了一个使用 DockPanel 的 ControlTemplate,该 DockPanel 的右侧设置了“Microsoft_Windows_Themes:ClassicBorderDecorator”类型,这就是我们真正想要控制的。 (到目前为止你和我在一起吗?) 这是图片:

<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>

精氨酸。 WPF 不是爆炸吗?

所以我提取了样式 ComboBoxTransparentButtonStyle 并将其放到另一个项目的 application.resources 中。问题是我无法将该样式应用于 ComboBox,因为我提取的样式的 targetType 为 ToggleButton,因此 TargetTypes 不匹配。

tl;博士你们会怎么做?

【问题讨论】:

【参考方案1】:

可行的解决方案


您好,此样式满足您的需求,请随时根据需要进行编辑:

    <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>

注意切换按钮样式中的DataTrigger 部分,它挂钩到其模板父级的IsKeyboardFocusWithin 属性而不是IsFocused 属性,因为如果您将ComboBox.IsEditable 设置为@987654326,最后一个将不起作用@ 就像我在这种风格中所做的那样。

<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 >

【讨论】:

【参考方案2】:

对此没有优雅的解决方案。您可以做的最好的事情是覆盖整个 ComboBox 的样式,以便您可以更改它为 ToggleButton 设置的样式。

您可以使用 Blend 来获取样式,但这可能不是最简单的方法。如果您安装了 Blend,请转到“[程序文件或 Blend 的安装位置]\SystemThemes\WPF\areo.normalcolor.xaml”。

【讨论】:

以上是关于在 WPF 中优雅地覆盖 ComboBox 的 ToggleButton 样式的主要内容,如果未能解决你的问题,请参考以下文章

WPF ComboBox 最初以错误的大小显示第一个项目

WPF,mvvm,在combobox中嵌套了checkbox控件,数据绑定完成后,无法实现双向绑定

在wpf中怎么绑定comboBox的值

在WPF中的Combobox中绑定

wpf combobox样式不显示倒三角

WPF 控件功能重写(ComboBox回车搜索)