Introduction
This is an implementation showing how to display thumbnail images in a DataGridView
, with image resizing and paging. The main reason I created this was to allow users to preview thousands of images quickly and efficiently, whilst still having the core functions of the DataGridView
, e.g., selecting cells etc.
Other solutions I have seen use owner panels or other controls, which work well, but don't allow a user to select images. Also, since the controls are owner drawn, they don't have the built-in events like SelectIndexChanged
etc., which, in my scenario, makes them unusable. Other solutions I have seen only display a main image with previous and next previews, like a film strip preview. Again, I couldn't use them since I had a requirement to display thousands of images and the ability to navigate quickly through them.
So, if it saves someone some time, or gives them inspiration, then good.
Demo/Sample
The implementation will attempt to dynamically create the correct number of columns to fit the DataGridView
width. The images are reloaded when the DataGridView
resizes to fit the correct width.
A slider control is used to select the image thumbnail size. When the value changes, the images are reloaded, with the current set of images at the new selected image size.
The number of images displayed per page can be modified through the drop down list. If the value changes, then the images are redisplayed from the beginning; this is purely to make the implementation easier!
Code
The column widths are calculated using the following code, which allows us to dynamically create the required number of columns to fit the actual width of the DataGridView
:
int numColumnsForWidth = (dataViewImages.Width - 10) / (_imageSize + 20);
Another requirement was to display the minimum number of rows required for the number of images per page, the reason being I wanted it to look as professional as possible. To calculate the number of rows, we need to calculate the number of images that are to be displayed, then use the number of columns required to fit the DataViewGrid
. Next, we use the modulus operator to check for a remainder to the calculation; if we get a result, then we add another row to display the overfill:
numRows = numImagesRequired / numColumnsForWidth;
if (numImagesRequired % numColumnsForWidth > 0)
{
numRows += 1;
}
The code dynamically creates the columns and rows using the previously calculated values. The value of 20 is simply a value used to create a border around the image, so that the user can see what images are selected:
{
dataViewImages.Columns.Add(dataGridViewColumn);
dataViewImages.Columns[index].Width = _imageSize + 20;
}
for (int index = 0; index < numColumnsForWidth; index++)
{
DataGridViewImageColumn dataGridViewColumn =
new DataGridViewImageColumn();
dataViewImages.Rows.Add();
dataViewImages.Rows[index].Height = _imageSize + 20;
}
The images are dynamically loaded from a previously loaded List<string>
variable, which contains the image paths. The actual images are loaded from the file system. The code assigns the file name to the tooltip so that the image can be easily identified:
Image image = Helper.ResizeImage(_files[index],
_imageSize,
_imageSize,
false);
dataViewImages.Rows[rowIndex].Cells[columnIndex].Value = image;
dataViewImages.Rows[rowIndex].Cells[columnIndex].ToolTipText =
Path.GetFileName(_files[index]);
The above code references a Helper.ResizeImage
function, which is a simple routine that will, strangely enough, resize an image in memory:
public static Image ResizeImage(string file,
int width,
int height,
bool onlyResizeIfWider)
{
using (Image image = Image.FromFile(file))
{
image.RotateFlip(RotateFlipType.Rotate180FlipNone);
image.RotateFlip(RotateFlipType.Rotate180FlipNone);
if (onlyResizeIfWider == true)
{
if (image.Width <= width)
{
width = image.Width;
}
}
int newHeight = image.Height * width / image.Width;
if (newHeight > height)
{
width = image.Width * height / image.Height;
newHeight = height;
}
Image NewImage = image.GetThumbnailImage(width,
newHeight,
null,
IntPtr.Zero);
return NewImage;
}
}
Because some rows will have cells without images, due to the number of images being displayed not being equal to the number of rows * the number of columns, we need to set the cell to a null value, since a generic image not found icon will be displayed otherwise, which is unsightly:
if (numGeneratedCells > numImagesRequired)
{
for (int index = 0; index <
numGeneratedCells - numImagesRequired; index++)
{
DataGridViewCellStyle dataGridViewCellStyle =
new DataGridViewCellStyle();
dataGridViewCellStyle.NullValue = null;
dataGridViewCellStyle.Tag = "BLANK";
dataViewImages.Rows[rowIndex].Cells[columnIndex +
index].Style = dataGridViewCellStyle;
}
}
The rest of the code deals with checking list boundary conditions to deal with situations like the user displaying less images than the number of images per page setting. Also, another key check is to ensure that we can display all the images when we get to the end of the list.
History
- v1.0.0 - Initial release.