Click here to Skip to main content
12,693,693 members (30,147 online)
Click here to Skip to main content
Add your own
alternative version


140 bookmarked

Image Thumbnail Viewer with .NET 2.0

, 30 Aug 2007 CPOL
Rate this:
Please Sign up or sign in to vote.
A simple way to create a thumbnail viewer with .NET 2.0
Screenshot - thumbnaildotnet2.png

Screenshot - thumbnaildotnet2_image.png


Ok, I know, you're thinking: another thumbnail viewer... and you are right!

But let me explain the purpose of this article: I saw a very good article by Marc Clifton Multi Image Viewer and I was just thinking that there must be a simple solution with .NET 2.0 to do a Picasa-like viewer.

So this article is about how to easily add thumbnail viewer functionality to an application with C# 2.0.


Let's define some requirements:

  • Load asynchronously images from a folder and show them in a thumbnail viewer
  • Manage the memory consumption nicely and smoothly
  • Adapt the layout from left to right dynamically depending on the size of the application
  • Add scrollbars when required
  • Add a cancel during loading
  • Add thumbnail zooming
  • Detect if someone clicks on an image, show the selected image in the thumbnail and in an external viewer
  • Make it nice, Picasa look&feel
  • Everything with C# 2.0 and minimal code

How To

The main idea is to use the FlowLayoutPanel control. This panel dynamically lays out its contents horizontally or vertically, contents are of course controls. Now you get the point: just use FlowLayoutPanel, set the property FlowDirection to FlowDirection.LeftToRight and that's it.

If you want to know more about anchoring and docking, have a look at How to: Anchor and Dock Child Controls in a FlowLayoutPanel Control.

Now let's continue with an image viewer: PictureBox. This control is good at showing images with some options like SizeMode = PictureBoxSizeMode.Zoom. So it looks like we found the solution. We could work with these two controls like this:

// for each image
PictureBox imageViewer = new PictureBox();
imageViewer.Image = Image.FromFile(imageFilename);
imageViewer.SizeMode = PictureBoxSizeMode.Zoom;
imageViewer.Dock = DockStyle.Bottom;
imageViewer.Height = 128;
imageViewer.Width = 128;

But I decided to write my own image viewer in order to improve the memory management (don't forget that we are working first with full size images) and we will be able to add some nice style (shadow, selection frame).

In summary, this is just a bitmap scaling in order to create the thumbnail (reduce memory size) and a GDI+ rendering:

// load the image and scale it to the given width and height
// we don't use GetThumbnailImage because GDI does not always provide
// an optimal quality
public void LoadImage(string imageFilename, int width, int height)
    Image tempImage = Image.FromFile(imageFilename);

    int dw = tempImage.Width;
    int dh = tempImage.Height;
    int tw = width;
    int th = height;
    double zw = (tw / (double)dw);
    double zh = (th / (double)dh);
    double z = (zw <= zh) ? zw : zh;
    dw = (int)(dw * z);
    dh = (int)(dh * z);

    m_Image = new Bitmap(dw, dh);
    Graphics g = Graphics.FromImage(m_Image);
    g.InterpolationMode = InterpolationMode.HighQualityBicubic;
    g.DrawImage(tempImage, 0, 0, dw, dh);

    tempImage.Dispose(); // do not forget to dispose and don't wait for GC

// render the image m_Image with a scaling to the size of the control
// for that we have to override the OnPaint of the usercontrol
protected override void OnPaint(PaintEventArgs e)
    Graphics g = e.Graphics;
    if (g == null) return;

    int dw = m_Image.Width;
    int dh = m_Image.Height;
    int tw = this.Width;
    int th = this.Height;
    double zw = (tw / (double)dw);
    double zh = (th / (double)dh);
    double z = (zw <= zh) ? zw : zh;

    dw = (int)(dw * z);
    dh = (int)(dh * z);
    int dl = (tw - dw) / 2;
    int dt = (th - dh) / 2;

    g.DrawImage(m_Image, dl, dt, dw, dh);

In the project demo, the class ImageViewer implements a user control for image rendering.

Model View Controller

In order to fulfill the asynchronous requirement, we will use a controller class that scans directories, looks for images and does its stuff in a thread.

The controller will simply start a thread with one parameter: the path to be scanned.

public void AddFolder(string folderPath)
    Thread thread = new Thread(new ParameterizedThreadStart(AddFolder));
    thread.IsBackground = true;

private void AddFolder(object folderPath)

In a Model-View-Controller product code, I would recommend to define a real model, for example a class containing enough information to be used by any source: disk, database or URL. Here, we will simply use a string, the path of the image.

The controller informs the View by using Event-Delegate each time an image is found. The View creates a new image control and adds it to the FlowLayoutPanel.

m_Controller = new ThumbnailController();
m_Controller.OnStart += new ThumbnailControllerEventHandler(m_Controller_OnStart);
m_Controller.OnAdd += new ThumbnailControllerEventHandler(m_Controller_OnAdd);
m_Controller.OnEnd += new ThumbnailControllerEventHandler(m_Controller_OnEnd);

private void AddImage(string imageFilename)
    ImageViewer imageViewer = new ImageViewer();
    imageViewer.LoadImage( imageFilename, 128, 128);

Threading Issues

If you are going to use threads in Windows Forms, be careful of threading issues. In short, your form is running in its own thread and a different thread interacts with it or its controls.

You cannot directly change properties of these controls without taking the chance of an ugly exception, it may happen or not. Luckily, .NET provides a simple way to check if you are running in the control thread and to invoke methods in a safe way.

Use InvokeRequired to perform the test: the property simply tests if the working thread Id is the same as the control thread Id.

Use Invoke to perform the call: the method posts a message in the queue of the control/window with your callback function (you therefore need a delegate), this is thread safe.

In this example, we used it whenever the controller sends an event to the view.

private void AddImage(string imageFilename)
    // thread safe
    if (this.InvokeRequired)
        this.Invoke(Delegate /* delegate */, object[] /* objects */);
        // do what we have to do

ScrollableControl Issues

It seems that the controls derived from ScrollableControl do not always act the way you would like them to. FlowLayoutPanel is derived from Panel, itself derived from ScrollableControl.

If you set AutoScroll = true, you will have nice scrollbars that will appear when required but also a strange bug: when you try to click on a thumbnail the scrollbars jump back to the origin.

As steven69 posted, you can remove Dock = DockStyle.Bottom and it works. But if you want to do it the tricky way, just override FlowLayoutPanel in a derived control ThumbnailFlowLayoutPanel with the following:

protected override Point ScrollToControl(Control activeControl)
    return this.AutoScrollPosition;

The control does not jump anymore because we are telling the panel to ignore any active control.

More Nice Features

Now that we have the basis of our application, we can easily add some nice features.

Thumbnail Zooming

The size of the thumbnails is hard-encoded in imageViewer.LoadImage(imageFilename, 256, 256) but the display size is defined by the size of the control.

So, we just have to dispatch the trackbar event ValueChanged to each ImageViewer and set the size according to the zoom value (e.g. 64, 128 or 256 pixels).

Thumbnail Selection

We just added a local member in order to store the selected thumbnail. Each time a thumbnail is clicked, the old one is inactivated and the new one is in activated status (blue border).


In order to fulfill our requirements, we used:

  • Thumbnail viewer: FlowLayoutPanel and ImageViewer
  • Add scrollbars: FlowLayoutPanel.AutoScroll = true and ThumbnailFlowLayoutPanel
  • Load asynchronously: Controller with thread and events.
  • Add a cancel: Add a flag in the controller
  • Detect if someone clicks on an image: Add an eventhandler to each instance of ImageViewer. We then show a dialog with the selected image.
  • Use a trackbar to simply set the size of the thumbnail control.


Nothing new here but a simple thread safe way to implement an asynchronous thumbnail viewer with C# 2.0, thanks to FlowLayoutPanel and some threading.

I hope this small demo will provide you with ideas. Any comment is welcome!


  • V1.3 - 30th August, 2007 - Added thumbnail zooming and selection - Code update
  • V1.2 - 21st August, 2007 - Fixed FlowLayoutPanel.AutoScroll issue - Code update
  • V1.1 - 16th August, 2007 - Added threading issues doc - Fixed memory usage - Code update - (thanks to the post from Anthony Yates)
  • V1.0 - 14th August, 2007 - First version


This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


About the Author

Marc Lievin
Canada Canada
Marc is software engineer at Avid Technology, Montreal, Canada.

You may also be interested in...

Comments and Discussions

GeneralNice Article & program Pin
GeertD3-Sep-07 21:24
memberGeertD3-Sep-07 21:24 
GeneralRe: Nice Article & program Pin
Marc Lievin4-Sep-07 3:44
memberMarc Lievin4-Sep-07 3:44 
GeneralSpeed up suggestions Pin
tomi_231-Aug-07 6:10
membertomi_231-Aug-07 6:10 
GeneralRe: Speed up suggestions Pin
Marc Lievin31-Aug-07 7:29
memberMarc Lievin31-Aug-07 7:29 
GeneralRe: Speed up suggestions Pin
rapid2k24-Sep-07 2:14
memberrapid2k24-Sep-07 2:14 
GeneralRe: Speed up suggestions Pin
Marc Lievin4-Sep-07 3:42
memberMarc Lievin4-Sep-07 3:42 
GeneralRe: Speed up suggestions Pin
Lewis Delport6-Nov-08 23:57
memberLewis Delport6-Nov-08 23:57 
JokeError ! Pin
ajai808521-Aug-07 23:47
memberajai808521-Aug-07 23:47 
Hi , This is a nice application , but since the thread is Background thread we can close the main thread by pressing the close button in the control box Big Grin | :-D . so the following error will come . better abandon the thread on close of the form, since this is background thread . if you avoid this just make it
 Thread thread = new Thread(new ParameterizedThreadStart(AddFolder));
//            thread.IsBackground = true;
            thread.IsBackground = false;//Modified to

The above code will make the main thread to wait for the threads spun by it to get finish . "This will cause the application to run for a while in tak manager even if its window is closed " so I feel the better is to 'Abort()' on close of the form ...

private void AddImage(string imageFilename)
            // thread safe
            if (this.InvokeRequired)
                this.Invoke(m_AddImageDelegate, imageFilename);//Show Error here if the I press Cancel button 

System.ObjectDisposedException was unhandled
  Message="Cannot access a disposed object.\r\nObject name: 'MainForm'."
       at System.Windows.Forms.Control.MarshaledInvoke(Control caller, Delegate method, Object[] args, Boolean synchronous)
       at System.Windows.Forms.Control.Invoke(Delegate method, Object[] args)
       at marlie.TumbnailDotnet.MainForm.AddImage(String imageFilename) in D:\Unearth\Thumbnail\ThumbnailDotnet\MainForm.cs:line 89
       at marlie.TumbnailDotnet.MainForm.m_Controller_OnAdd(Object sender, ThumbnailControllerEventArgs e) in D:\Unearth\Thumbnail\ThumbnailDotnet\MainForm.cs:line 63
       at marlie.TumbnailDotnet.ThumbnailController.AddFolderIntern(String folderPath) in D:\Unearth\Thumbnail\ThumbnailDotnet\ThumbnailController.cs:line 103
       at marlie.TumbnailDotnet.ThumbnailController.AddFolderIntern(String folderPath) in D:\Unearth\Thumbnail\ThumbnailDotnet\ThumbnailController.cs:line 115
       at marlie.TumbnailDotnet.ThumbnailController.AddFolder(Object folderPath) in D:\Unearth\Thumbnail\ThumbnailDotnet\ThumbnailController.cs:line 70
       at System.Threading.ThreadHelper.ThreadStart_Context(Object state)
       at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
       at System.Threading.ThreadHelper.ThreadStart(Object obj)


Warm Regards
Ajai NP

GeneralRe: Error ! Pin
Marc Lievin22-Aug-07 0:02
memberMarc Lievin22-Aug-07 0:02 
GeneralRe: Error ! Pin
Marc Lievin30-Aug-07 0:02
memberMarc Lievin30-Aug-07 0:02 
GeneralRe: Error ! Pin
ajai80852-Sep-07 23:02
memberajai80852-Sep-07 23:02 
GeneralRe: Error ! Pin
Marc Lievin3-Sep-07 0:50
memberMarc Lievin3-Sep-07 0:50 
GeneralVery nice Pin
merlin98121-Aug-07 11:33
membermerlin98121-Aug-07 11:33 
GeneralRe: Very nice Pin
Marc Lievin21-Aug-07 21:32
memberMarc Lievin21-Aug-07 21:32 
GeneralHorizontal Scroll Pin
ajalilqarshi15-Aug-07 21:51
memberajalilqarshi15-Aug-07 21:51 
GeneralRe: Horizontal Scroll Pin
Marc Lievin16-Aug-07 3:02
memberMarc Lievin16-Aug-07 3:02 
GeneralFix to threading issue... Pin
Tonster10115-Aug-07 0:57
memberTonster10115-Aug-07 0:57 
GeneralRe: Fix to threading issue... [modified] Pin
Marc Lievin15-Aug-07 2:30
memberMarc Lievin15-Aug-07 2:30 
GeneralRe: Fix to threading issue... Pin
Marc Lievin16-Aug-07 4:30
memberMarc Lievin16-Aug-07 4:30 
GeneralRe: Fix to threading issue... Pin
Tonster10116-Aug-07 12:36
memberTonster10116-Aug-07 12:36 
GeneralRe: Fix to threading issue... Pin
steven6921-Aug-07 6:27
membersteven6921-Aug-07 6:27 
GeneralRe: Fix to threading issue... Pin
Marc Lievin21-Aug-07 6:49
memberMarc Lievin21-Aug-07 6:49 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web01 | 2.8.170118.1 | Last Updated 30 Aug 2007
Article Copyright 2007 by Marc Lievin
Everything else Copyright © CodeProject, 1999-2017
Layout: fixed | fluid