如何知道ListBoxItem
是Wpf的ListBox
內部收藏的最後一項(在ItemContainerStyle
或ItemContainer
的模板中)?如何知道ListBoxItem是否是Wpf列表框中的最後一項?
這個問題是因爲我需要知道一個項目是否是以其他方式顯示它的最後一個項目。例如:假設我想顯示用分號分隔的項目,但最後一項:a; b; c
這很容易在html和ccs中使用ccs選擇器來完成。但是,我怎樣才能在Wpf中做到這一點?
如何知道ListBoxItem
是Wpf的ListBox
內部收藏的最後一項(在ItemContainerStyle
或ItemContainer
的模板中)?如何知道ListBoxItem是否是Wpf列表框中的最後一項?
這個問題是因爲我需要知道一個項目是否是以其他方式顯示它的最後一個項目。例如:假設我想顯示用分號分隔的項目,但最後一項:a; b; c
這很容易在html和ccs中使用ccs選擇器來完成。但是,我怎樣才能在Wpf中做到這一點?
因爲它似乎是rather difficult to implement一個「Index」附加屬性到ListBoxItem做正確的工作,我相信更容易的方式來完成它將在MVVM。 您可以將必要的邏輯(「IsLast」屬性等)添加到列表的實體類型中,並讓ViewModel處理此操作,並在修改或替換集合時對其進行更新。
編輯
一些嘗試後,我成功地實現ListBoxItems(並因此檢查最後)使用附加屬性的組合和繼承的ListBox的索引。檢查出來:
public class IndexedListBox : System.Windows.Controls.ListBox
{
public static int GetIndex(DependencyObject obj)
{
return (int)obj.GetValue(IndexProperty);
}
public static void SetIndex(DependencyObject obj, int value)
{
obj.SetValue(IndexProperty, value);
}
/// <summary>
/// Keeps track of the index of a ListBoxItem
/// </summary>
public static readonly DependencyProperty IndexProperty =
DependencyProperty.RegisterAttached("Index", typeof(int), typeof(IndexedListBox), new UIPropertyMetadata(0));
public static bool GetIsLast(DependencyObject obj)
{
return (bool)obj.GetValue(IsLastProperty);
}
public static void SetIsLast(DependencyObject obj, bool value)
{
obj.SetValue(IsLastProperty, value);
}
/// <summary>
/// Informs if a ListBoxItem is the last in the collection.
/// </summary>
public static readonly DependencyProperty IsLastProperty =
DependencyProperty.RegisterAttached("IsLast", typeof(bool), typeof(IndexedListBox), new UIPropertyMetadata(false));
protected override void OnItemsSourceChanged(System.Collections.IEnumerable oldValue, System.Collections.IEnumerable newValue)
{
// We capture the ItemsSourceChanged to check if the new one is modifiable, so we can react to its changes.
var oldSource = oldValue as INotifyCollectionChanged;
if(oldSource != null)
oldSource.CollectionChanged -= ItemsSource_CollectionChanged;
var newSource = newValue as INotifyCollectionChanged;
if (newSource != null)
newSource.CollectionChanged += ItemsSource_CollectionChanged;
base.OnItemsSourceChanged(oldValue, newValue);
}
void ItemsSource_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
this.ReindexItems();
}
protected override void PrepareContainerForItemOverride(System.Windows.DependencyObject element, object item)
{
// We set the index and other related properties when generating a ItemContainer
var index = this.Items.IndexOf(item);
SetIsLast(element, index == this.Items.Count - 1);
SetIndex(element, index);
base.PrepareContainerForItemOverride(element, item);
}
private void ReindexItems()
{
// If the collection is modified, it may be necessary to reindex all ListBoxItems.
foreach (var item in this.Items)
{
var itemContainer = this.ItemContainerGenerator.ContainerFromItem(item);
if (itemContainer == null) continue;
int index = this.Items.IndexOf(item);
SetIsLast(itemContainer, index == this.Items.Count - 1);
SetIndex(itemContainer, index);
}
}
}
爲了測試它,我們建立一個簡單的視圖模型和項目類:
public class ViewModel : INotifyPropertyChanged
{
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
private ObservableCollection<Item> items;
public ObservableCollection<Item> Items
{
get { return this.items; }
set
{
if (this.items != value)
{
this.items = value;
this.OnPropertyChanged("Items");
}
}
}
public ViewModel()
{
this.InitItems(20);
}
public void InitItems(int count)
{
this.Items = new ObservableCollection<Item>();
for (int i = 0; i < count; i++)
this.Items.Add(new Item() { MyProperty = "Element" + i });
}
}
public class Item
{
public string MyProperty { get; set; }
public override string ToString()
{
return this.MyProperty;
}
}
的觀點:
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication3"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" x:Class="WpfApplication3.MainWindow"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<DataTemplate x:Key="DataTemplate">
<Border x:Name="border">
<StackPanel Orientation="Horizontal">
<TextBlock TextWrapping="Wrap" Text="{Binding (local:IndexedListBox.Index), RelativeSource={RelativeSource AncestorType={x:Type ListBoxItem}}}" Margin="0,0,8,0"/>
<TextBlock TextWrapping="Wrap" Text="{Binding (local:IndexedListBox.IsLast), RelativeSource={RelativeSource AncestorType={x:Type ListBoxItem}}}" Margin="0,0,8,0"/>
<ContentPresenter Content="{Binding}"/>
</StackPanel>
</Border>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding (local:IndexedListBox.IsLast), RelativeSource={RelativeSource AncestorType={x:Type ListBoxItem}}}" Value="True">
<Setter Property="Background" TargetName="border" Value="Red"/>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</Window.Resources>
<Window.DataContext>
<local:ViewModel/>
</Window.DataContext>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="0.949*"/>
</Grid.RowDefinitions>
<local:IndexedListBox ItemsSource="{Binding Items}" Grid.Row="1" ItemTemplate="{DynamicResource DataTemplate}"/>
<Button Content="Button" HorizontalAlignment="Left" Width="75" d:LayoutOverrides="Height" Margin="8" Click="Button_Click"/>
<Button Content="Button" HorizontalAlignment="Left" Width="75" Margin="110,8,0,8" Click="Button_Click_1" d:LayoutOverrides="Height"/>
<Button Content="Button" Margin="242,8,192,8" Click="Button_Click_2" d:LayoutOverrides="Height"/>
</Grid>
</Window>
在視圖代碼後面我把一些在更新集合時測試解決方案的行爲的邏輯:
public partial class MainWindow : Window
{
public ViewModel ViewModel { get { return this.DataContext as ViewModel; } }
public MainWindow()
{
InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
this.ViewModel.Items.Insert(5, new Item() { MyProperty= "NewElement" });
}
private void Button_Click_1(object sender, RoutedEventArgs e)
{
this.ViewModel.Items.RemoveAt(5);
}
private void Button_Click_2(object sender, RoutedEventArgs e)
{
this.ViewModel.InitItems(new Random().Next(10,30));
}
}
該解決方案可以處理靜態列表以及ObservableCollections,並添加,刪除項目。希望你覺得它有用。
編輯
與CollectionViews測試,它工作得很好。
在第一次測試中,我更改了ListBox.Items中的Sort/GroupDescriptions。當其中一個被更改時,ListBox重新創建容器,然後PrepareContainerForItemOverride命中。由於它在ListBox.Items本身中查找正確的索引,所以訂單已正確更新。
在第二個我在ViewModel ListCollectionView項目屬性。在這種情況下,當描述被改變時,CollectionChanged被引發並且ListBox反應如預期。
這是一個複雜的解決方案,但似乎工作。但是,這在SortDescription改變時起作用了嗎?例如,當用戶對視圖中的項目進行排序時,會改變SortDescriptions。 –
好點,我還沒有測試過。如果你綁定到一個CollectionView,它就會工作,因爲它實現INotifyCollectionChanged,並且可能在重新排序時引發一個CollectionChanged。如果沒有,我們可以嘗試在方法OnItemsSourceChanged中偵聽對ListBox.Items的修改,而不是對ItemsSource的修改。我必須看一下。在我的第一個方法中,我試圖只聽ItemContainerGenerator,但它不能很好地工作,因爲顯然物品是由於虛擬化而按需創建的。 –
剛剛測試過!見編輯的答案。 –
我試過了,但是我找不到辦法做到這一點。 –
你能粘貼一些xaml代碼嗎? –
看看[這個答案](http://stackoverflow.com/questions/2511227/how-can-a-separator-be-added-between-items-in-an-itemscontrol)。 – markmuetz