Как отобразить номера строк в ListView? - PullRequest
26 голосов
/ 19 марта 2009

Очевидное решение состоит в том, чтобы иметь свойство номера строки в элементе ModelView, но недостатком является то, что вы должны заново генерировать их при добавлении записей или изменении порядка сортировки.

Есть ли элегантное решение?

Ответы [ 10 ]

41 голосов
/ 19 марта 2009

Я думаю, у вас есть элегантное решение, но это работает.

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();
    }
}
6 голосов
/ 30 октября 2012

Если у вас есть динамический список, в котором элементы добавляются, удаляются или перемещаются, вы все равно можете использовать это очень хорошее решение и просто позволить текущему представлению вашего просмотра списка обновиться после внесения изменений в исходный список. Этот образец кода удаляет текущий элемент непосредственно из списка источников данных «mySourceList» (который в моем случае является ObservableCollection) и, наконец, обновляет номера строк до правильных значений.

ICollectionView cv = CollectionViewSource.GetDefaultView(listviewNames.ItemsSource);
if (listviewNames.Items.CurrentItem != null)
{
    mySourceList.RemoveAt(cv.CurrentPosition);
    cv.Refresh();
}
4 голосов
/ 02 августа 2014

Сначала вам нужно установить AlternationCount на items count + 1, например:

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

Тогда AlternationIndex покажет реальный индекс, даже во время прокрутки:

 <GridViewColumn
       Header="#" Width="30"
       DisplayMemberBinding="{Binding (ItemsControl.AlternationIndex),
       RelativeSource={RelativeSource AncestorType=ListViewItem}}" />
2 голосов
/ 24 декабря 2017

Это будет работать как шарм, Я не знаю о производительности, Тем не менее, мы можем попробовать

Создание конвертера нескольких значений

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>

Идея состоит в том, чтобы отправить объект и список обоим конвертеру и позволить конвертеру определить число. Вы можете изменить конвертер для отображения упорядоченного списка.

1 голос
/ 31 августа 2018

Я нашел решение, которое будет работать даже в том случае, если вам нужно переместить свои элементы в коллекцию. Так что на самом деле нам нужно сделать это - уведомлять фиктивное свойство «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");
    }
1 голос
/ 21 октября 2015

Вот еще один способ, включая комментарии к коду, которые помогут вам понять, как это работает.

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

RelativeSource используется в особых случаях привязки, когда мы пытаемся связать свойство данного объекта с другим свойством самого объекта [1] .

Используя Mode = FindAncestor , мы можем пройти по слоям иерархии и получить указанный элемент, например ListViewItem (мы могли бы даже получить GridViewColumn). Если у вас есть два элемента ListViewItem, вы можете указать, какой вы хотите, с помощью «AncestorLevel = x».

Путь : Здесь я просто беру содержимое ListViewItem (который является моим объектом "Person").

Converter Поскольку я хочу отображать номера строк в столбце 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();
    }
}
0 голосов
/ 13 сентября 2018

Следуя наилучшему решению, я обнаружил проблему, когда индексы все еще не обновлялись после удаления / замены элементов в представлении списка. Чтобы решить, что есть одна не очень понятная подсказка (я предлагаю использовать ее в небольших коллекциях): после выполнения удаления / замены элемента необходимо вызвать событие ObservableCollection(INotifyCollectionChanged).CollectionChanged с действием Reset. Это можно сделать с расширением существующего ObservableCollection, то есть ItemsSource, или использовать отражение, когда это невозможно.

Ex.

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


private void ItemsRearranged() 
{
    Items.NotifyReset();
}
0 голосов
/ 03 июля 2018

Вот мой маленький конвертер, который отлично работает с WPF в 2017 году с .NET 4.7.2 , в том числе с полностью включенным 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();
};

Добавьте экземпляр этого IValueConverter в Resources из GridViewColumn.CellTemplate или в другое место. Или создайте его in-situ на Binding связанного элемента, как я покажу здесь. В любом случае вам нужно создать экземпляр ItemIndexConverter и не забудьте привязать к нему всю исходную коллекцию. Здесь я извлекаю ссылку на исходную коллекцию из свойства ItemsSource ListView, но это влечет за собой некоторые не связанные с этим проблемы с доступом к корню XAML, поэтому, если у вас есть более удобный и простой способ обратиться к Исходная коллекция, вы должны сделать это.

Что касается доступа к свойству в корне 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>

Вот и все! Кажется, он работает довольно быстро с большим количеством строк, и опять же, вы можете видеть, что сообщаемые индексы являются правильными при произвольной прокрутке, и что VirtualizingStackPanel.IsVirtualizing действительно установлено на True.

enter image description here

Не уверен, что на самом деле необходимо следующее, но обратите внимание, что объявление xmlns= для WPF обновлено, чтобы указать XAML 2009 , в поддержку использования {x:Reference}, упомянутого выше , Обратите внимание, что есть два изменения; "/ winfx /" необходимо изменить на "/ netfx /" при переключении с "2006" на "2009".

0 голосов
/ 20 октября 2016

amaca ответ отлично подходит для статических списков. Для динамического:

  1. Мы должны использовать MultiBinding, вторая привязка предназначена для изменения коллекции;
  2. После удаления ItemsControl не содержит удаленный объект, но ItemContainerGenerator содержит. Конвертер для динамических списков (я использую его для 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 голосов
/ 18 февраля 2015

Это дополнение к ответу amaca за проблемы, обнаруженные Аллоном Гуралнеком и Вахидом. Проблема с прокруткой решается установкой ListView.ItemsPanel в StackPanel в XAML:

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

Эта замена по умолчанию VirtualizingStackPanel простой StackPanel отключает автоматическое восстановление внутренней коллекции ListViewItem. Таким образом, индексы не будут хаотично меняться при прокрутке. Но эта замена может снизить производительность на больших коллекциях. Кроме того, динамические изменения нумерации могут быть достигнуты с помощью вызова CollectionViewSource.GetDefaultView(ListView.ItemsSource).Refresh() при изменении коллекции ItemsSource. Как и в случае фильтрация ListView . Когда я попытался добавить обработчик с этим вызовом для события INotifyCollectionChanged.CollectionChanged, мой вывод ListView дублировал последнюю добавленную строку (но с правильной нумерацией). Исправлено это путем обновления вызова после каждого изменения коллекции в коде. Плохое решение, но оно идеально подходит для меня.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...