How to display row numbers in a ListView?

前端 未结 10 848
盖世英雄少女心
盖世英雄少女心 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:22

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

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

    Then AlternationIndex will show the real index, even during the scrolling:

     <GridViewColumn
           Header="#" Width="30"
           DisplayMemberBinding="{Binding (ItemsControl.AlternationIndex),
           RelativeSource={RelativeSource AncestorType=ListViewItem}}" />
    
    0 讨论(0)
  • 2020-11-27 05:25

    Here's my little converter which works great as of WPF in 2017 with .NET 4.7.2, including with the VirtualizingStackPanel fully enabled:

    [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 IValueConverter to the Resources of the GridViewColumn.CellTemplate, or elsewhere. Or, instantiate it in-situ on the Binding of the bound element, like I show here. In any case, you need to create an instance of the ItemIndexConverter and don't forget to bind the whole source collection to it. Here I'm pulling a reference to the source collection out of the ItemsSource property 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.

    As for accessing a property on the XAML root, the ListView root in XAML is given the name w_root, and the XAML 2009 markup 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.

    <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.IsVirtualizing is indeed set to True.

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

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

    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.

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

    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

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

    0 讨论(0)
提交回复
热议问题