在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>
其中名為Bd
的Border
就是分隔符,此時每一項的頭部都可以看見分隔符,現在我們的目標是要隱藏掉第一項的分隔符,這就達到了項與項之間才有分隔符的目的。
第一種實現方式最簡單,使用集合項前向綁定PreviousData
,這是四種綁定方式中的一種,估計也是平時用得最少的一種,不過此時就派上用場了,代碼如下:
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource PreviousData}}"
Value="{x:Null}">
<Setter TargetName="Bd" Property="Visibility" Value="Collapsed" />
</DataTrigger>
</DataTemplate.Triggers>
當某一項的前項為空時就隱藏分隔符,簡單的一行代碼搞定。不過這種實現方式有個缺點就是如果使用的是Insert
方式向綁定的數據源的最前面添加數據則就會出現不止一個沒有分隔符的項,如果是往隊尾或者隊中添加則不會出現這個問題。
第二種實現方式是借助ItemsControl
的AlternationCount
和AlternationIndex
屬性來為集合項標記索引號,再隱藏索引號為0的項的分隔符,代碼如下:
<ItemsControl ItemsSource="{Binding Source}" BorderThickness="1" BorderBrush="Blue"
VerticalAlignment="Stretch" AlternationCount="{Binding Source.Count}">
首先在ItemsControl
上綁定AlternationCount
到數據源的Count
屬性上,然后此時ItemsControl
的AlternationIndex
屬性就變成的該集合數據源的索引號了,在觸發器中寫上邏輯即可:
<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
,這種方式代碼量也不大,優點是能絕對實現這個功能,無論向隊首插入還是隊尾插入,但是AlternationCount
和AlternationIndex
屬性本來的含義是用來實現比如隔行變色等功能,此時這種功能被占用,所以如果你的集合要同時實現分隔符和隔行樣式的功能可能需要額外加轉換器,不過轉換器內容也很簡單,求個余數就能還原之前的功能了。
(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