Scale components in FlowLayout as big as possible
Asked Answered
R

1

1

How can I scale PictureBox components to best fit the given space on the screen while keeping their aspect ratio (interdependent of the actual image or its SizeMode) ?

I tested setting the Dock of the FlowLayout and the PictureBox to Fill. I also tested using a Panel as a wrapper and tested different settings for AutoSize and AutoSizeMode.

To give more information about the background: I want to dynamically add and remove images in the viewport of the application, so a TableLayout is in the first step sort of to static. I have to admit I'm was also thinking of calculating the size an position manually - or to dynamically adapt the row and column count of the TableLayout - but it seems to me prone to errors. I thought having a FlowLayout and automatically sized components should be the correct way - but it seems not to work that way. (To speak as web developer, I simply want to "float the images left", "with width and height set to 'auto'" and no scrolling.)

The images should visualize this a bit: the first figure should point out the layout, if there is only one PictureBox - it takes the whole space (or as big as possible with the given aspect ratio). The second shows how I would like the Layout to be, if there are two (three or four) images. The third figure is showing basically a resized window with three (to six) images.

Is there some point I'm missing?

Desired behaviour of PictureBox components on dynamically adding more PictureBox components or resizing the application

Rosewood answered 10/4, 2013 at 15:30 Comment(2)
You should probably use a TableLayoutPanel instead of the FlowLayoutPanel, but your post doesn't describe the logic you would employ that determines how many columns and how many rows you are expecting to use. That logic would most likely be your custom code.Cristobal
Thanks, actually that is part of the problem. I just adapted the description a bit. The number of rows should be (at least that would be great) implicitely determined of the present number of images and a given (fixed) aspect ratio, for example 4:3 for all images.Rosewood
D
0

This code snippet do this:

It arranges the visible controls inside a container in respect of the aspect ratio (see R variable in the code), and uses the container margin values to get horizontal and vertical gaps between items. The padding of the container is also handled.

public static void Arrange(Control container)
{
    var H = container.DisplayRectangle.Height;
    var W = container.DisplayRectangle.Width;
    var N = container.Controls.OfType<Control>().Count(c => c.Visible);
    var R = 4 / 3d; // item aspect ratio

    var margin = container.Margin;
    var padding = container.Padding;

    var horizontalGap = margin.Left + margin.Right;
    var verticalGap = margin.Top + margin.Bottom;

    if (N == 0)
        return;

    var bestSizedItem = (

        // Try n rows
        Enumerable.Range(1, N).Select(testRowCount =>
        {
            var testItemHeight = (H - verticalGap * (testRowCount - 1)) / testRowCount;

            return new
            {
                testColCount = (int)Math.Ceiling((double)N / testRowCount),
                testRowCount = testRowCount,
                testItemHeight = (int)testItemHeight,
                testItemWidth = (int)(testItemHeight * R)
            };
        })

        // Try n columns
        .Concat(
        Enumerable.Range(1, N).Select(testColCount =>
        {
            var testItemWidth = (W - horizontalGap * (testColCount - 1)) / testColCount;

            return new
            {
                testColCount = testColCount,
                testRowCount = (int)Math.Ceiling((double)N / testColCount),
                testItemHeight = (int)(testItemWidth / R),
                testItemWidth = (int)testItemWidth
            };
        })))

        // Remove when it's too big
        .Where(item => item.testItemWidth * item.testColCount + horizontalGap * (item.testColCount - 1) <= W &&
                       item.testItemHeight * item.testRowCount + verticalGap * (item.testRowCount - 1) <= H)

        // Get the biggest area
        .OrderBy(item => item.testItemHeight * item.testItemWidth)
        .LastOrDefault();

    Debug.Assert(bestSizedItem != null);

    if (bestSizedItem == null)
        return;

    int x = container.DisplayRectangle.X;
    int y = container.DisplayRectangle.Y;

    foreach (var control in container.Controls.OfType<Control>().Where(c => c.Visible))
    {
        control.SetBounds(x, y,
            bestSizedItem.testItemWidth,
            bestSizedItem.testItemHeight);

        x += bestSizedItem.testItemWidth + horizontalGap;
        if (x + bestSizedItem.testItemWidth - horizontalGap > W)
        {
            x = container.DisplayRectangle.X;
            y += bestSizedItem.testItemHeight + verticalGap;
        }
    }
}

I put this snippet on Gist so you can contribute if you wish.

Daddy answered 22/5, 2013 at 20:29 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.