在WPF的集合控件中常常需要在每一个集合项之间插入一个分隔符样式,但是WPF的ItemsControl没有相关功能的直接实现,所以只能考虑曲线救国,经过研究,大概想到了以下两种实现方式。

先写出ItemsControl的数据模板,如下:

<ItemsControl ItemsSource="{Binding Source}" BorderThickness="1" BorderBrush="Blue" VerticalAlignment="Stretch">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Border Name="Bd" Grid.Row="0" Height="1" Background="Red" />
<TextBlock Grid.Row="1" Text="{Binding}" />
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>

其中名为BdBorder就是分隔符,此时每一项的头部都可以看见分隔符,现在我们的目标是要隐藏掉第一项的分隔符,这就达到了项与项之间才有分隔符的目的。

第一种实现方式最简单,使用集合项前向绑定PreviousData,这是四种绑定方式中的一种,估计也是平时用得最少的一种,不过此时就派上用场了,代码如下:

<DataTemplate.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource PreviousData}}"
Value="{x:Null}">
<Setter TargetName="Bd" Property="Visibility" Value="Collapsed" />
</DataTrigger>
</DataTemplate.Triggers>

当某一项的前项为空时就隐藏分隔符,简单的一行代码搞定。不过这种实现方式有个缺点就是如果使用的是Insert方式向绑定的数据源的最前面添加数据则就会出现不止一个没有分隔符的项,如果是往队尾或者队中添加则不会出现这个问题。

第二种实现方式是借助ItemsControlAlternationCountAlternationIndex属性来为集合项标记索引号,再隐藏索引号为0的项的分隔符,代码如下:

<ItemsControl ItemsSource="{Binding Source}" BorderThickness="1" BorderBrush="Blue"
VerticalAlignment="Stretch" AlternationCount="{Binding Source.Count}">

首先在ItemsControl上绑定AlternationCount到数据源的Count属性上,然后此时ItemsControlAlternationIndex属性就变成的该集合数据源的索引号了,在触发器中写上逻辑即可:

<Border Name="Bd" Grid.Row="0" Height="1" Background="Red">
<Border.Style>
<Style TargetType="{x:Type Border}">
<Style.Triggers>
<DataTrigger
Binding="{Binding Path=(ItemsControl.AlternationIndex),
RelativeSource={RelativeSource AncestorType={x:Type ContentPresenter}}}"
Value="0">
<Setter Property="Visibility" Value="Collapsed" />
</DataTrigger>
</Style.Triggers>
</Style>
</Border.Style>
</Border>

触发器判定当索引号为0时就隐藏Border,这种方式代码量也不大,优点是能绝对实现这个功能,无论向队首插入还是队尾插入,但是AlternationCountAlternationIndex属性本来的含义是用来实现比如隔行变色等功能,此时这种功能被占用,所以如果你的集合要同时实现分隔符和隔行样式的功能可能需要额外加转换器,不过转换器内容也很简单,求个余数就能还原之前的功能了。


(2017年4月15日补充)

经过网友vbfool提示,补充第三种方式,按照第二种思路自定义附加属性,这样就不用占用原生ItemsControl的属性了。并且可以用附加属性标记出所有的索引号,供其他场景使用。

先自定义一个MarkIndex属性用于标记ItemsControl,如果这个属性被设为True再在代码逻辑中去订阅数据项的变更,然后向ItemContainer中设置一个ItemIndex附加属性,标记出索引号。

定义的依赖属性如下:

#region MarkIndex

public static readonly DependencyProperty MarkIndexProperty = DependencyProperty.RegisterAttached(
"MarkIndex", typeof(bool), typeof(ItemsControlHelper), new PropertyMetadata(default(bool), OnMarkIndexPropertyChanged)); public static bool GetMarkIndex(DependencyObject obj)
{
return (bool)obj.GetValue(MarkIndexProperty);
} public static void SetMarkIndex(DependencyObject obj, bool value)
{
obj.SetValue(MarkIndexProperty, value);
} private static void OnMarkIndexPropertyChanged(DependencyObject dependencyObject,
DependencyPropertyChangedEventArgs args)
{
if ((bool)args.NewValue)
{
var itemsControl = dependencyObject as ItemsControl;
if (itemsControl != null)
{
itemsControl.ItemContainerGenerator.StatusChanged -= ItemContainerGeneratorOnStatusChanged;
itemsControl.ItemContainerGenerator.ItemsChanged -= ItemContainerGeneratorOnItemsChanged; itemsControl.ItemContainerGenerator.StatusChanged += ItemContainerGeneratorOnStatusChanged;
itemsControl.ItemContainerGenerator.ItemsChanged += ItemContainerGeneratorOnItemsChanged;
}
}
else
{
var itemsControl = dependencyObject as ItemsControl;
if (itemsControl != null)
{
itemsControl.ItemContainerGenerator.StatusChanged -= ItemContainerGeneratorOnStatusChanged;
itemsControl.ItemContainerGenerator.ItemsChanged -= ItemContainerGeneratorOnItemsChanged;
}
}
} private static void ItemContainerGeneratorOnItemsChanged(object sender, ItemsChangedEventArgs itemsChangedEventArgs)
{
var itemContainerGenerator = (ItemContainerGenerator)sender; if (itemContainerGenerator.Status == GeneratorStatus.ContainersGenerated)
{
for (int i = 0; i < itemContainerGenerator.Items.Count; i++)
{
var dp = itemContainerGenerator.ContainerFromIndex(i); if (dp != null)
{
var oldIndex = (int)dp.GetValue(ItemIndexProperty);
if (oldIndex != i)
{
dp.SetValue(ItemIndexProperty, i);
}
}
}
}
} private static void ItemContainerGeneratorOnStatusChanged(object sender, EventArgs eventArgs)
{
var itemContainerGenerator = (ItemContainerGenerator)sender; if (itemContainerGenerator.Status == GeneratorStatus.ContainersGenerated)
{
for (int i = 0; i < itemContainerGenerator.Items.Count; i++)
{
var dp = itemContainerGenerator.ContainerFromIndex(i); if (dp != null)
{
var oldIndex = (int)dp.GetValue(ItemIndexProperty);
if (oldIndex != i)
{
dp.SetValue(ItemIndexProperty, i);
}
}
}
}
} #endregion #region ItemIndex public static readonly DependencyProperty ItemIndexProperty = DependencyProperty.RegisterAttached(
"ItemIndex", typeof(int), typeof(ItemsControlHelper), new PropertyMetadata(default(int))); public static int GetItemIndex(DependencyObject obj)
{
return (int)obj.GetValue(ItemIndexProperty);
} public static void SetItemIndex(DependencyObject obj, bool value)
{
obj.SetValue(ItemIndexProperty, value);
} #endregion

使用方式如下:

<ItemsControl ItemsSource="{Binding Source}" BorderThickness="1" BorderBrush="Blue"
VerticalAlignment="Stretch"
wpfItemsControlSeparator:ItemsControlHelper.MarkIndex="True">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border>
<Border.Style>
<Style TargetType="{x:Type Border}">
<Setter Property="BorderBrush" Value="Red" />
<Setter Property="BorderThickness" Value="0,1,0,0" />
<Style.Triggers>
<DataTrigger
Binding="{Binding Path=(wpfItemsControlSeparator:ItemsControlHelper.ItemIndex),
RelativeSource={RelativeSource AncestorType={x:Type ContentPresenter}}}"
Value="0">
<Setter Property="BorderThickness" Value="0" />
</DataTrigger>
</Style.Triggers>
</Style>
</Border.Style>
<TextBlock Grid.Row="1" Text="{Binding}"
ToolTip="{Binding Path=(wpfItemsControlSeparator:ItemsControlHelper.ItemIndex),
RelativeSource={RelativeSource AncestorType={x:Type ContentPresenter}}}" />
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>

这个小功能的代码参见:https://github.com/fengrui358/WPFLabs/tree/master/WpfItemsControlSeparator

WPF集合控件实现分隔符(ItemsControl Separator)的更多相关文章

  1. WPF集合控件实现分隔符(ItemsControl Splitter)

    在WPF的集合控件中常常需要在每一个集合项之间插入一个分隔符样式,但是WPF的ItemsControl没有相关功能的直接实现,所以只能考虑曲线救国,经过研究,大概想到了以下两种实现方式. 先写出Ite ...

  2. Windows Phone中的几种集合控件

    前言 Windows Phone开发过程中不可避免的就是和集合数据打交道,如果之前做过WP App的开发的话,相信你已经看过了各种集合控件的使用.扩展和自定义.这些个内容在这篇博客里都没有,那么我们今 ...

  3. 重新想象 Windows 8 Store Apps (5) - 控件之集合控件: ComboBox, ListBox, FlipView, ItemsControl, ItemsPresenter

    原文:重新想象 Windows 8 Store Apps (5) - 控件之集合控件: ComboBox, ListBox, FlipView, ItemsControl, ItemsPresente ...

  4. WPF布局控件常用属性介绍

    WPF布局控件常用属性介绍 其它 | 作者:慧都控件网 | 2011-04-06 13:41:57| 阅读 0次 有用(0) 评论(0)   概述:WPF布局控件都是派生自System.Windows ...

  5. WPF ListView控件设置奇偶行背景色交替变换以及ListViewItem鼠标悬停动画

    原文:WPF ListView控件设置奇偶行背景色交替变换以及ListViewItem鼠标悬停动画 利用WPF的ListView控件实现类似于Winform中DataGrid行背景色交替变换的效果,同 ...

  6. WPF Popup 控件导致被遮挡内容不刷新的原因

    WPF Popup 控件导致被遮挡内容不刷新的原因 周银辉 今天在写一个WPF控件时用到了Popup控件,很郁闷的情况是:当popup关闭时,原来被popup挡住的界面部分不刷新,非要手动刷新一下(比 ...

  7. 创建 WPF 工具箱控件

    创建 WPF 工具箱控件 WPF (Windows Presentation Framework) 工具箱控件模板允许您创建 WPF 控件,会自动添加到 工具箱 安装扩展的安装. 本主题演示如何使用模 ...

  8. wpf打印控件 实现分页打印控件功能

    因为 要实现打印 wpf  listbox控件  数据特别多 要打印在 几张纸上    找了几天 都没有找到相关的例子 现在 解决了 在这里和大家分享一下 public void print(Fram ...

  9. WPF常用控件应用demo

    WPF常用控件应用demo 一.Demo 1.Demo截图如下: 2.demo实现过程 总体布局:因放大缩小窗体,控件很根据空间是否足够改变布局,故用WrapPanel布局. <ScrollVi ...

随机推荐

  1. erlang中检查内存泄露

    最近项目内存占用过多,检查一下erlang的内存使用情况. 1. 通过etop可以很方便得出erlang内存使用的情况 spawn(fun() -> etop:start([{output, t ...

  2. iptables 工具的使用

    试验建议:关闭CentOS 7 或 CentOS 6的防火墙 (systemctl stop firewalld ; systemctl disable firewalld 或 service ipt ...

  3. RAID卡服务器安装2003教程

     这里先讲讲安装系统的几个思路: 1.U盘安装法(U盘只做可启动PE,常用的大白菜,IT天空,老毛桃.....拷贝系统ISO镜像到U盘,进入PE之后找到ISO,用虚拟光驱加载,运行WIN系统安装器 ...

  4. [翻译]API Guides - Layouts

    官方文档地址:http://developer.android.com/guide/topics/ui/declaring-layout.html PS:API Guides里面的内容不免都简单些,翻 ...

  5. Mysql中关键词执行顺序

    MySQL的语句执行顺序 MySQL的语句一共分为11步,最先执行的总是FROM操作,最后执行的是LIMIT操作.其中每一个操作都会产生一张虚拟的表,这个虚拟的表作为一个处理的输入,只是这些虚拟的表对 ...

  6. PHP中大括号用法

    Php中"{}"大括号的用法总结 在PHP中,大括号“{}”可以起到如下作用: 1.将多个独立语句合并为一个复合语句,例如 if ... else ...中经常如此使用 2.在变量 ...

  7. js & right click menu & 鼠标滑词

    js & right click menu & 鼠标滑词 // 鼠标滑词 mouseSlipGetWords() { const getSelectionText = () => ...

  8. 【C++】new和delete表达式与内存管理

    new和delete表达式可以用来动态创建和释放单个对象,也可以用来动态创建和释放动态数组. 定义变量时,必须指定其数据类型和名字.而动态创建对象时,只需指定其数据类型,而不必为该对象命名.new表达 ...

  9. CPU测试--通过proc获取CPU信息

    adb shell cat /proc/stat | grep cpu > totalcpu0 此处第一行的数值表示的是CPU总的使用情况,所以我们只要用第一行的数字计算就可以了.下表解析第一行 ...

  10. 第109天:Ajax请求GET和POST的区别

    一.Ajax请求GET和POST的区别 1.使用Get请求时,参数在URL中显示,而使用Post方式,则不会显示出来 2.使用Get请求发送数据量小,Post请求发送数据量大 3.get请求需注意缓存 ...