WPF ListBox を使用して Graph コントロールを作成しようとしています。VirtualizingPanel から派生する独自の Canvas を作成し、アイテムの実現と仮想化を自分で処理します。
次に、リストボックスのアイテム パネルをカスタムの仮想化キャンバスに設定します。
私が遭遇している問題は、次のシナリオで発生します。
- ListBox アイテム A が最初に作成されます。
- ListBox アイテム B は、キャンバス上のアイテム A の右側に作成されます。
- ListBox アイテム A が最初に仮想化されます (パンしてビューから外します)。
- ListBox アイテム B は 2 番目に仮想化されます (これもパンしてビューから外します)。
- ListBox アイテム A と B を表示します (つまり、それらを認識します)。
- Snoop を使用して、ListBox に 3 つの項目があり、そのうちの 1 つは ListBox 項目 B のすぐ下にある「DisconnectedItem」であることを検出しました。
この「DisconnectedItem」が作成される原因は何ですか? 最初に B を仮想化し、次に A を仮想化すると、このアイテムは作成されません。私の理論では、ListBox 内の他の項目の前にある項目を仮想化すると、子が切断されるというものです。
この問題は、数百のノードを持つグラフを使用するとさらに明白になります。
キャンバスのコードの一部を次に示します。
/// <summary>
/// Arranges and virtualizes child element positionned explicitly.
/// </summary>
public class VirtualizingCanvas : VirtualizingPanel
{
(...)
protected override Size MeasureOverride(Size constraint)
{
ItemsControl itemsOwner = ItemsControl.GetItemsOwner(this);
// For some reason you have to "touch" the children collection in
// order for the ItemContainerGenerator to initialize properly.
var necessaryChidrenTouch = Children;
IItemContainerGenerator generator = ItemContainerGenerator;
IDisposable generationAction = null;
int index = 0;
Rect visibilityRect = new Rect(
-HorizontalOffset / ZoomFactor,
-VerticalOffset / ZoomFactor,
ActualWidth / ZoomFactor,
ActualHeight / ZoomFactor);
// Loop thru the list of items and generate their container
// if they are included in the current visible view.
foreach (object item in itemsOwner.Items)
{
var virtualizedItem = item as IVirtualizingCanvasItem;
if (virtualizedItem == null ||
visibilityRect.IntersectsWith(GetBounds(virtualizedItem)))
{
if (generationAction == null)
{
GeneratorPosition startPosition =
generator.GeneratorPositionFromIndex(index);
generationAction = generator.StartAt(startPosition,
GeneratorDirection.Forward, true);
}
GenerateItem(index);
}
else
{
GeneratorPosition itemPosition =
generator.GeneratorPositionFromIndex(index);
if (itemPosition.Index != -1 && itemPosition.Offset == 0)
{
RemoveInternalChildRange(index, 1);
generator.Remove(itemPosition, 1);
}
// The generator needs to be "reseted" when we skip some items
// in the sequence...
if (generationAction != null)
{
generationAction.Dispose();
generationAction = null;
}
}
++index;
}
if (generationAction != null)
{
generationAction.Dispose();
}
return default(Size);
}
(...)
private void GenerateItem(int index)
{
bool newlyRealized;
var element =
ItemContainerGenerator.GenerateNext(out newlyRealized) as UIElement;
if (newlyRealized)
{
if (index >= InternalChildren.Count)
{
AddInternalChild(element);
}
else
{
InsertInternalChild(index, element);
}
ItemContainerGenerator.PrepareItemContainer(element);
element.RenderTransform = _scaleTransform;
}
element.Measure(new Size(double.PositiveInfinity,
double.PositiveInfinity));
}