如何在ListView中显示行号?

时间:2009-03-18 23:45:04

标签: wpf listview

显而易见的解决方案是在ModelView元素上使用行号属性,但缺点是在添加记录或更改排序顺序时必须重新生成这些属性。

是否有优雅解决方案?

10 个答案:

答案 0 :(得分:41)

我认为你拥有优雅的解决方案,但这很有效。

XAML:

<ListView Name="listviewNames">
  <ListView.View>
    <GridView>
      <GridView.Columns>
        <GridViewColumn
          Header="Number"
          DisplayMemberBinding="{Binding RelativeSource={RelativeSource FindAncestor, 
                                         AncestorType={x:Type ListViewItem}}, 
                                         Converter={StaticResource IndexConverter}}" />
        <GridViewColumn
          Header="Name"
          DisplayMemberBinding="{Binding Path=Name}" />
      </GridView.Columns>
    </GridView>
  </ListView.View>
</ListView>

ValueConverter:

public class IndexConverter : IValueConverter
{
    public object Convert(object value, Type TargetType, object parameter, CultureInfo culture)
    {
        ListViewItem item = (ListViewItem) value;
        ListView listView = ItemsControl.ItemsControlFromItemContainer(item) as ListView;
        int index = listView.ItemContainerGenerator.IndexFromContainer(item);
        return index.ToString();
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

答案 1 :(得分:6)

如果您有一个动态列表,其中添加,删除或移动了项目,您仍然可以使用这个非常好的解决方案,只需在源列表中的更改完成后让列表视图的当前视图自行刷新。 此代码示例直接在数据源列表“mySourceList”(在我的示例中为ObservableCollection)中删除当前项,最后将行号更新为正确的值。

ICollectionView cv = CollectionViewSource.GetDefaultView(listviewNames.ItemsSource);
if (listviewNames.Items.CurrentItem != null)
{
    mySourceList.RemoveAt(cv.CurrentPosition);
    cv.Refresh();
}

答案 2 :(得分:4)

首先,您需要将AlternationCount设置为items count + 1,例如:

<ListView AlternationCount="1000" .... />

然后AlternationIndex即使在滚动期间也会显示真实的索引:

 <GridViewColumn
       Header="#" Width="30"
       DisplayMemberBinding="{Binding (ItemsControl.AlternationIndex),
       RelativeSource={RelativeSource AncestorType=ListViewItem}}" />

答案 3 :(得分:2)

这会像魅力一样 我不知道表现, 我们仍然可以尝试一下

创建多值转换器

public class NumberingConvertor : IMultiValueConverter
 {
  public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
  {
   if (values != null && values.Any() && values[0] != null && values[1] != null)
   {
    //return (char)(((List<object>)values[1]).IndexOf(values[0]) + 97);
    return ((List<object>)values[1]).IndexOf(values[0]) + 1;
   }
   return "0";
  }

  public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
  {
   return null;
  }
 }
}

和 像你这样的Xaml

<ItemsControl ItemsSource="{Binding ListObjType}">
        <ItemsControl.ItemsPanel>
            <ItemsPanelTemplate>
                <StackPanel />
            </ItemsPanelTemplate>
        </ItemsControl.ItemsPanel>
        <ItemsControl.ItemTemplate>
            <DataTemplate>
                <StackPanel Orientation="Horizontal">
                    <Label>
                        <MultiBinding Converter="{StaticResource NumberingConvertor}">
                            <Binding Path="" />
                            <Binding Path="ItemsSource"
                                     RelativeSource="{RelativeSource AncestorType=ItemsControl}" />
                        </MultiBinding>
                    </Label>
                    <TextBlock Text="{Binding }" />
                </StackPanel>
            </DataTemplate>
        </ItemsControl.ItemTemplate>
    </ItemsControl>

想法是发送对象并列出两者到转换器并让转换器决定数字。您可以修改转换器以显示有序列表。

答案 4 :(得分:1)

这是另一种方式,包括可帮助您了解其工作原理的代码注释。

public class Person
{
    private string name;
    private int age;
    //Public Properties ....
}

public partial class MainWindow : Window
{

    List<Person> personList;
    public MainWindow()
    {
        InitializeComponent();

        personList= new List<Person>();
        personList.Add(new Person() { Name= "Adam", Agen= 25});
        personList.Add(new Person() { Name= "Peter", Agen= 20});

        lstvwPerson.ItemsSource = personList;
//After updates to the list use lstvwPerson.Items.Refresh();
    }
}

XML

            <GridViewColumn Header="Number" Width="50" 
                DisplayMemberBinding="{ 
                    Binding RelativeSource= {RelativeSource Mode=FindAncestor, AncestorType={x:Type ListViewItem}},
                   DELETE Path=Content, DELETE
                    Converter={StaticResource IndexConverter}, 
                    ConverterParameter=1
                }"/>
当我们尝试将给定对象的属性绑定到对象本身的另一个属性 [1]时,

RelativeSource 用于特定的绑定情况。

使用 Mode = FindAncestor ,我们可以遍历层次结构层并获取指定的元素,例如ListViewItem(我们甚至可以获取GridViewColumn)。如果你有两个ListViewItem元素,你可以指定你想要的&#34; AncestorLevel = x&#34;。

路径:这里我简单地介绍ListViewItem的内容(这是我的对象&#34; Person&#34;)。

转换器因为我想在我的Number列中显示行号而不是对象Person我需要创建一个Converter类,它可以某种方式将我的Person对象转换为相应的数字行。 但它不可能,我只想表明路径转到转换器。删除路径会将ListViewItem发送到转换器。

ConverterParameter 指定要传递给IValueConverter类的参数。如果您希望行号从0,1,100或其他任何位置开始,您可以在此处发送状态。

public class IndexConverter : IValueConverter
{
    public object Convert(object value, Type TargetType, object parameter, System.Globalization.CultureInfo culture)
    {
        //Get the ListViewItem from Value remember we deleted Path, so the value is an object of ListViewItem and not Person
        ListViewItem lvi = (ListViewItem)value;
        //Get lvi's container (listview)
        var listView = ItemsControl.ItemsControlFromItemContainer(lvi) as ListView;

        //Find out the position for the Person obj in the ListView
//we can get the Person object from lvi.Content
        // Of course you can do as in the accepted answer instead!
        // I just think this is easier to understand for a beginner.
        int index = listView.Items.IndexOf(lvi.Content);

        //Convert your XML parameter value of 1 to an int.
        int startingIndex = System.Convert.ToInt32(parameter);

        return index + startingIndex;
    }

    public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

答案 5 :(得分:1)

我发现即使您需要在集合中移动元素的情况下,该解决方案也可以使用。因此,实际上,我们需要做的是每次更改集合时通知虚拟属性“ ListNumbersNotify”,并使用该棘手的MultiBinding转换器绑定所有内容。

XAML:

                <Window ...
                   x:Name="This">
                   ...
                 <ListView Name="ListViewCurrentModules">
                    <ListView.ItemTemplate>
                        <DataTemplate>
                            <StackPanel Orientation="Horizontal">
                                <Label>
                                    <MultiBinding Converter="{helpers:NumberingConvertor}">
                                        <Binding Path="" />
                                        <Binding ElementName="ListViewCurrentModules" />
                                        <Binding Path="ListNumbersNotify" ElementName="This" />
                                    </MultiBinding>
                                </Label>
                                <Border>
                                 ...
                                </Border>
                            </StackPanel>
                        </DataTemplate>
                    </ListView.ItemTemplate>
                </ListView>

转换器:

    public abstract class MultiConvertorBase<T> : MarkupExtension, IMultiValueConverter
where T : class, new()
{
    public abstract object Convert(object[] values, Type targetType, object parameter, CultureInfo culture);

    public virtual object[] ConvertBack(object value, Type[] targetType, object parameter, CultureInfo culture)
    {
        return null;
    }

    public override object ProvideValue(IServiceProvider serviceProvider)
    {
        if (_converter == null)
            _converter = new T();
        return _converter;
    }

    private static T _converter = null;
}

public class NumberingConvertor : MultiConvertorBase<NumberingConvertor>
{
    public override object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
    {
        return ((ListView)values[1]).Items.IndexOf(values[0]) + 1;
    }
}

后面的代码:

    public partial class AddModulesWindow: Window, INotifyPropertyChanged
    {
    ...
    public event PropertyChangedEventHandler PropertyChanged;
    private void OnPropertyChanged(string prop)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(prop));
    }

    public object ListNumbersNotify { get; }

    public AddModulesWindow(ICore core)
    {
        InitializeComponent();

        this.core = core;
        CurrentModuleInfos = new ObservableCollection<ModuleInfo>(core.Modules.Select(m => m?.ModuleInfo));
        CurrentModuleInfos.CollectionChanged += CurrentModuleTypes_CollectionChanged;

        ListViewCurrentModules.ItemsSource = CurrentModuleInfos;
    }

    private void CurrentModuleTypes_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
    {
        OnPropertyChanged("ListNumbersNotify");
    }

答案 6 :(得分:0)

对于Allon Guralnek和VahidN发现的问题,这是amaca的answer的补充。通过在XAML中将ListView.ItemsPanel设置为StackPanel来解决滚动问题:

<ListView.ItemsPanel>
    <ItemsPanelTemplate>
        <StackPanel />
    </ItemsPanelTemplate>
</ListView.ItemsPanel>

使用简单的StackPanel替换默认的VirtualizingStackPanel会禁用ListViewItem的内部集合的自动重新生成。因此滚动时索引不会发生混乱变化。但是这种替代可以降低大型集合的性能。此外,当ItemsSource集合更改时,可以通过调用CollectionViewSource.GetDefaultView(ListView.ItemsSource).Refresh()实现动态编号更改。就像ListView filtering一样。当我尝试在事件INotifyCollectionChanged.CollectionChanged上使用此调用添加处理程序时,我的ListView输出重复了最后添加的行(但具有正确的计数)。通过在代码中的每次集合更改后放置刷新调用来解决此问题解决方案不好,但对我来说非常适合。

答案 7 :(得分:0)

amaca 答案非常适合静态列表。对于动态:

  1. 我们应该使用MultiBinding,第二个绑定用于更改集合;
  2. 删除ItemsControl后不包含已删除的对象,但ItemContainerGenerator包含。 用于动态列表的转换器(我将它用于TabControl TabItem):

    public class TabIndexMultiConverter : MultiConverterBase
    {
       public override object Convert(object[] value, Type targetType, object parameter, CultureInfo culture)
       {
          TabItem tabItem = value.First() as TabItem;
          ItemsControl ic = ItemsControl.ItemsControlFromItemContainer(tabItem);
          object context = tabItem?.DataContext;
    
          int idx = ic == null || context == null // if all objects deleted
                 ? -1
                 : ic.Items.IndexOf(context) + 1;
    
          return idx.ToString(); // ToString necessary
       }
    }
    

答案 8 :(得分:0)

这是我的小转换器,它在 .NET 4.7.2 (包括已完全启用的VirtualizingStackPanel)中,在 WPF 上于2017年运行良好:

[ValueConversion(typeof(IList), typeof(int))]
public sealed class ItemIndexConverter : FrameworkContentElement, IValueConverter
{
    public Object Convert(Object data_item, Type t, Object p, CultureInfo _) =>
        ((IList)DataContext).IndexOf(data_item);

    public Object ConvertBack(Object o, Type t, Object p, CultureInfo _) =>
        throw new NotImplementedException();
};

将此IValueConverter的实例添加到Resources的{​​{1}}或其他地方。或者,在绑定元素的GridViewColumn.CellTemplate上实例化 ,如我在此处所示。无论如何,您都需要创建Binding的实例,并且不要忘记将整个源集合绑定到该实例。在这里,我从ItemIndexConverter的{​​{1}}属性中提取了对源集合的引用-但这在访问XAML根目录方面带来了一些无关的麻烦,因此,如果您有更好和更轻松的方法,要引用源集合,您应该这样做。

对于访问 XAML 根目录上的属性, XAML 中的ItemsSource根目录命名为ListView,而 XAML 2009 标记扩展名ListView用于访问XAML根元素。我认为“ ElementName”绑定在这里不起作用,因为该引用发生在模板上下文中。

w_root

就是这样!它似乎可以在大量行中快速运行,并且可以再次看到,任意滚动时所报告的索引是正确的,并且{x:Reference ...}确实设置为<ListView x:Class="myApp.myListView" x:Name="w_root" xmlns="http://schemas.microsoft.com/netfx/2009/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:myApp" VirtualizingStackPanel.IsVirtualizing="True" VirtualizingStackPanel.VirtualizationMode="Recycling"> <ListView.View> <GridView> <GridViewColumn Width="50"> <GridViewColumn.CellTemplate> <DataTemplate> <TextBlock> <TextBlock.Text> <Binding> <Binding.Converter> <local:ItemIndexConverter DataContext="{Binding Source={x:Reference w_root}, Path=(ItemsControl.ItemsSource)}" /> </Binding.Converter> </Binding> </TextBlock.Text> </TextBlock> </DataTemplate> </GridViewColumn.CellTemplate> </GridViewColumn> </GridView> </ListView.View> </ListView>

enter image description here

不确定实际上是否有必要执行以下操作,但请注意, WPF VirtualizingStackPanel.IsVirtualizing声明已更新为指示 XAML 2009 ,以支持{{ 1}}上面提到的用法。请注意,有两个更改;从“ 2006”切换到“ 2009”时,必须将“ / winfx /”更改为“ / netfx /”。

答案 9 :(得分:0)

通过遵循最佳答案解决方案,我在删除/替换列表视图中的项目后仍未更新索引时发现一个问题。为了解决一个不太清晰的提示(我建议在小集合中使用它):在执行项目删除/替换之后,您应该使用ObservableCollection(INotifyCollectionChanged).CollectionChanged动作调用Reset事件。可以通过扩展现有的ObservableCollection(即ItemsSource)来实现,或者在不可能的情况下使用反射。

例如。

public class ResetableObservableCollection<T> : ObservableCollection<T>
{
        public void NotifyReset()
        {
            OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
        }
}


private void ItemsRearranged() 
{
    Items.NotifyReset();
}