virtual listview with thumbnails

D

Def Daemon

Hi There,

I'm trying to create a program which uses a listview. This listview should
contain about 10.000 records of image information. The listview should be
able to display thumbnails from these images. Since it doesn't make sence to
create an imagelist which holds 10.000 thumbnails, I'm forced to use the
virtual mode.

In my approach I'm setting up an imagelist, which holds the thumbnails, in
the CacheVirtualItems event, but since the RetrieveVirtualItem event is
called multiple times at startup, this approach doesn't work, since there is
no filled imagelist at startup.

I also tried to setup the imagelist when the first 50 records are added to
the listview (and recreate the imagelist when setting up the cache, but only
the last added thumbnail will be display (for all entries).

Can anyone point me in the right direction?

thanx
 
D

Def Daemon

Hi Pete

Here is the code I'm working on.

This event clears my itemlist (myList) and add's new items as specified in the openfiledialog. The ImageKey is simply generate by counting each file.

private void button6_Click(object sender, EventArgs e)
{
int iTeller = 0;
openFileDialog1.Multiselect = true;
if (openFileDialog1.ShowDialog() == DialogResult.OK)
{
mylist.Items.Clear();
ilThumbnails.Images.Clear();
listView1.Visible = false;
foreach (string myFile in openFileDialog1.FileNames)
{
ListViewItem myItem = new ListViewItem(myFile);
myItem.ImageKey = "IMG" + iTeller.ToString("00000");
mylist.Items.Add(myItem);
iTeller++;
}
listView1.VirtualListSize = mylist.Items.Count;
listView1.Visible = true;
}
}

Here is my RetrieveVirtualItem event. The thumbnails are created on fly for now. When retrieving a new item, this routine checks wether the imagekey of myList.Item occurs in my thumbnail imagelist. If not, it will add the newly created thumbnail to the thumbnail imagelist, using 'the imagekey of the item in myList. This works fine, because all keys are found after adding them to the imagelist.
private void listView1_RetrieveVirtualItem(object sender, RetrieveVirtualItemEventArgs e)
{
if (myCache != null && e.ItemIndex >= firstItem && e.ItemIndex < firstItem + myCache.Length)
{
e.Item = myCache[e.ItemIndex - firstItem];
}
else
{
int offset = e.ItemIndex - firstItem;
e.Item = mylist.Items[offset];
// For debugging purposes
int iThumbnailIndex = ilThumbnails.Images.IndexOfKey(mylist.Items[offset].ImageKey);
Console.WriteLine("Thumbnail index {0} for key {1}", iThumbnailIndex.ToString(), mylist.Items[offset].ImageKey);
if (!ilThumbnails.Images.ContainsKey(mylist.Items[offset].ImageKey))
{
Image myImg = new Bitmap(mylist.Items[offset].Text);
ilThumbnails.Images.Add(mylist.Items[offset].ImageKey, CreateThumbnail(myImg));
}
}
}

After selecting 5 imagefiles, no thumbnails are displayed, although 5 images are available in the thumbnail image list.

Can you help me out?


----- Original Message -----
From: "Peter Duniho" <[email protected]>
Newsgroups: microsoft.public.dotnet.languages.csharp
Sent: Sunday, April 05, 2009 9:14 AM
Subject: Re: virtual listview with thumbnails

[...]
In my approach I'm setting up an imagelist, which holds the thumbnails,
in
the CacheVirtualItems event, but since the RetrieveVirtualItem event is
called multiple times at startup, this approach doesn't work, since
there is
no filled imagelist at startup.

I also tried to setup the imagelist when the first 50 records are added
to
the listview (and recreate the imagelist when setting up the cache, but
only
the last added thumbnail will be display (for all entries).

Can anyone point me in the right direction?

The CacheVirtualItems event is basically a hint to your code. The thing
you need to ensure is that when you return an item from your
RetrieveVirtualItem event handler, it has a valid thumbnail index. Get
that to work, and everything else will fall into place.

Without a concise-but-complete code example that reliably demonstrates the
problem, it's not possible to comment on why your attempts to do this
don't work. But, it sounds as though you're not properly connecting the
logic that retrieves an item (your RetrieveVirtualItem event handler) with
the logic that maintains the ImageList.

In your latter attempt, you're either not setting the image index for each
list item correctly, or you've filled the ImageList with a single
thumbnail image. Without seeing the code, there's no way to know which it
is. Heck, you could even have made both mistakes, for all we know.

Note: because the ImageList contents may be constantly varying as your
virtual view of the list changes, you may find it more useful to assign
images by key, rather than by index. Come up with a string that uniquely
identifies each unique list item, and use that as the key in the ImageList
and for the ListViewItem.ImageKey property. That way, the ImageList
itself can have items added and removed at will, without having to go back
and fix up all the ImageIndex properties for your displayed ListViewItems.

Pete


Peter Duniho said:
[...]
In my approach I'm setting up an imagelist, which holds the thumbnails,
in
the CacheVirtualItems event, but since the RetrieveVirtualItem event is
called multiple times at startup, this approach doesn't work, since
there is
no filled imagelist at startup.

I also tried to setup the imagelist when the first 50 records are added
to
the listview (and recreate the imagelist when setting up the cache, but
only
the last added thumbnail will be display (for all entries).

Can anyone point me in the right direction?

The CacheVirtualItems event is basically a hint to your code. The thing
you need to ensure is that when you return an item from your
RetrieveVirtualItem event handler, it has a valid thumbnail index. Get
that to work, and everything else will fall into place.

Without a concise-but-complete code example that reliably demonstrates the
problem, it's not possible to comment on why your attempts to do this
don't work. But, it sounds as though you're not properly connecting the
logic that retrieves an item (your RetrieveVirtualItem event handler) with
the logic that maintains the ImageList.

In your latter attempt, you're either not setting the image index for each
list item correctly, or you've filled the ImageList with a single
thumbnail image. Without seeing the code, there's no way to know which it
is. Heck, you could even have made both mistakes, for all we know.

Note: because the ImageList contents may be constantly varying as your
virtual view of the list changes, you may find it more useful to assign
images by key, rather than by index. Come up with a string that uniquely
identifies each unique list item, and use that as the key in the ImageList
and for the ListViewItem.ImageKey property. That way, the ImageList
itself can have items added and removed at will, without having to go back
and fix up all the ImageIndex properties for your displayed ListViewItems.

Pete
 
D

Def Daemon

Hi Pete,

Your code works great. I've made some modications using the
CacheVirtualItems event so the number of images in the imagelist stays
low.Of course this has it's drawbacks (eg. speed), but I will try to
implement a background thread.

Anyway

thank you very much for your solution. It's great


Peter Duniho said:
[...]
After selecting 5 imagefiles, no thumbnails are displayed, although 5
images are available in the thumbnail image list.

Can you help me out?

As I alluded to in my previous reply, without a proper code example from
you, it's impossible to say exactly what things are wrong with your code.
There are things even about the code you did post that don't look quite
right to me, but out of context I can't say for sure anything is wrong.

That said, I decided to play a little bit with the virtual mode of the
ListView, and discovered a couple of important facts, one very pertinent
to your concern:

-- In virtual mode, assigning an ImageList item by key, rather than by
index, DOES NOT WORK!
-- The ImageList.ImageCollection class has a very poor key-mapping
implementation; it's a linear search!

Frankly, having looked at it a bit closer, I find the virtual mode of the
ListView class to be almost (but not completely) worthless. It has one
redeeming value: it allows for immediate display of the control,
populating the data structure only as the user scrolls through it.

But, the lack of a proper key-value access to the ImageList, both in terms
of it working at all, and in terms of performance, is a serious problem.
In addition, the class provides no way for you to know when you can
discard Image instances that are in the ImageList, which means that even
in virtual mode, you will eventually have to populate the ImageList with a
thumbnail for every single item in the list.

So over the long term -- i.e. the duration of the display using the
ListView -- virtual mode doesn't reduce your memory costs, and in fact if
the user eventually looks at every element, the memory cost increases
slightly due to the overhead of maintaining the virtual data structures.

I will grant that it does have that one redeeming value, and I admit
that's not an insignificant value. But I wish the class were more useful
than that. To me, "virtual" implies the ability to discard aged data, but
the most expensive data that will be associated with a ListViewItem -- its
thumbnail image -- can never be discarded once created.

Anyway, I've attached below the Form1.cs file for my little test
application. It doesn't handle caching, just the basic virtual mode
stuff. It bypasses the ImageList key-value mapping, using instead its own
Dictionary instance to maintain the mapping. I tested it with a
VirtualListSize of 10000, and performance is pretty good (and MUCH better
than when I used the key-value mapping of the ImageList...that is, using
the ContainsKey() and IndexOfKey() methods). Of course, the bitmaps I'm
using a very small.

This partial class works with the default Form1 template from VS2008, with
a single ListView instance named "listView1" added to it, and the
default-named handler for the ListView's RetrieveVirtualItem event (i.e.
the obvious configuration).

Hopefully with the code sample, you can easily see the approach needed to
work around the limitations in the ListView class.

Pete


using System;
using System.Collections.Generic;
using System.Drawing;
using System.Windows.Forms;
using System.Drawing.Imaging;

namespace TestVirtualListView
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();

listView1.LargeImageList = new ImageList();
listView1.LargeImageList.ColorDepth = ColorDepth.Depth24Bit;
listView1.LargeImageList.ImageSize =
TextRenderer.MeasureText("10000", Font);

listView1.SmallImageList = listView1.LargeImageList;
}

private void listView1_RetrieveVirtualItem(object sender,
RetrieveVirtualItemEventArgs e)
{
e.Item = _LviItemForIndex(e.ItemIndex);
}

private Dictionary<string, int> _mpstriimage = new
Dictionary<string, int>();

private ListViewItem _LviItemForIndex(int i)
{
int iimage;
string strName = i.ToString();
ImageList.ImageCollection imgc =
listView1.LargeImageList.Images;

// Don't call the ImageList key-lookup methods, because they
are
// slow! Just maintain the key-to-image mapping ourselves.
if (!_mpstriimage.TryGetValue(strName, out iimage))
{
iimage = imgc.Count;

imgc.Add(_ImageForIndex(i));

_mpstriimage.Add(strName, iimage);
}

return new ListViewItem(strName, iimage);
}

private Image _ImageForIndex(int i)
{
Image imageRet =
new Bitmap(listView1.LargeImageList.ImageSize.Width,
listView1.LargeImageList.ImageSize.Height,
PixelFormat.Format24bppRgb);

using (Graphics gfx = Graphics.FromImage(imageRet))
{
gfx.Clear(this.ForeColor);

TextRenderer.DrawText(gfx, i.ToString(), Font,
new Rectangle(new Point(), imageRet.Size),
this.BackColor,
TextFormatFlags.HorizontalCenter);
}

return imageRet;
}
}
}
 
Joined
Mar 17, 2010
Messages
1
Reaction score
0
I know it's been a while since anyone posted to this thread, but I have a question about the approach outlined herein.

Suppose that during the course of my application's execution, the source of images could change multiple times. In between each source change, would it be necessary to call dispose() on the thumbnails accumulated from the previous source, or is this done automagically somehow?

Thanks,
Robert
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top