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

posted @ 2016-07-22 22:53  士官长117  阅读(527)  评论(5)    收藏  举报