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.
Related
I'm currently working on a new GUI that has a listbox as a crucial element in it. So far I've managed to display and select multiple items in this listbox, which wasn't that big of a deal.
My goal is to have several .cs files(maybe in further expansion als vb script files) in a folder within the project, which are properly displayed in the main view listbox and will be executed if the corresponding item in the listbox is selected.
So far I've tried to build the listbox and all the other GUI stuff (buttons, text,...) and connected the listbox with a bindable collection of a script model(which is a own class for testing purposes at the moment and should be replaced with the correct .cs files)
In the code below you can see the work around with this custom class and the selection check for multiple listbox items.
private void Run_Click(object sender, RoutedEventArgs e)
{
//Show user the scripts which are being processed, these are the previous selected Scripts
List<string> selectedList = new List<string>();
foreach (ScriptModel selectedScript in MainListBox.SelectedItems)
{
selectedList.Add(selectedScript.Name.ToString());
}
//check if no Script was selected, and if so, just return/do nothing
if (selectedList.Count() == 0) { return; }
MessageBox.Show("The following Scripts will be processed: " + Environment.NewLine +
string.Join(Environment.NewLine, selectedList));
//Call the Connection for Data-Export
}
private BindableCollection<ScriptModel> _scriptscollection=new BindableCollection<ScriptModel>();
public BindableCollection<ScriptModel> ScriptsCollection
{
get { return _scriptscollection; }
set { _scriptscollection = value; }
}
I would like to know, how I can replace(or connect) these custom class with actual .cs files (which are some kind of scripts) in a project folder, so that I can display these file names and select the corresponding files for execution. (so the connection should work both ways)
I'm sorry if this question seems a bit weird and general, but I'm really confused about that.
I believe you have over complicated the matter. Here is the code that will find all of the .cs files in a directory and then upon selecting one in the ListBox will start that file.
It's hard to tell exactly what you're asking for but hopefully this helps.
XAML
<ListBox ItemsSource="{Binding ScriptFiles}" SelectedItem="{Binding SelectedScript}"/>
Code behind / ViewModel
public List<string> ScriptFiles => Directory.GetFiles(FilePath, "*.cs").ToList();
private string selectedScript;
public string SelectedScript
{
get { return selectedScript; }
set { selectedScript = value; Process.Start(value); }
}
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 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 have a c# windows form application.I have 2 charts in my windows form. i also have a combobox and two buttons among others. What i want is according to the text of the combobox, when i press the start button to load different graphs. So at button start event according to value of combobox i call a different function that loads the charts with what i want each time. And the second button , the stop button has the code below in order to clear the charts.
chart1.Series.Clear();
chart2.Series.Clear();
Sometimes my code runs ok but there are times that it throws the error
" A chart element with the name 'kwh_price' already exists in the 'SeriesCollection'." My code for load the chart is:
string[] seriesArray = { "kwh_price", "p_cost" };
for (int i = 0; i < seriesArray.Length; i++)
{
this.chart1.Series.Add(seriesArray[i]);
this.chart1.Series[seriesArray[i]].BorderWidth = 7;
}
Am i doing something wrong??is there something more needed in order to clear the chart?? And i don't understand why sometimes it runs ok and others not.
Put the clear code in before the load code. That way you can be sure the data is cleared before adding new data.
chart1.Series.Clear();
chart2.Series.Clear();
string[] seriesArray = { "kwh_price", "p_cost" };
for (int i = 0; i < seriesArray.Length; i++)
{
this.chart1.Series.Add(seriesArray[i]);
this.chart1.Series[seriesArray[i]].BorderWidth = 7;
}
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.