WPF:跟踪ItemsControl/ListBox中的相对项目位置

WPF:跟踪ItemsControl/ListBox中的相对项目位置,wpf,listbox,Wpf,Listbox,请参阅以下代码 它创建了一个包含五项的列表框。列表框的选定项为黄色,以前的项(选定索引下方的索引)为绿色,将来的项(选定索引上方的索引)为红色 ItemViewModel.vb Public Class ItemViewModel Implements INotifyPropertyChanged Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChang

请参阅以下代码

它创建了一个包含五项的
列表框
列表框的选定项
为黄色,以前的项(选定索引下方的索引)为绿色,将来的项(选定索引上方的索引)为红色

ItemViewModel.vb

Public Class ItemViewModel
    Implements INotifyPropertyChanged

    Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged

    Private _title As String
    Private _isOld As Boolean
    Private _isNew As Boolean

    Protected Overridable Sub OnPropertyChanged(<CallerMemberName> Optional propertyName As String = Nothing)
        If String.IsNullOrEmpty(propertyName) Then
            Exit Sub
        End If

        RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propertyName))
    End Sub

    Public Property Title As String
        Get
            Return _title
        End Get
        Set(value As String)
            _title = value
            Me.OnPropertyChanged()
        End Set
    End Property

    Public Property IsOld As Boolean
        Get
            Return _isOld
        End Get
        Set(value As Boolean)
            _isOld = value
            Me.OnPropertyChanged()
        End Set
    End Property

    Public Property IsNew As Boolean
        Get
            Return _isNew
        End Get
        Set(value As Boolean)
            _isNew = value
            Me.OnPropertyChanged()
        End Set
    End Property

End Class
公共类ItemViewModel
实现INotifyPropertyChanged
公共事件PropertyChanged为PropertyChangedEventHandler实现INotifyPropertyChanged.PropertyChanged
私有标题作为字符串
Private _isOld为布尔值
Private\u是新的布尔值
受保护的可重写子OnPropertyChanged(可选propertyName As String=Nothing)
如果String.IsNullOrEmpty(propertyName),则
出口接头
如果结束
RaiseEvent PropertyChanged(Me,新PropertyChangedEventArgs(propertyName))
端接头
公共财产所有权作为字符串
得到
返回标题
结束
设置(值为字符串)
_标题=价值
Me.OnPropertyChanged()
端集
端属性
公共属性隔离为布尔值
得到
返回隔离
结束
设置(值为布尔值)
_isOld=值
Me.OnPropertyChanged()
端集
端属性
公共属性作为布尔值是新的
得到
返回是新的
结束
设置(值为布尔值)
_isNew=值
Me.OnPropertyChanged()
端集
端属性
末级
MainViewModel:

Public Class MainViewModel
    Implements INotifyPropertyChanged

    Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged

    Private ReadOnly _items As ObservableCollection(Of ItemViewModel)
    Private _selectedIndex As Integer

    Public Sub New()
        _items = New ObservableCollection(Of ItemViewModel)
        _items.Add(New ItemViewModel With {.Title = "Very old"})
        _items.Add(New ItemViewModel With {.Title = "Old"})
        _items.Add(New ItemViewModel With {.Title = "Current"})
        _items.Add(New ItemViewModel With {.Title = "New"})
        _items.Add(New ItemViewModel With {.Title = "Very new"})

        Me.SelectedIndex = 0
    End Sub

    Protected Overridable Sub OnPropertyChanged(<CallerMemberName> Optional propertyName As String = Nothing)
        If String.IsNullOrEmpty(propertyName) Then
            Exit Sub
        End If

        RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propertyName))
    End Sub

    Public ReadOnly Property Items As ObservableCollection(Of ItemViewModel)
        Get
            Return _items
        End Get
    End Property

    Public Property SelectedIndex As Integer
        Get
            Return _selectedIndex
        End Get
        Set(value As Integer)
            _selectedIndex = value
            Me.OnPropertyChanged()

            For index As Integer = 0 To Me.Items.Count - 1
                Me.Items(index).IsOld = (index < Me.SelectedIndex)
                Me.Items(index).IsNew = (index > Me.SelectedIndex)
            Next index
        End Set
    End Property

End Class
Public类主视图模型
实现INotifyPropertyChanged
公共事件PropertyChanged为PropertyChangedEventHandler实现INotifyPropertyChanged.PropertyChanged
私有只读项作为可观察集合(属于ItemViewModel)
Private\u选择索引作为整数
公共分新()
_items=新的ObservableCollection(ItemViewModel的)
_添加(带有{.Title=“Very old”}的新ItemViewModel)
_添加(带有{.Title=“Old”}的新ItemViewModel)
_items.Add(带有{.Title=“Current”}的新ItemViewModel)
_添加(带有{.Title=“New”}的新ItemViewModel)
_items.Add(带有{.Title=“Very New”}的新ItemViewModel)
Me.SelectedIndex=0
端接头
受保护的可重写子OnPropertyChanged(可选propertyName As String=Nothing)
如果String.IsNullOrEmpty(propertyName),则
出口接头
如果结束
RaiseEvent PropertyChanged(Me,新PropertyChangedEventArgs(propertyName))
端接头
作为(ItemViewModel的)ObservableCollection的公共只读属性项
得到
退货项目
结束
端属性
公共属性已将索引选择为整数
得到
返回所选索引
结束
设置(值为整数)
_selectedIndex=值
Me.OnPropertyChanged()
对于Me.Items.Count-1,索引为整数=0
Me.Items(index.IsOld=(indexMe.SelectedIndex)
下一个索引
端集
端属性
末级
MainWindow.xaml

<Window x:Class="MainWindow"
        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:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfApp1"
        mc:Ignorable="d"
        Title="MainWindow" Height="300" Width="200">
    <Window.DataContext>
        <local:MainViewModel />
    </Window.DataContext>

    <ListBox ItemsSource="{Binding Items}" SelectedIndex="{Binding SelectedIndex}">
        <ListBox.ItemTemplate>
            <DataTemplate>
                <TextBlock Text="{Binding Title}">
                    <TextBlock.Style>
                         <Style TargetType="{x:Type TextBlock}">
                            <Style.Triggers>
                                <DataTrigger Binding="{Binding IsOld}" Value="True">
                                    <Setter Property="Foreground" Value="Green" />
                                </DataTrigger>
                                <DataTrigger Binding="{Binding IsSelected, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListBoxItem}}}" Value="True">
                                    <Setter Property="Foreground" Value="Yellow" />
                                </DataTrigger>
                                <DataTrigger Binding="{Binding IsNew}" Value="True">
                                    <Setter Property="Foreground" Value="Red" />
                                </DataTrigger>
                            </Style.Triggers>
                        </Style>
                    </TextBlock.Style>
                </TextBlock>
            </DataTemplate>
        </ListBox.ItemTemplate>
    </ListBox>
</Window>

这与预期的一样,但我不喜欢,
ItemViewModel
保存属性
IsOld
IsNew
,而
MainViewModel
负责更新这些属性。在我看来,这应该由
列表框
来完成,而不是由可能是我的
列表框
数据上下文
的每个视图模型来完成

我已经尝试为
ListBoxItem
创建两个附加属性并绑定到它们(就像我为当前项绑定到
IsSelected
)。但我无法找出更新这些附加属性的事件

使用这些附加属性是正确的做法吗?何时和/或在何处更新这些附加属性? 我试图附加到
ListBox
ItemsSource
属性的
ValueChanged
事件,以便能够附加到基础集合的
CollectionChanged
事件。但是我无法获取项目的
ListBoxItem
,因为这些容器是异步创建的(我假设是这样)。由于默认情况下,
ListBox
使用了一个
virtualzingstackpanel
,因此我不会为我的基础集合的每个项目都获得一个
ListBoxItem

请记住,我绑定的项目集合是可观察的,并且可以更改。因此,每当源集合本身更改、源集合的内容更改以及所选索引更改时,必须更新
IsOld
IsNew
属性

或者我怎样才能实现我想实现的目标


我没有故意标记VB.net,因为这个问题与VB.net没有任何关系,我对C#的答案也很满意


谢谢。

实现这一点的一种方法是通过附加行为。这使您可以使用
列表框保持显示行为,并远离视图模型等

首先,我创建了一个枚举来存储项的状态:

namespace WpfApp4
{
    public enum ListBoxItemAge
    {
        Old,
        Current,
        New,
        None
    }
}
接下来,我创建了一个带有两个附加属性的附加行为类:

    using System.Windows; using System.Windows.Controls; namespace WpfApp4 { public class ListBoxItemAgeBehavior { #region IsActive (Attached Property) public static readonly DependencyProperty IsActiveProperty = DependencyProperty.RegisterAttached( "IsActive", typeof(bool), typeof(ListBoxItemAgeBehavior), new PropertyMetadata(false, OnIsActiveChanged)); public static bool GetIsActive(DependencyObject obj) { return (bool)obj.GetValue(IsActiveProperty); } public static void SetIsActive(DependencyObject obj, bool value) { obj.SetValue(IsActiveProperty, value); } private static void OnIsActiveChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { if (!(d is ListBox listBox)) return; if ((bool) e.NewValue) { listBox.SelectionChanged += OnSelectionChanged; } else { listBox.SelectionChanged -= OnSelectionChanged; } } private static void OnSelectionChanged(object sender, SelectionChangedEventArgs e) { var listBox = (ListBox) sender; var selectedIndex = listBox.SelectedIndex; SetItemAge(listBox.ItemContainerGenerator.ContainerFromIndex(selectedIndex), ListBoxItemAge.Current); foreach (var item in listBox.ItemsSource) { var index = listBox.Items.IndexOf(item); if (index < selectedIndex) { SetItemAge(listBox.ItemContainerGenerator.ContainerFromIndex(index), ListBoxItemAge.Old); } else if (index > selectedIndex) { SetItemAge(listBox.ItemContainerGenerator.ContainerFromIndex(index), ListBoxItemAge.New); } } } #endregion #region ItemAge (Attached Property) public static readonly DependencyProperty ItemAgeProperty = DependencyProperty.RegisterAttached( "ItemAge", typeof(ListBoxItemAge), typeof(ListBoxItemAgeBehavior), new FrameworkPropertyMetadata(ListBoxItemAge.None)); public static ListBoxItemAge GetItemAge(DependencyObject obj) { return (ListBoxItemAge)obj.GetValue(ItemAgeProperty); } public static void SetItemAge(DependencyObject obj, ListBoxItemAge value) { obj.SetValue(ItemAgeProperty, value); } #endregion } }
<ListBox
    local:ListBoxItemAgeBehavior.IsActive="True"
    ItemsSource="{Binding Data}">
    <ListBox.ItemTemplate>
        <DataTemplate>
            <TextBlock Text="{Binding Title}">
                <TextBlock.Style>
                    <Style TargetType="{x:Type TextBlock}">
                        <Style.Triggers>
                            <DataTrigger Binding="{Binding Path=(local:ListBoxItemAgeBehavior.ItemAge), RelativeSource={RelativeSource AncestorType={x:Type ListBoxItem}}}" Value="Old">
                                <Setter Property="Foreground" Value="Red" />
                            </DataTrigger>
                            <DataTrigger Binding="{Binding Path=(local:ListBoxItemAgeBehavior.ItemAge), RelativeSource={RelativeSource AncestorType={x:Type ListBoxItem}}}" Value="Current">
                                <Setter Property="Foreground" Value="Yellow" />
                            </DataTrigger>
                            <DataTrigger Binding="{Binding Path=(local:ListBoxItemAgeBehavior.ItemAge), RelativeSource={RelativeSource AncestorType={x:Type ListBoxItem}}}" Value="New">
                                <Setter Property="Foreground" Value="Green" />
                            </DataTrigger>
                        </Style.Triggers>
                    </Style>
                </TextBlock.Style>
            </TextBlock>
        </DataTemplate>
    </ListBox.ItemTemplate>
</ListBox>