最近、ObservableCollection 内のアイテムを個々の ListBox に「ビニング」する方法に関して、かなり深刻なパフォーマンスの問題に遭遇しました。これは現在の反復で機能し、ユーザーがタイムラインをズームイン/ズームアウトしたいときに問題が発生します。
ズームは、タイムライン上の各ブロックが表す時間を変更し、1 つの ListBox 内の一部の要素を隣接する ListBox に移動することが必要になる可能性があります。また、ListBox の追加または削除が必要になる場合もあります。
問題は、OnZoomChanged メソッドでエントリを再シャッフルする方法にあるのではないかと推測しています。もう 1 つの問題は、エントリが追加されるたびにすべてのエントリが再描画されているように見えることです。これらの両方を組み合わせて、更新プロセス (追加/ズーム) 中にかなりの遅延を発生させることができます。エントリー数が 50 ~ 60 を超えると、ズームによるパフォーマンスの低下に気付き始めます。
ここでの私の最終的な目標は、互いに重複するエントリを個別のビン (StackPanel/ListBox など) にソートし、ユーザーが必要に応じてそれらすべてを参照できるようにすることです。エントリは固定サイズです (これが重複の問題を引き起こします)。
洞察/提案をありがとう!
すべての設定方法の内訳は次のとおりです。
ビュー- これはコントロールの簡略化されたバージョンです。DateTime (GroupingTime) は、カスタム ビューでタイムラインに配置するために使用されます (これは機能します)。次に、タイムライン エントリ (GroupEntries) のリストが TimelineEntryControl に入力され、ListBox にエントリが適切に描画されます。
<ItemsControl ItemsSource="{Binding TimelineEntries}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Views:CustomView>
<ListBox>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Controls:TimelineEntryControl TimelineEntry="{Binding}" Height="50" Width="50" Margin="0,0,0,12"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ListBox>
</Views:CustomView>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
ViewModel - TimelineEntryViewModel
internal class TimelineEntryViewModel : ViewModel, IDisposable
{
private readonly ObservableList<TimelineEntry> _groupEntries = new ObservableList<TimelineEntry>();
private readonly ObservableCollectionHandler<TimelineEntry> _groupEntriesHandler;
public TimelineEntryViewModel( TimelineViewModel timelineViewModel )
{
Tvm = timelineViewModel;
_groupEntriesHandler = new ObservableCollectionHandler<TimelineEntry>(_groupEntries, OnAdded, OnRemoved);
GroupEntries = _groupEntries.CreateView();
GroupEntries.SortDescriptions.Add( new SortDescription( "TimeOccured", ListSortDirection.Ascending) );
}
private void OnAdded( TimelineEntry tle )
{
GroupEntries.Refresh();
}
private void OnRemoved( TimelineEntry tle )
{
GroupEntries.Refresh();
}
public void AddEntry( TimelineEntry tle )
{
_groupEntries.Add( tle );
}
public void Dispose()
{
_groupEntriesHandler.Dispose();
}
public TimelineViewModel Tvm {get; private set;}
public DateTime GroupingTime {get; set;}
public ICollectionView GroupEntries {get; private set;}
}
TimelineViewModel - エントリ/ズームの処理を管理するパーツ。eventAggregator を使用して、これらのエントリに入る情報を含む特定のメッセージに応答し、AddEntry 関数を呼び出してエントリを作成し、それをビュー内の適切な場所に配置して応答します。
internal sealed class TimelineViewModel : ViewModel, IDisposable
{
private readonly ObservableList<TimelineEntryViewModel> _timelineEntries = new ObservableList<TimelineEntryViewModel>();
private readonly ObservableHandler _timelineEntriesHandler;
public TimelineViewModel ( )
{
// Omitting unnecessary code here
_timelineEntriesHandler = new ObservableHandler( _timelineEntries );
TimelineEntries = _timelineEntries.CreateView();
TimelineEntries.SortDescription.Add( new SortDescription( "GroupingTime", ListSortDirection.Ascending ) );
TimelineEntries.Refresh();
}
private void AddEntry( TimelineEntry tle )
{
var timeForNoOverlap = Projection.UnprojectDuration( 90 ); //This just gets the number of pixels needed for no overlap between panel groups.
if( _timelineEntries.Count == 0 )
{
_timelineEntries.Add( new TimelineEntryViewModel( this ) );
_timelineEntries[0].GroupingTime = tle.TimeOccured;
_timelineEntries[0].AddEntry( tle );
}
else if ( tle.TimeOccured > _timelineEntries.Last().GroupingTime + timeForNoOverlap )
{
_timelineEntries.Add( new TimeLineViewModel( this );
_timelineEntries.Last().GroupingTime = tle.TimeOccured;
_timelineEntries.Last().AddEntry( tle );
}
else
{
foreach( TimelineEntryViewModel tevm in _timelineEntries )
{
if( tle.TimeOccured >= tevm.GroupingTime && tle.TimeOccured <= tevm.GroupingTime + timeForNoOverlap)
{
tevm.AddEntry( tle );
break;
}
}
}
TimelineEntries.Refresh();
}
private void OnZoomChanged()
{
var tempEntries = new ObservableList<TimelineEntryViewModel>( _timelineEntries );
foreach( var group in _timelineEntries )
group.Dispose();
_timelineEntries.Clear();
foreach( var group in tempEntries )
{
foreach( var item in group.GroupEntries )
AddEntry( (TimelineEntry)item );
}
foreach( var group in tempEntries )
group.Dispose();
tempEntries.Clear();
}
}