/// <summary> /// WPF Measure override for measuring the control /// </summary> /// <param name="availableSize">Available size will be the viewport size in the scroll viewer</param> /// <returns>availableSize</returns> protected override Size MeasureOverride(Size availableSize) { //base.MeasureOverride(availableSize); // We will be given the visible size in the scroll viewer here. CalculateExtent(); if (availableSize != _viewPortSize) { SetViewportSize(availableSize); } foreach (FrameworkElement child in this.InternalChildren) { IVirtualChild n = child.GetValue(VirtualChildProperty) as IVirtualChild; if (n != null) { Rect bounds = n.Bounds; child.Measure(bounds.Size); } } if (double.IsInfinity(availableSize.Width)) { return(_extent); } else { return(availableSize); } }
/// <summary> /// Check all child nodes to see if any leaked from LazyRemoveNodes and remove their visuals. /// </summary> /// <param name="quantum">Amount of work we can do here</param> /// <returns>The amount of work we did</returns> int LazyGarbageCollectNodes(int quantum) { int count = 0; // Now after every update also do a full incremental scan over all the children // to make sure we didn't leak any nodes that need to be removed. while (count < quantum && _nodeCollectCycle < Children.Count) { UIElement e = Children[_nodeCollectCycle++]; IVirtualChild n = e.GetValue(VirtualChildProperty) as IVirtualChild; if (n != null) { Rect nrect = n.Bounds; if (!nrect.IntersectsWith(_visible)) { e.ClearValue(VirtualChildProperty); n.DisposeVisual(); e.UpdateLayout(); Children.Remove(e); _removed++; } count++; } _nodeCollectCycle++; } if (_nodeCollectCycle < Children.Count) { _done = false; } return(count); }
/// <summary> /// WPF ArrangeOverride for laying out the control /// </summary> /// <param name="finalSize">The size allocated by parents</param> /// <returns>finalSize</returns> protected override Size ArrangeOverride(Size finalSize) { //base.ArrangeOverride(finalSize); CalculateExtent(); if (finalSize != _viewPortSize) { SetViewportSize(finalSize); } // base.Arrange(new Rect(0, 0, Width, Height)); foreach (FrameworkElement child in this.InternalChildren) { IVirtualChild n = child.GetValue(VirtualChildProperty) as IVirtualChild; if (n != null) { Rect bounds = n.Bounds; bounds.X -= _translate.X / _scale.ScaleX; bounds.Y -= _translate.Y / _scale.ScaleY; child.Arrange(bounds); } } if (_index == null) { StartLazyUpdate(); } return(finalSize); }
/// <summary> /// Resets the state so there is no Visuals associated with this canvas. /// </summary> private void RebuildVisuals() { // need to rebuild the index. _index = null; _visualPositions = null; _visible = Rect.Empty; _done = false; // var stopWatch = Stopwatch.StartNew(); foreach (UIElement e in _content.Children) { IVirtualChild n = e.GetValue(VirtualChildProperty) as IVirtualChild; if (n != null) { e.ClearValue(VirtualChildProperty); n.DisposeVisual(); } } // 耗时 2.5us - 8.0us // Console.WriteLine("foreach loop execution time = {0} us\n", stopWatch.Elapsed.TotalSeconds*1000000); _content.Children.Clear(); _content.Children.Add(_backdrop); InvalidateArrange(); StartLazyUpdate(); }
/// <summary> /// Resets the state so there is no Visuals associated with this canvas. /// </summary> private void RebuildVisuals() { // need to rebuild the index. _index = null; _visualPositions = null; _visible = Rect.Empty; _done = false; foreach (FrameworkElement e in Children) { IVirtualChild n = e.GetValue(VirtualChildProperty) as IVirtualChild; if (n != null) { e.ClearValue(VirtualChildProperty); n.DisposeVisual(); } } UpdateLayout(); Children.Clear(); InvalidateArrange(); StartLazyUpdate(); }
/// <summary> /// Insert the visual for the child in the same order as is is defined in the /// VirtualChildren collection so the visuals draw on top of each other in the expected order. /// The trick is that GetNodesIntersecting returns the nodes in pretty much random order depending /// on how the QuadTree decides to break up the canvas. /// /// The thing we should avoid is a linear search through the potentially large collection of /// IVirtualChildren to compute its visible index which is why we have the _visualPositions map. /// We should also avoid a N*M algorithm where N is the number of nodes returned from GetNodesIntersecting /// and M is the number of children already visible. For example, Page down in a zoomed out situation /// gives potentially high N and and M which would basically be an O(n2) algorithm. /// /// So the solution is to use the _visualPositions map to get the expected visual position index /// of a given IVirtualChild, then do a binary search through existing visible children to find the /// insertion point of the new child. So this is O(Log M). /// </summary> /// <param name="child">The IVirtualChild to add visual for</param> public void EnsureVisual(IVirtualChild child) { if (child.Visual != null) { return; } FrameworkElement e = child.CreateVisual(this); e.SetValue(VirtualChildProperty, child); Rect bounds = child.Bounds; Canvas.SetLeft(e, bounds.Left); Canvas.SetTop(e, bounds.Top); // Get the correct absolute position of this child. int position = _visualPositions[child]; // Now do a binary search for the correct insertion position based // on the visual positions of the existing visible children. UIElementCollection c = Children; int min = 0; int max = c.Count - 1; while (max > min + 1) { int i = (min + max) / 2; UIElement v = Children[i]; IVirtualChild n = v.GetValue(VirtualChildProperty) as IVirtualChild; if (n != null) { int index = _visualPositions[n]; if (index > position) { // search from min to i. max = i; } else { // search from i to max. min = i; } } else { // Any nodes without IVirtualChild should be behind the // IVirtualChildren by definition (like the Backdrop). min = i; } } // If 'max' is the last child in the collection, then we need to see // if we have a new last child. if (c.Count > 0 && max == c.Count - 1) { UIElement v = c[max]; IVirtualChild maxchild = v.GetValue(VirtualChildProperty) as IVirtualChild; int maxpos = position; if (maxchild == null || position > _visualPositions[maxchild]) { // Then we have a new last child! max++; } } if (max < 0) { c.Add(e); } else { c.Insert(max, e); } }
/// <summary> /// Add a new IVirtualChild. The VirtualCanvas will call CreateVisual on them /// when the Bounds of your child intersects the current visible view port. /// </summary> /// <param name="c"></param> public void AddVirtualChild(IVirtualChild child) { _children.Add(child); }
/// <summary> /// Insert the visual for the child in the same order as is is defined in the /// VirtualChildren collection so the visuals draw on top of each other in the expected order. /// The trick is that GetNodesIntersecting returns the nodes in pretty much random order depending /// on how the QuadTree decides to break up the canvas. /// /// The thing we should avoid is a linear search through the potentially large collection of /// IVirtualChildren to compute its visible index which is why we have the _visualPositions map. /// We should also avoid a N*M algorithm where N is the number of nodes returned from GetNodesIntersecting /// and M is the number of children already visible. For example, Page down in a zoomed out situation /// gives potentially high N and and M which would basically be an O(n2) algorithm. /// /// So the solution is to use the _visualPositions map to get the expected visual position index /// of a given IVirtualChild, then do a binary search through existing visible children to find the /// insertion point of the new child. So this is O(Log M). /// </summary> /// <param name="child">The IVirtualChild to add visual for</param> public void EnsureVisual(IVirtualChild child) { if (child.Visual != null) { return; } UIElement e = child.CreateVisual(this); e.SetValue(VirtualChildProperty, child); Rect bounds = child.Bounds; Canvas.SetLeft(e, bounds.Left); Canvas.SetTop(e, bounds.Top); // Get the correct absolute position of this child. int position = _visualPositions[child]; // Now do a binary search for the correct insertion position based // on the visual positions of the existing visible children. UIElementCollection c = _content.Children; int min = 0; int max = c.Count - 1; while (max > min + 1) { int i = (min + max) / 2; UIElement v = _content.Children[i]; IVirtualChild n = v.GetValue(VirtualChildProperty) as IVirtualChild; if (n != null) { int index = _visualPositions[n]; if (index > position) { // search from min to i. max = i; } else { // search from i to max. min = i; } } else { // Any nodes without IVirtualChild should be behind the // IVirtualChildren by definition (like the Backdrop). min = i; } } // If 'max' is the last child in the collection, then we need to see // if we have a new last child. if (max == c.Count - 1) { UIElement v = c[max]; IVirtualChild maxchild = v.GetValue(VirtualChildProperty) as IVirtualChild; int maxpos = position; if (maxchild == null || position > _visualPositions[maxchild]) { // Then we have a new last child! max++; } } c.Insert(max, e); }