トップ回答者
ItemsControl 仮想化時のAlternationIndexについて

質問
-
毎々お世話になっております。
ItemsControlで仮想化時にすばやくコントロールの大きさを変更したりアイテムをスクロールさせたりすると
AlternationIndexの順序が変わります(不定)。通常、例えばアイテムが50件あるときのindex番号は
仮想化されていなければ0,1,2,3・・・49のようになるかと思いますが
仮想化(リサイクル)している場合、48,49,0,1,2・・・47のようにその都度変わると思います。以下、サンプル。
<Window x:Class="Sample1" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:WPFSample" mc:Ignorable="d" Title="Sample1" Height="300" Width="300"> <Window.Resources> <AlternationConverter x:Key="ItemsRowBackground"> <SolidColorBrush Color="White"/> <SolidColorBrush Color="AliceBlue"/> </AlternationConverter> <AlternationConverter x:Key="ItemsRowForeground"> <SolidColorBrush Color="#5B9BD5"/> <SolidColorBrush Color="#ED7D31"/> <SolidColorBrush Color="#A5A5A5"/> <SolidColorBrush Color="#FFC000"/> <SolidColorBrush Color="#4472C4"/> <SolidColorBrush Color="#70AD47"/> </AlternationConverter> </Window.Resources> <Grid> <ListBox ItemsSource="{Binding Mode=OneWay}" AlternationCount="{Binding Count}"> <ListBox.Resources> <Style TargetType="{x:Type ScrollViewer}"> <Setter Property="CanContentScroll" Value="True" /> <Setter Property="HorizontalScrollBarVisibility" Value="Disabled" /> <Setter Property="VerticalScrollBarVisibility" Value="Visible" /> <Setter Property="VirtualizingPanel.IsVirtualizing" Value="True" /> <Setter Property="VirtualizingPanel.VirtualizationMode" Value="Recycling" /> <Setter Property="Padding" Value="10" /> </Style> </ListBox.Resources> <ListBox.ItemContainerStyle> <Style TargetType="{x:Type ListBoxItem}"> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type ContentControl}"> <Border Background="{Binding Path=(ItemsControl.AlternationIndex), RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource ItemsRowBackground}}"> <ContentPresenter/> </Border> </ControlTemplate> </Setter.Value> </Setter> </Style> </ListBox.ItemContainerStyle> <ListBox.ItemsPanel> <ItemsPanelTemplate> <VirtualizingStackPanel /> </ItemsPanelTemplate> </ListBox.ItemsPanel> <ListBox.ItemTemplate> <DataTemplate> <StackPanel Orientation="Horizontal" Margin="5"> <TextBlock Text="{Binding (ItemsControl.AlternationIndex),RelativeSource={RelativeSource AncestorType={x:Type ListBoxItem}}}"/> <TextBlock Text="■" Foreground="{Binding Path=(ItemsControl.AlternationIndex), RelativeSource={RelativeSource AncestorType={x:Type ListBoxItem}}, Converter={StaticResource ItemsRowForeground}}"/> <TextBlock Text="{Binding}"/> </StackPanel> </DataTemplate> </ListBox.ItemTemplate> </ListBox> </Grid> </Window>
Imports System.Collections.ObjectModel Public Class Sample1 Public Sub New() ' この呼び出しはデザイナーで必要です。 InitializeComponent() ' InitializeComponent() 呼び出しの後で初期化を追加します。 Dim data As New ObservableCollection(Of String) For i As Integer = 1 To 50 data.Add("アイテム" & i.ToString) Next Me.DataContext = data End Sub End Class
行の背景色など縞々にしたりする場合よくAlternationIndexを使用して対応する旨が
MSDNやインターネットに記載されていますが仮想化時の対応方法などを見つけることができませんでした。
https://msdn.microsoft.com/ja-jp/library/system.windows.controls.itemscontrol.alternationcount(v=vs.110).aspx
https://msdn.microsoft.com/ja-jp/library/system.windows.controls.itemscontrol.alternationindex(v=vs.110).aspx
https://msdn.microsoft.com/ja-jp/library/system.windows.controls.alternationconverter(v=vs.110).aspxindex番号が変わってしまうためその都度の見栄えが変わってしまう可能性がありますが触れられていません。
皆様はどのように対応されているのでしょうか?
VMにindexプロパティを自前で持つかConverterを作るなどして対応するのが一般的なのでしょうか?(データ件数をある程度絞り込んでページャーなどで対応して仮想化パネルは使用しない?)
それとも、私の理解不足か何か見落としているのでしょうか?
以上、アドバイスをよろしくお願いいたします。
<開発環境>
Visual Studio2015 Community(VB)
.NET Framework4.6.1
WPFアプリケーション
Windows7 Pro x64
- 編集済み mikupedia 2016年7月11日 4:03 誤字等訂正
回答
-
どうにもうまい具合に更新ができないので、あきらめて愚直に書き換えてみる
Class ListBoxEx Inherits ListBox Shared Sub New() Dim fi = ItemsControl.AlternationIndexProperty.GetType().GetField("_readOnlyKey", Reflection.BindingFlags.NonPublic Or Reflection.BindingFlags.Instance) _AlternationIndexPropertyKey = CType(fi.GetValue(ItemsControl.AlternationIndexProperty), DependencyPropertyKey) End Sub Private Shared _AlternationIndexPropertyKey As DependencyPropertyKey Private flag As Boolean Private panel As Panel Protected Overrides Sub PrepareContainerForItemOverride(element As DependencyObject, item As Object) If (Not flag) Then flag = True '生成処理が済んだら振り直しさせる Dim act As Action = AddressOf RefreshAlternationIndex Me.Dispatcher.BeginInvoke(act, Windows.Threading.DispatcherPriority.Loaded, New Object() {}) End If MyBase.PrepareContainerForItemOverride(element, item) panel = TryCast(VisualTreeHelper.GetParent(element), Panel) End Sub Private Sub RefreshAlternationIndex() System.Diagnostics.Debug.WriteLine("Refresh ") flag = False If (TypeOf panel Is VirtualizingPanel) Then If (VirtualizingPanel.GetIsVirtualizing(Me)) Then For Each d As DependencyObject In panel.Children Dim lbi As ListBoxItem = d Dim index As Integer = CInt(lbi.GetValue(ItemsControl.AlternationIndexProperty)) Dim indexFix As Integer = Me.ItemContainerGenerator.IndexFromContainer(lbi) If (index <> indexFix) Then Dim o As Object = Me.ItemContainerGenerator.ItemFromContainer(lbi) lbi.SetValue(_AlternationIndexPropertyKey, indexFix) System.Diagnostics.Debug.WriteLine(index & " -> " & indexFix) End If Next End If End If End Sub End Class
これでもスクロール中はズレて見えてしまいます...orz個別に明示されていない限りgekkaがフォーラムに投稿したコードにはフォーラム使用条件に基づき「MICROSOFT LIMITED PUBLIC LICENSE」が適用されます。(かなり自由に使ってOK!)
- 回答としてマーク mikupedia 2016年7月19日 2:20
すべての返信
-
むりやり書き換えて修正してみる
2016/07/12 追記
PrepareContainerForItemOverrideで設定し直す方法のが簡単でしたClass ListBoxEx Inherits ListBox Shared Sub New() Dim fi = ItemsControl.AlternationIndexProperty.GetType().GetField("_readOnlyKey", Reflection.BindingFlags.NonPublic Or Reflection.BindingFlags.Instance) _AlternationIndexPropertyKey = CType(fi.GetValue(ItemsControl.AlternationIndexProperty), DependencyPropertyKey) End Sub Private Shared _AlternationIndexPropertyKey As DependencyPropertyKey Protected Overrides Sub PrepareContainerForItemOverride(element As DependencyObject, item As Object) Dim lbi As ListBoxItem = CType(element, ListBoxItem) Dim indexFix = Me.ItemContainerGenerator.IndexFromContainer(element) lbi.SetValue(_AlternationIndexPropertyKey, indexFix) MyBase.PrepareContainerForItemOverride(element, item) End Sub End Class
以下旧コード
Class ListBoxEx Inherits ListBox Shared Sub New() Dim fi = ItemsControl.AlternationIndexProperty.GetType().GetField("_readOnlyKey", Reflection.BindingFlags.NonPublic Or Reflection.BindingFlags.Instance) _AlternationIndexPropertyKey = CType(fi.GetValue(ItemsControl.AlternationIndexProperty), DependencyPropertyKey) End Sub Private Shared _AlternationIndexPropertyKey As DependencyPropertyKey Protected Overrides Function GetContainerForItemOverride() As DependencyObject Dim lbi As ListBoxItem = CType(MyBase.GetContainerForItemOverride(), ListBoxItem) Dim bnd As Binding = New Binding() bnd.Path = New PropertyPath(ContentControl.ContentProperty) bnd.Source = lbi lbi.SetBinding(ListBoxEx.ContentProperty, bnd) Return lbi End Function Private Shared ContentProperty As DependencyProperty _ = DependencyProperty.RegisterAttached("Content", GetType(Object), GetType(ListBoxEx), New PropertyMetadata(Nothing, AddressOf OnContentPropertyChanged)) Private Shared Sub OnContentPropertyChanged(ByVal dpo As DependencyObject, ByVal e As DependencyPropertyChangedEventArgs) Dim lbi As ListBoxItem = CType(dpo, ListBoxItem) Dim listBox As ListBox = GetListBox(lbi) If (listBox Is Nothing) Then Return End If Dim index As Integer = CInt(lbi.GetValue(ItemsControl.AlternationIndexProperty)) Dim indexFix As Integer = listBox.ItemContainerGenerator.IndexFromContainer(lbi) If (index <> indexFix) Then Dim o As Object = listBox.ItemContainerGenerator.ItemFromContainer(lbi) If Not (o Is DependencyProperty.UnsetValue) Then lbi.SetValue(_AlternationIndexPropertyKey, indexFix) End If End If End Sub Private Shared Function GetListBox(ByVal lbi As ListBoxItem) As ListBox Dim d As DependencyObject = lbi While d IsNot Nothing d = VisualTreeHelper.GetParent(d) If (TypeOf d Is ListBox) Then Return CType(d, ListBox) End If End While Return Nothing End Function End Class
個別に明示されていない限りgekkaがフォーラムに投稿したコードにはフォーラム使用条件に基づき「MICROSOFT LIMITED PUBLIC LICENSE」が適用されます。(かなり自由に使ってOK!)
- 編集済み gekkaMVP 2016年7月12日 3:27 コードを追加
-
どうにもうまい具合に更新ができないので、あきらめて愚直に書き換えてみる
Class ListBoxEx Inherits ListBox Shared Sub New() Dim fi = ItemsControl.AlternationIndexProperty.GetType().GetField("_readOnlyKey", Reflection.BindingFlags.NonPublic Or Reflection.BindingFlags.Instance) _AlternationIndexPropertyKey = CType(fi.GetValue(ItemsControl.AlternationIndexProperty), DependencyPropertyKey) End Sub Private Shared _AlternationIndexPropertyKey As DependencyPropertyKey Private flag As Boolean Private panel As Panel Protected Overrides Sub PrepareContainerForItemOverride(element As DependencyObject, item As Object) If (Not flag) Then flag = True '生成処理が済んだら振り直しさせる Dim act As Action = AddressOf RefreshAlternationIndex Me.Dispatcher.BeginInvoke(act, Windows.Threading.DispatcherPriority.Loaded, New Object() {}) End If MyBase.PrepareContainerForItemOverride(element, item) panel = TryCast(VisualTreeHelper.GetParent(element), Panel) End Sub Private Sub RefreshAlternationIndex() System.Diagnostics.Debug.WriteLine("Refresh ") flag = False If (TypeOf panel Is VirtualizingPanel) Then If (VirtualizingPanel.GetIsVirtualizing(Me)) Then For Each d As DependencyObject In panel.Children Dim lbi As ListBoxItem = d Dim index As Integer = CInt(lbi.GetValue(ItemsControl.AlternationIndexProperty)) Dim indexFix As Integer = Me.ItemContainerGenerator.IndexFromContainer(lbi) If (index <> indexFix) Then Dim o As Object = Me.ItemContainerGenerator.ItemFromContainer(lbi) lbi.SetValue(_AlternationIndexPropertyKey, indexFix) System.Diagnostics.Debug.WriteLine(index & " -> " & indexFix) End If Next End If End If End Sub End Class
これでもスクロール中はズレて見えてしまいます...orz個別に明示されていない限りgekkaがフォーラムに投稿したコードにはフォーラム使用条件に基づき「MICROSOFT LIMITED PUBLIC LICENSE」が適用されます。(かなり自由に使ってOK!)
- 回答としてマーク mikupedia 2016年7月19日 2:20