Emulates Windows 8 Start Menu Tile Layout Engine

10,391

I've looked around myself and couldn't find anything to do what I/we want. I knew that to get this behavior we'd need some sort of custom panel object, so I set about creating one...

What it boils down to, is the tiles need to be arranged vertically, with double-width tiles taking up a whole row in that column, and normal width tiles to pair up. When it reaches the bottom of the container, it needs to create a new column and follow the same pattern.

Here's my implementation:

    public class MetroTilePanel : Panel
{
    protected override Size ArrangeOverride(System.Windows.Size finalSize)
    {
        double x = 0, y = 0, colWidth = 0, rowHeight = 0;
        int col = 0;
        colWidth = Children.Cast<UIElement>().Select(c => c.DesiredSize.Width).Max();

        foreach (UIElement child in Children)
        {
            rowHeight = Math.Max(rowHeight, child.DesiredSize.Height);

            if (x + child.DesiredSize.Width > (colWidth * (col + 1)))
            {
                // New row
                y += rowHeight;
                x = (colWidth * (col));
                rowHeight = child.DesiredSize.Height;
            }

            if (y + rowHeight > finalSize.Height)
            {
                // New column
                col++;
                x = (colWidth * (col));
                y = 0;
            }

            child.Arrange(new Rect(x, y, child.DesiredSize.Width, child.DesiredSize.Height));
            x += child.DesiredSize.Width;
        }
        return finalSize;
    }

    protected override Size MeasureOverride(Size availableSize)
    {
        double x = 0, y = 0, colWidth = 0;

        foreach (UIElement child in Children)
        {
            child.Measure(availableSize);

            if (x + child.DesiredSize.Height > availableSize.Height)
            {
                x += colWidth;
                y = 0;
                colWidth = 0;
            }

            y += child.DesiredSize.Height;
            if (child.DesiredSize.Width > colWidth)
            {
                colWidth = child.DesiredSize.Width;
            }
        }
        x += colWidth;

        var resultSize = new Size();

        resultSize.Width = double.IsPositiveInfinity(availableSize.Width) ? x : availableSize.Width;
        resultSize.Height = double.IsPositiveInfinity(availableSize.Height) ? y : availableSize.Height;

        return resultSize;
    }
}

Screenshot of the control in action: enter image description here

Disclaimers:

  • The MeasureOverride only works by chance, and isn't setup correctly.
  • If you want the nice MetroTile layout, then stick to uniform sizes i.e 100x100 and 200x100
  • I haven't fully tested it, but I will be implementing it into my fake-Metro app, so if you want to see any future changes, just holler.
  • If you want the proper GridView tiling behavior, then we'd have to create a brand new control (to support dragging items around etc).

I hope this helps.

Share:
10,391
thenonhacker
Author by

thenonhacker

Updated on July 17, 2022

Comments

  • thenonhacker
    thenonhacker almost 2 years

    So anyone out there knows of sample code or control that perfectly emulates the Windows 8 Start Menu Tile Layout Engine?

    It should support mixed Square and Rectangle Tiles, and repacks the square tiles over or under rectangle tiles properly.

    Note: WrapPanel works if ALL TILES are Square. But once you mix in tiles that span 2-Squares worth of space, the layout breaks, and is inconsistent with the Windows 8 Start Menu

    I am expecting code that extends the WPF Panel.


    Disclaimer: Yes I have searched the Internet, the closest thing I've found is the CodeProject example, but that only works if all tiles are same-sized squares.

  • thenonhacker
    thenonhacker almost 12 years
    Thank you very much! This is exactly what I am looking for.
  • thenonhacker
    thenonhacker almost 12 years
    Yes I will be interested in your updates; In fact, if you start this in CodePlex and add the Drag-Drop animated, that would be awesome, and I'm sure lots of people will follow it. UPDATES: The fix for MeasureOverride, is to use your Arrange Override Logic. The colwidth assignment also becomes: colWidth = Children.Cast<UIElement>().Select(c => { c.Measure(availableSize); return c.DesiredSize.Width; } ).Max(); The return statement is same.
  • mortware
    mortware over 11 years
    Unfortunately not, the project has been shelved for now.
  • inva
    inva over 11 years
    @mortware Are there any reasons why you did not used a ListView with a custom view simulating a tile? Was the reason perhaps the first tile which should be bigger than the other tiles?
  • mortware
    mortware about 11 years
    @inva - I wanted to simulate the different sizes of tiles available in the metro tiles, to do this in a ListView would have meant I needed to have a TemplateSelector. Also I wanted it to be re-usable panel which could be used in a Selector control, or simply to display some non-interactive items. Hope that answers your questions.