How to display row numbers in a ListView?

前端 未结 10 847
盖世英雄少女心
盖世英雄少女心 2020-11-27 04:33

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 sor

相关标签:
10条回答
  • 2020-11-27 05:10

    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).CollectionChanged event with Reset action. This is possible to make with extending existing ObservableCollection, which is ItemsSource or use reflection when this is not possible.

    Ex.

    public class ResetableObservableCollection<T> : ObservableCollection<T>
    {
            public void NotifyReset()
            {
                OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
            }
    }
    
    
    private void ItemsRearranged() 
    {
        Items.NotifyReset();
    }
    
    0 讨论(0)
  • 2020-11-27 05:12

    amaca answer is great for static lists. For dynamic:

    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
         }
      }
      
    0 讨论(0)
  • 2020-11-27 05:14

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

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

    This replacement of default VirtualizingStackPanel with 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.CollectionChanged my 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.

    0 讨论(0)
  • 2020-11-27 05:17

    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

                <GridViewColumn Header="Number" Width="50" 
                    DisplayMemberBinding="{ 
                        Binding RelativeSource= {RelativeSource Mode=FindAncestor, AncestorType={x:Type ListViewItem}},
                       DELETE Path=Content, DELETE
                        Converter={StaticResource IndexConverter}, 
                        ConverterParameter=1
                    }"/>
    

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

    Using Mode=FindAncestor we 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".

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

    Converter Since 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.

    ConverterParameter Specify 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.

    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();
        }
    }
    
    0 讨论(0)
  • 2020-11-27 05:20

    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 .

    ICollectionView cv = CollectionViewSource.GetDefaultView(listviewNames.ItemsSource);
    if (listviewNames.Items.CurrentItem != null)
    {
        mySourceList.RemoveAt(cv.CurrentPosition);
        cv.Refresh();
    }
    
    0 讨论(0)
  • 2020-11-27 05:22

    I think you have the elegant solution, but this works.

    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();
        }
    }
    
    0 讨论(0)
提交回复
热议问题