Подтвердить что ты не робот

Элегантно переопределить стиль ComboBox ToggleButton в WPF

У меня вопрос о том, как элегантно переопределить произвольный элемент внутри дерева визуальных элементов управления. Я также попытался разрешить это несколькими способами, но у меня возникли проблемы с каждым из них. Обычно, когда я пробую три разных пути и терплю неудачу, я спускаюсь вниз, выпью кофе и спрашиваю кого-то умнее меня. Итак, я здесь.

Особенности:

Я хочу сгладить стиль комбинированного блока, чтобы он не привлекал к себе внимания. Я хочу, чтобы он был похож на 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. Стиль устанавливает ControlTemplate, который использует DockPanel с типом "Microsoft_Windows_Themes: ClassicBorderDecorator", установленным справа, и именно это мы пытаемся контролировать. (Вы со мной до сих пор?)
Здесь pic:

Example

<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. Разве WPF не взрывается?

Итак, я извлек стиль ComboBoxTransparentButtonStyle и опустил его в другой проект application.resources. Проблема в том, что я не могу применить этот стиль к ComboBox, потому что у моего стильного стиля есть targetType ToggleButton, поэтому TargeTypes не совпадают.

tl; dr как бы вы, ребята, это сделали?

4b9b3361

Ответ 1

Для этого нет элегантного решения. Лучшее, что вы можете сделать, это переопределить стиль для всего ComboBox, чтобы вы могли изменить стиль, который он устанавливает для ToggleButton.

Вы можете использовать Blend для получения стилей, однако этот probabliy не самый простой способ. Если у вас установлен Blend, перейдите к "[Файлы программы или где установлен Blend]\SystemThemes\WPF\areo.normalcolor.xaml".

Ответ 2

Рабочее решение


Привет, этот стиль удовлетворяет ваши потребности, не стесняйтесь редактировать его по мере необходимости:

    <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 свойство istead свойства IsFocused, потому что последний не будет работать, если вы установите ComboBox.IsEditable в True как я сделал в этом стиле.

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