明らかな解決策は、ModelView要素に行番号プロパティを設定することですが、欠点は、レコードを追加したり、並べ替え順序を変更したりするときに、それらを再生成する必要があることです。
エレガントソリューションはありますか?
私はあなたが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();
}
}
アイテムが追加、削除、または移動される動的リストがある場合でも、この非常に優れたソリューションを使用して、ソースリストの変更が行われた後、リストビューの現在のビューを更新することができます。このコードサンプルは、データソースリスト「mySourceList」(私の場合はObservableCollection)内の現在のアイテムを直接削除し、最後に行番号を正しい値に更新します。
ICollectionView cv = CollectionViewSource.GetDefaultView(listviewNames.ItemsSource);
if (listviewNames.Items.CurrentItem != null)
{
mySourceList.RemoveAt(cv.CurrentPosition);
cv.Refresh();
}
まず、AlternationCount
をitems count
+1に設定する必要があります。例:
<ListView AlternationCount="1000" .... />
次に、AlternationIndex
は、スクロール中でも実際のインデックスを表示します。
<GridViewColumn
Header="#" Width="30"
DisplayMemberBinding="{Binding (ItemsControl.AlternationIndex),
RelativeSource={RelativeSource AncestorType=ListViewItem}}" />
これは魅力のように機能します、私はパフォーマンスについて知りません、それでも私たちはそれを試すことができます
マルチバリューコンバーターを作成する
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>
アイデアは、オブジェクトとリストの両方をコンバーターに送信し、コンバーターに番号を決定させることです。コンバーターを変更して、順序付きリストを表示できます。
コレクション内で要素を移動する必要がある場合でも機能するソリューションを見つけました。したがって、実際に行う必要があるのは、コレクションが変更されるたびにダミープロパティ「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");
}
これは、それがどのように機能するかを理解するのに役立つコードコメントを含む別の方法です。
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();
}
}
ベストアンサーソリューションに従うことで、リストビュー内のアイテムを削除/置換した後もインデックスが更新されないという問題を見つけました。それほど明確ではないヒントがあることを解決するには(小さなコレクションで使用することを提案します):アイテムの削除/置換を実行した後、ObservableCollection(INotifyCollectionChanged).CollectionChanged
イベントをReset
アクションで呼び出す必要があります。これは、既存のObservableCollection
(ItemsSource
)を拡張することで作成できます。または、これが不可能な場合はリフレクションを使用します。
例.
public class ResetableObservableCollection<T> : ObservableCollection<T>
{
public void NotifyReset()
{
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
}
private void ItemsRearranged()
{
Items.NotifyReset();
}
これは、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出力が最後に追加された行を複製していました(ただし、正しい番号が付けられています)。コード内のコレクションが変更されるたびに更新呼び出しを行うことで、これを修正しました。悪い解決策ですが、私にとっては完璧に機能します。
amaca回答は静的リストに最適です。動的の場合:
削除後、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
}
}
これが私の小さなコンバーターで、2017年の[〜#〜] wpf [〜#〜]の時点で。NET4.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
のインスタンスをGridViewColumn.CellTemplate
のResources
または他の場所に追加します。または、ここに示すように、バインドされた要素のBinding
でその場in-situをインスタンス化します。いずれの場合も、ItemIndexConverter
のインスタンスを作成する必要があり、ソースコレクション全体をそれにバインドすることを忘れないでください。ここでは、ItemsSource
のListView
プロパティからソースコレクションへの参照を取得していますが、これには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
に設定されていることがわかります。
以下が実際に必要かどうかはわかりませんが、[〜#〜] wpf [〜#〜]のxmlns=
宣言が更新されてXAML 2009を示していることに注意してください。 、上記の{x:Reference}
の使用法をサポートします。 2つの変更があることに注意してください。 「2006」から「2009」に切り替える場合は、「/ winfx /」を「/ netfx /」に変更する必要があります。