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..
Related
So I'm trying to make a matching game in WinForms with C#. I looked at this MSDN project: https://msdn.microsoft.com/en-us/library/dd553230.aspx. I replaced the icon list with an imagelist. According to this article my images should show up twice per image. But whenever it hits the same number again it says that it's out of bounds, this is what I think is happening. Here is my code:
public frmMain()
{
InitializeComponent();
imageInit();
imgToLbl();
}
Random rndImage = new Random();
ImageList images = new ImageList();
//list of file names
List<string> files = new List<string>()
{
"Bavaria", "Denemarken", "Engeland",
"Frankrijk", "Nederland", "oostenrijk",
"Polen", "Pruissen", "Rusland", "Schotland",
"Spanje", "Zweden"
};
// Method to put the files into the imagelist
private void imageInit()
{
for(int i = 0; i < 12; i++)
{
images.Images.Add(files[i], Image.FromFile("../../images/" + files[i] + ".png"));
}
}
// method to assign the images to a label in my form
private void imgToLbl()
{
foreach (Control ctrl in tableLayoutPanel1.Controls)
{
Label imgLbl = ctrl as Label;
if (imgLbl != null)
{
int rndNum = rndImage.Next(images.Images.Count);
images.ImageSize = imgLbl.Size;
imgLbl.ImageList = images;
imgLbl.ImageIndex = rndNum;
imgLbl.ImageList.Images.RemoveAt(rndNum);// this is where the exception is being thrown
}
}
}
Here is the full exception:
An unhandled exception of type 'System.ArgumentOutOfRangeException' occurred in System.Windows.Forms.dll
Additional information: InvalidArgument=Value of '8' is not valid for 'index'.
I have a feeling that this should be easy but I can't figure out how to fix this exception. There's also another thing. The colors are all messed up and I don't know why.
Here is the original:
Here is the messed up one which is being shown in the app:
Can someone please help me?
It looks like you're trying to remove the images from the Labels ImageList that you've just assigned to it. You will eventually end up with 0 entries in the ImageList and Labels still referencing indexes. your assignment of imgLbl.ImageList is a reference to the already existing ImageList object, it is not cloning it and as such when you remove an entry from its list it is removing it for every label already assigned the instance. You want to clone the list (or better yet) maintain 1 full list and ensure you dont pick the same number twice.
You are destroying the images in the ImageList but you still want the Lables, ListItems etc.. to refer to them. This can't work
You need to keep them around as long as any control or other item needs to display them.
You see that you are assigning just a number as the ImageIndex. This number points into the ImageList and therefore the image still needs to be there..
Also: The quality is limited by your choice of images.ColorDepth. Set it to Depth32Bit! The default is only Depth8Bit
For some reason my some of my code seems to get skipped without throwing an error.
Im writing a program for school that uses a combobox with every word in the bible that searches a bible for the word thats clicked on and outputs every verse that contains that word to a listbox.
The code that gets skipped is the forloop and output. (apperantly)
Further info:
Wordsinthebible.length = about 33000
Verses in the Bible.length = about 24000
Here is the relevant code:
private void Window_Loaded(object sender, RoutedEventArgs e)
{
//Read bible
bible = File.ReadAllText("../../LUTHER.TXT", Encoding.UTF7);
VersesInTheBible = File.ReadAllLines("../../LUTHER.TXT", Encoding.UTF7);
//"Suchwort" is just a class that saves a string(the word) and a string array(the verses)
WordsToSearch = new List<Suchwort>();
//Split bible in to words
WordsInTheBible = new List<string>(GetWords(bible).Distinct());
WordsInTheBible.Sort();
//Fill list with word and verses
for (int i = 0; i < WordsInTheBible.Count; i++)
{
WordsToSearch.Add(new Suchwort(WordsInTheBible[i], GetVerses(WordsInTheBible[i])));
}
//Give combobox Words to search by
cbx_wörter.ItemsSource = WordsToSearch;
}
public string[] GetVerses(string wort)
{
List<string> Verse = new List<string>();
for (int i = 0; i < VersesInTheBible.Length; i++)
{
if (VersesInTheBible[i].Contains(wort))
{
Verse.Add(VersesInTheBible[i]);
}
}
return Verse.ToArray();
}
Now what happens when i run the program is it doesn't crash or throw any exception but all i get is an empty combobox:
Am I looping for too long through all the words and verses or should that be OK?
but all i get is an empty combobox:
That is because the program is loading the combobox with 24,000 items on the GUI Thread and due to the huge performance hit the GUI is taking, one never seea any items.
The standard recommendation is to only do labor intensive work in a background thread/process and when done provide the item to a ViewModel property which via InotifyPropertyChange that data is ready to be shown. While the process is running alert the user to the long running process by using a WPF BusyIndicator.
Frankly no one will ever use a 24000 word drop down. It is just not feasible. I recommend you put in an edit box with a button to initiate a search. When the user pushes the button provide a managable list of items to select based on the TextBox data.
If MVVM is new check out my blog article Xaml: ViewModel Main Page Instantiation and Loading Strategy for Easier Binding to get you started.
I made a program which adds checkedListBox.Items from a text written in a TextBox . Regarding this, to make everything more esthetic , I made a rule so that if the number of Items added in CheckedListBox1 is bigger than a number I set, it will go to a second CheckedListBox and so on.
I can also save my Entries in a .txt file so I have easy access to my previous references. So naturally I also made a Load References which ,obviously, load the file I saved.
Anyhow, my dillemma is the following : When I press the Load References button it loads ALL the references (Lines) in the text into the first checkedListBox. I want it to respect the previous law. If I click Load References I want that if there are more than, lets say, 10 entries, all the other ones will go into the other checkedListBox ,by consequence, if the limit number is passed from the second checkedListBox the rest will go into the third one and so on.
I have searched StackOverflow and the Web for several solutions ,some of the more relevant ones :
First found link semi-regarding the subject
Second found link
So to not get it wrong I will state that I want to have all the entries that pass the limit be MOVED to another checkedlistBox ,not copied like the links would suggest.
This is the Line of code for my Load Reference button :
private void button8_Click(object sender, EventArgs e)
{
string fileData = File.ReadAllText(#" To-Do References .txt");
checkedListBox1.Items.AddRange(fileData.Split(new string[] { "\r\n" }, StringSplitOptions.RemoveEmptyEntries));
}
Also I tried several methods but this one seemed to be the closest ,even though I got almost no satisfactory result :
var i = checkedListBox1.Items.Count;
if (i >= 10)
checkedListBox2.Items.Insert(0, checkedListBox1.Items);
Regarding this line of code : It does get an entry send into the second checkedList Box it is just that the entry is called (Collection) and has nothing to do with my references.
I hope I made myself clear and thank you for support!
UPDATE
The marked answer works perfectly for this kind of program. As I have not found anything similar I believe this is most likely the best way to implement the separation of text lines into different checkedListBoxes.
if you populate listboxes properly there will be no need to move items
private void button8_Click(object sender, EventArgs e)
{
int limit = 10;
string[] fileData = File.ReadAllText(#" To-Do References .txt").Split(new string[] { "\r\n" }, StringSplitOptions.RemoveEmptyEntries);
// this loop adds items to the 1st list until limit is reached
for(int i =0; i<limit && i<fileData.Length; i++)
checkedListBox1.Items.Add(fileData[i]);
// if there extra items, 2nd loop adds them to list №2
for(int i =limit; i<fileData.Length; i++)
checkedListBox2.Items.Add(fileData[i]);
}
Set a limit, and maybe a multiplier to control the checkedList the data will be added to.
int limit = 10;
int checkList = 1;
string[] fileData = File.ReadAllText(#" To-Do References .txt").Split(new string[] { "\r\n" }, StringSplitOptions.RemoveEmptyEntries);
for (int i = 0; i < fileData.Length; i++)
{
if (i == limit * checkList)
{
checkList++;
}
switch (checkList)
{
case 1: checkedListBox1.Items.Add(fileData[i]); break;
case 2: checkedListBox2.Items.Add(fileData[i]); break;
case 3: checkedListBox3.Items.Add(fileData[i]); break;
}
}
As big as your text file gets, adding data to a checkedListBox just requires you to add a new line to the switch statement.
Well, my head is stuck in wpf land, so I would just bind it to a list of lists, in an itemscontrol, or something similar. Reading back, of course, it appears you are using winforms, so this may not be applicable...but i'll post anyways, because it can still be done this way using the WinForms DataRepeater control.
List<List<string>> mainList = new List<List<string>>();
int listIndex = 0;
string[] fileData = File.ReadAllText(#" To-Do References.txt").Split(new string[] { "\r\n" }, StringSplitOptions.RemoveEmptyEntries);
for(int i = 0; i<=fileData.Length; i++)
{
mainList[listIndex].Add(fileData[i]);
if (i%10 == 0)
{
listIndex++;
}
}
Then bind the mainList to the control and configure your ItemTemplate.
There was lots of info on binding to the DataRepeater, but here's one link:
https://msdn.microsoft.com/en-us/library/cc488279.aspx
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();
});
}
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.