Title: Let the user drag pictures in a picture list in C#
The post Improve the picture list in C# showed how you can let the user add, remove, and rearrange images in a picture list. To rearrange the list, the user right-clicks on a picture and selects the Move Left or Move Right command. That works, but can be slow if the user wants to move an image many positions in the picture list. It would be easier to delete and re-add the image. This example shows how you can allow the user to drag a picture to a new position in the list
The previous example stored the images in a list. Whenever the user rearranged the pictures, the program created PictureBox controls to display them. This example uses a list holding the PictureBox controls rather than the images. When the user rearranges the pictures, the program rearranges the PictureBox controls instead of creating new ones.
The program uses the following code to store the controls.
// The currently loaded PictureBoxes.
private List<PictureBox> PictureBoxes =
new List<PictureBox>();
The code uses the following variables to keep track of the PictureBox that the user is dragging.
// Used to drag PictureBoxes.
private PictureBox DragPic = null;
private Point DragOffset;
The value DragPic is the PictureBox that the user is currently dragging. The DragOffset value gives the X and Y distances between the location of DragPic and the mouse's position. You'll see how that works when you look at the mouse handling code.
The following code executes when the user presses the mouse down over a PictureBox.
// Start dragging the control or display the context menu.
private void pic_MouseDown(object sender, MouseEventArgs e)
{
PictureBox pic = sender as PictureBox;
if (e.Button == MouseButtons.Left)
{
// Start dragging.
DragPic = pic;
int dx = -e.Location.X;
int dy = -e.Location.Y;
DragOffset = new Point(dx, dy);
// Move the PictureBox to the top of the
// panPictures stacking order.
panPictures.Controls.SetChildIndex(pic, 0);
// Let panPictures handle the MouseMove and MouseUp.
DragPic.Capture = false;
panPictures.Capture = true;
panPictures.MouseMove += panPictures_MouseMove;
panPictures.MouseUp += panPictures_MouseUp;
}
else
{
// Get the mouse's location in panPictures coordinates.
Point screen_point = pic.PointToScreen(e.Location);
Point parent_point = panPictures.PointToClient(screen_point);
// Display the context menu.
ShowContextMenu(new Point(
parent_point.X,
parent_point.Y));
}
}
If the user has pressed the left mouse button, the code saves the DragPic and sets DragOffset to the negative of the mouse's location. Because the PictureBox raised this event handler, the mouse's position is with respect to that control's origin so DragOffset indicates the X and Y distances between the mouse and the control's upper left corner.
The code then calls the SetChildIndex method for the collection of PictureBox controls within the Panel control that forms the picture list. It uses that method to move the control under the mouse to the top of the stacking order so it appears above the other pictures in the picture list. (Dragging behind the other controls works but looks weird.)
Next, the program sets dragPic.Capture to false to release the mouse capture that started when the user pressed the mouse down over the control. It sets panPictures.Capture to true to give the Panel control the mouse so it receives future mouse events.
Finally, the event handler installs MouseMove and MouseUp event handlers to capture future mouse events.
If the user pressed the right mouse button over the PictureBox, the code displays a context menu just as the previous example did.
The following code shows the MouseMove event handler.
// Move a PictureBox.
private void panPictures_MouseMove(object sender, MouseEventArgs e)
{
int x = e.Location.X + DragOffset.X;
int y = e.Location.Y + DragOffset.Y;
DragPic.Location = new Point(x, y);
}
This code adds the DragOffset value to the mouse's current position. Remember that the mouse is now captured by the Panel control that holds the PictureBox, so its position is with respect to that control. The DragOfffset value gives the distance between the mouse's initial position and the PictureBox control's upper left corner, so the result is where the PictureBox should be moved to keep the mouse over the same position on the control.
After it calculates the new position, the code simply moves the PictureBox.
The following code shows the new MouseUp event handler.
// Stop dragging DragPic.
private void panPictures_MouseUp(object sender, MouseEventArgs e)
{
DragPic = null;
panPictures.MouseMove -= panPictures_MouseMove;
panPictures.MouseUp -= panPictures_MouseUp;
OrderPictureBoxes();
}
This code sets DragPic to null to indicate that no drag is in progress. It uninstalls the new MouseMove and MouseUp event handlers, and then calls the following OrderPictureBoxes method.
// Rearrange the picture list so the controls
// are ordered by their X coordinates.
private void OrderPictureBoxes()
{
// Sort the PictureBoxes list.
PictureBoxes.Sort((pic1, pic2) =>
pic1.Location.X.CompareTo(pic2.Location.X));
// Rearrange the controls.
ArrangePictureBoxes();
}
Recall that PictureBoxes is the program's list of PictureBox controls. This method calls that list's Sort method. It uses a lambda expression to indicate the function that should be used to sort the PictureBox controls in the list. This lambda expression takes two PictureBox controls as parameters and compares their X coordinates.
After the controls are sorted by their X coordinates, the code calls the following ArrangePictureBoxes method.
// Arrange the PictureBoxes.
private void ArrangePictureBoxes()
{
int ymax = 0;
int x = PictureMargin;
int y = PictureMargin;
foreach (PictureBox pic in PictureBoxes)
{
pic.Location = new Point(x, y);
x += pic.Width + PictureMargin;
if (ymax < pic.Height) ymax = pic.Height;
}
// Position one placeholder PictureBox.
y = ymax + 2 * PictureMargin;
Placeholder.Location = new Point(x, y);
}
This method is similar to the code used by the previous example to arrange its picture list. It sets variables x and y to the position where the first control should be placed. It then loops through the controls, positions each, and adds each control's width plus a margin to the value x.
The method finishes by placing the Placeholder PictureBox to the right of the other controls so there is room for the user to right-click to the right of all of the pictures in the picture list. See the previous example for information about the placeholder control.
With the ability to drag images into new positions, the picture list is quite easy to use. To see additional details, look at the code and see the previous example.
Download the example to experiment with it and to see additional details.
|