Make a picture list in C#

[picture list]

This example shows how to build a picture list that lets the user add, remove, and rearrange pictures at run time. If you right-click between two pictures or to the left or right of all of the pictures, the program displays a context menu with the single command Insert Picture. If you select that command, the program displays an OpenFileDialog to let you select a new picture to add at that position.

If you right-click on, above, or below a picture, the context menu contains the commands Move Left, Move Right, and Delete Picture. The Move Left command is disabled for the leftmost picture. Similarly the Move Right command is disabled for the rightmost picture. The Delete Picture command asks you to confirm the deletion before it removes the picture that you clicked.

The following sections explain how the program works.

Arranging Pictures

At design time, I created a Panel control named panPictures. That control’s AutoScroll property is true, so the control displays scroll bars if it is not big enough for all of its contents to be visible.

The program uses the following form-level variables to keep track of the pictures being displayed.

// The currently loaded pictures.
private List<Bitmap> Pictures = new List();
private const int PictureMargin = 8;

// The index of the picture we clicked or
// the picture before which we clicked.
private int ClickedIndex = -1;

The Pictures list holds the pictures in left-to-right order. The value PictureMargin indicates how much space should be left between the pictures displayed in the panPictures control.

The code uses the ClickedIndex value later when it deals with mouse events.

Whenever the program changes its picture list, it calls the following method to display the pictures.

private void ArrangePanel()
{
    panPictures.Controls.Clear();
    int x = PictureMargin;
    int y = PictureMargin;
    foreach (Bitmap picture in Pictures)
    {
        PictureBox pic = new PictureBox();
        pic.SizeMode = PictureBoxSizeMode.AutoSize;
        pic.Location = new Point(x, y);
        pic.Image = picture;
        pic.Visible = true;
        pic.MouseDown += pic_MouseDown;
        panPictures.Controls.Add(pic);

        x += pic.Width + PictureMargin;
    }

    // Add one placeholder PictureBox.
    PictureBox placeholder = new PictureBox();
    placeholder.Location = new Point(x, y);
    placeholder.Size = new Size(0, 0);
    placeholder.Visible = true;
    placeholder.MouseDown += pic_MouseDown;
    panPictures.Controls.Add(placeholder);
}

This method first removes the children from the panPictures control. It then sets variables x and y to the spot where the first picture’s upper left corner should be.

Next, the code loops through the pictures in the Pictures list. For each picture, it creates a PictureBox control, positions it at (x, y), and makes it display the picture. It registers the control to receive MouseDown events and adds it to the panPictures control. The loop finishes by moving the value x so it leaves distance PictureMargin between this picture and the next one.

If the method stopped at this point, then the Panel control would provide scroll bars if necessary so you could scroll to the right edge of the last picture. Unfortunately that would not provide any space to the right of the final picture where you could right-click to open the context menu and use the Insert Picture command.

To work around that problem, the method adds one more PictureBox to the Panel control. This control has no size but is positioned PictureMargin pixels to the right of the final picture so you have an area where you can right-click.

Displaying the Context Menu

When you press the mouse down on one of the PictureBox controls, the following event handler executes.

private void pic_MouseDown(object sender, MouseEventArgs e)
{
    // Ignore left mouse clicks.
    if (e.Button != MouseButtons.Right) return;

    // Display the context menu.
    PictureBox pic = sender as PictureBox;
    ShowContextMenu(new Point(pic.Left + e.X, pic.Top + e.Y));
}

If you pressed the left mouse button, the code simply returns. If you pressed the right mouse button, the code gets the PictureBox under the mouse. Next, it adds the PictureBox control’s Left and Top values to the mouse’s position within the PictureBox to get the mouse’s position within the panPictures control. It then passes those coordinates to the ShowContextMenu method described shortly.

When you press the mouse down on the panPictures control, the following event handler executes.

private void panPictures_MouseDown(object sender, MouseEventArgs e)
{
    // Ignore left mouse clicks.
    if (e.Button != MouseButtons.Right) return;

    // Display the context menu.
    ShowContextMenu(e.Location);
}

This event handler also ignores left mouse button presses. If you pressed the right mouse button, the code passes the mouse’s coordinates to the ShowContextMenu method.

The following code shows the ShowContextMenu, which displays the picture list.

// Prepare the context menu and display it.
private void ShowContextMenu(Point location)
{
    // Assume we click after the final picture.
    bool clicked_on_picture = false;
    ClickedIndex = Pictures.Count;

    // See if we clicked on or before a picture.
    int x = location.X + panPictures.HorizontalScroll.Value;
    for (int i = 0; i < Pictures.Count; i++)
    {
        // See if we are before the next picture.
        x -= PictureMargin;
        if (x < 0)
        {
            ClickedIndex = i;
            break;
        }   

        // See if we are on this picture.
        x -= panPictures.Controls[i].Width;
        if (x < 0)
        {
            ClickedIndex = i;
            clicked_on_picture = true;
            break;
        }
    }

    // Enable and disable contect menu items.
    mnuMoveLeft.Enabled =
        (clicked_on_picture && (ClickedIndex > 0));
    mnuMoveRight.Enabled =
        (clicked_on_picture && (ClickedIndex < Pictures.Count - 1));
    mnuDeletePicture.Enabled = clicked_on_picture;
    mnuInsertPicture.Enabled = !clicked_on_picture;

    // Display the context menu.
    ctxPictures.Show(panPictures, location);
}

This method determines whether you pressed the mouse down on (or above or below) a picture, or whether you pressed it between two pictures (or to the left and right of all of the pictures). To do that, the code assumes that you did not click on a picture. It sets ClickedIndex to the index one beyond the last index in the Pictures list.

Next, the code sets variables x equal to the mouse location within the Panel control. It adds the control’s horizontal scroll value in case you have scrolled that control. For example, if you have scrolled the Panel by 100 pixels, then the pictures that it contains have been moved 100 pixels to the left. That means, in the coordinate system that contains the pictures, the mouse’s X position is 100 greater than the value given by the location parameter.

Having initialized x, the method loops through the pictures. For each picture, the code subtracts the value PictureMargin. If that makes x become less than zero, then the mouse lies just before the current picture. In that case, the code sets ClickedIndex equal to the current picture’s index and breaks out of the loop.

If x is still positive, the code subtracts the width of the current picture’s PictureBox control. If x is now negative, then the mouse lies above the current picture, so the code sets ClickedIndex to the current picture’s index. In that case it also sets clicked_on_picture to true so we remember that the mouse was over a picture.

After the loop ends, the code enables and disables the context menu’s commands appropriately. For example, it enables the Move Left command if the mouse is over a picture and it is not the leftmost picture.

The ShowContextMenu method finishes by displaying the context menu named ctxPictures.

Context Menu Commands

The following code executes when you select the Move Left command.

private void mnuMoveLeft_Click(object sender, EventArgs e)
{
    Bitmap bm = Pictures[ClickedIndex];
    Pictures.RemoveAt(ClickedIndex);
    Pictures.Insert(ClickedIndex - 1, bm);
    ArrangePanel();
}

This code sets variable bm equal to the picture that was clicked. It then removes that picture from the Pictures list and reinserts it one position to the left of its original position. The code finishes by calling ArrangedPanel to redisplay the picture list.

The following code executes when you select the Move Right command.

private void mnuMoveRight_Click(object sender, EventArgs e)
{
    Bitmap bm = Pictures[ClickedIndex];
    Pictures.RemoveAt(ClickedIndex);
    Pictures.Insert(ClickedIndex + 1, bm);
    ArrangePanel();
}

This code is similar to the Move Left command except it reinserts the clicked picture one position to the right of its original position.

The following code executes when you select the Delete Picture command.

private void mnuDeletePicture_Click(object sender, EventArgs e)
{
    if (MessageBox.Show(
        "Are you sure you want to delete this picture?",
        "Delete Picture?", MessageBoxButtons.YesNo) == DialogResult.Yes)
    {
        Pictures.RemoveAt(ClickedIndex);
        ArrangePanel();
    }
}

This code asks if you really want to delete the picture that you right-clicked. If you click Yes, the code simply removes the picture from the Pictures list and calls ArrangedPanel to redisplay the picture list.

Finally, the following code executes when you select the Insert Picture command.

// Let the user insert a picture.
private void mnuInsertPicture_Click(object sender, EventArgs e)
{
    try
    {
        if (ofdPicture.ShowDialog() == DialogResult.OK)
        {
            int i = 0;
            foreach (string filename in ofdPicture.FileNames)
            {
                Bitmap bm = new Bitmap(filename);
                Pictures.Insert(ClickedIndex + i, bm);
                i++;
            }
            ArrangePanel();
        }
    }
    catch (Exception ex)
    {
        MessageBox.Show(ex.Message);
    }
}

This code displays an OpenFileDialog. If you select one or more files and click Open, the code loops through the files that you selected. It loads each file into a Bitmap and adds the file to the next position in the Pictures list. Increasing the value i each time places the pictures in the Pictures list in the order in which they are returned by the OpenFileDialog. (The dialog returns the files in the order in which they were listed in the dialog, not in the order in which you selected them.)

Again, the method finishes by calling ArrangePanel to show the new arrangement of pictures in the picture list.

Conclusion

This method lets a program display a picture list that the user can manage reasonably intuitively. You could add other variations. For example, you could arrange the pictures vertically in a column or wrap the pictures in rows and columns.

You could also convert the whole picture list into a PictureList UserControl. (In fact, I even did that before I decided few people would be interested in that. If you do want to see the picture list converted into a control, post a comment below. If enough people want it, I’ll post it.

Meanwhile, download the example and give it a try. I think you’ll find the picture list very easy to use.


Download Example   Follow me on Twitter   RSS feed   Donate




About RodStephens

Rod Stephens is a software consultant and author who has written more than 30 books and 250 magazine articles covering C#, Visual Basic, Visual Basic for Applications, Delphi, and Java.
This entry was posted in controls, lists, user interface and tagged , , , , , , , , . Bookmark the permalink.

1 Response to Make a picture list in C#

  1. Pingback: Improve the picture list in C# - C# HelperC# Helper

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.