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>