Wednesday, April 13, 2011

How can I create a Silverlight Panel that arranges child items to prevent overlapping?

I am trying to create a custom panel that utilizes absolute positionioning but will re-arrange items so that none of the children overlap. This panel will be used for the ItemsPanel of an ItemsControl. The ItemsControl is bound to data that is displayed as an inset on a map for various cities so the goal is to keep the insets as close to their cities without overlapping any other insets. I have an algorithm to rearrange items (although it's slow and I'm interested in finding a better one so if anyone has suggestions, please let me know) but I've been really struggling with the custom panel.

The latest code can be found below minus the algorithm to keep it shorter. I've also attached an image of what the visual tree looks like under overlappanel.

Problems:

1.) The first issue I ran into is that despite MSDN saying that MeasureOverride should return a height and width of positive infinity if it wants to take up the full space available, I get an error when I try to do so. As a result of this, I'm having an issue where some of my child items are getting clipped because the ItemsPresenter that contains the OverlapPanel is only sizing to the returned MeasureOverride size and clipping some of my insets because after ArrangeOverride, they are moved around and the final size is larger.

2.) I tried using VisualTreeHelper.FindElementInHostCoordinates using the Canvas.Left and Canvas.Top properties of each "ItemsControl" a few levels underneath the "ContentPresenter" of each inset to find other overlapping items within the OverlapPanel subtree. The reason I had to drill down to the ItemsControl is because it's the first item with valid canvas.top and left properties as well as DesiredSize.Width and DesiredSize.Height. However, FindElementInHostCoordinates was returning 0 items no matter what I seemed to try. I even tried using TransformToVisual to translate my coordinates to various levels before using it and I still couldn't get any expected results.

3.) Since I'm using the OverlapPanel as an ItemsPanel for an ItemsControl, I have to drill down into the visual tree a couple levels to get an actual left/top value and width/height to calculate positioning but this wouldn't allow the panel to be used as a generic panel. How could I make this more useable as a whole?

Here's the code I am currently working with. Note that "PreArrange" is what ends up calling "Arrange" on each child element, and sets its left/top properties. I left the code for it out for brevity.:

Protected Overrides Function MeasureOverride(ByVal availableSize As System.Windows.Size) As System.Windows.Size
        'Return MyBase.MeasureOverride(availableSize)
        Dim infinite As New Size(Double.PositiveInfinity, Double.PositiveInfinity)

        Dim maxX, maxY As Double
        maxX = 0
        maxY = 0

        For Each elem As FrameworkElement In Children
            elem.Measure(infinite)
            Dim ic As ItemsControl = CType(VisualTreeHelper.GetChild(VisualTreeHelper.GetChild(elem, 0), 0), ItemsControl)
            If (elem.GetValue(Canvas.LeftProperty) + ic.DesiredSize.Width) > maxX Then
                maxX = elem.GetValue(Canvas.LeftProperty) + ic.DesiredSize.Width
            End If
            If (elem.GetValue(Canvas.TopProperty) + ic.DesiredSize.Height) > maxY Then
                maxY = elem.GetValue(Canvas.TopProperty) + ic.DesiredSize.Height
            End If
        Next

        Return New Size(maxX, maxY)
    End Function

    Protected Overrides Function ArrangeOverride(ByVal finalSize As System.Windows.Size) As System.Windows.Size
        If Children.Count = 0 Then
            Return MyBase.ArrangeOverride(finalSize) 'New Size(Width, Height)
        End If

        Dim maxX As Double = 0
        Dim minX As Double = 0
        Dim maxY As Double = 0
        Dim minY As Double = 0

        For i As Integer = 0 To Children.Count - 1
            PreArrange(Children(i), 5, 1, 10)
            Dim ic As ItemsControl = CType(VisualTreeHelper.GetChild(VisualTreeHelper.GetChild(Children(i), 0), 0), ItemsControl)
            Dim x1 As Double = Children(i).GetValue(Canvas.LeftProperty)
            Dim x2 As Double = ic.DesiredSize.Width + x1
            Dim y1 As Double = Children(i).GetValue(Canvas.TopProperty)
            Dim y2 As Double = ic.DesiredSize.Height + y1

            If x1 < minX Then minX = x1
            If x2 > maxX Then maxX = x2
            If y1 < minY Then minY = y1
            If y2 > maxY Then maxY = y2
        Next


        Return New Size(Math.Abs(minX) + maxX, Math.Abs(minY) + maxY)
        'Return MyBase.ArrangeOverride(finalSize)
    End Function

alt text

From stackoverflow

0 comments:

Post a Comment