最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

C# WinForms SplitContainer.SplitterMoved resizing of thumbnails - Stack Overflow

programmeradmin14浏览0评论

I have for now given up on doing this myself, as I have tried all day without getting this right, so I would appreciate some hints here. I have created a new mock-up, where I can experiment with this and keep it simple.

My scenario

I have a SplitContainer where its Panel2 is a list of thumbnails (inside the panelThumbnails panel). I can then move the SplitterDistance, and then it will resize the thumbnail images. The thumbnail images are large and resized based on ratio towards the available width for the thumbnail.

As such, this works perfectly fine, when there is no vertical scrollbar.

As soon as the vertical scrollbar comes in to play, and I do not want to see the horizontal scrollbar, then it gets very messy for me, as it quickly can end up in an endless loop. I then tried a lot with substracting the scrollbar width from the available width, and this works, but then in some other situations then it does not.

How the UI looks like

This is the basic FORM I have:

It will give me this UI (splitter position is repositioned in code). When not expanding below view-port then everything is fine:

But when I move the splitter, so I get the vertical scrollbar visible, then the scrollbar shadows for some of the content from the thumbnails - which exactly what I want to avoid:

End-goal

My end-goal is now, that no matter what position the splitContainer1.SplitterDistance has, then the thumbnail images are perfectly resized, with respect of their ratio, and IF there is shown a vertical scrollbar, then I can still view the full image. At no point there should be shown any horizontal scrollbar (which is the triggy part).

I would like to have it look like his - without the horizontal scrollbar, and where I can see all part of the thumbnails, where the vertical scrollbar does not hide any information:

Would anyone have any ideas here?

I have the below full code. A requirement here is that you do have 5 images in the output folder:

using System.Drawing;
using System.Windows.Forms;

namespace MyTest
{
    public partial class Form1 : Form
    {
        private Panel panelThumbnails;

        public Form1()
        {
            InitializeComponent();

            // I have added via "Toolbox" a "SplitContainer" to the UI form.
            // Then change some of its settings.
            splitContainer1.Dock = DockStyle.Fill;
            splitContainer1.SplitterDistance = 500;

            // Create a thumbnails panel, and add it to the "splitContainer1.Panel2".
            panelThumbnails = new Panel
            {
                Dock = DockStyle.Fill,
                AutoScroll = true
            };
            panelThumbnails.BackColor = Color.Khaki;
            splitContainer1.Panel2.Controls.Add(panelThumbnails);

            splitContainer1.SplitterMoved += (s, e) => RedrawThumbnails();
            RedrawThumbnails();
        }

        private void RedrawThumbnails()
        {
            // Start from scratch when redrawing or doing this first time
            panelThumbnails.Controls.Clear();

            int padding = 5; // have some padding around each thumbnail
            int yPos = 5; // initial start-Y position

            for (int i = 1; i <= 5; i++)
            {
                // Calculate the available width for the thumbnail, excluding two times padding
                int availableWidth = panelThumbnails.ClientSize.Width - (padding * 2);

                // Add an image panel
                Image image = Image.FromFile($"file{i}.png");
                Panel panelThumbnail = new Panel
                {
                    BackgroundImage = image,
                    BackgroundImageLayout = ImageLayout.Zoom,
                    BorderStyle = BorderStyle.FixedSingle,
                    Location = new Point(padding, yPos)
                };

                // Calculate the height of the thumbnail based on the aspect ratio
                float scaleFactor = (float)image.Width / availableWidth;
                int newThumbnailHeight = (int)(image.Height / scaleFactor);
                panelThumbnail.Size = new Size(availableWidth, newThumbnailHeight);

                // Add the thumbnail panel to the thumbnails panel
                panelThumbnails.Controls.Add(panelThumbnail);

                // Update the Y position for the next thumbnail
                yPos += newThumbnailHeight + padding;
            }
        }
    }
}

UPDATE 1

After a comment suggesting looking into FlowLayoutPanel, then I have adapted my code. I did take the liberty to include the label I also originally need, as it may be important for what is suggested here.

I might have completely misunderstood the person commenting, but I did think the width would be automatically set, depending on the vertical scrollbar being shown or not, but this seems not to be the case.

Anyway, I do now have this code here:

using System.Drawing;
using System.Windows.Forms;

namespace MyTest
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();

            panel1.Dock = DockStyle.Fill;
            panel1.AutoScroll = true;
            flowLayoutPanel1.Dock = DockStyle.Fill;

            splitContainer1.SplitterMoved += (s, e) => RedrawThumbnails();
            RedrawThumbnails();
        }

        private void RedrawThumbnails()
        {
            // Start from scratch when redrawing or doing this first time.
            // Do NOT do this is real code, unless proper disposing controls!
            flowLayoutPanel1.Controls.Clear();

            int padding = 5; // have some padding around each thumbnail
            int yPos = 5; // initial start-Y position

            for (int i = 1; i <= 5; i++)
            {
                // Calculate the available width for the thumbnail, excluding two times padding
                int availableWidth = flowLayoutPanel1.ClientSize.Width - (padding * 2);

                // Add a panel container, which will include the label + the image
                Panel panelContainer = new Panel
                {
                    BorderStyle = BorderStyle.None,
                    Location = new Point(padding, yPos),
                    Size = new Size(availableWidth, 100), // initial height, not considering image height
                };

                // Add a label
                Label labelImage = new Label
                {
                    BorderStyle = BorderStyle.FixedSingle,
                    Location = new Point(0, 0),
                    Text = $"Thumbnail {i}",
                    Size = new Size(availableWidth, 15),
                };

                // Add an image panel
                Image image = Image.FromFile($"file{i}.png");
                PictureBox panelImage = new PictureBox
                {
                    BorderStyle = BorderStyle.FixedSingle,
                    Location = new Point(0, labelImage.Height),
                    BackgroundImage = image,
                    BackgroundImageLayout = ImageLayout.Zoom,
                };

                // Calculate the height of the image, based on the fixed-width of panel
                float scaleFactor = (float)image.Width / availableWidth;
                int newImageHeight = (int)(image.Height / scaleFactor);
                panelImage.Size = new Size(availableWidth, newImageHeight);

                // Recalculate he height of the panel container
                panelContainer.Size = new Size(availableWidth, labelImage.Height + panelImage.Height);

                // Add the elements to the "panelThumbnails" panel
                flowLayoutPanel1.Controls.Add(panelContainer);
                panelContainer.Controls.Add(labelImage);
                panelContainer.Controls.Add(panelImage);

                // Update the Y position for the next thumbnail
                yPos += panelContainer.Height + padding;
            }
        }
    }
}

I do have this UI/layout setup:

The code will give this output, and it does not show a vertical scrollbar, which I would expect as the flowLayoutPanel1 expands below viewable space:

PS: I know now (thanks for the comment), that I should not do this, flowLayoutPanel1.Controls.Clear(); but this is PoC only, and I can handle the logic for reusing this later - this is just now what is the easiest for this simple PoC.

I have for now given up on doing this myself, as I have tried all day without getting this right, so I would appreciate some hints here. I have created a new mock-up, where I can experiment with this and keep it simple.

My scenario

I have a SplitContainer where its Panel2 is a list of thumbnails (inside the panelThumbnails panel). I can then move the SplitterDistance, and then it will resize the thumbnail images. The thumbnail images are large and resized based on ratio towards the available width for the thumbnail.

As such, this works perfectly fine, when there is no vertical scrollbar.

As soon as the vertical scrollbar comes in to play, and I do not want to see the horizontal scrollbar, then it gets very messy for me, as it quickly can end up in an endless loop. I then tried a lot with substracting the scrollbar width from the available width, and this works, but then in some other situations then it does not.

How the UI looks like

This is the basic FORM I have:

It will give me this UI (splitter position is repositioned in code). When not expanding below view-port then everything is fine:

But when I move the splitter, so I get the vertical scrollbar visible, then the scrollbar shadows for some of the content from the thumbnails - which exactly what I want to avoid:

End-goal

My end-goal is now, that no matter what position the splitContainer1.SplitterDistance has, then the thumbnail images are perfectly resized, with respect of their ratio, and IF there is shown a vertical scrollbar, then I can still view the full image. At no point there should be shown any horizontal scrollbar (which is the triggy part).

I would like to have it look like his - without the horizontal scrollbar, and where I can see all part of the thumbnails, where the vertical scrollbar does not hide any information:

Would anyone have any ideas here?

I have the below full code. A requirement here is that you do have 5 images in the output folder:

using System.Drawing;
using System.Windows.Forms;

namespace MyTest
{
    public partial class Form1 : Form
    {
        private Panel panelThumbnails;

        public Form1()
        {
            InitializeComponent();

            // I have added via "Toolbox" a "SplitContainer" to the UI form.
            // Then change some of its settings.
            splitContainer1.Dock = DockStyle.Fill;
            splitContainer1.SplitterDistance = 500;

            // Create a thumbnails panel, and add it to the "splitContainer1.Panel2".
            panelThumbnails = new Panel
            {
                Dock = DockStyle.Fill,
                AutoScroll = true
            };
            panelThumbnails.BackColor = Color.Khaki;
            splitContainer1.Panel2.Controls.Add(panelThumbnails);

            splitContainer1.SplitterMoved += (s, e) => RedrawThumbnails();
            RedrawThumbnails();
        }

        private void RedrawThumbnails()
        {
            // Start from scratch when redrawing or doing this first time
            panelThumbnails.Controls.Clear();

            int padding = 5; // have some padding around each thumbnail
            int yPos = 5; // initial start-Y position

            for (int i = 1; i <= 5; i++)
            {
                // Calculate the available width for the thumbnail, excluding two times padding
                int availableWidth = panelThumbnails.ClientSize.Width - (padding * 2);

                // Add an image panel
                Image image = Image.FromFile($"file{i}.png");
                Panel panelThumbnail = new Panel
                {
                    BackgroundImage = image,
                    BackgroundImageLayout = ImageLayout.Zoom,
                    BorderStyle = BorderStyle.FixedSingle,
                    Location = new Point(padding, yPos)
                };

                // Calculate the height of the thumbnail based on the aspect ratio
                float scaleFactor = (float)image.Width / availableWidth;
                int newThumbnailHeight = (int)(image.Height / scaleFactor);
                panelThumbnail.Size = new Size(availableWidth, newThumbnailHeight);

                // Add the thumbnail panel to the thumbnails panel
                panelThumbnails.Controls.Add(panelThumbnail);

                // Update the Y position for the next thumbnail
                yPos += newThumbnailHeight + padding;
            }
        }
    }
}

UPDATE 1

After a comment suggesting looking into FlowLayoutPanel, then I have adapted my code. I did take the liberty to include the label I also originally need, as it may be important for what is suggested here.

I might have completely misunderstood the person commenting, but I did think the width would be automatically set, depending on the vertical scrollbar being shown or not, but this seems not to be the case.

Anyway, I do now have this code here:

using System.Drawing;
using System.Windows.Forms;

namespace MyTest
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();

            panel1.Dock = DockStyle.Fill;
            panel1.AutoScroll = true;
            flowLayoutPanel1.Dock = DockStyle.Fill;

            splitContainer1.SplitterMoved += (s, e) => RedrawThumbnails();
            RedrawThumbnails();
        }

        private void RedrawThumbnails()
        {
            // Start from scratch when redrawing or doing this first time.
            // Do NOT do this is real code, unless proper disposing controls!
            flowLayoutPanel1.Controls.Clear();

            int padding = 5; // have some padding around each thumbnail
            int yPos = 5; // initial start-Y position

            for (int i = 1; i <= 5; i++)
            {
                // Calculate the available width for the thumbnail, excluding two times padding
                int availableWidth = flowLayoutPanel1.ClientSize.Width - (padding * 2);

                // Add a panel container, which will include the label + the image
                Panel panelContainer = new Panel
                {
                    BorderStyle = BorderStyle.None,
                    Location = new Point(padding, yPos),
                    Size = new Size(availableWidth, 100), // initial height, not considering image height
                };

                // Add a label
                Label labelImage = new Label
                {
                    BorderStyle = BorderStyle.FixedSingle,
                    Location = new Point(0, 0),
                    Text = $"Thumbnail {i}",
                    Size = new Size(availableWidth, 15),
                };

                // Add an image panel
                Image image = Image.FromFile($"file{i}.png");
                PictureBox panelImage = new PictureBox
                {
                    BorderStyle = BorderStyle.FixedSingle,
                    Location = new Point(0, labelImage.Height),
                    BackgroundImage = image,
                    BackgroundImageLayout = ImageLayout.Zoom,
                };

                // Calculate the height of the image, based on the fixed-width of panel
                float scaleFactor = (float)image.Width / availableWidth;
                int newImageHeight = (int)(image.Height / scaleFactor);
                panelImage.Size = new Size(availableWidth, newImageHeight);

                // Recalculate he height of the panel container
                panelContainer.Size = new Size(availableWidth, labelImage.Height + panelImage.Height);

                // Add the elements to the "panelThumbnails" panel
                flowLayoutPanel1.Controls.Add(panelContainer);
                panelContainer.Controls.Add(labelImage);
                panelContainer.Controls.Add(panelImage);

                // Update the Y position for the next thumbnail
                yPos += panelContainer.Height + padding;
            }
        }
    }
}

I do have this UI/layout setup:

The code will give this output, and it does not show a vertical scrollbar, which I would expect as the flowLayoutPanel1 expands below viewable space:

PS: I know now (thanks for the comment), that I should not do this, flowLayoutPanel1.Controls.Clear(); but this is PoC only, and I can handle the logic for reusing this later - this is just now what is the easiest for this simple PoC.

Share Improve this question edited Mar 31 at 18:35 Beauvais asked Mar 30 at 19:26 BeauvaisBeauvais 2,3195 gold badges34 silver badges70 bronze badges 4
  • I don't see much point to the "split container"; you basically have a "ListView" of images right-aligned on your form. – Gerry Schmitz Commented Mar 31 at 13:46
  • @GerrySchmitz - I doubt I can use that, as I am only showing a basic example here. In reality I am having a list of panels, in where I have for each panel a label (name of image) and another panel with the image - so it is not just the image itself. In that "parent" panel then the label is shown first, and after that then the image. But to be honest, then I do no know the ListView element at all, so if you think it can be used for this case also, then please let me know? – Beauvais Commented Mar 31 at 15:19
  • @Jimi - hmmm... very interesting. Thanks for the idea for the FlowLayoutPanel, which I do not know either. As you can understand now, then I do not do this by living, ha. I did know about the DoubleBuffered, and I am dealing with that already. Back to the basic PoC area for me :-) – Beauvais Commented Mar 31 at 15:55
  • @Jimi - I have updated my question with "Update 1". Maybe you could comment the obvious issue here, as I must have misunderstood what you wrote? :-) – Beauvais Commented Mar 31 at 18:08
Add a comment  | 

1 Answer 1

Reset to default 1

A few notes:

  • You should replace the auto-scrolling Panel that contains the other Panels with a FlowLayoutPanel, which takes care of the ClientSize.Width when the Vertical ScrollBar is visible. Otherwise, you'd need to consider the SystemInformation.VerticalScrollBarWidth yourself
  • You should also replace the Panel that shows the images with a PictureBox, which is double-buffered by default and has a better drawing surface (you can double-buffer a Panel, but the result is not the same anyway)
  • You MUST not have this: [Control].Controls.Clear(), when a Control holds unmanaged resources (as the internal Bitmap handle). In the best-case scenario, you'll cause high memory pressure, and the UI is going to become very clunky. In the current scenario, you most probably don't need to reload the images at all. If you do, then explicitly dispose of those Controls before you create new instances

As a note, the FlowLayoutPanel shows its ScrollBars after the new Layout has been calculated, when the size of its child Controls changes (or new Controls are added). This may cause a race condition when you scale the child Controls to tightly fit the size of their container. After the Layout has been performed, you can verify whether the Horizontal scrollbar is visible and act accordingly. This is handled in the ScaleThumbnails() method here


To test this code, just have a Form with a SplitContainer child control.
A FlowLayoutPanel is added to the right-side of the SplitContainer (Panel2) at run-time, then it's filled with Controls that present the images. The FlowLayoutPanel is set to auto-scroll.

If you also want to add a Label, set AutoSize = false and Dock = DockStyle.Top, and Height = Font.Height + 4 in its Constructor.
You should build a UserControl to handle this scenario, though.

public partial class form1: Form
{
    private FlowLayoutPanel panelThumbnails;
    private int thumbNailMargin = 5;

    public form1()
    {
        InitializeComponent();

        splitContainer1.Dock = DockStyle.Fill;
        splitContainer1.SplitterDistance = 500;

        panelThumbnails = new FlowLayoutPanel {
            AutoScroll = true,
            BackColor = Color.Khaki,
            Dock = DockStyle.Fill
        };

        splitContainer1.Panel2.Controls.Add(panelThumbnails);
        splitContainer1.SplitterMoved += (s, e) => ScaleThumbnails();
        LoadThumbNails();
    }

    protected override void OnLoad(EventArgs e) { 
        base.OnLoad(e);
        ScaleThumbnails();
    }

    private void ScaleThumbnails() {
        int availableWidth = panelThumbnails.ClientSize.Width - (thumbNailMargin * 2);

        panelThumbnails.SuspendLayout();
        foreach (PictureBox thumbnail in panelThumbnails.Controls) {
            float scaleFactor = (float)thumbnail.Image.Width / availableWidth;
            int newThumbnailHeight = (int)(thumbnail.Image.Height / scaleFactor);
            thumbnail.Size = new Size(availableWidth, newThumbnailHeight);
        }
        panelThumbnails.ResumeLayout(true);
        // Handle edge case, avoid race condition when the scrollbars are shown
        if (panelThumbnails.HorizontalScroll.Visible) {
            panelThumbnails.PerformLayout();
        }
    }

    private void LoadThumbNails() {
        for (int i = 1; i <= 5; i++) {
            // Important: the image loads the ICM information without verification
            var image = Image.FromStream(
                new MemoryStream(File.ReadAllBytes($"file{i}.png")), true, false);
            var thumbnail = new PictureBox {
                BorderStyle = BorderStyle.FixedSingle,
                Dock = DockStyle.Top,
                Image = image,
                SizeMode = PictureBoxSizeMode.Zoom,
            };
            panelThumbnails.Controls.Add(thumbnail);
        }
    }
}

Modified method if a Label is also part of the child Controls:

private void ScaleThumbnails() {
    int availableWidth = panelThumbnails.ClientSize.Width - (thumbNailMargin * 2);

    panelThumbnails.SuspendLayout();
    foreach (Control thumbnail in panelThumbnails.Controls) {
        if (thumbnail is PictureBox pcb) {
            float scaleFactor = (float)pcb.Image.Width / availableWidth;
            int newThumbnailHeight = (int)(pcb.Image.Height / scaleFactor);
            pcb.Size = new Size(availableWidth, newThumbnailHeight);
        }
        else {
            thumbnail.Width = availableWidth;
        }
    }

    panelThumbnails.ResumeLayout(true);
    if (panelThumbnails.HorizontalScroll.Visible) {
        panelThumbnails.PerformLayout();
    }
}

private void LoadThumbNails() {
    for (int i = 1; i <= 5; i++) {
        var image = Image.FromStream(
            new MemoryStream(File.ReadAllBytes($"file{i}.png")), true, false);

        Label labelImage = new Label {
            AutoSize = false,
            BorderStyle = BorderStyle.FixedSingle,
            Dock = DockStyle.Top,
            Height = Font.Height + 4,
            Text = $"Thumbnail {i}",
        };

        var thumbnail = new PictureBox {
            BorderStyle = BorderStyle.FixedSingle,
            Dock = DockStyle.Top,
            Image = image,
            SizeMode = PictureBoxSizeMode.Zoom,
        };
        panelThumbnails.Controls.Add(labelImage);
        panelThumbnails.Controls.Add(thumbnail);
    }
}
发布评论

评论列表(0)

  1. 暂无评论