[C# Helper]
Index Books FAQ Contact About Rod
[Beginning Database Design Solutions, Second Edition]

[Beginning Software Engineering, Second Edition]

[Essential Algorithms, Second Edition]

[The Modern C# Challenge]

[WPF 3d, Three-Dimensional Graphics with WPF and C#]

[The C# Helper Top 100]

[Interview Puzzles Dissected]

[C# 24-Hour Trainer]

[C# 5.0 Programmer's Reference]

[MCSD Certification Toolkit (Exam 70-483): Programming in C#]

Title: Make a row/column montage editor in C#

[Make a row/column montage editor in C#]

Over the years I've made several montage editors including:

This one arranges images in rows that wrap when they become too full. (I wanted this kind of montage for a tweet about the example Book Sample Pictures: WPF 3d.)

I've divided this post into the following sections:

Arranging Images

This example arranges image in rows. If a row become too wide, it wraps and starts a new row.

You could build that functionality yourself from scratch, but there's already a control that does this: FlowLayoutPanel. This program simply drops PictureBox controls in a FlowLayoutPanel and lets the panel do all of the arraning work.

If you open the Settings menu and select Flow Direction, you reach a submenu with the choices Left to Right, Right to Left, Top to Bottom, and Bottom to Top. If you pick one of those commands, the following code executes.

// Set flow direction. private void mnuFlow_Click(object sender, EventArgs e) { // Uncheck all menu items. ToolStripMenuItem[] items = { mnuFlowLeftToRight, mnuFlowRightToLeft, mnuFlowTopToBottom, mnuFlowBottomToTop, }; foreach (ToolStripMenuItem item in items) { item.Checked = false; } // Check the correct item. ToolStripMenuItem selected = sender as ToolStripMenuItem; selected.Checked = true; // Update the FlowLayoutPanel. switch (selected.Text) { case "&Left to Right": flpImages.FlowDirection = FlowDirection.LeftToRight; break; case "&Right to Left": flpImages.FlowDirection = FlowDirection.RightToLeft; break; case "&Top to Bottom": flpImages.FlowDirection = FlowDirection.TopDown; break; case "&Bottom to Top": flpImages.FlowDirection = FlowDirection.BottomUp; break; } }

This method creates an array holding the four layout menu items and loops through them to clear all of their Checked properties. It then converts the sender control into the ToolStripMenuItem that was clicked and sets its Checked property to true.

The code them switches on the checked control's text and sets the appropriate FlowDiretion value for the FlowLayoutPanel control.

Adjusting Spacing

The spacing between the images is control by the PictureBox controls' Margin properties. If you open the Settings menu and select Spacing, the following code executes.

private Padding Spacing = new Padding(0); ... private void mnuSettingsSpacing_Click(object sender, EventArgs e) { DlgSpacing dlg = new DlgSpacing(); dlg.Spacing = Spacing; if (dlg.ShowDialog() == DialogResult.OK) { Spacing = dlg.Spacing; foreach (Control ctl in flpImages.Controls) ctl.Margin = Spacing; } }

The program declares the variable Spacing at the class level. It has type Padding, which is the type of the PictureBox controls' Margin properties.

The menu item's event handler creates a new DlgSpacing form and displays it as a modal dialog. On that dialog, you should enter a margin value as either a single value or four values giving left, top, right, and bottom margins. (For example, either "5" or "5, 10, 5, 10".) The dialog only returns OK if you enter a valid value and click OK. Download the example and look at that form to see how it works.

If you enter a valid margin and click OK, the main program loops through the controls inside the FlowLayoutPanel and sets their margins to the value that you entered.

Setting Background Color

To set the FlowLayoutPanel control's background color, the program uses the technique described in the post Let the user select colors from menus in C#. See that post for details.

Loading Images

When you open the File menu and select Open, the following code executes.

private void mnuFileOpen_Click(object sender, EventArgs e) { if (ofdImages.ShowDialog() == DialogResult.OK) { foreach (string filename in ofdImages.FileNames) { PictureBox pic = new PictureBox(); pic.SizeMode = PictureBoxSizeMode.AutoSize; pic.Margin = Spacing; pic.Image = LoadBitmapUnlocked(filename); pic.Parent = flpImages; } } }

This code displays an OpenFileDialog. If you select one or more files and click Open, the code loops through the selected file names. For each file, it creates a PictureBox, sets some properties for it (including the Margin property), loads the selected file into the new control's Image property, and adds the control to the FlowLayoutPanel.

See the post Load images without locking their files in C# for information about the LoadBitmapUnlocked method.

Note that the file names listed in the OpenFileDialog control's FileNames property do not necessarily have the same order in which you selected them. If you care about the ordering of the images in the montage, select the files separately, one per use of the Open command.

Saving Results

After you have created a montage, use the File menu's Save As command to save it. That command executes the following code.

private void mnuFileSaveAs_Click(object sender, EventArgs e) { if (sfdImages.ShowDialog() == DialogResult.OK) { // Find the maximum X and Y bounds. int max_x = 0; int max_y = 0; foreach (PictureBox pic in flpImages.Controls) { if (max_x < pic.Right) max_x = pic.Right; if (max_y < pic.Bottom) max_y = pic.Bottom; } max_x += Spacing.Right; max_y += Spacing.Bottom; // Create and save the montage. Bitmap bm = new Bitmap(max_x, max_y); using (Graphics gr = Graphics.FromImage(bm)) { gr.Clear(flpImages.BackColor); // Find the maximum X and Y bounds. foreach (PictureBox pic in flpImages.Controls) { gr.DrawImage(pic.Image, pic.Left, pic.Top, pic.Width, pic.Height); } } SaveImage(bm, sfdImages.FileName); } }

This code displays a SaveFileDialog. If you enter a file name and click Save, the program saves the result image.

To save the result, the code first loops through the PictureBox controls inside the FlowLayoutPanel and finds the maximum X and Y coordinates used by any of them. It then adds the Spacing value's Right and Bottom values to max_x and max_y respectively to add an appropriate border on the right and bottom edges of the result.

Next, the program creates a bitmap big enough to hold the maximu X and Y coordinates. It clears the bitmap and loops through the PictureBox controls again, drawing each at the location it was assigned by the FlowLayoutPanel.

After it has drawn all of the images, the code calls the SaveImage method to save the result. See the post Save images with an appropriate format depending on the file name's extension in C# to see how that method works.


This example demonstrates a couple of useful techniques such as using a FlowLayoutPanel to arrange controls, adjusting spacing around controls inside a container, and sizing the montage to fit the PictureBox controls.

Download the example to experiment with it and to see additional details.

© 2009-2023 Rocky Mountain Computer Consulting, Inc. All rights reserved.