Jak wyświetlić numery wierszy w widoku listy?

Oczywistym rozwiązaniem byłoby posiadanie właściwości row number na elemencie ModelView, ale wadą jest to, że musisz je ponownie wygenerować, gdy dodajesz rekordy lub zmieniasz kolejność sortowania.

Czy istnieje eleganckie rozwiązanie?

Author: Dave Clemmer, 2009-03-19

10 answers

Myślę, że masz eleganckie rozwiązanie, ale to działa.

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();
    }
}
 36
Author: amaca,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-09-16 23:10:03

Jeśli masz dynamiczną listę, w której elementy są dodawane, usuwane lub przenoszone, możesz nadal używać tego bardzo ładnego rozwiązania i po prostu pozwolić, aby bieżący widok listy odświeżał się po dokonaniu zmian na liście źródeł. Ta próbka kodu usuwa bieżący element bezpośrednio z listy źródeł danych " mySourceList "(która jest w moim przypadku ObservableCollection) i na koniec aktualizuje numery linii do poprawnych wartości .

ICollectionView cv = CollectionViewSource.GetDefaultView(listviewNames.ItemsSource);
if (listviewNames.Items.CurrentItem != null)
{
    mySourceList.RemoveAt(cv.CurrentPosition);
    cv.Refresh();
}
 6
Author: zzz,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-10-30 17:33:31

Najpierw musisz ustawić AlternationCount na items count+1, na przykład:

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

Następnie AlternationIndex pokaże prawdziwy indeks, nawet podczas przewijania:

 <GridViewColumn
       Header="#" Width="30"
       DisplayMemberBinding="{Binding (ItemsControl.AlternationIndex),
       RelativeSource={RelativeSource AncestorType=ListViewItem}}" />
 4
Author: VahidN,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-08-02 11:14:21

Oto inny sposób, w tym komentarze do kodu, które pomogą Ci zrozumieć, jak to działa.

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 jest używane w szczególnych przypadkach wiążących, gdy staramy się powiązać właściwość danego obiektu z inną właściwością samego obiektu [1].

Używając Mode = FindAncestor możemy przemierzać warstwy hierarchii i uzyskać określony element, na przykład ListViewItem (możemy nawet pobrać GridViewColumn). Jeśli posiada dwa elementy ListViewItem, które można określić za pomocą "AncestorLevel = x".

Path: tutaj po prostu biorę zawartość Listvewitem (który jest moim obiektem "Person").

Converter ponieważ chcę wyświetlać numery wierszy w mojej kolumnie Number, a nie osobę obiektu, muszę utworzyć klasę konwertera, która może w jakiś sposób przekształcić mój obiekt Person do odpowiedniego wiersza liczb. ale to niemożliwe, chciałem tylko pokazać, że droga prowadzi do konwerter . Usunięcie ścieżki spowoduje wysłanie ListViewItem do konwertera.

ConverterParameter Określ parametr, który chcesz przekazać do klasy IValueConverter. Tutaj możesz wysłać stan, jeśli chcesz, aby numer wiersza zaczynał się od 0,1,100 lub cokolwiek innego.

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();
    }
}
 1
Author: user3711421,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-10-22 07:09:18

To będzie działać jak urok, Nie znam się na występach, Mimo to możemy spróbować

Utwórz konwerter wielu wartości

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

Oraz your XAML like this

<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 polega na wysłaniu obiektu i listy zarówno do konwertera, jak i niech konwerter zdecyduje o liczbie. Możesz zmodyfikować konwerter, aby wyświetlić uporządkowaną listę.

 1
Author: threepin india,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-12-24 12:56:36

Jest to dodatek do odpowiedzi amaca na problemy znalezione przez Allona Guralnka i Vahidna. Problem z przewijaniem został rozwiązany przy ustawieniu ListView.ItemsPanel do StackPanel w XAML:

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

Zastąpienie domyślnego VirtualizingStackPanel prostym StackPanel wyłącza automatyczną regenerację wewnętrznej kolekcji ListViewItem. Tak więc indeksy nie zmieniałyby się chaotycznie podczas przewijania. Ale ten zamiennik może zmniejszyć wydajność na dużych kolekcjach. Również dynamiczne zmiany numeracji można osiągnąć wywołaniem CollectionViewSource.GetDefaultView(ListView.ItemsSource).Refresh(), gdy kolekcja ItemsSource została zmieniona. Podobnie jak w przypadku filtrowania ListView . Kiedy próbowałem dodać obsługę z tym wywołaniem przy zdarzeniu INotifyCollectionChanged.CollectionChanged, moje wyjście ListView duplikowało Ostatnio dodany wiersz (ale z prawidłową numeracją). Naprawiono to, umieszczając wywołanie refresh po każdej zmianie kolekcji w kodzie. Złe rozwiązanie, ale działa idealnie dla mnie.

 0
Author: Alexander Bezhin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-02-18 10:01:26

Odpowiedź Amaca jest świetna dla list statycznych. Dla dynamiki:

  1. powinniśmy używać Multibindingu, drugie Wiązanie służy do zmiany zbioru;
  2. Po usunięciu ItemsControl nie zawiera usuniętego obiektu, ale ItemContainerGenerator zawiera. Konwerter dla list dynamicznych (używam go do 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
Author: Lev,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-10-20 14:06:55

Oto mój mały konwerter, który działa świetnie od WPF w 2017 roku z . NET 4.7.2 , w tym zVirtualizingStackPanel w pełni włączonym:

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

Dodaj instancję tego IValueConverter do Resources z GridViewColumn.CellTemplate, lub gdzie indziej. Albo utworzyć instancję in-situ na Binding elementu związanego, jak pokazałem tutaj. W każdym razie musisz utworzyć instancję ItemIndexConverter i nie zapomnij powiązać z nią całej kolekcji źródłowej. Tutaj wyciągam odniesienie do źródła zbiór z ItemsSource właściwości ListView -- ale pociąga to za sobą pewne niepowiązane problemy z dostępem do korzenia XAML, więc jeśli masz lepszy i łatwiejszy sposób odwoływania się do kolekcji źródłowej, powinieneś to zrobić.

Jeśli chodzi o dostęp do właściwości XAML root, ListView root wXAML otrzymuje nazwę w_root, aXAML 2009 rozszerzenie znaczników {x:Reference ...} jest używane do uzyskania dostępu do elementu root XAML. Nie sądzę, aby Wiązanie "ElementName" działało tutaj, ponieważ odniesienie występuje w kontekście szablonu.

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

To jest to! Wydaje się, że działa dość szybko z dużą liczbą wierszy i znowu widać, że zgłaszane indeksy są poprawne podczas arbitralnego przewijania, a VirtualizingStackPanel.IsVirtualizing jest rzeczywiście ustawiony na True.

Tutaj wpisz opis obrazka

Nie jestem pewien, czy poniższe informacje są rzeczywiście konieczne, ale zauważ, że xmlns= deklaracja dla WPF została zaktualizowana, aby wskazać XAML 2009, na poparcie {x:Reference} użycie wymienione powyżej. Zauważ, że są dwie zmiany; "/winfx/" musi zostać zmieniony na "/netfx/" podczas przełączania z "2006" na "2009".

 0
Author: Glenn Slayden,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-07-03 09:37:52

Znalazłem rozwiązanie, które sprawdzi się nawet w przypadku, gdy trzeba przenieść swoje elementy wewnątrz kolekcji.

XAML:

                <ListView Name="ListViewCurrentModules">
                    <ListView.ItemTemplate>
                        <DataTemplate>
                            <StackPanel Orientation="Horizontal">
                                <Label Width="60" HorizontalAlignment="Left" FontSize="20" Grid.Column="0" Grid.Row="1" FontWeight="Bold" Foreground="#38CA1E">
                                    <MultiBinding Converter="{helpers:NumberingConvertor}">
                                        <Binding Path="" />
                                        <Binding ElementName="ListViewCurrentModules" />
                                        <Binding Path="ListNumbersNotify" ElementName="This" />
                                    </MultiBinding>
                                </Label>
                                <Border>
                                 ...
                                </Border>
                            </StackPanel>
                        </DataTemplate>
                    </ListView.ItemTemplate>
                </ListView>

Konwerter:

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

Kod za:

    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");
    }
 0
Author: random one,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-08-31 09:58:50

Podążając za rozwiązaniem best answer znalazłem problem, gdy indeksy nadal nie są aktualizowane po usunięciu / zastąpieniu elementów w widoku listy. Aby rozwiązać ten problem, jest jedna niezbyt jasna wskazówka (proponuję użyć jej w małych kolekcjach): po wykonaniu usuwania/zastępowania elementów należy wywołać zdarzenie ObservableCollection(INotifyCollectionChanged).CollectionChanged z Reset akcją. Można to zrobić z rozszerzeniem istniejącego ObservableCollection, czyli ItemsSource lub użyć reflection, gdy nie jest to możliwe.

Ex.

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


private void ItemsRearranged() 
{
    Items.NotifyReset();
}
 0
Author: Vasyl Mosiiuk,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-09-13 14:33:10