So I'm trying to populate a large amount of icons to a listview. In order to avoid long wait times, I'm trying to get it to load the first 1000 results and then load more if the user presses a load more button.
Here's where I'm stuck. If I load all 10,000+ icons at once it takes me 37 sec. However, if I decide to add 500 more icons everytime the user clicks the button it takes me 40 sec which is worse than adding it all at once! The only difference in code is that I had to make this one line a delegate to avoid cross-threading issues. Is there a faster way to do this?
for (int i = lastLoadedIndex; i < lastLoadedIndex+500; i++)
{
string file = resultArr[i];
Invoke((MethodInvoker)delegate()
{
this.imageList1.Images.Add(Image.FromFile(file));
});
}
To speed up any bulk operation, consider using the bulk method if it is available.
For instance, the ImageCollection type has the AddRange method.
Try to use it:
int newCount = 500;
// Get a desired part of the `resultArr` array as a new array:
string[] tmp = new string[newCount];
Array.Copy(resultArr, lastLoadedIndex, tmp, 0, newCount);
// Load images:
Image[] images = Array.ConvertAll(tmp, file => Image.FromFile(file));
// Bulk add images to the ImageList:
Invoke((MethodInvoker)(() => imageList1.Images.AddRange(images)));
If won't help, please check which operation is slow: reading of images or appending to the ImageList.
EDIT #2: The expensive part of the function is loading the image from the file. Placing it before locking the mutex should allow for some parallelism worth the overhead of using the mutex. No, this method does not preserve order.
EDIT: Add images directly to image list rather than to a temporary collection.
public void LoadImagesFromFiles(string[] files)
{
Mutex imageListLock = new Mutex();
files.AsParallel().ForAll(file =>
{
var img = Image.FromFile(file);
imageListLock.WaitOne();
this.imageList1.Images.Add(img);
imageListLock.ReleaseMutex();
});
}
Related
So I want to be able to populate 1000s of thumbnails based on a list of image filepaths. I tried using the following code but I realized that after the 200+ image, my program would throw me a "A first chance exception of type 'System.OutOfMemoryException' occurred in System.Drawing.dll".
private void PopulateThumbnails(List<string> queryResults)
{
this.playerListView.Items.Clear();
this.imageList1.Images.Clear();
ImageViewer imageViewer = new ImageViewer();
foreach (string file in queryResults)
{
try
this.imageList1.Images.Add(Image.FromFile(file));
catch
Console.WriteLine("This is not an image file");
}
this.ListView.View = View.LargeIcon;
this.imageList1.ImageSize = new Size(256, 144);
this.ListView.LargeImageList = this.imageList1;
for (int j = 0; j < this.imageList1.Images.Count; j++)
{
ListViewItem item = new ListViewItem();
item.ImageIndex = j;
this.ListView.Items.Add(item);
}
}
I realize that I should probably only populate the thumbnails as needed but...
a) how do I know how many items are being loaded in the listview?
b) how do I detect scroll events in listview?
This is rather tricky.
You have two problems:
You need to know which Images you need to display
You need to make sure that you don't run out of memory or GDI resources
Unfortunately the ListView doesn't help with Events or Properties that would notify you about scrolling or tell you which items are visible.
Here is a little piece of code that will do at least the latter; it load a few thousand filenames into a list images and keep a list of 100 items, last visible:
List<string> images = new List<string>();
List<int> visibleItems = new List<int>();
void listView1_DrawItem(object sender, DrawListViewItemEventArgs e)
{
// this loads the images on demand:
if (!imageList1.Images.ContainsKey(images[e.ItemIndex]) )
{ loadImage(e.ItemIndex); e.Item.ImageIndex = imageList1.Images.Count - 1; }
// this makes sure the listView is drawn by the system
e.DrawDefault = true;
// these lines would maintain a list of items that are or were recently visible
if (!visibleItems.Contains(e.ItemIndex)) visibleItems.Add(e.ItemIndex);
if (visibleItems.Count > 1000)
{
visibleItems.Clear();
imageList1.Images.Clear();
listView1.Invalidate();
}
}
void loadImage(int index)
{
imageList1.Images.Add(images[index], new Bitmap(images[index]) );
}
You need to set the ListView.OwnerDraw = true;; then the DrawItem event will be called and after setting e.DrawDefault = true; the actual drawing will be done by the system after all.
But the event will keep telling you exactly which Items are visible.
Now you can make sure that the respective Bitmaps are loaded in your ImageList or those far away are disposed again.. so far I have only implemented a load on demand only..
Update: Like Plutonix, I have runa few test and can load 3000 Images with 128x128 pixels each with out any problems at all. I load them on demand and it seems ImageList takes care of things quite nicely: No need to dispose of the Images, GDI resources stay at a low level (<100) for my thest program. The memory foorprint keep rising while I scroll but doesn't look as if it'll be a problem. It it is I can simply do a
imageList1.Images.Clear();
..and memory drops back to the starting point. Display works fine, reloading of the list starts without a glitch.. In fact I don't use the visibleItems list any longer. I keep it in the answer, as it is a feasible way to know about these Items but atm I see no need for it..
I'm trying to use a Microsoft.Windows.APICodePack.Shell.ShellContainer as ItemsSource for a ListBox, showing each child's (ShellObject) Thumbnail and Name via ListBox.ItemTemplate.
The problem arises when ShellContainer refers to a VERY BIG folder (say more than one thousand files): if I simply declare
ShellContainer source=ShellObject.FromParsingName(#"C:\MyFolder") as ShellContainer:
listBox1.ItemsSource=source.GetEnumerator();
it freezes the UI for two or three minutes, then displays ShellContainer's content all at once.
The best workaround I've found is to create an async filler class like this
class AsyncSourceFiller
{
private ObservableCollection<ShellObject> source;
private ShellContainer path;
private Control parent;
private ShellObject item;
public AsyncSourceFiller(ObservableCollection<ShellObject> source, ShellContainer path, Control parent)
{
this.source = source;
this.path = path;
this.parent = parent;
}
public void Fill()
{
foreach (ShellObject obj in path)
{
item = obj;
parent.Dispatcher.Invoke(new Action(Add));
Thread.Sleep(4);
}
}
private void Add()
{
source.Add(item);
}
}
and then call it via
ObservableCollection<ShellObject> source = new ObservableCollection<ShellObject>();
listBox1.ItemsSource = source;
ShellContainer path = ShellObject.FromParsingName(#"C:\MyFolder"):
AsyncSourceFiller filler = new AsyncSourceFiller(source, path, this);
Thread th = new Thread(filler.Fill);
th.IsBackground = true;
th.Start();
This takes more time than the previous way, but doesn't freeze the UI and begins to show some content immediately.
Is there any better way to obtain a similar behavior, possibly shortening total operation time?
load all data in background thread and when finished update itemssource of your listbox. and do not forget to set Virtualization to true in your listbox.
The time consuming operation is to enumerate your ShellContainer and create thousands of ShellObject. ListBox is not the issue.
When you set an IEnumerable as source to an ItemControl, I think that it creates and internal list out of the enumerator the first time displayed, and that is why it freezes for two minutes before showing anything.
You do not have many options here:
Create yourself a List<ShellObject> and set it as source of our ListBox. It isn't faster but at least you can display a "Loading, please wait" message to your users.
Load the list in another thread (as you do) and display items as they load. It's a bit weird as the list "grows" over time.
Find a way to wrap your ShellContainer in a class that implements IList. For this, you need to be able to get an item at a given index in the ShellContainer class (I don't know "Windows API code pack"). If you use this as source of you ListBox and virtualization is enabled, only the displayed ShellObjects will be loaded, and it will be fast and smooth.
I have a databound object within a WPF control that is 'previewing' a mutlipage tiff.
The object has a public PreviewImage, and CurrentPreviewPage.
It has a private PreviewPages which is a collection of MemoryStreams (each representing a page of the Tiff).
Upon the get of the PreviewImage (the first time) this code runs:
if (PreviewPages.Count == 0)
{
Image myImg = System.Drawing.Image.FromFile(_LocalFile);
for (int i = 0; i < (NumberOfPages); i++)
{
myImg.SelectActiveFrame(System.Drawing.Imaging.FrameDimension.Page, i);
System.IO.MemoryStream ms = new System.IO.MemoryStream();
myImg.Save(ms, System.Drawing.Imaging.ImageFormat.Tiff);
PreviewPages.Add(ms);
}
}
The previous code takes about 10 seconds to run for a 1100KB 17 page TIFF. There must be a better way of handling this.
Afterwards, this is called:
BitmapImage bi = new BitmapImage();
bi.BeginInit();
bi.DecodePixelWidth = 1000;
//bi.UriSource = new Uri(fiTemp.FullName);
bi.StreamSource = new System.IO.MemoryStream(PreviewPages[CurrentPreviewPage - 1].ToArray());
bi.EndInit();
_PreviewImage = bi;
Now, after the initialization this code works fantastically (it can change pages as fast as you can drag a bound slider). Any help would be much appreciated.
Load your first page on the primary thread and then additional pages in the background - see BackGroundWorker. Only primary thread can access the UI. On the BackGroundWorker you need to decide if you are going to get the pages 2-x one at a time or all at once. The user cannot get to page 2 until you bring page 2 to the UI thread. I would implement cancel. If the user gives up you don't want to tie up CPU.
In my listview I show thumbnails of small images in a certain folder. I setup the listview as follows:
var imageList = new ImageList();
foreach (var fileInfo in dir.GetFiles())
{
try
{
var image = Image.FromFile(fileInfo.FullName);
imageList.Images.Add(image);
}
catch
{
Console.WriteLine("error");
}
}
listView.View = View.LargeIcon;
imageList.ImageSize = new Size(64, 64);
listView.LargeImageList = imageList;
for (int j = 0; j < imageList.Images.Count; j++)
{
var item = new ListViewItem {ImageIndex = j, Text = "blabla"};
listView.Items.Add(item);
}
The user can rightclick on an image in the listview to remove it. I remove it from the listview and then I want to delete this image from the folder. Now I get the error that the file is in use. Of course this is logical since the imagelist is using the file.
I tried to first remove the image from the imagelist, but I keep on having the file lock.
Can somebody tell me what I am doing wrong?
Thanks!
You need to load the file into a MemoryStream, like this:
var image = Image.FromStream(new MemoryStream(File.ReadAllBytes(fileInfo.FullName)));
This way, the file will only be read once, and will not remain locked.
EDIT
You're loading the images into an ImageList.
Since the ImageList makes copies of its images, you should simply dispose the originals right away, like this:
using (var image = Image.FromFile(fileInfo.FullName))
imageList.Images.Add(image);
The image will need to be disposed of before it will unlock the file. Try calling Dispose on the image object after you remove it from the image list.
So long as you have a reference to the image object and the GC hasn't collected it, it will keep the lock. Calling Dispose should force it to give up its handle and cause the file to be unlocked.
You also have to make sure that the app didn't CopyHandle or otherwise get a second reference to the image resource before doing this.
Use GetThumbnailImage and then dispose image:
var image = Image.FromFile(fileN);
Image imgThumb = image.GetThumbnailImage(100, 100, null, new IntPtr());
imageList1.Images.Add(imgThumb);
image.Dispose();
listView1.LargeImageList = imageList1;
Now you can delete the file:
File.Delete(FileN);
I want to select the pictures (They are select during program is running) and show them on the form. for that i take a panel on the form and populate the panel with pictureboxes.i write the following code for that but it is very time consuming:
if(openDialoge1.ShowDialog() == DialogResult.OK)
{
string[] fileName = open.FileNames;
foreach (string s in fileName)
{
pBox = new PictureBox();
pBox.Size = new System.Drawing.Size(w, h);
pBox.Location = new System.Drawing.Point(x, y);
pBox.Image = Image.FromFile(s);
pBox.SizeMode = PictureBoxSizeMode.StretchImage;
.
.//here i add some eventHandler of picture boxes.
this.panel1.Controls.Add(pBox);
x += pBox.Width + 4;
}
} //here w,h,x,y are integers.
This code work well, but it is very time consuming and take much time to populate the panel with picture boxes. for example when i slect the 20,30 pictures, it take much time.
Is there any way that reduce the time to populate the panel with pictureboxes.
Thanks in advance.
You might consider profiling your method. If you don't have a profiler like ANTS available, you can roll your own:
Stopwatch watch = new Stopwatch();
watch.Start();
//code to profile goes here
watch.Stop();
Console.Writeln("Elapsed time: " + watch.Elapsed.TotalMilliseconds + "ms");
This will help you pinpoint which part of your code is slow.
I can tell you right now that Image.FromFile() is probably the slowest part. What you might consider is loading the images into a List<> first, using a separate thread or background worker. This will let you show a progress bar or hourglass, to let the user know that images are being read from disk.
Once the images are in memory, creating the picture boxes will go a lot faster.
Edit:
You've requested an example showing how to load images into memory first. It's really simple:
// this code should run in its own thread - BackgroundWorker is perfect for this
List<Image> images = new List<Image>();
foreach (string imagePath in paths)
{
images.Add(Image.FromFile(imagePath));
// update progress bar here?
}
Now, you have a list of your images which you can use to fill your picture boxes.