wpf 如何在 ListView 中显示行号?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/660528/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-08 20:09:27  来源:igfitidea点击:

How to display row numbers in a ListView?

wpflistview

提问by Sergey Aldoukhov

The obvious solution would be to have a row number property on a ModelView element, but the drawback is that you have to re-generate those when you add records or change sort order.

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

Is there an elegantsolution?

有没有优雅的解决方案?

回答by amaca

I think you havethe elegant solution, but this works.

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

XAML:

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();
    }
}

回答by zzz

If you have a dynamic list where items are added, deleted or moved, you can still use this very nice solution and simply let the currentview of your listview refresh itself after the changements in your source list are done. This code sample removes the current item directly in the data source list "mySourceList" (which is in my case an ObservableCollection) and finally updates the line numbers to correct values .

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

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

回答by VahidN

First you need to set the AlternationCountto items count+1, for instance:

首先,您需要将 设置AlternationCountitems count+1,例如:

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

Then AlternationIndexwill show the real index, even during the scrolling:

然后AlternationIndex将显示真实索引,即使在滚动期间:

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

回答by Roshan Soni

This will work like a charm, I don't know about performance, Still we can give it a try

这将像魅力一样工作,我不知道性能,但我们仍然可以试一试

Create a Multi Value Converter

创建多值转换器

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;
  }
 }
}

and your Xaml like this

和你的 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>

Idea is to send Object and list both to the converter and let converter decide the number. You can modify converter to display ordered list.

想法是将 Object 和 list 都发送给转换器并让转换器决定数量。您可以修改转换器以显示有序列表。

回答by user3711421

Here is another way, including code comments that will help you understand how it works.

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

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();
    }
}

The XML

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
                }"/>

RelativeSourceis used in particular binding cases when we try to bind a property of a given object to another property of the object itself[1].

当我们尝试将给定对象的属性绑定到对象本身的另一个属性时,RelativeSource用于特定的绑定情况[1]

Using Mode=FindAncestorwe can traverse the hierarchy layers and get a specified element, for example the ListViewItem (we could even grab the GridViewColumn). If you have two ListViewItem elements you can specify which you want with "AncestorLevel = x".

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

Path: Here I simply take the content of the ListViewItem (which is my object "Person").

路径:这里我只是获取 ListViewItem 的内容(这是我的对象“Person”)。

ConverterSince I want to display row numbers in my Number column and not the object Person I need to create a Converter class which can somehow transform my Person object to a corresponding number row. But its not possible, I just wanted to show that the Path goes to the converter. Deleting the Path will send the ListViewItem to the Converter.

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

ConverterParameterSpecify a parameter you want to pass to the IValueConverter class. Here you can send the state if you want the row number to start at 0,1,100 or whatever.

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();
    }
}

回答by random one

I found solution that will work even in case when you need to move your elements inside the collection. So actually what we need to do for it is notify dummy property "ListNumbersNotify" every time our collection is changed and bind everything with that tricky MultiBinding converter.

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

XAML:

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>

Converter:

转换器:

    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;
    }
}

Code behind:

后面的代码:

    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");
    }

回答by Vasyl Mosiiuk

By following best answer solution I found an issue when indexes still not updated after removing/replacing items inside list view. To solve that there is one not so clear hint (I propose to use it in small collections): after executing item removing/replacing you should invoke ObservableCollection(INotifyCollectionChanged).CollectionChangedevent with Resetaction. This is possible to make with extending existing ObservableCollection, which is ItemsSourceor use reflection when this is not possible.

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

Ex.

前任。

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


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

回答by Lev

amacaanswer is great for static lists. For dynamic:

amacaanswer 非常适合静态列表。对于动态:

  1. We should use MultiBinding, second binding is for changing collection;
  2. After deleting ItemsControl not contains deleted object, but ItemContainerGenerator contains. Converter for dynamic lists (I use it for TabControl TabItem's):

    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
       }
    }
    
  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
       }
    }
    

回答by Glenn Slayden

Here's my little converter which works great as of WPFin 2017 with .NET 4.7.2, including with the VirtualizingStackPanelfully enabled:

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

[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();
};

Add an instance of this IValueConverterto the Resourcesof the GridViewColumn.CellTemplate, or elsewhere. Or, instantiate it in-situon the Bindingof the bound element, like I show here. In any case, you need to create an instance of the ItemIndexConverterand don't forget to bind the whole source collection to it. Here I'm pulling a reference to the source collection out of the ItemsSourceproperty of the ListView--but this entails some unrelated hassles over accessing the XAML root, so if you have a better and easier way to refer to the source collection, you should do so.

这方面的一个实例添加IValueConverterResourcesGridViewColumn.CellTemplate,或其他地方。或者,它实例原位Binding绑定元素,就像我在这里展示。在任何情况下,您都需要创建 的实例,ItemIndexConverter并且不要忘记将整个源集合绑定到它。在这里,我从 -- 的ItemsSource属性中ListView提取对源集合的引用,但这会带来一些与访问 XAML 根无关的麻烦,因此如果您有更好、更简单的方法来引用源集合,则应该这样做所以。

As for accessing a property on the XAMLroot, the ListViewroot in XAMLis given the name w_root, and the XAML 2009markup extension {x:Reference ...}is used to access the XAML root element. I don't think "ElementName" binding will work here since the reference occurs in a template context.

作为用于对访问属性XAML根,则ListView在根XAML被赋予名称w_root,并且XAML 2009标记扩展{x:Reference ...}用于访问所述XAML根元素。我认为“ElementName”绑定在这里不起作用,因为引用出现在模板上下文中。

<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>

That's it! It seems to work pretty quickly with a large number of rows, and again, you can see that the reported indices are correct when arbitrarily scrolling around, and that VirtualizingStackPanel.IsVirtualizingis indeed set to True.

就是这样!它似乎在处理大量行时工作得非常快,再次,您可以看到在任意滚动时报告的索引是正确的,并且VirtualizingStackPanel.IsVirtualizing确实设置为True.

enter image description here

在此处输入图片说明

Not sure the following is actually necessary, but notice that the xmlns=declaration for WPFis updated to indicate XAML 2009, in support of the {x:Reference}usage mentioned above. Do notice that there are twochanges; "/winfx/" has to be changed to "/netfx/" when switching from "2006" to "2009".

不确定以下是否真的需要,但请注意WPFxmlns=声明已更新以指示XAML 2009,以支持上述用法。请注意有两个变化;从“2006”切换到“2009”时,“/winfx/”必须更改为“/netfx/”。{x:Reference}

回答by Alexander Bezhin

It's the addition to answerof amaca for problems found by Allon Guralnek and VahidN. Scrolling problem is solved with setting ListView.ItemsPanel to StackPanel in XAML:

这是除了回答amaca的由阿隆Guralnek和VahidN发现的问题。通过在 XAML 中将 ListView.ItemsPanel 设置为 StackPanel 来解决滚动问题:

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

This replacement of default VirtualizingStackPanelwith simple StackPanel disables automatic regeneration of internal collection of ListViewItem. So indices would not chaotically change when scrolling. But this replacement can decrease perfomance on large collections. Also, dynamic numeration changes can be achieved with call CollectionViewSource.GetDefaultView(ListView.ItemsSource).Refresh()when ItemsSource collection changed. Just like with ListView filtering. When I tried to add handler with this call on event INotifyCollectionChanged.CollectionChangedmy ListView output was duplicating last added row (but with correct numeration). Fixed this by placing refresh call after every collection change in code. Bad solution, but it works perfect for me.

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