Michael Schnerring
Michael Schnerring

Reputation: 3661

How to prevent this layout cycle?

enter image description here

The picture shows these little gaps. The big, blue containers between the gaps are content holders which widths are determined. The red border is the resizable holder.

So the two outer gaps describe the left and right padding of the red holder. The two gaps next to each container are the left and right margins of each container.

Everytime I resize the holder the SizeChanged event of it gets raised.

gapsize = (holderWidth - summedUpWidthsOfAllContainers) / numberOfGaps

I get the holderWidth via the ActualWidth (UIElement) property. I rebind (Silverlight hack) the MarginProperty of each container and the PaddingProperty of the holder everytime I the SizeChanged event is raised (UpdateTarget doesn't work in Silverlight and INotifyPropertyChanged is not available). This is done in the UpdateMargins() method which gets called in the SizeChanged event handler.

I try to prevent non-visible refreshing of the margins (less than one pixel) with comparing previous with new margins.

But with this approach I cause layout cycles from time to time. Now I just wanted to ask if there is a logic mistake. I've read this blog and tried to solve it this way. But these layout cycles still appear.

I do this to center the contentholders (containers) everytime the holder gets resized. I know that a Grid with two columns would be a viable solution, as well. But the problem is that the red holder must be a WrapPanel, that in this case the second blue container jumps under the first one, if the holder gets too small to show them next to each other.

Upvotes: 1

Views: 717

Answers (2)

Gabe
Gabe

Reputation: 86698

It's not hard to create a panel that will simply do what you want. Here's an example that gives an equal amount of space to each child:

using System.Linq;
using System.Windows;
using System.Windows.Controls;

namespace GapPanel
{
    public class GapPanel : Panel
    {
        protected override Size MeasureOverride(Size availableSize)
        {
            if (Children.Count == 0)
                return base.MeasureOverride(availableSize);
            // allot equal space to each child; you may want different logic 
            var spacePerChild = new Size(availableSize.Width / Children.Count,
                                         availableSize.Height);
            foreach (var child in Children)
                child.Measure(spacePerChild);
            var totalWidth = Children.Sum(child => child.DesiredSize.Width);
            var maxHeight = Children.Max(child => child.DesiredSize.Height);
            return new Size(totalWidth, maxHeight);
        }

        protected override Size ArrangeOverride(Size finalSize)
        {
            if (Children.Count == 0)
                return base.ArrangeOverride(finalSize);
            var gap = (finalSize.Width - Children.Sum(
                       child => child.DesiredSize.Width)) / (Children.Count + 1);
            var spacePerChild = (finalSize.Width - gap * (Children.Count + 1))
                                / Children.Count;
            for (int i = 0; i < Children.Count; i++)
            {
                var child = Children[i];
                child.Arrange(new Rect(i * spacePerChild + (i + 1) * gap, 0,
                                       spacePerChild, finalSize.Height));
            }
            return finalSize;
        }
    }
}

You would use it just like a regular panel:

<my:GapPanel>
    <Button HorizontalAlignment="Center" Width="200" Height="200">abc!</Button>
    <Button HorizontalAlignment="Center" Width="200" Height="200">foo!</Button>
    <Button HorizontalAlignment="Center" Width="100" Height="200">bar!</Button>
</my:GapPanel>

Upvotes: 1

Dave S
Dave S

Reputation: 775

In my opinion changing anything on the visual tree in the SizeChanged event can end up giving you performance issues due to the multiple layout cycles that get invoked if you are not careful (this seems consistent with the issue you're describing).

If you need to layout content in a specific way I would advise creating a new panel and overriding the ArrangeOverride and MeasureOverride methods which are used during the layout cycle. You could use the existing WrapPanel source code (downloadable from CodePlex under Ms-Pl) and change the logic to layout the content as you require.

There are plenty of articles online about creating custom panels and the Silverlight layout cycle..

Sorry I can't help directly with your problem but hopefully this information can be of some use to you

Upvotes: 3

Related Questions