web-dev-qa-db-ja.com

ListViewに行番号を表示するにはどうすればよいですか?

明らかな解決策は、ModelView要素に行番号プロパティを設定することですが、欠点は、レコードを追加したり、並べ替え順序を変更したりするときに、それらを再生成する必要があることです。

エレガントソリューションはありますか?

26

私はあなたがhaveエレガントな解決策だと思いますが、これはうまくいきます。

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

アイテムが追加、削除、または移動される動的リストがある場合でも、この非常に優れたソリューションを使用して、ソースリストの変更が行われた後、リストビューの現在のビューを更新することができます。このコードサンプルは、データソースリスト「mySourceList」(私の場合はObservableCollection)内の現在のアイテムを直接削除し、最後に行番号を正しい値に更新します。

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

まず、AlternationCountitems count +1に設定する必要があります。例:

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

次に、AlternationIndexは、スクロール中でも実際のインデックスを表示します。

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

これは魅力のように機能します、私はパフォーマンスについて知りません、それでも私たちはそれを試すことができます

マルチバリューコンバーターを作成する

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>

アイデアは、オブジェクトとリストの両方をコンバーターに送信し、コンバーターに番号を決定させることです。コンバーターを変更して、順序付きリストを表示できます。

2
Roshan Soni

コレクション内で要素を移動する必要がある場合でも機能するソリューションを見つけました。したがって、実際に行う必要があるのは、コレクションが変更されるたびにダミープロパティ「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
random one

これは、それがどのように機能するかを理解するのに役立つコードコメントを含む別の方法です。

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要素が2つある場合は、「AncestorLevel = x」でどちらを指定するかを指定できます。

Path:ここでは、ListViewItem(私のオブジェクト "Person")のコンテンツを取得します。

ConverterオブジェクトPersonではなくNumber列に行番号を表示したいので、Personオブジェクトを何らかの方法で変換できるConverterクラスを作成する必要があります。対応する番号の行。 しかし、それは不可能です。パスがコンバーターに行くことを示したかっただけです。パスを削除すると、ListViewItemがコンバーターに送信されます。

ConverterParameterIValueConverterクラスに渡すパラメーターを指定します。ここで、行番号を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();
    }
}
1
user3711421

ベストアンサーソリューションに従うことで、リストビュー内のアイテムを削除/置換した後もインデックスが更新されないという問題を見つけました。それほど明確ではないヒントがあることを解決するには(小さなコレクションで使用することを提案します):アイテムの削除/置換を実行した後、ObservableCollection(INotifyCollectionChanged).CollectionChangedイベントをResetアクションで呼び出す必要があります。これは、既存のObservableCollectionItemsSource)を拡張することで作成できます。または、これが不可能な場合はリフレクションを使用します。

例.

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


private void ItemsRearranged() 
{
    Items.NotifyReset();
}
0
Vasyl Mosiiuk

これは、Allon GuralnekとVahidNによって発見された問題に対するamacaの answer への追加です。スクロールの問題は、XAMLでListView.ItemsPanelをStackPanelに設定することで解決されます。

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

このデフォルトの VirtualizingStackPanel を単純なStackPanelに置き換えると、ListViewItemの内部コレクションの自動再生成が無効になります。したがって、スクロール時にインデックスが無秩序に変化することはありません。ただし、この置き換えにより、大規模なコレクションのパフォーマンスが低下する可能性があります。また、ItemsSourceコレクションが変更されたときにCollectionViewSource.GetDefaultView(ListView.ItemsSource).Refresh()を呼び出すことで、動的な数値変更を実現できます。 ListViewフィルタリング と同じです。イベント_INotifyCollectionChanged.CollectionChanged_でこの呼び出しを使用してハンドラーを追加しようとすると、ListView出力が最後に追加された行を複製していました(ただし、正しい番号が付けられています)。コード内のコレクションが変更されるたびに更新呼び出しを行うことで、これを修正しました。悪い解決策ですが、私にとっては完璧に機能します。

0

amaca回答は静的リストに最適です。動的の場合:

  1. MultiBindingを使用する必要があります。2番目のバインディングはコレクションを変更するためのものです。
  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
       }
    }
    
0
Lev

これが私の小さなコンバーターで、2017年の[〜#〜] wpf [〜#〜]の時点で。NET4.7.2VirtualizingStackPanel完全に有効:

[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のインスタンスをGridViewColumn.CellTemplateResourcesまたは他の場所に追加します。または、ここに示すように、バインドされた要素のBindingでその場in-situをインスタンス化します。いずれの場合も、ItemIndexConverterのインスタンスを作成する必要があり、ソースコレクション全体をそれにバインドすることを忘れないでください。ここでは、ItemsSourceListViewプロパティからソースコレクションへの参照を取得していますが、これにはXAMLルートへのアクセスに関係のない面倒な作業が伴うため、ソースコレクションを参照する簡単な方法は、そうする必要があります。

[〜#〜] xaml [〜#〜]ルートのプロパティへのアクセスについては、[〜#〜] xaml [〜#〜]のListViewルート]には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

以下が実際に必要かどうかはわかりませんが、[〜#〜] wpf [〜#〜]xmlns=宣言が更新されてXAML 2009を示していることに注意してください。 、上記の{x:Reference}の使用法をサポートします。 2つの変更があることに注意してください。 「2006」から「2009」に切り替える場合は、「/ winfx /」を「/ netfx /」に変更する必要があります。

0
Glenn Slayden