仿IOS的UISegmentControl控件
先前从UI中国上面看到类似效果,今天得空用wpf实现一下,先上效果图:

首先需要考虑的是我们能不能从WPF原始控件中找到类似的来改造呢,答案是肯定的。我们发现该控件选中项是唯一的并且最外层有一个边框,那么可以发现ListBox就满符合的嘛,那么改造开始。
我们需要重写ListBox与ListBoxItem的样式,设置ListBox的Border边框与ListBoxItem的选中样式。但是之后发现了两个问题
1、从效果图中也可以看出只有左右两边的Item有圆角,中间的几个Item都是没有圆角的,如果为了契合左右两边的圆角,势必会导致选中中间Item的时候也出现圆角,这样就不好看了
2、每个Item之间都有一根竖线进行分割,这个效果如何实现呢
我的做法就是
1、在后台中使用视觉树查找到ListBoxItem样式中的Border,每次切换Item的时候判断该Item的Index,如果Index为0或者是第Items.Count的的时候,分别设置Border的CornerRadius
2、给每个ListBoxItem样式中的Border设置BorderThickness为1,0,0,0,这样每个Item就会出现一根竖线,但是同样会造成最后一个Item多出来一根竖线,这个我们必须去除掉。同样的使用视觉树去除,即使用FindVisualChildren方法找出所有名称为lineBorder的Border,判断其Index,如果是最后一个则隐藏
样式代码如下:
<UserControl x:Class="SegmentControlDemo.SegmentControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:SegmentControlDemo"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" Width="auto" Height="auto"
mc:Ignorable="d">
<UserControl.Resources>
<Style x:Key="ListBoxItem" TargetType="{x:Type ListBoxItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListBoxItem}">
<Grid x:Name="grid">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="auto" />
</Grid.ColumnDefinitions>
<Border x:Name="border" Background="Transparent" Padding="15,7" SnapsToDevicePixels="True"
UseLayoutRounding="True" CornerRadius="5,0,0,5">
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center" />
</Border>
<Border x:Name="lineBorder" Grid.Column="1" BorderBrush="#0079FF" BorderThickness="1,0,0,0" SnapsToDevicePixels="True"
UseLayoutRounding="True" />
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter TargetName="border" Property="Background" Value="#0079FF" />
<Setter Property="Foreground" Value="White" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<SolidColorBrush x:Key="ListBox.Static.Background" Color="#FFFFFFFF" />
<SolidColorBrush x:Key="ListBox.Static.Border" Color="#0079FF" />
<SolidColorBrush x:Key="ListBox.Disabled.Background" Color="#FFFFFFFF" />
<SolidColorBrush x:Key="ListBox.Disabled.Border" Color="#FFD9D9D9" />
<Style x:Key="ListBoxStyle1" TargetType="{x:Type ListBox}">
<Setter Property="Padding" Value="0,-1,-1,-1" />
<Setter Property="Background" Value="{StaticResource ListBox.Static.Background}" />
<Setter Property="BorderBrush" Value="{StaticResource ListBox.Static.Border}" />
<Setter Property="BorderThickness" Value="1,1,1,1" />
<Setter Property="Foreground" Value="Black" />
<Setter Property="FontFamily" Value="微软雅黑" />
<Setter Property="FontSize" Value="14" />
<Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Auto" />
<Setter Property="ScrollViewer.VerticalScrollBarVisibility" Value="Auto" />
<Setter Property="ScrollViewer.CanContentScroll" Value="true" />
<Setter Property="ScrollViewer.PanningMode" Value="Both" />
<Setter Property="Stylus.IsFlicksEnabled" Value="False" />
<Setter Property="VerticalContentAlignment" Value="Center" />
<Setter Property="ItemContainerStyle" Value="{StaticResource ListBoxItem}" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListBox}">
<Border x:Name="Bd"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="7" SnapsToDevicePixels="true" UseLayoutRounding="True">
<ScrollViewer Focusable="false" Padding="{TemplateBinding Padding}">
<ItemsPresenter SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
</ScrollViewer>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsEnabled" Value="false">
<Setter TargetName="Bd" Property="Background" Value="{StaticResource ListBox.Disabled.Background}" />
<Setter TargetName="Bd" Property="BorderBrush" Value="{StaticResource ListBox.Disabled.Border}" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
<Setter Property="ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate>
<StackPanel IsItemsHost="True" Orientation="Horizontal" />
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
</Style>
</UserControl.Resources>
<Grid>
<ListBox x:Name="listbox" Style="{StaticResource ListBoxStyle1}"
SelectedIndex="0" SelectionChanged="ListBox_SelectionChanged">
<!--<ListBoxItem>全部</ListBoxItem>
<ListBoxItem>主任医师</ListBoxItem>
<ListBoxItem>副主任医师</ListBoxItem>
<ListBoxItem>主治医师</ListBoxItem>
<ListBoxItem>住院医生</ListBoxItem>
<ListBoxItem>其他</ListBoxItem>-->
</ListBox>
</Grid>
</UserControl>
后台代码如下:
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.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace SegmentControlDemo
{
/// <summary>
/// SegmentControl.xaml 的交互逻辑
/// </summary>
public partial class SegmentControl : UserControl
{
#region 依赖属性
#region ItemsSource数据源
public static readonly DependencyProperty ItemsSourceProperty = DependencyProperty.Register("ItemsSource"
, typeof(System.Collections.IEnumerable), typeof(SegmentControl));
/// <summary>
/// 数据源
/// </summary>
public System.Collections.IEnumerable ItemsSource
{
get { return (System.Collections.IEnumerable)GetValue(ItemsSourceProperty); }
set { SetValue(ItemsSourceProperty, value); }
}
#endregion
#region DisplayMemberPath
public static readonly DependencyProperty DisplayMemberPathProperty = DependencyProperty.Register("DisplayMemberPath"
, typeof(string), typeof(SegmentControl));
/// <summary>
/// 显示的属性名称
/// </summary>
public string DisplayMemberPath
{
get { return (string)GetValue(DisplayMemberPathProperty); }
set { SetValue(DisplayMemberPathProperty, value); }
}
#endregion
#endregion
public SegmentControl()
{
InitializeComponent();
this.Loaded += SegmentControl_Loaded;
}
private void SegmentControl_Loaded(object sender, RoutedEventArgs e)
{
var borderList = this.FindVisualChildren<Border>(this.listbox, "lineBorder").ToList();
for(int i = 0; i < borderList.Count; i++)
{
if(i == listbox.ItemContainerGenerator.Items.Count - 1)
{
var border = borderList[i];
border.Visibility = Visibility.Collapsed;
}
}
}
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
//注意赋值的地方,需现在OnApplyTemplate赋予ItemsSource,然后在Loaded中查找元素时才能找得到
this.listbox.ItemsSource = this.ItemsSource;
this.listbox.DisplayMemberPath = this.DisplayMemberPath;
}
private void ListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
int index = this.listbox.SelectedIndex;
var listboxitem = this.listbox.ItemContainerGenerator.ContainerFromIndex(index) as FrameworkElement;
var border = this.FindChild<Border>(listboxitem, "border");
if(border != null)
{
if (index == 0)
{
border.CornerRadius = new CornerRadius(5, 0, 0, 5);
}
else if (index == listbox.ItemContainerGenerator.Items.Count - 1)
{
border.CornerRadius = new CornerRadius(0, 5, 5, 0);
}
else
{
border.CornerRadius = new CornerRadius(0, 0, 0, 0);
}
}
}
#region 元素查找
public IEnumerable<T> FindVisualChildren<T>(DependencyObject depObj, string childName) where T : DependencyObject
{
if (depObj != null)
{
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(depObj); i++)
{
DependencyObject child = VisualTreeHelper.GetChild(depObj, i);
var frameworkElement = child as FrameworkElement;
if (child != null && child is T && frameworkElement.Name.Equals(childName))
{
yield return (T)child;
}
foreach (T childOfChild in FindVisualChildren<T>(child, childName))
{
yield return childOfChild;
}
}
}
}
public T FindChild<T>(DependencyObject parent, string childName) where T : DependencyObject
{
if (parent == null) return null;
T foundChild = null;
int childrenCount = VisualTreeHelper.GetChildrenCount(parent);
for (int i = 0; i < childrenCount; i++)
{
var child = VisualTreeHelper.GetChild(parent, i);
T childType = child as T;
if (childType == null)
{
// 住下查要找的元素
foundChild = FindChild<T>(child, childName);
// 如果找不到就反回
if (foundChild != null) break;
}
else if (!string.IsNullOrEmpty(childName))
{
var frameworkElement = child as FrameworkElement;
// 看名字是不是一样
if (frameworkElement != null && frameworkElement.Name == childName)
{
//如果名字一样返回
foundChild = (T)child;
break;
}
}
else
{
// 找到相应的元素了就返回
foundChild = (T)child;
break;
}
}
return foundChild;
}
#endregion
}
}
需要注意的是为了使该控件具备通用性,需给该UserControl设置两个依赖属性:ItemsSource与DisplayMemberPath,这个与ListBox的使用方法一致
源代码:https://yunpan.cn/cBCNRzRPQayA7 (提取码:8ae6)

浙公网安备 33010602011771号