WPF 自定义ComboBox样式

时间:2023-03-08 17:48:30

一、ComboBox基本样式

ComboBox有两种状态,可编辑和不可编辑状态。通过设置IsEditable属性可以切换控件状态。

先看基本样式效果:
WPF 自定义ComboBox样式

基本样式代码如下:

<!--ComboBox-->
<!--ComBoBox项选中背景色-->
<SolidColorBrush x:Key="ComboBoxSelectdBackground" Color="#ff8c69"/>
<!--ComBoBox项鼠标经过背景色-->
<SolidColorBrush x:Key="ComboBoxMouseOverBackground" Color="#ff3030"/>
<!--ComBoBox项选中前景色-->
<SolidColorBrush x:Key="ComboBoxSelectedForeground" Color="White"/>
<!--ComBoBox项鼠标经过前景色-->
<SolidColorBrush x:Key="ComboBoxMouseOverForegrond" Color="White"/>
<Style TargetType="{x:Type ComboBox}">
<Setter Property="ItemContainerStyle">
<Setter.Value>
<Style TargetType="ComboBoxItem">
<Setter Property="Height" Value="20"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ComboBoxItem}">
<Grid Height="{TemplateBinding Height}" Width="{TemplateBinding Width}">
<Border x:Name="_borderbg" Background="Transparent"/>
<TextBlock Margin="3 0 3 0" VerticalAlignment="Center" x:Name="_txt" Foreground="#333" Text="{Binding Content,RelativeSource={RelativeSource TemplatedParent}}"/>
<Border x:Name="_border" Background="White" Opacity="0"/>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsSelected" Value="true">
<Setter TargetName="_borderbg" Property="Background" Value="{StaticResource ComboBoxSelectdBackground}" />
<Setter TargetName="_txt" Property="Foreground" Value="{StaticResource ComboBoxSelectedForeground}"/>
</Trigger>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="IsSelected" Value="false"/>
<Condition Property="IsMouseOver" Value="true"/>
</MultiTrigger.Conditions>
<Setter TargetName="_borderbg" Property="Background" Value="{StaticResource ComboBoxMouseOverBackground}" />
<Setter TargetName="_txt" Property="Foreground" Value="{StaticResource ComboBoxMouseOverForegrond}"/>
</MultiTrigger> </ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Setter.Value>
</Setter>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ComboBox}">
<Grid> <Grid.ColumnDefinitions>
<ColumnDefinition Width="0.7*"/>
<ColumnDefinition Width="0.3*" MaxWidth="30"/>
</Grid.ColumnDefinitions>
<Border Grid.Column="0" Grid.ColumnSpan="2" BorderThickness="1" BorderBrush="{TemplateBinding BorderBrush}" CornerRadius="1,0,0,1"/>
<ContentPresenter HorizontalAlignment="Left" Margin="3,3,0,3" x:Name="ContentSite" VerticalAlignment="Center" Content="{TemplateBinding SelectionBoxItem}" ContentTemplate="{TemplateBinding SelectionBoxItemTemplate}" ContentTemplateSelector="{TemplateBinding ItemTemplateSelector}" IsHitTestVisible="False"/> <!--ToggleButton 已数据绑定到 ComboBox 本身以切换 IsDropDownOpen-->
<ToggleButton Grid.Column="0" Grid.ColumnSpan="2" Template="{StaticResource ComboBoxToggleButton}" x:Name="ToggleButton" Focusable="false" IsChecked="{Binding Path=IsDropDownOpen, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}" ClickMode="Press"/>
<!--必须将 TextBox 命名为 PART_EditableTextBox,否则 ComboBox 将无法识别它-->
<TextBox Visibility="Hidden" BorderThickness="0" Margin="2 0 0 0" x:Name="PART_EditableTextBox" VerticalAlignment="Center" Focusable="True" Background="Transparent" IsReadOnly="{TemplateBinding IsReadOnly}"/> <!--Popup 可显示 ComboBox 中的项列表。IsOpen 已数据绑定到通过 ComboBoxToggleButton 来切换的 IsDropDownOpen-->
<Popup IsOpen="{TemplateBinding IsDropDownOpen}" Placement="Bottom" x:Name="Popup" Focusable="False" AllowsTransparency="True" PopupAnimation="Slide">
<Grid MaxHeight="150" MinWidth="{TemplateBinding ActualWidth}" x:Name="DropDown" SnapsToDevicePixels="True">
<Border x:Name="DropDownBorder" BorderBrush="#e8e8e8" BorderThickness="1 0 1 1"/>
<ScrollViewer Margin="1" SnapsToDevicePixels="True" HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto" CanContentScroll="True">
<!--StackPanel 用于显示子级,方法是将 IsItemsHost 设置为 True-->
<StackPanel IsItemsHost="True" KeyboardNavigation.DirectionalNavigation="Contained" Background="White"/>
</ScrollViewer>
</Grid>
</Popup>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsEditable" Value="true">
<Setter TargetName="PART_EditableTextBox" Property="Visibility" Value="Visible" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>

引用示例:

 <ComboBox x:Name="combobox"  Width="150" Margin="10" IsEditable="False" BorderBrush="#e8e8e8">
<CheckBox Content="上海" Tag="1"></CheckBox>
<CheckBox Content="北京" Tag="2"></CheckBox>
<CheckBox Content="天津" Tag="3"></CheckBox>
<CheckBox Content="广州" Tag="4"></CheckBox>
</ComboBox>

二、ComboBox扩展样式(多选控件)

ComBoBox能够单选选择数据,那么能不能实现多选的操作呢,答案是肯定的。这里多选的自定义控件参考了博主“梦里花落知多少”的内容。我对样式做了补充,使其能够更方便的进行移除多选的内容。同时也更好的展示了已选的内容,大家可以根据实际需求做出更好的展示效果。

先看效果:

WPF 自定义ComboBox样式

2.1、添加自定义控件MultiComboBox

 public class MultiComboBox : ComboBox
{
static MultiComboBox()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(MultiComboBox), new FrameworkPropertyMetadata(typeof(MultiComboBox)));
} private static void OnPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
d.SetValue(e.Property, e.NewValue);
} /// <summary>
/// 选中项列表
/// </summary>
public ObservableCollection<MultiCbxBaseData> ChekedItems
{
get { return (ObservableCollection<MultiCbxBaseData>)GetValue(ChekedItemsProperty); }
set { SetValue(ChekedItemsProperty, value); }
} public static readonly DependencyProperty ChekedItemsProperty =
DependencyProperty.Register("ChekedItems", typeof(ObservableCollection<MultiCbxBaseData>), typeof(MultiComboBox), new PropertyMetadata(new ObservableCollection<MultiCbxBaseData>(), OnPropertyChanged)); /// <summary>
/// ListBox竖向列表
/// </summary>
private ListBox _ListBoxV; /// <summary>
/// ListBox横向列表
/// </summary>
private ListBox _ListBoxH; public override void OnApplyTemplate()
{
base.OnApplyTemplate();
_ListBoxV = Template.FindName("PART_ListBox", this) as ListBox;
_ListBoxH = Template.FindName("PART_ListBoxChk", this) as ListBox;
_ListBoxH.ItemsSource = ChekedItems;
_ListBoxV.SelectionChanged += _ListBoxV_SelectionChanged;
_ListBoxH.SelectionChanged += _ListBoxH_SelectionChanged; if (ItemsSource != null)
{
foreach (var item in ItemsSource)
{
MultiCbxBaseData bdc = item as MultiCbxBaseData;
if (bdc.IsCheck)
{
_ListBoxV.SelectedItems.Add(bdc);
}
}
}
} private void _ListBoxH_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
foreach (var item in e.RemovedItems)
{
MultiCbxBaseData datachk = item as MultiCbxBaseData; for (int i = ; i < _ListBoxV.SelectedItems.Count; i++)
{
MultiCbxBaseData datachklist = _ListBoxV.SelectedItems[i] as MultiCbxBaseData;
if (datachklist.ID == datachk.ID)
{
_ListBoxV.SelectedItems.Remove(_ListBoxV.SelectedItems[i]);
}
}
}
} void _ListBoxV_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
foreach (var item in e.AddedItems)
{
MultiCbxBaseData datachk = item as MultiCbxBaseData;
datachk.IsCheck = true;
if (ChekedItems.IndexOf(datachk) < )
{
ChekedItems.Add(datachk);
}
} foreach (var item in e.RemovedItems)
{
MultiCbxBaseData datachk = item as MultiCbxBaseData;
datachk.IsCheck = false;
ChekedItems.Remove(datachk);
}
} public class MultiCbxBaseData
{
private int _id;
/// <summary>
/// 关联主键
/// </summary>
public int ID
{
get { return _id; }
set { _id = value; }
} private string _viewName;
/// <summary>
/// 显示名称
/// </summary>
public string ViewName
{
get { return _viewName; }
set
{
_viewName = value;
}
} private bool _isCheck;
/// <summary>
/// 是否选中
/// </summary>
public bool IsCheck
{
get { return _isCheck; }
set { _isCheck = value;}
}
}
}

2.2、定义MultiComboBox控件的样式

 <ControlTemplate x:Key="ComboBoxToggleButton" TargetType="{x:Type ToggleButton}">
<Grid Height="25">
<Border Grid.Column="1" Background="White" Opacity="0" Cursor="Hand"/>
<Path x:Name="Arrow" Grid.Column="1" Data="M 0 0 6 6 12 0 Z" VerticalAlignment="Center" HorizontalAlignment="Center" Stretch="None" Fill="#B1B1B1" />
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsChecked" Value="true">
<Setter TargetName="Arrow" Property="RenderTransform">
<Setter.Value>
<RotateTransform CenterX="6" CenterY="3" Angle="180"></RotateTransform>
</Setter.Value>
</Setter>
<Setter TargetName="Arrow" Property="Margin" Value="0 0 0 2"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate> <!--MultiComboBox普通样式-->
<Style TargetType="{x:Type local:MultiComboBox}">
<Setter Property="Width" Value="200" />
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
<Setter Property="VerticalContentAlignment" Value="Center" />
<Setter Property="SnapsToDevicePixels" Value="True" />
<Setter Property="MaxDropDownHeight" Value="400" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:MultiComboBox}">
<Grid>
<Border x:Name="Bg" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" Width="{TemplateBinding Width}" Height="{TemplateBinding Height}" BorderBrush="#eaeaea" BorderThickness="1" >
<Grid x:Name="PART_Root"> <Grid x:Name="PART_InnerGrid" Margin="0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="0.3*" MaxWidth="30" />
</Grid.ColumnDefinitions>
<ListBox x:Name="PART_ListBoxChk" SelectionMode="Multiple" BorderThickness="0" ScrollViewer.VerticalScrollBarVisibility="Disabled">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel Orientation="Horizontal" VirtualizingStackPanel.IsVirtualizing="True" />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="IsSelected" Value="True"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListBoxItem">
<CheckBox BorderThickness="0" VerticalAlignment="Center" HorizontalAlignment="Center" Content="{Binding ViewName}" IsChecked="{Binding Path=IsSelected,RelativeSource={RelativeSource TemplatedParent},Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ListBox.ItemContainerStyle>
</ListBox> <!--下拉按钮-->
<ToggleButton x:Name="PART_DropDownToggle" IsTabStop="False"
IsChecked="{Binding IsDropDownOpen, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}"
Grid.Column="1" Template="{StaticResource ComboBoxToggleButton}" />
</Grid>
</Grid>
</Border>
<!--弹出多选列表-->
<Popup x:Name="PART_Popup" AllowsTransparency="True" Focusable="False" StaysOpen="False"
IsOpen="{Binding IsDropDownOpen, RelativeSource={RelativeSource TemplatedParent}}"
PopupAnimation="{DynamicResource {x:Static SystemParameters.ComboBoxPopupAnimationKey}}" Placement="Bottom">
<Grid Width="{Binding ActualWidth, RelativeSource={RelativeSource TemplatedParent}}" MaxHeight="{Binding MaxDropDownHeight, RelativeSource={RelativeSource TemplatedParent}}" >
<ListBox x:Name="PART_ListBox" SelectionMode="Multiple" BorderThickness="1 0 1 1" Background="White" ItemsSource="{Binding ItemsSource,RelativeSource={RelativeSource TemplatedParent}}"
MaxHeight="{TemplateBinding MaxDropDownHeight}" BorderBrush="#eaeaea" >
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListBoxItem}" >
<Grid Height="22">
<Border x:Name="bg" BorderBrush="#eaeaea" BorderThickness="0"/>
<ContentPresenter x:Name="content" />
<Border Background="White" Opacity="0"/>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter TargetName="bg" Property="Background" Value="#ADD6FF" />
</Trigger>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="IsMouseOver" Value="true" />
<Condition Property="IsSelected" Value="false"/>
</MultiTrigger.Conditions>
<Setter TargetName="bg" Property="Background" Value="#009BDB" />
<Setter TargetName="bg" Property="Opacity" Value="0.7"/>
<Setter Property="Foreground" Value="White" />
</MultiTrigger>
<Trigger Property="IsEnabled" Value="False">
<Setter TargetName="bg" Property="Opacity" Value="0.3" />
<Setter Property="Foreground" Value="Gray" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<CheckBox x:Name="chk" Visibility="Hidden" IsChecked="{Binding IsCheck,Mode=TwoWay}" VerticalAlignment="Center"/>
<CheckBox VerticalAlignment="Center" Foreground="{Binding Foreground,RelativeSource={RelativeSource AncestorType=ListBoxItem}}" IsChecked="{Binding RelativeSource={RelativeSource AncestorType=ListBoxItem},Path=IsSelected,Mode=TwoWay}" Content="{Binding Path=ViewName}" />
</Grid>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource AncestorType=ListBoxItem},Path=IsSelected}" Value="true">
<Setter TargetName="chk" Property="IsChecked" Value="true"/>
</DataTrigger>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource AncestorType=ListBoxItem},Path=IsSelected}" Value="false">
<Setter TargetName="chk" Property="IsChecked" Value="false"/>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</Popup>
</Grid> </ControlTemplate>
</Setter.Value>
</Setter>
</Style>

2.3、引用示例:

<local:MultiComboBox x:Name="multiCmb" Margin="10" Width="200"/>

2.4、后台代码(初始化绑定数据):

  public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
MultiComboBoxList = new ObservableCollection<MultiCbxBaseData>()
{
new MultiCbxBaseData(){
ID=,
ViewName="张三",
IsCheck=false
},
new MultiCbxBaseData(){
ID=,
ViewName="李四",
IsCheck=false
},
new MultiCbxBaseData(){
ID=,
ViewName="王五",
IsCheck=false
},
new MultiCbxBaseData(){
ID=,
ViewName="马六",
IsCheck=false
},
new MultiCbxBaseData(){
ID=,
ViewName="赵七",
IsCheck=false
},
};
this.multiCmb.ItemsSource = MultiComboBoxList;
} private ObservableCollection<MultiCbxBaseData> MultiComboBoxList;
}

所有代码已经上传到github:https://github.com/cmfGit/WpfDemo.git