ListBoxItemに「Index」アタッチされたプロパティを実装して正しく機能させるのはかなり難しいように思われるので、それを実現するためのより簡単な方法はMVVMであると思います。必要なロジック(「IsLast」プロパティなど)をリストのエンティティタイプに追加し、ViewModelにこれを処理させ、コレクションが変更または置換されたときに更新することができます。
編集
何度か試みた後、添付されたプロパティと継承されたListBoxを組み合わせて使用して、ListBoxItemsのインデックス作成を実装しました(その結果、最後をチェックしました)。見てみな:
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);
}
}
}
それをテストするために、単純なViewModelとItemクラスを設定します。
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を変更しました。それらの1つが変更されると、ListBoxはcontaineirsを再作成し、PrepareContainerForItemOverrideがヒットします。ListBox.Items自体で正しいインデックスを探すと、順序が正しく更新されます。
2番目では、ViewModelのItemsプロパティをListCollectionViewにしました。この場合、説明が変更されると、CollectionChangedが発生し、ListBoxが期待どおりに反応しました。