views:

453

answers:

2

I have a FlowDocument with much content inside. I need to get the controls that are currently in the visible area.

with

DependencyObject obj = FlowDocumentScrollViewerCtrl;

do
{
    if (VisualTreeHelper.GetChildrenCount(obj) > 0)
    {
        obj = VisualTreeHelper.GetChild(obj as Visual, 0);
    }
}
while (!(obj is ScrollViewer));

ScrollViewer sv = obj as ScrollViewer;

I was able to get the current scrolling Position.

Does anyone know how I get the currently controls within the visible area?

Thanks a lot

A: 

One way is to recursively descend the Visual tree using VisualTreeHelper.GetChildrenCount and VisualTreeHelper.GetChild() and check each Visual using this procedure:

  1. Throw out any visual that isn't interesting to your code (eg you may only care about Controls)
  2. Get each Visual's bounding box using new Rect(0, 0, visual.ActualWidth, visual.ActualHeight). This will give you the bounding box in the Visual's coordinate system.
  3. Use the transform returned by visual.TransformToAncestor(viewer) to transform the bounding box into the viewer's coordinate system.
  4. Check to see if the transformed visual's bounding box intersects the viewer's bounding box. A rough check can be done by taking the minimum and maximum X and Y of the visual bounding box corners and just comparing one axis at a time. This is easier than full rectangle intersection, and should serve for most purposes.

This will tell you all the visuals in the visible area. If you want to map to FrameworkContentElements such as <Paragraph>, just check out the Content property of the ContentPresenters you run across in your tree walk.

Ray Burns
A: 

Thanks Ray for your answer. I followed your tips in some points yesterday and thats the working code for my problem:

public class FrameworkElementInfo
{
 Point _position    = new Point();
 FrameworkElement _element = null;

 public Point Position
 {
  get { return _position; }
  set { _position = value; }
 }

 public FrameworkElement FrameworkElement
 {
  get { return _element; }
  set { _element = value; }
 }
}

public class ScrollViewPositionManager
{
 ScrollViewer _scrollViewer    = null;
 List<FrameworkElementInfo> _elements = new List<FrameworkElementInfo>();
 double _zoom       = 100.0;

 public ScrollViewPositionManager(ScrollViewer scrollViewer, double zoom)
 {
  _scrollViewer = scrollViewer;
  _zoom = zoom;
 }

 public void RegisterElement(FrameworkElement element, Boolean registerOnly)
 {
  FrameworkElementInfo info = new FrameworkElementInfo();

  if (!registerOnly) info.Position = CalculatePosition(element);
  info.FrameworkElement = element;

  _elements.Add(info);
 }

 public void RecalculatePositions()
 {
  int Counter = 0;

  foreach(FrameworkElementInfo info in _elements)
  {
   Counter += 1;
   info.Position = CalculatePosition(info.FrameworkElement);
  }
 }

 public List<FrameworkElement> GetElementsInViewPort()
 {
  List<FrameworkElement> elements = new List<FrameworkElement>();

  double verticalOffsetHigh = _scrollViewer.ViewportHeight + _scrollViewer.VerticalOffset;

  foreach (FrameworkElementInfo info in _elements)
  {
   Point point = info.Position;

   if (point.Y >= _scrollViewer.VerticalOffset &&
    point.Y <= verticalOffsetHigh)
   {
    elements.Add(info.FrameworkElement);
   }
  }

  return elements;
 }

 private Point CalculatePosition(FrameworkElement element)
 {
  GeneralTransform elementTransform = element.TransformToAncestor(_scrollViewer);
  Point elementPoint = elementTransform.Transform(new Point(0, 0));
  Point transformedPoint = new Point(elementPoint.X, elementPoint.Y);

  transformedPoint = GetZoomedPoint(elementPoint, _zoom, _scrollViewer.HorizontalOffset, _scrollViewer.VerticalOffset);

  return transformedPoint;
 }

 static public Point GetZoomedPoint(Point unzoomedPoint, double zoom, double offsetX, double offsetY)
 {
  Point zoomedPoint = new Point();

  double zoomFactor = 100.0 / zoom;

  zoomedPoint.X = offsetX + unzoomedPoint.X * zoomFactor;
  zoomedPoint.Y = offsetY + unzoomedPoint.Y * zoomFactor;

  return zoomedPoint;
 }

 public int ElementCount
 {
  get { return _elements.Count; }
 }

 public FrameworkElement GetFirstElement()
 {
  FrameworkElement firstElement = null;

  if(_elements.Count > 0) firstElement = _elements[0].FrameworkElement;

  return firstElement;
 }

 public FrameworkElement GetLastElement()
 {
  FrameworkElement lastElement = null;

  if (_elements.Count > 0) lastElement = _elements[_elements.Count-1].FrameworkElement;

  return lastElement;
 }

 public FrameworkElement GetNextElement(FrameworkElement element)
 {
  FrameworkElement nextElement = null;
  int index = GetElementIndex(element);

  if(index != -1 && index != _elements.Count -1)
  {   
   nextElement = _elements[index + 1].FrameworkElement;
  }

  return nextElement;
 }

 public FrameworkElement GetPreviousElement(FrameworkElement element)
 {
  FrameworkElement previousElement = null;
  int index = GetElementIndex(element);

  if (index > 1)
  {
   previousElement = _elements[index - 1].FrameworkElement;
  }

  return previousElement;
 }

 public int GetElementIndex(FrameworkElement element)
 {
  return _elements.FindIndex(
       delegate(FrameworkElementInfo currentElement)
       {
        if(currentElement.FrameworkElement == element) return true;
        return false;
       }
  );
 }
}

I use a register function for the elements of interest and working only on them. The zoom is only neccessary for a FlowDocument I think. This code should work on every control which uses a ScrollViewer. I would appreciate if anyone may comment this if this is a practical solution.

Rod