在不少的应用程序中,经常需要使用下拉框(ComboBox)控件来从类别字段中选择,这样可以限定值的范围,让数据更加的规范,便于统计和分析。本文将介绍一下自定义的下拉框控件FlatComboBox,它与FlatCheckBox实现过程非常类似,它是继承自ComboBox,但使用自定义的UI样式来美化界面。下面将详细介绍具体的实现细节。
1 WPF项目结构
基于之前创建的WPF示例项目,在其中创建一个新的关于FlatComboBox的项目文件。本质上,FlatComboBox是继承ComboBox控件,利用ComboBox控件自身的属性和方法,可以减少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="" 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="" 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; } }
运行界面如下: