如何在ListBox中绑定和显示ListBoxItem索引?
How to bind and display ListBoxItem index in ListBox?
我有一个包含以下 Nuget 包的 .NET 5 项目:
- UI
的 HandyControls
- 用于在列表中拖放元素的 Gong-Wpf-DragDrop
我有一个模型的 XAML with a
ListBoxand a ViewModel with a
ObservableCollection`。
ObservableCollection
绑定为 ItemSource
的 ListBox
我想达到的目标:
当我将项目拖放到不同位置(或 Add/Delete)时,我希望刷新索引。
示例:
之前Drag/Drop
Drag/Drop
之后
其实我绑定了gong-wpf-dragdrop的drophandler
在删除结束时,我手动刷新列表中的每个索引。
有什么方法可以轻松做到吗?因为实际上我必须手动刷新索引。
总结:
当我 reorder/delete/add 项目时,我希望每个项目的 Model.Index 更新为 ListBox 中的正确索引位置。
我的任务是:
- 显示索引(基于一个)
- 提供重新排序元素的可能性
我尝试寻找类似的问题,但没有找到太多可以帮助我的问题。
提前致谢:)
型号:
public class Model : BindablePropertyBase
{
private int index;
private string name;
public int Index
{
get { return index; }
set
{
index = value;
RaisePropertyChanged();
}
}
public string Name
{
get { return name; }
set
{
name = value;
RaisePropertyChanged();
}
}
}
下面是一个带有简单绑定列表的 xaml
MainWindow.xaml
<hc:Window x:Class="ListBoxIndexTest.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:hc="https://handyorg.github.io/handycontrol"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:dd="clr-namespace:GongSolutions.Wpf.DragDrop;assembly=GongSolutions.Wpf.DragDrop"
xmlns:local="clr-namespace:ListBoxIndexTest"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="600">
<Window.DataContext>
<local:MainWindowViewModel />
</Window.DataContext>
<Grid>
<ListBox ItemsSource="{Binding TestList}" dd:DragDrop.DropHandler="{Binding}"
dd:DragDrop.IsDropTarget="True" dd:DragDrop.IsDragSource="True">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid Margin="10"
Background="Aqua">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition />
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding Index}" Margin="10,0"/>
<TextBlock Text="{Binding Name}"
Grid.Column="1"/>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
MainWindowViewModel.cs
public class MainWindowViewModel : BindablePropertyBase, IDropTarget
{
private ObservableCollection<Model> test_list;
public ObservableCollection<Model> TestList
{
get
{
return test_list;
}
set
{
test_list = value;
RaisePropertyChanged();
}
}
// Constructor
public MainWindowViewModel()
{
TestList = new ObservableCollection<Model>()
{
new Model()
{
Index = 1,
Name = "FirstModel"
},
new Model()
{
Index = 2,
Name = "SecondModel"
},
new Model()
{
Index = 3,
Name = "ThirdModel"
}
};
}
public void DragOver(IDropInfo dropInfo)
{
Model sourceItem = dropInfo.Data as Model;
Model targetItem = dropInfo.TargetItem as Model;
if (sourceItem != null && targetItem != null)
{
dropInfo.DropTargetAdorner = DropTargetAdorners.Insert;
dropInfo.Effects = DragDropEffects.Move;
}
}
public void Drop(IDropInfo dropInfo)
{
Model sourceItem = dropInfo.Data as Model;
Model targetItem = dropInfo.TargetItem as Model;
if(sourceItem != null && targetItem != null)
{
int s_index = sourceItem.Index - 1;
int t_index = targetItem.Index - 1;
TestList.RemoveAt(s_index);
TestList.Insert(t_index, sourceItem);
RefreshAllIndexes();
}
}
private void RefreshAllIndexes()
{
for (int i = 0; i < TestList.Count; i++)
{
TestList[i].Index = i + 1;
}
}
}
我认为在 WPF 中没有现成的绑定到容器索引的方法。您的解决方案其实很容易理解。
如果您发现自己经常绑定到索引,您可以创建自己的附加 property/value 转换器,使用 these helpers until it finds the parent ItemsControl
and makes use of the IndexFromContainer
方法在内部爬升可视化树。
下面是一些让您开始使用此方法的代码:
首先是一个小的辅助函数,用于爬上可视化树以查找通用类型的项目:
public static DependencyObject FindParentOfType<T>(DependencyObject child) where T : DependencyObject {
//We get the immediate parent item
DependencyObject parentObject = VisualTreeHelper.GetParent(child);
//we've reached the end of the tree
if (parentObject == null) {
return null;
}
//check if the parent matches the type we're looking for
if (parentObject is T parent) {
return parent;
} else {
return FindParentOfType<T>(parentObject);
}
}
然后一个值转换器将控件作为输入值,returns它的索引在第一个遇到的ItemsControl
:
public class ContainerToIndexConverter : IValueConverter {
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) {
//Cast the passed value as an ItemsControl container
DependencyObject container = value as ContentPresenter;
if (container == null) {
container = value as ContentControl;
}
//Finds the parent ItemsControl by looking up the visual tree
var itemControls = (ItemsControl)FindParentOfType<ItemsControl>(container);
//Gets the index of the container from the parent ItemsControl
return itemControls.ItemContainerGenerator.IndexFromContainer(container);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) {
throw new NotImplementedException();
}
}
这就是您在 XAML 中使用它的方式:
<ListBox.ItemTemplate>
<DataTemplate>
<!-- This will display the index of the list item. -->
<TextBlock Text="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=ContentPresenter}, Converter={StaticResource ContainerToIndexConverter}}" />
</DataTemplate>
</ListBox.ItemTemplate>
我有一个包含以下 Nuget 包的 .NET 5 项目:
- UI 的 HandyControls
- 用于在列表中拖放元素的 Gong-Wpf-DragDrop
我有一个模型的 XAML with a
ListBoxand a ViewModel with a
ObservableCollection`。
ObservableCollection
绑定为 ItemSource
的 ListBox
我想达到的目标:
当我将项目拖放到不同位置(或 Add/Delete)时,我希望刷新索引。
示例:
之前Drag/Drop
Drag/Drop
之后其实我绑定了gong-wpf-dragdrop的drophandler 在删除结束时,我手动刷新列表中的每个索引。
有什么方法可以轻松做到吗?因为实际上我必须手动刷新索引。
总结: 当我 reorder/delete/add 项目时,我希望每个项目的 Model.Index 更新为 ListBox 中的正确索引位置。
我的任务是:
- 显示索引(基于一个)
- 提供重新排序元素的可能性
我尝试寻找类似的问题,但没有找到太多可以帮助我的问题。 提前致谢:)
型号:
public class Model : BindablePropertyBase
{
private int index;
private string name;
public int Index
{
get { return index; }
set
{
index = value;
RaisePropertyChanged();
}
}
public string Name
{
get { return name; }
set
{
name = value;
RaisePropertyChanged();
}
}
}
下面是一个带有简单绑定列表的 xaml MainWindow.xaml
<hc:Window x:Class="ListBoxIndexTest.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:hc="https://handyorg.github.io/handycontrol"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:dd="clr-namespace:GongSolutions.Wpf.DragDrop;assembly=GongSolutions.Wpf.DragDrop"
xmlns:local="clr-namespace:ListBoxIndexTest"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="600">
<Window.DataContext>
<local:MainWindowViewModel />
</Window.DataContext>
<Grid>
<ListBox ItemsSource="{Binding TestList}" dd:DragDrop.DropHandler="{Binding}"
dd:DragDrop.IsDropTarget="True" dd:DragDrop.IsDragSource="True">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid Margin="10"
Background="Aqua">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition />
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding Index}" Margin="10,0"/>
<TextBlock Text="{Binding Name}"
Grid.Column="1"/>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
MainWindowViewModel.cs
public class MainWindowViewModel : BindablePropertyBase, IDropTarget
{
private ObservableCollection<Model> test_list;
public ObservableCollection<Model> TestList
{
get
{
return test_list;
}
set
{
test_list = value;
RaisePropertyChanged();
}
}
// Constructor
public MainWindowViewModel()
{
TestList = new ObservableCollection<Model>()
{
new Model()
{
Index = 1,
Name = "FirstModel"
},
new Model()
{
Index = 2,
Name = "SecondModel"
},
new Model()
{
Index = 3,
Name = "ThirdModel"
}
};
}
public void DragOver(IDropInfo dropInfo)
{
Model sourceItem = dropInfo.Data as Model;
Model targetItem = dropInfo.TargetItem as Model;
if (sourceItem != null && targetItem != null)
{
dropInfo.DropTargetAdorner = DropTargetAdorners.Insert;
dropInfo.Effects = DragDropEffects.Move;
}
}
public void Drop(IDropInfo dropInfo)
{
Model sourceItem = dropInfo.Data as Model;
Model targetItem = dropInfo.TargetItem as Model;
if(sourceItem != null && targetItem != null)
{
int s_index = sourceItem.Index - 1;
int t_index = targetItem.Index - 1;
TestList.RemoveAt(s_index);
TestList.Insert(t_index, sourceItem);
RefreshAllIndexes();
}
}
private void RefreshAllIndexes()
{
for (int i = 0; i < TestList.Count; i++)
{
TestList[i].Index = i + 1;
}
}
}
我认为在 WPF 中没有现成的绑定到容器索引的方法。您的解决方案其实很容易理解。
如果您发现自己经常绑定到索引,您可以创建自己的附加 property/value 转换器,使用 these helpers until it finds the parent ItemsControl
and makes use of the IndexFromContainer
方法在内部爬升可视化树。
下面是一些让您开始使用此方法的代码:
首先是一个小的辅助函数,用于爬上可视化树以查找通用类型的项目:
public static DependencyObject FindParentOfType<T>(DependencyObject child) where T : DependencyObject {
//We get the immediate parent item
DependencyObject parentObject = VisualTreeHelper.GetParent(child);
//we've reached the end of the tree
if (parentObject == null) {
return null;
}
//check if the parent matches the type we're looking for
if (parentObject is T parent) {
return parent;
} else {
return FindParentOfType<T>(parentObject);
}
}
然后一个值转换器将控件作为输入值,returns它的索引在第一个遇到的ItemsControl
:
public class ContainerToIndexConverter : IValueConverter {
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) {
//Cast the passed value as an ItemsControl container
DependencyObject container = value as ContentPresenter;
if (container == null) {
container = value as ContentControl;
}
//Finds the parent ItemsControl by looking up the visual tree
var itemControls = (ItemsControl)FindParentOfType<ItemsControl>(container);
//Gets the index of the container from the parent ItemsControl
return itemControls.ItemContainerGenerator.IndexFromContainer(container);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) {
throw new NotImplementedException();
}
}
这就是您在 XAML 中使用它的方式:
<ListBox.ItemTemplate>
<DataTemplate>
<!-- This will display the index of the list item. -->
<TextBlock Text="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=ContentPresenter}, Converter={StaticResource ContainerToIndexConverter}}" />
</DataTemplate>
</ListBox.ItemTemplate>