Loading Thumbnails

Loading Images from a Folder

You can populate Better Thumbnail Browser with auto-population in case you want to display images from an image folder:

C#

thumbnailBrowser.Path = "c:\\images";

Visual Basic

thumbnailBrowser.Path = "c:\images"

This will automatically add items to Better Thumbnail Browser and starts loading them using default image loading provider:

Starting, Stopping and Restarting Loading

You can control item loading by calling StartLoading(), StopLoading() and RestartLoading() methods.

The StartLoading() method is asynchronous, so the code will continue after the call and the thumbnail items will be loaded on a separate thread.

The StopLoading() method is synchronous - it will wait until current item finishes loading and then stops.

The RestartLoading() method has an override with LoadingRestartOptions parameter. It can have the following values:

When calling RestartLoading() method without parameters, the LoadingRestartOptions.None is used.

Loading Events

During the loading process, two events are raised by the Better ThumbnailBrowser control:

Loading Options

Refreshing Delay

When large number of small thumbnails are loaded, or when loading each thumbnail is quick, it is inefficient to refresh control after every single item is loaded. Instead, a timer looks for loaded items in a predefined interval and refreshes the control only if any items get loaded. By default, the interval is set to 250 millisecons, but you can set your own value through RefreshDelay property. When you set property value to 0, the control will be refreshed after every item loaded.

Better Thumbnail Browser uses System.Threading.Timer, so its accurracy is in order of tens of milliseconds.

Loading Thread Options

You can setup thumbnail item loading thread by setting the following properties:

Skipping Individual Items

If you want not to load specific thumbnail items, set BetterThumbnailBrowserItem.Skip property to true.

Loading Providers

Thumbnail item loading is performed by so called loading providers (instances of LoadingProvider class).

These instances are listed in the LoadingProviders property. In case there is more than one loading provider, a multiple passes over the thumbnail items are done, one for each loading provider.

Better Thumbnail Browser have two bases classes for deriving your own loading providers:

The both of these classes are derived from LoadingProvider class and implement a LoadItem method. In this method, the loader calls LoadItemAsync method, which have to be provided by the user as well as the LoadItemSync method. The difference between these two methods is that LoadItemAsync is called on the (background) loader thread and the LoadItemSync is then called on the UI (foreground) thread.

Only LoadItemAsync and LoadItemSync methods need to be implemented by the user.

There is also LoadItemsSync method which is used by LoadItem when timer is used (see RefreshDelay property), in that case, the loading provider calls only LoadItemAsync and stores the result data, and then performs synchronization and calls LoadItemSync in a batch.

Loading Image Thumbnails in Better Thumbnail Browser

Thumbnail loading is designed to be fast and effective.

First of all, images need not to be loaded in full resolution and resized. Instead, you can load image in lower resolution or simply get low resolution version already available. For example, the default image loading provider in Better Thumbnail Browser makes use of existing image thumbnails in JPEG image, avoiding resizing.

You can still load images in full resolution. These images will be present in BetterThumbnailBrowser.Image property in full resolution, but will be resized internally for viewing.

The thumbnail items can be zoomed and images may need to be reloaded in higher resolution. The loading mechanism of Better ThumbnailBrowser never reloads images if a full resolution is already loaded (smaller image gets centered in the thumbnail item instead). It also avoid reloading if user provided image of high-enough resolution. Of course, if you provide image in full resolution, then every thumbnail item gets loaded just once. To determine what "full resolution" means, there is a maximumImageSize property in the loading method. One provides loaded image and informs about the full resolution. You can also set maximumImageSize property to the same size as thumbnail image, but your image will not be reloaded in higher resolution if needed (and get centered in thumbnail items instead).

Custom Image Loader

We will illustrate how to implement a minimalist image loading provider. It assumes that every item contains path to image file in its Path property:

C#

class MyImageLoadingProvider : ImageLoadingProvider
{
    public MyLoadingProvider(BetterThumbnailBrowser thumbnailBrowser)
        : base(thumbnailBrowser)
    {
    }

    protected override void LoadItemAsync(
        BetterThumbnailBrowserItem item,
        Size targetImageSize,
        out Image image,
        out Size maximumImageSize,
        out ILoadingProviderData data)
    {
        image = Image.FromFile(item.Path);
        maximumImageSize = image.Size;
        data = null;
    }
}

Visual Basic

Class MyImageLoadingProvider
    Inherits ImageLoadingProvider
    
    Public Sub New(thumbnailBrowser As BetterThumbnailBrowser)
        MyBase.New(thumbnailBrowser)
    End Sub

    Protected Overrides Sub LoadItemAsync(
        item As BetterThumbnailBrowserItem,
        targetImageSize As Size,
        ByRef image As Image,
        ByRef maximumImageSize As Size,
        ByRef data As ILoadingProviderData)
    
        image = Image.FromFile(item.Path)
        maximumImageSize = image.Size
        data = Nothing
        
    End Sub
    
End Class

As you can see, the implementation is very simple.

The entire code of LoadItemAsync consists of just providing these three parameters:

The LoadItemSync method need not to be provided by the user since it is already implemented in the ImageLoadingProvider. The default implementation only takes the image and maximumImageSize and sets it into respective properties of item: BetterThumbnailBrowserItem.Image and BetterThumbnailBrowserItem.MaximumImageSize.

Loading Non-image Data

Sometimes one would like to load other than image data - for example: metadata embedded in photos, file information, retrieve item info from remote database etc.

This is where CustomLoadingProvider comes into place. It is more general, so it can be used for loading images as well. User have to provide both LoadImageAsync and LoadItemSync methods:

C#

class MyCustomLoadingProvider : CustomLoadingProvider
{
    public MyCustomLoadingProvider(BetterThumbnailBrowser thumbnailBrowser)
        : base(thumbnailBrowser)
    {
    }

    protected override void LoadItemAsync(
        BetterThumbnailBrowserItem item,
        Size targetImageSize,
        out ILoadingProviderData data)
    {
        // obtain custom data (this runs on background thread)
        MyLoadingProviderData myLoadingProviderData = /* obtain the data here */;

        data = myLoadingProviderData;
    }

    protected override void LoadItemSync(BetterThumbnailBrowserItem item, ILoadingProviderData data)
    {
        MyLoadingProviderData myLoadingProviderData = (MyLoadingProviderData)data;

        // set loaded data to item (this runs on main thread)
        item.Text = myLoadingProviderData.Label;
        item.ToolTips.Add(new BetterListViewToolTipInfo(BetterListViewToolTipLocation.Image, myLoadingProviderData.Description));
    }
}

Visual Basic

Class MyCustomLoadingProvider
    Inherits CustomLoadingProvider
    
    Public Sub New(thumbnailBrowser As BetterThumbnailBrowser)
        MyBase.New(thumbnailBrowser)
    End Sub

    Protected Overrides Sub LoadItemAsync(item As BetterThumbnailBrowserItem, targetImageSize As Size, ByRef data As ILoadingProviderData)
        
        ' obtain custom data (this runs on background thread)
        Dim myLoadingProviderData As MyLoadingProviderData = ' obtain the data here 
            
        data = myLoadingProviderData
        
    End Sub

    Protected Overrides Sub LoadItemSync(item As BetterThumbnailBrowserItem, data As ILoadingProviderData)
        
            Dim myLoadingProviderData As MyLoadingProviderData = DirectCast(data, MyLoadingProviderData)
    
            ' set loaded data to item (this runs on main thread)
            item.Text = myLoadingProviderData.Label
            item.ToolTips.Add(New BetterListViewToolTipInfo(BetterListViewToolTipLocation.Image, myLoadingProviderData.Description))
            
    End Sub

End Class

As you can see, we have used custom type MyLoadingProviderData to hold loaded data, which is passed to foreground thread to further processing. This type implements ILoadingProviderData - an empty interface - so its code is very simple:

C#

class MyLoadingProviderData : ILoadingProviderData
{
    public string Label
    {
        get;
        set;
    }

    public string Description
    {
        get;
        set;
    }
}

Visual Basic

Class MyLoadingProviderData
    Implements ILoadingProviderData
    
    Public Property Label() As String
        Get
            Return label
        End Get
        Set
            label = Value
        End Set
    End Property
    
    Public Property Description() As String
        Get
            Return description
        End Get
        Set
            description = Value
        End Set
    End Property
    
    Private label As String
    Private description As String
    
End Class

Custom Loading Order

You can specify custom order in which the items will be loaded by setting an IComparer<BetterThumbnailBrowserItem> instance in the LoadingProvider.ItemComparer property. This can result in loading images in order you want:

For example, we would like to load visible items first, then all the others. This can be done with the following comparer:

C#

class CustomOrderItemComparer : IComparer<BetterThumbnailBrowserItem>
{
    private readonly BetterThumbnailBrowser thumbnailBrowser;

    public CustomOrderItemComparer(BetterThumbnailBrowser thumbnailBrowser)
    {
        this.thumbnailBrowser = thumbnailBrowser;
    }

    int IComparer<BetterThumbnailBrowserItem>.Compare(BetterThumbnailBrowserItem itemA, BetterThumbnailBrowserItem itemB)
    {
        // get put visible item indices in sorted array
        ReadOnlyCollection<BetterListViewItem> visibleItems = this.thumbnailBrowser.VisibleItems;
        int[] visibleIndices = new int[visibleItems.Count];

        for (int indexItem = 0; indexItem < visibleItems.Count; indexItem++)
        {
            visibleIndices[indexItem] = visibleItems[indexItem].Index;
        }

        Array.Sort(visibleIndices);

        int valueA = ((Array.BinarySearch(visibleIndices, itemA.Index) >= 0) ? 0 : 1);
        int valueB = ((Array.BinarySearch(visibleIndices, itemB.Index) >= 0) ? 0 : 1);

        // compare items according to their visibility
        int result = valueA.CompareTo(valueB);

        if (result != 0)
        {
            return result;
        }
        
        // compare items according to their indices
        valueA = itemA.Index;
        valueB = itemB.Index;
        
        return valueA.CompareTo(valueB);
    }
}

Visual Basic

Class CustomOrderItemComparer
    Implements IComparer(Of BetterThumbnailBrowserItem)
    
    Private ReadOnly thumbnailBrowser As BetterThumbnailBrowser

    Public Sub New(thumbnailBrowser As BetterThumbnailBrowser)
        Me.thumbnailBrowser = thumbnailBrowser
    End Sub

    Private Function IComparer_Compare(itemA As BetterThumbnailBrowserItem, itemB As BetterThumbnailBrowserItem) As Integer Implements IComparer(Of BetterThumbnailBrowserItem).Compare
        
            ' get put visible item indices in sorted array
            Dim visibleItems As ReadOnlyCollection(Of BetterListViewItem) = Me.thumbnailBrowser.VisibleItems
            Dim visibleIndices As Integer() = New Integer(visibleItems.Count - 1) {}
    
            For indexItem As Integer = 0 To visibleItems.Count - 1
                visibleIndices(indexItem) = visibleItems(indexItem).Index
            Next
    
            Array.Sort(visibleIndices)
    
            Dim valueA As Integer = (If((Array.BinarySearch(visibleIndices, itemA.Index) >= 0), 0, 1))
            Dim valueB As Integer = (If((Array.BinarySearch(visibleIndices, itemB.Index) >= 0), 0, 1))
    
            ' compare items according to their visibility
            Dim result As Integer = valueA.CompareTo(valueB)
    
            If result <> 0 Then
                Return result
            End If
    
            ' compare items according to their indices
            valueA = itemA.Index
            valueB = itemB.Index
    
            Return valueA.CompareTo(valueB)
    End Function

End Class

Automatic Restaring on Scroll, Resize and Thumbnail Zoom

Sometimes we need to restart loading because of scrolling or resizing the control, or when thumbnails are zoomed. In the above example, we have loaded visible items first. When user scrolls the control, this set of visible items changes and hence we would like to restart loading.

For example, we load thumbnails only at the necessary resolution. But when the user resizes the thumbnails, we need to reload them in higher resolution.

Another case is that we use custom item loading order such that visible items are loaded first. When user scrolls the control, the order is changed and we need to restart loading.

This functionality is provided by Better Thumbnail Browser. You only need to set the following boolean properties to true:

Safe Cross-threaded Operations

Better Thumbnail Browser uses two mechanisms for thread synchronization: Control.Invoke and Mutex.

The Control.Invoke is used in LoadingProvider.LoadItem method. Here a BetterThumbnailBrowser instance is used as an synchronization object, then LoadItemSync is called so that images and other data can be set to items on UI thread.

Mutex is used whenever Better Thumbnail Browser works with item data. When you work with items on both loader thread and UI thread, use BetterThumbnailBrowserItem.SyncRoot as the synchronization object. Here is a sample of safely setting Path property of BetterThumbnailBrowserItem:

C#

lock (item.SyncRoot)
{
    item.Path = path;
}

Visual Basic

SyncLock item.SyncRoot
    item.Path = path
End SyncLock

Multi-pass Loading

In case you need to load items in multiple sweeps, you can set multiple instances of LoadingProvider in the LoadingProviders property. The MultiPassLoadingSample uses several instances of a custom loading provider, each with different image quality setting. The result is that paimages are loaded in successively higher levels of detail:

You can in which pass any item currently resides by reading the BetterThumbnailBrowserItem.PassIndex property. When item loading is restarted, the items are loaded from their current pass. To load all items again from scratch, set the PassIndex property to 0 and then restart item loading.