I have a problem in manipulating comboBoxes. There are three comboboxes. If I change the selected index on the 1st combobox then values in the 2nd and 3rd should be updated. An
IndexOutOfRange exception occurs. I know, in start, I have 3 data items... when I change the index of the 1st then the 2nd must have 8 to 9 values. Here occur exception
2nd combobox have 3 values.
now if 1st is changed then here occur exception
private void comboBox3_SelectedIndexChanged(object sender, EventArgs e)
{
if (comboBox3.SelectedIndex == 1)
{
comboBox1.Items[0]="Kilometer";
comboBox1.Items[1]="Meter";
comboBox1.Items[2]="Centimeter";
comboBox1.Items[3]="Millimeter";
comboBox1.Items[4]="Mile";
comboBox1.Items[5]="Yard";
comboBox1.Items[6]="Foot";
comboBox1.Items[7]="Inch";
comboBox1.Items[8] = "Nautical Mile";
comboBox2.Items[0] = "Meter";
comboBox2.Items[1] = "Centimeter";
comboBox2.Items[2] = "Millimeter";
comboBox2.Items[3] = "Mile";
comboBox2.Items[4] = "Yard";
comboBox2.Items[5] = "Foot";
comboBox2.Items[6] = "Inch";
comboBox2.Items[7] = "Nautical Mile";
comboBox2.Items[8] = "Kilometer";
}
else if (comboBox3.SelectedIndex == 2)
{
comboBox1.Items[0] = "Metric ton";
comboBox1.Items[1] = "Kilogram";
comboBox1.Items[2] = "Gram";
comboBox1.Items[3] = "Milligram";
comboBox1.Items[4] = "Mcg";
comboBox1.Items[5] = "Long ton";
comboBox1.Items[6] = "Short ton";
comboBox1.Items[7] = "Stone";
comboBox1.Items[8] = "Pound";
comboBox1.Items[9] = "Ounce";
}
}
When you can it's often better practice to avoid accessing and changing objects by their index in a collection, e.g. when you can use foreach, use that rather than a for with the index.
For instance, in this case you can create a List from an array (defined in the code of the objects), and set the .Items collection to this. This avoids the usage of any numbers. You can also store references to the comboBox1 items and use .SelectedItem rather than .SelectedIndex, for instance if there was ever any chance that more items would be added to that combo box.
private void comboBox3_SelectedIndexChanged(object sender, EventArgs e)
{
if (comboBox3.SelectedIndex == 1)
{
comboBox1.Items.Clear();
comboBox1.Items.Add("Kilometer");
// and so on
comboBox2.Items.Clear();
comboBox2.Items.Add("Meter");
// and so on
}
else if(comboBox3.SelectedIndex == 2)
{
comboBox1.Items.Clear();
comboBox1.Items.Add("Metric ton");
// and so on
}
}
Alternatively
private String[] comboBoxOneItemsArraySetOne = { "SetOneItem1", "SetOneItems2" };
private String[] comboBoxOneItemsArraySetTwo = { "SetTwoItem1", "SetTwoItems2" };
private String[] comboBoxTwoItemsArray = { "Item1", "Items2" };
private void comboBox3_SelectedIndexChanged(object sender, EventArgs e)
{
if (comboBox3.SelectedIndex == 1)
{
comboBox1.Items.Clear();
foreach(String s in comboBoxOneItemsArraySetOne)
{
comboBox1.Items.Add(s);
}
comboBox2.Items.Clear();
foreach(String s in comboBoxTwoItemsArray)
{
comboBox2.Items.Add(s);
}
}
else if(comboBox3.SelectedIndex == 2)
{
comboBox1.Items.Clear();
foreach(String s in comboBoxOneItemsArraySetTwo)
{
comboBox1.Items.Add(s);
}
}
}
I assume you mean ArgumentOutOfRangeException. It is likely because you are directly assigning to non-existent index locations. You need to use Add() or AddRange() to add the items.
comboBox1.Items.Add("Metric ton");
//...
comboBox1.Items.Add("Ounce");
Winforms ComboBox.Items is of type ObjectCollection. Use of index[0] notation is valid for values that already exist, but not for adding.
The same exception can also be caused by setting ComboBox.SelectedIndex to a value that is out of range. Remember the collection index is zero-based, which means if you have 3 items, they use indexes [0..2]
You can also clear the items with Items.Clear() or remove specific Items with Items.Remove("something") or Items.RemoveAt(i)
http://msdn.microsoft.com/en-us/library/system.windows.forms.combobox.objectcollection.add(v=vs.110).aspx
Related
I am currently developing a WPF app in which there are multiple forms to manage a database. One of these forms is a "Add form". This form contains a combobox with some values. Though if the user's wanted value is not in the combobox list, it can be added by selecting the last option which will display a new form for entering the wanted value. If the form is submitted, the last option in the combobox will be changed to this value. The code below describes how this works:
private void BrandBoxSelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (BrandBox.SelectedItem != null &&
BrandBox.SelectedIndex < BrandBox.Items.Count - 1)
{
InitSubCategoryBox(subCatArray[BrandBox.SelectedIndex]);
}
else if (BrandBox.SelectedItem != null &&
BrandBox.SelectedIndex >= BrandBox.Items.Count - 1)
{
OtherInputWindow newInputForm = new OtherInputWindow("brand", BrandBox.SelectedItem.ToString());
if (newInputForm.ShowDialog() == true)
{
BrandBox.Items[BrandBox.Items.Count - 1] = newInputForm.returnValue;
BrandBox.SelectedIndex = BrandBox.Items.Count - 1;
}
}
}
The last code line will set the submitted value as selected item. I know this does not work. This last line of code will create a SelectionChanged event and that is exactly what this method is. The result is that it will keep looping and there is no way out. I'm searching for the right way to do this for a long time but I wasn't able yet to find the answer. Hopefully I will find the answer here. Is there someone who can help me solving this problem?
I would suggest to not change the SelectedItem from the SelecetionChanged event handler. Instead handle the UIElement.PreviewMouseLeftButtonUp event from the ComboBox:
<ComboBox PreviewMouseLeftButtonUp="OnComboBoxItemPreviewMouseLeftButtonUp" />
private void OnComboBoxItemPreviewMouseLeftButtonUp(object sender, RoutedEventArgs e)
{
var selector = sender as Selector;
var clickedItemContainer = Selector.ContainerFromElement(selector, e.OriginalSource as DependencyObject);
bool isClickTargetItemContainer = clickedItemContainer is not null;
if (!isClickTargetItemContainer)
{
return;
}
int selectedIndex = selector.ItemContainerGenerator.IndexFromContainer(clickedItemContainer);
int lastIndex = selector.Items.Count - 1;
if (selector.SelectedIndex == lastIndex)
{
var newInputForm = new OtherInputWindow("brand", selectedItem.ToString());
if (newInputForm.ShowDialog() == true)
{
selector.Items[lastIndex] = newInputForm.returnValue;
selector.SelectedIndex = lastIndex;
}
}
else
{
InitSubCategoryBox(subCatArray[selector.SelectedIndex]);
}
}
I'm working with a ListView in Virtual Mode (.NET 4.6).
I tried to find the index of Items in the virtual ListView: when I enter a letter, the first item with text that start with that letter should be selected.
Here is the FindItemWithText in listView1_KeyDown:
if (char.IsLetterOrDigit(e.KeyChar))
{
var str = e.KeyChar.ToString();
if (tempStr != str)
{
Index = 0;
tempStr = str;
}
var item = listView1.FindItemWithText(str, false, Index, true);
if (item != null)
{
item.Selected = true;
item.Focused = true;
item.EnsureVisible();
Index = item.Index + 1;
}
}
Here is my SearchForVirtualItem method:
var item = lvis.OfType<ListViewItem>().FirstOrDefault(
i => i.Text.ToLower().StartsWith(e.Text.ToLower()) &&
i.Index >= e.StartIndex);
if (item == null)
{
}
else
{
e.Index = item.Index;
}
If the result is one of the visible items before I scroll at all the code works and I can select the result item. But if the result is not visible and I didn't scroll anything at all the method return null.
But if I scroll to the end of the list even once I can get the index of the item that before I couldn't.
Example: If I have 200 items in a virtual list (populated from a list
of 200 ListViewItem) and only the first 50 are visible, if I press the c
letter and items that start with c letter are among the first 50, they will
be selected.
But if I press x and the items in the virtual ListView are
at the last 50, the method will return null. If I instead scroll the list to
the end and then I press x, the items
that start with x will be selected.
Why I have to show the item at least once to have an index and not having index = -1?
Is this the normal behavior of a virtual ListView or is there something wrong?
Side question, when does a ListView in normal mode become slow? After 100,000 items, or 1,000,000 items?
Edit1:
Here is my listView1_RetrieveVirtualItem code:
private void listView1_RetrieveVirtualItem(object sender, RetrieveVirtualItemEventArgs e)
{
if (lvis.Count > 0)
{
e.Item = lvis[e.ItemIndex];
}
}
I don't use a cache.
I use BackGroundWorker to get the data from a SQLite database; I create ListViewitems and add them to a List (var lvis = new List<ListViewItem>).
The RunWorkerCompleted method:
private void Pl_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
var obj = e.Result;
if (obj != null)
{
RemoveSelection();
lvis = (List<ListViewItem>)obj;
listView1.Items.Clear();
listView1.VirtualListSize = lvis.Count;
listView1.Invalidate();
var No_of_items = listView1.Items.Count + " pin(s)";
count.Text = No_of_items;
tabLabel.Text = GetButton().Text + " | " + No_of_items;
}
}
lvis is the source where the virtual ListView get its data from.
It looks like it's a simple misunderstanding related to the stored ListViewItem Index value: when you create a ListViewItem, you cannot set the Index, so this method to retrieve and return a matching ListViewItem:
private void listView1_RetrieveVirtualItem(object sender, RetrieveVirtualItemEventArgs e)
{
var item = lvis.OfType<ListViewItem>().FirstOrDefault([...]);
e.Index = item.Index;
}
will fail: item.Index is always -1, since was never set when the ListViewItem was created.
That's why the ListView will find Items that have already been shown (these have an Index, the virtual list doesn't need to retrieve them calling SearchForVirtualItem(), it will just call FindItem()).
A simple solution is to use the List.FindIndex() method, instead of finding an Item using FirstOrDefault(). This method returns the index in the List that contains the object that meets the criteria defined by the Predicate<T> argument.
This is the value of e.Index that the ListView.SearchForVirtualItem handler is expecting.
How many items a ListView can hold before it becomes difficult to manage or too slow: without any further specifications, this is a question difficult to answer. It may behave perfectly fine with 100000 items in List mode (as in the example), but setting the View = View.Details may change the scenario completely. Does it have to also handle graphics object? Well, how large? How many handles are needed, in this case? In practice, it's a question you answer yourself testing different scenarios.
The User perspective is also to consider (or should it come first? :). Maybe the list is scrollable with ease, but is it also easy to locate a specific Item?
If you have a lot of Items to present in the UI, you should most probably organize them in sub cathegories and provide easy, quick, visual methods to search and filter them, so your Users end up working with much less crowded subsets, probably closer to what they actually need to use or find.
Here's a fix and a code sample that should allow to test the functionality of the ListView.FindItemWithText() method (this one also needs a small tweak).
The ListView.VirtualMode is set in the Designer
In the example, the ListViewItems collection is represented by a list of 1,000 items, repeated 100 times, so the ListView VirtualListSize is set to 100,000 items
→ btnLVSearch: the Button used to search the ListView items.
→ btnLVLoadData: the Button used to load the data and sets the VirtualListSize.
→ chkPrefixSearch: the CheckBox that selects a PrefixSearch or a TextSearch.
→ chkCaseSensitiveSearch: the CheckBox used to set/reset the case sensitive search
int currentStartIndex = 0;
List<ListViewItem> listItems = null;
private void btnLVLoadData_Click(object sender, EventArgs e)
{
listItems = new List<ListViewItem>();
// [...]
// Fill the listItems collection
listView1.VirtualListSize = listItems.Count;
}
private void listView1_RetrieveVirtualItem(object sender, RetrieveVirtualItemEventArgs e)
{
if (e.ItemIndex >= 0) {
e.Item = listItems[e.ItemIndex];
}
}
private void listView1_SearchForVirtualItem(object sender, SearchForVirtualItemEventArgs e)
{
StringComparison comparison = chkCaseSensitiveSearch.Checked
? StringComparison.CurrentCulture
: StringComparison.CurrentCultureIgnoreCase;
int itemIndex = -1;
if (e.IsPrefixSearch) {
itemIndex = listItems.FindIndex(e.StartIndex,
itm => itm.Text.StartsWith(e.Text, comparison));
}
else if (e.IsTextSearch) {
itemIndex = listItems.FindIndex(e.StartIndex,
itm => itm.Text.IndexOf(e.Text, comparison) >= 0);
}
e.Index = itemIndex;
}
private void btnLVSearch_Click(object sender, EventArgs e)
{
var item = listView1.FindItemWithText(
txtLVSearch.Text, false, currentStartIndex, chkPrefixSearch.Checked);
if (item != null) {
currentStartIndex = item.Index + 1;
listView1.SelectedIndices.Add(item.Index);
item.Selected = true;
listView1.EnsureVisible(item.Index);
listView1.Focus();
}
else {
currentStartIndex = 0;
}
}
When handling the ListView.KeyPress event, set e.Handled = true to suppress the key press, otherwise a second SearchForVirtualItem event is triggered immediately after e.Index = itemIndex is assigned (this time, with e.IsPrefixSearch set to false):
private void listView1_KeyPress(object sender, KeyPressEventArgs e)
{
e.Handled = true;
var item = listView1.FindItemWithText(
e.KeyChar.ToString(), false, currentStartIndex, chkPrefixSearch.Checked);
// [...]
}
I need to add a search box to a listbox that has data pulling from SQL - I'm not sure how as it isn't my code. I just need to add the search function. The listbox holds user names and surnames. So all I have to work with is lbUsers (the listbox name).
Thus far I have it to search a user name but it's only displaying the closest search - I want the code to filter out everything containing what I have typed into the search box:
private void btnSearch_Click(object sender, EventArgs e)
{
this.lbUsers.SelectedItems.Clear();
for (int s = this.lbUsers.Items.Count - 1; s >= 0; s--)
{
if (this.lbUsers.Items[s].ToString().ToLower().Contains(this.tbSearch.Text.ToLower()))
{
this.lbUsers.SetSelected(s, true);
}
}
}
I also don't want all the users to display - only those relating to the search box's criteria.
You will have to do this manually:
Save all users in a list
Filter the list accoring the text in the TextBox
Add the results to the ListBox
This is a minimal example:
List<User> users = new List<User>();
private void txtFilter_TextChanged(object sender, EventArgs e)
{
List<User> displayList = this.users;
if(this.txtFilter.Text != string.Empty)
{
displayList = this.users.Select(u => u.Name == this.txtFilter.Text);
}
this.lbUsers.Items.Clear();
this.lbUsers.Items.AddRange(displayList);
}
I think the best way to do this is through visibility. This way you don't have to keep creating/disposing of listbox items.
For example, the code below would do what you want:
foreach (var item in lbUsers.Items)
{
if (item.ToString().Contains(this.tbSearch.Text))
{
item.Visible = true;
}
else
{
item.Visible = false;
}
}
I wanted to delete a selected item from the listbox, and after that, to remove the object which was referenced to that item I wanted to remove. I tried the following
private void Remove_candidate_Click(object sender, EventArgs e)
{
int i = candidate_list.SelectedIndex; // candidate_list is the ListBox
if (candidate_list.SelectedItems.Count > 0) candidate_list.Items.Remove(candidate_list.SelectedItem);
candidates.RemoveAt(i); //candidates is the object list
}
When I start the application, it crushes after clicking on the "Remove" button.
Just use
if (candidate_list.SelectedItems.Count > 0) candidate_list.Items.RemoveAt(i);
More robust way of doing this
int i = candidate_list.SelectedIndex; // candidate_list is the ListBox
if (i >= 0)
{
candidate_list.Items.RemoveAt(i);
candidates.RemoveAt(i); //candidates is the object list
}
In general case, when many items can be selected you can use
this code to remove all the selected items:
// To prevent candidate_list repainting while items updating
candidate_list.BeginUpdate();
try {
// When using RemoveAt() one should use backward loop
for (int i = candidate_list.SelectedIndices.Count - 1; i >= 0; --i) {
int index = candidate_list.SelectedIndices[i];
candidate_list.Items.RemoveAt(index);
candidates.RemoveAt(index);
}
}
finally {
candidate_list.EndUpdate();
}
This is really strange. I want to select a State and load Cities from that State in another combobox.
It's working EXCEPT when selecting the first item in the combobox:
Here's my entire class. The if statement in the selectedIndexChanged is to make sure that something is selected. The problem is that if I set that to cmbState.SelectedIndex >= 0 then an exception is raised because on initial load the comboBox doesn't has a .State variable there and not a .Value.
I don't know if this makes any sense.
private void MainForm_Load(object sender, EventArgs e)
{
LoadDepartmentsToComboBox();
}
private void LoadCitiesToComboBox(long StateID)
{
cmbCity.DataSource = null;
CityRepository cityRepo = new CityRepository();
cmbCity.DataSource = cityRepo.FindAllCities().Where(c => c.IDState == StateID);
cmbCity.DisplayMember = "Name";
cmbCity.ValueMember = "ID";
}
private void LoadDepartmentsToComboBox()
{
cmbState.DataSource = null;
StateRepository stateRepo = new StateRepository();
cmbState.DataSource = stateRepo.FindAllStates();
cmbState.DisplayMember = "Name";
cmbState.ValueMember = "ID";
}
private void cmbState_SelectedIndexChanged(object sender, EventArgs e)
{
if (cmbState.SelectedIndex > 0)
{
LoadCitiesToComboBox(Convert.ToInt64(cmbState.SelectedValue));
}
}
If I do use cmbState.SelectedIndex >= 0 then I receive this exception:
Unable to cast object of type
'DocumentScannerDanyly.State' to type
'System.IConvertible'.'System.IConvertible'.
When I don't use the SelectedIndex >= 0 and use plain old >0 then everything works except when selected the first item, which does nothing; understandably because it doesn't take the first item into account.
Thanks a lot for the help.
Don't assign the Display member & the Value member in each load, just assign them once in constructor for example.
add ToList() to the result which will assign to DataSource,
Complex DataBinding accepts as a data source either an IList or an IListSource.
check this.