WPF自定义控件06:FlatComboBox

       在不少的应用程序中,经常需要使用下拉框(ComboBox)控件来从类别字段中选择,这样可以限定值的范围,让数据更加的规范,便于统计和分析。本文将介绍一下自定义的下拉框控件FlatComboBox,它与FlatCheckBox实现过程非常类似,它是继承自ComboBox,但使用自定义的UI样式来美化界面。下面将详细介绍具体的实现细节。

1 WPF项目结构


     基于之前创建的WPF示例项目,在其中创建一个新的关于FlatComboBox的项目文件。本质上,FlatComboBox是继承ComboBox控件,利用ComboBox控件自身的属性和方法,可以减少FlatComboBox实现的难度和复杂度。首先,给出本项目文件结构,如下图所示:

WPF自定义控件06:FlatComboBox

   其中的Fonts目录下存放各种图标字体文件,Style目录下存放各种控件的UI 样式定义文件,FlatComboBox.xaml就是FlatComboBox控件的样式定义文件。另外,需要将其注册到Generic.xaml文件中,示例代码如下:

<ResourceDictionary
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:Yd.WpfControls">
    <ResourceDictionary.MergedDictionaries>
        <ResourceDictionary Source="/Yd.WpfControls;component/Style/IconFont.xaml"/>
        <ResourceDictionary Source="/Yd.WpfControls;component/Style/FlatButton.xaml"/>
        <ResourceDictionary Source="/Yd.WpfControls;component/Style/FlatCheckBox.xaml"/>
        <ResourceDictionary Source="/Yd.WpfControls;component/Style/FlatRadioButton.xaml"/>
        <ResourceDictionary Source="/Yd.WpfControls;component/Style/ToggleButton.xaml"/>
        <ResourceDictionary Source="/Yd.WpfControls;component/Style/FlatComboBox.xaml"/>
    </ResourceDictionary.MergedDictionaries>

2 WPF FlatComboBox实现


     首先在Yd.WpfControls项目中添加一个类FlatComboBox.cs,它继承自ComboBox控件,示例代码如下:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
namespace Yd.WpfControls
{
    public class FlatComboBox : ComboBox
    {
        static FlatComboBox()
        {
            DefaultStyleKeyProperty.OverrideMetadata(typeof(FlatComboBox),
                new FrameworkPropertyMetadata(typeof(FlatComboBox)));
        }
    }
}

FlatComboBox控件的UI样式主要就是依靠FlatComboBox.xaml文件进行定义的,示例代码如下:

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                    xmlns:local="clr-namespace:Yd.WpfControls">
    <!--图标字体引入-->
    <ResourceDictionary.MergedDictionaries>
        <ResourceDictionary  Source="/Yd.WpfControls;component/Style/IconFont.xaml"/>
    </ResourceDictionary.MergedDictionaries>
    <!-- Flat ComboBox -->
    <SolidColorBrush x:Key="ComboBoxNormalBorderBrush" Color="#e3e9ef" />
    <SolidColorBrush x:Key="ComboBoxNormalBackgroundBrush" Color="#fff" />
    <SolidColorBrush x:Key="ComboBoxDisabledForegroundBrush" Color="#999" />
    <SolidColorBrush x:Key="ComboBoxDisabledBorderBrush" Color="#999" />
    <SolidColorBrush x:Key="ComboBoxDisabledBackgroundBrush" Color="#eee" />

    <Style x:Key="{x:Type ComboBoxItem}" TargetType="{x:Type ComboBoxItem}">
        <Setter Property="SnapsToDevicePixels" Value="True" />
        <Setter Property="OverridesDefaultStyle" Value="True" />
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type ComboBoxItem}">
                    <Border x:Name="Border"
                Padding="1" SnapsToDevicePixels="True" Background="Transparent">
                        <VisualStateManager.VisualStateGroups>
                            <VisualStateGroup x:Name="SelectionStates">
                                <VisualState x:Name="Unselected" />
                                <VisualState x:Name="Selected">
                                    <Storyboard>
                                        <ColorAnimationUsingKeyFrames Storyboard.TargetName="Border"
                                                Storyboard.TargetProperty="(Panel.Background).(SolidColorBrush.Color)">
                                            <EasingColorKeyFrame KeyTime="0"
                                         Value="#BDC3C7" />
                                        </ColorAnimationUsingKeyFrames>
                                    </Storyboard>
                                </VisualState>
                                <VisualState x:Name="SelectedUnfocused">
                                    <Storyboard>
                                        <ColorAnimationUsingKeyFrames Storyboard.TargetName="Border"
                                                Storyboard.TargetProperty="(Panel.Background). (SolidColorBrush.Color)">
                                            <EasingColorKeyFrame KeyTime="0"
                                         Value="#e3e9ef" />
                                        </ColorAnimationUsingKeyFrames>
                                    </Storyboard>
                                </VisualState>
                            </VisualStateGroup>
                        </VisualStateManager.VisualStateGroups>
                        <ContentPresenter />
                    </Border>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
    <ControlTemplate TargetType="ToggleButton" x:Key="ComboBoxToggleButtonTemplate">
        <Grid>
            <Grid.ColumnDefinitions>
                <ColumnDefinition />
                <ColumnDefinition Width="26" />
            </Grid.ColumnDefinitions>
            <Border Grid.ColumnSpan="2" Name="Border"
              BorderBrush="#16a085" 
              CornerRadius="0" BorderThickness="1, 1, 1, 1" 
              Background="#16a085" />
            <Border Grid.Column="1" Margin="1, 1, 1, 1" BorderBrush="#16a085" Name="ButtonBorder"
              CornerRadius="0, 0, 0, 0" BorderThickness="0, 0, 0, 0" 
              Background="#16a085" />

            <TextBlock  Name="Arrow" Grid.Column="1"  Text="&#xf0d7;" 
                        Style="{StaticResource faFont}" SnapsToDevicePixels="True"
                        FontSize="20" HorizontalAlignment="Left"   VerticalAlignment="Center" 
                        Margin="3,3,3,3" Background="Transparent"
                        Foreground="White"/>

        </Grid>
        <ControlTemplate.Triggers>
            <Trigger Property="UIElement.IsMouseOver" Value="True">
                <Setter Property="Panel.Background" TargetName="ButtonBorder" Value="#16a085"/>
                <Setter Property="Shape.Fill" TargetName="Arrow" Value="Transparent"/>
            </Trigger>
            <Trigger Property="ToggleButton.IsChecked" Value="True">
                <Setter Property="Panel.Background" TargetName="ButtonBorder" Value="#16a085"/>
                <Setter Property="Shape.Fill" TargetName="Arrow" Value="Transparent"/>
            </Trigger>
            <Trigger Property="UIElement.IsEnabled" Value="False">
                <Setter Property="Panel.Background" TargetName="Border" Value="{StaticResource ComboBoxDisabledBackgroundBrush}"/>
                <Setter Property="Panel.Background" TargetName="ButtonBorder" Value="{StaticResource ComboBoxDisabledBackgroundBrush}"/>
                <Setter Property="Border.BorderBrush" TargetName="ButtonBorder" Value="{StaticResource ComboBoxDisabledBorderBrush}"/>
                <Setter Property="TextElement.Foreground" Value="{StaticResource ComboBoxDisabledForegroundBrush}"/>
                <Setter Property="Shape.Fill" TargetName="Arrow" Value="Transparent"/>
            </Trigger>
        </ControlTemplate.Triggers>
    </ControlTemplate>

    <Style x:Key="ComboBoxFlatStyle"  TargetType="{x:Type ComboBox}">
        <Setter Property="UIElement.SnapsToDevicePixels" Value="True"/>
        <Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Auto"/>
        <Setter Property="ScrollViewer.VerticalScrollBarVisibility" Value="Auto"/>
        <Setter Property="ScrollViewer.CanContentScroll" Value="True"/>
        <Setter Property="TextElement.Foreground" Value="White"/>
        <Setter Property="FrameworkElement.FocusVisualStyle" Value="{x:Null}"/>
        <Setter Property="Control.Template">
            <Setter.Value>
                <ControlTemplate TargetType="ComboBox">
                    <Grid>
                        <ToggleButton Name="ToggleButton" Grid.Column="2"
                ClickMode="Press" Focusable="False"
                IsChecked="{Binding Path=IsDropDownOpen, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}"
                Template="{StaticResource ComboBoxToggleButtonTemplate}"/>

                        <ContentPresenter Name="ContentSite" Margin="5, 3, 23, 3" IsHitTestVisible="False"
                              HorizontalAlignment="Left" VerticalAlignment="Center"   
                              Content="{TemplateBinding SelectionBoxItem}" 
                              ContentTemplate="{TemplateBinding ComboBox.SelectionBoxItemTemplate}"
                              ContentTemplateSelector="{TemplateBinding ItemTemplateSelector}"/>
                        <TextBox Name="PART_EditableTextBox" Margin="3, 3, 23, 3"                     
                     IsReadOnly="{TemplateBinding IsReadOnly}"
                     Visibility="Hidden" Background="Transparent"
                     HorizontalAlignment="Left" VerticalAlignment="Center" Foreground="{x:Static local:FlatColors.CLOUDS}"
                     Focusable="True" >
                            <TextBox.Template>
                                <ControlTemplate TargetType="TextBox" >
                                    <Border Name="PART_ContentHost" Focusable="False" />
                                </ControlTemplate>
                            </TextBox.Template>
                        </TextBox>
                        <!-- Popup showing items -->
                        <Popup Name="Popup" Placement="Bottom"
                   Focusable="False" AllowsTransparency="True"
                   IsOpen="{TemplateBinding ComboBox.IsDropDownOpen}"
                   PopupAnimation="Slide">
                            <Grid Name="DropDown" SnapsToDevicePixels="True"
                    MinWidth="{TemplateBinding FrameworkElement.ActualWidth}"
                    MaxHeight="{TemplateBinding ComboBox.MaxDropDownHeight}">
                                <Border Name="DropDownBorder" Background="#ECF0F1" Margin="0, 1, 0, 0"
                        CornerRadius="0" BorderThickness="1,1,1,1" 
                        BorderBrush="#ECF0F1"/>
                                <ScrollViewer Margin="1" SnapsToDevicePixels="True" Foreground="Black" FocusVisualStyle="{x:Null}">
                                    <ItemsPresenter KeyboardNavigation.DirectionalNavigation="Contained" />
                                </ScrollViewer>
                            </Grid>
                        </Popup>
                    </Grid>
                    <ControlTemplate.Triggers>
                        <Trigger Property="ItemsControl.HasItems" Value="False">
                            <Setter Property="FrameworkElement.MinHeight" TargetName="DropDownBorder" Value="95"/>
                        </Trigger>
                        <Trigger Property="UIElement.IsEnabled" Value="False">
                            <Setter Property="TextElement.Foreground" Value="{StaticResource ComboBoxDisabledForegroundBrush}"/>
                        </Trigger>
                        <Trigger Property="ItemsControl.IsGrouping" Value="True">
                            <Setter Property="ScrollViewer.CanContentScroll" Value="False"/>
                        </Trigger>
                        <Trigger Property="ComboBox.IsEditable" Value="True">
                            <Setter Property="KeyboardNavigation.IsTabStop" Value="False"/>
                            <Setter Property="UIElement.Visibility" TargetName="PART_EditableTextBox" Value="Visible"/>
                            <Setter Property="UIElement.Visibility" TargetName="ContentSite" Value="Hidden"/>
                        </Trigger>
                    </ControlTemplate.Triggers>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
    <Style TargetType="{x:Type local:FlatComboBox}" BasedOn="{StaticResource ComboBoxFlatStyle}" >
        <Setter Property="FontSize" Value="{x:Static local:FlatFonts.contentFontSize}"></Setter>
        <Setter Property="Foreground" Value="White"></Setter>
    </Style>
</ResourceDictionary>

    其中  <Style TargetType="{x:Type local:FlatComboBox}" BasedOn="{StaticResource ComboBoxFlatStyle}"  > 语句中的BasedOn表示继承关系,即一个新的Style可以通过BasedOn来继承,这样就可以更加方便的管理样式。另外,下拉框中的小三角形状是通过图标字体实现的,<TextBlock  Name="Arrow" Grid.Column="1"  Text="&#xf0d7;"  Style="{StaticResource faFont}" ...>  。

3 WPF FlatComboBox测试


     首先,需要重新生成一下项目文件,然后在WpfControls项目中添加自定义控件FlatComboBox,MainWindow.xaml部分示例代码如下:

<WpfControls:FlatComboBox HorizontalAlignment="Left" Height="30" 
             x:Name="ccb" 
             Margin="424,181,0,0" VerticalAlignment="Top" Width="255"/>

其中的x:Name="ccb"给控件起一个名称,这样可以在后台用C#来进行访问,下面给出后台初始化数据的示例代码:

public partial class MainWindow : Window
{
        public MainWindow()
        {
            InitializeComponent();
            List<String> list = new List<string>();
            list.Add("国学");
            list.Add("高数");
            list.Add("绘画");
            this.ccb.ItemsSource = list;
        }
}

运行界面如下:

WPF自定义控件06:FlatComboBox

上一篇:WPF自定义控件05:ToggleButton


下一篇:Python的Django框架的运行方式及处理流程