Clear(remove) combobox items throws exception - c#

I have combobox which is correctly populated with some field ID when button is clicked.
private void Button_Click(object sender, RoutedEventArgs e)
{
results.Items.Add(ID);
}
Now I want when I change some value to delete previous value (or values in case I have multiple values in combobox) but I am always getting exception (if some value is already selected in Combo Box)
I tried to add in that method on the top this:
results.Items.Clear();
and I tried this:
for (int i = 0; i < results.Items.Count; i++)
{
results.Items.RemoveAt(i);
i--;
}
But always getting exception:
System.ArgumentException: Value does not fall within the expected range.
at MS.Internal.XcpImports.MethodEx(IntPtr ptr, String name, CValue[] cvData)
at MS.Internal.XcpImports.MethodPack(IntPtr objectPtr, String methodName, Object[] rawData)
at MS.Internal.XcpImports.Collection_Add[T](PresentationFrameworkCollection'1 collection, Object value)
at System.Windows.PresentationFrameworkCollection'1.AddImpl(Object value)
at System.Windows.Controls.ItemCollection.AddImpl(Object value)
at System.Windows.Controls.ItemCollection.AddInternal(Object value)
at System.Windows.PresentationFrameworkCollection'1.Add(T value)
at SXPCreateIncident3.SilverlightControl1.results_SelectionChanged(Object sender, SelectionChangedEventArgs e)
at System.Windows.Controls.Primitives.Selector.OnSelectionChanged(SelectionChangedEventArg
If I don't have this part with Clear (Remove) then combobox has more elements on every button Click but I need to clear previous content when button is clicked.

Did you try unselecting all items before deleting:
results.SelectedIndex = -1;
results.Items.Clear();
And in case Clear would still cause some trouble, shouldn't your second method be:
for (int i = results.Items.Count - 1; i >= 0; i--)
{
results.Items.RemoveAt(i);
}

I'm not entirely sure how result.Items is bound to you Combobox but you might try to replace the unwanted item with a new one:
private void Button_Click(object sender, RoutedEventArgs e)
{
// itemToRemove should already be set
var index = result.Items.IndexOf(itemToRemove);
results.Items[index ] = ID;
}
To remove multiple items, do not use an iterator. Removing things from a collection while using an iterator messes the iterator up. You could however do this:
private void Button_Click(object sender, RoutedEventArgs e)
{
for(var i = 0; i < result.Items.Count; i++)
{
// itemsToRemove should be populated with the IDs you want to remove.
if(itemsToRemove.Contains(result.Items[i])
{
result.RemoveAt(i);
}
}
result.Items.Add(ID);
}
This loop won't get messed up because every time the expression i < result.Items.Count is evaluated and Count be one less then the previous Count when an ID has been removed.
EDIT
To clear the combobox and populate it with new items you'll have to provide a new ItemsSource for the combobox:
private void Button_Click(object sender, RoutedEventArgs e)
{
results.ItemsSource = null;
results.ItemsSource = new List<SomeType>(); // replace SomeType with the type of ID.
results.Items.Add(ID);
}

Related

Dont Save Item in ComboBox

I have a TextBox, a Button, and a Combobox
When I click the Button, I want the text in Textbox to be added to the Combobox Items
Here is my code:
private void button1_Click(object sender, EventArgs e)
{
comboBox1.Items.Add(textBox1.Text);
}
My form until open. This text shows in the Combobox, but when I close the Form and open it again the text is not longer shown in the Combobox.
I want to save the text to the Collection Items of the Combobox. I don't want to use a database.
As others have mentioned in the comments, you need to understand and decide where you wish to store your values.
For the purpose of my example, I have created a simple text file to store these values. The code reads from the file and adds each line as an item into the ComboBox.
private void Form1_Load(object sender, EventArgs e)
{
// Read items from file into a string array
string[] items = System.IO.File.ReadAllLines(#"D:\ComboBoxValues.txt");
// Add items to the comobobox when opening the form
comboBox1.Items.AddRange(items);
}
private void button1_Click(object sender, EventArgs e)
{
// Add your new value to the combobox
comboBox1.Items.Add(textBox1.Text);
// Put all existing comobo box items into a string array
string[] items = comboBox1.Items.OfType<string>().ToArray();
// Save the array of items to a text file (this will not append, it will re-write the file)
System.IO.File.WriteAllLines(#"D:\ComboBoxValues.txt", items);
}
This may not be the most elegant way of going about it, but from the point of providing you an understanding - this should be more than sufficient.
if you don't like to use File System, you can use Preferences(but it's not recommendable to use preference to memorize large values), check this link to see how create a new setting
private void Form1_Load(object sender, EventArgs e)
{
string[] strItems = Properties.Settings.Default.items.Split(", ");
for(int i = 0; i < strItems.length; i++) {
comboBox1.Items.Add(strItems[i]);
}
}
private void button1_Click(object sender, EventArgs e)
{
//add your new value to the combobox
comboBox1.Items.Add(textBox1.Text);
//put all existing combo box items into a string array
string[] items = comboBox1.Items.OfType<string>().ToArray();
for(int i = 0; i < items.length; i++) {
//I assumed you had an items key in your settings
if(i == items.length - 1) {
Properties.Settings.Default.items += value;
} else {
Properties.Settings.Default.items += value + ", ";
}
}
//then you should to save your settings
Properties.Settings.Default.Save();
}

select row from winforms listview

I have listview which is populated with list of data. Now I want to select desired row and on click button to recognize that item to delete from a collection.
Question is how to recognize selected row from the listview?
private void buttonDelete_Click(object sender, EventArgs e)
{
//selected data is of custom type MyData
var selected = (MyData)....?
}
Thanks
This should works
private void buttonDelete_Click(object sender, EventArgs e)
{
//selected data is of custom type MyData
var selected = yourListView.SelectedItems.First();
}
To add to #Zaphod's answer and make a little more robust:
private void buttonDelete_Click(object sender, EventArgs e)
{
if (yourListView.SelectedItems.Any())
{
//selected data is of custom type MyData
var selected = yourListView.SelectedItems.First();
}
}
You could use .Count > 0 instead of .Any() and .SelectedItems[0] instead of .First(). Whatever you find more readable/maintainable.
Old School answer :) without any LINQ statements
if(yourListView.SelectedItems.Count > 0)
{
var item = yourListView.SelectedItems[0];
}
I don't think you have to use casting for a deleting operation, just remove all the selected indices like this:
private void buttonDelete_Click(object sender, EventArgs e){
for (int i = listView1.SelectedIndices.Count - 1; i >= 0; i--)
listView1.Items.RemoveAt(listView1.SelectedIndices[i]);
}
or more simply:
private void buttonDelete_Click(object sender, EventArgs e){
foreach(ListViewItem item in listView1.SelectedItems)
listView1.Items.Remove(item);
}
As you can see the item which is selected is of type ListViewItem, you can bind your data to this item via Text property (if the data is string) or Tag property. I don't understand what your CustomData is, is it a type inheriting ListViewItem?
YOu should do this
private void buttonDelete_Click(object sender, EventArgs e)
{
if (yourListView.SelectedItems.Any())
{
//selected data is of custom type MyData
var selected = (MyData)yourListView.SelectedItems[0];
YourCollection.Remove(selected);
}
}

Moving item from one list box to another list box (c# webforms)

I am encountering an odd issue whereby I can move items from one list box to another, but cannot move any items back to the original list box. Here is my code:
private void MoveListBoxItems(ListBox from, ListBox to)
{
for(int i = 0; i < first_listbox.Items.Count; i++)
{
if (first_listbox.Items[i].Selected)
{
to.Items.Add(from.SelectedItem);
from.Items.Remove(from.SelectedItem);
}
}
from.SelectedIndex = -1;
to.SelectedIndex = -1;
}
protected void Button2_Click(object sender, EventArgs e)
{
MoveListBoxItems(first_listbox, second_listbox);
}
protected void Button1_Click(object sender, EventArgs e)
{
MoveListBoxItems(second_listbox, first_listbox);
}
The button2 event works fine, however the button1 event does not. The list boxes are not data bound and I have manually added items to them.
Maybe there is something very obvious that I am missing here?
Thanks for your help in advance.
Change it to this:
private void MoveListBoxItems(ListBox from, ListBox to)
{
for(int i = 0; i < from.Items.Count; i++)
{
if (from.Items[i].Selected)
{
to.Items.Add(from.SelectedItem);
from.Items.Remove(from.SelectedItem);
// should probably be this:
to.Items.Add(from.Items[i]);
from.Items.Remove(from.Items[i]);
}
}
from.SelectedIndex = -1;
to.SelectedIndex = -1;
}
Your original method was using first_listbox in these two places, instead of from. Also, I imagine your code does not work if more than one item is selected.
Change your for loops to iterate over the local parameter from, not specifically the first_listbox:
private void MoveListControlItems(ListControl from, ListControl to)
{
for(int i = 0; i < from.Items.Count; i++)
{
if (from.Items[i].Selected)
{
to.Items.Add(from.Items[i]);
from.Items.Remove(from.Items[i]);
}
}
from.SelectedIndex = -1;
to.SelectedIndex = -1;
}
You also want to switch the add and remove if you want to move multiple items at a time.
Just another thought, though it is mostly personal preference, if you switch the parameter types to ListControl you can use the same method for ComboBox's as well.

How can I create only one event handler method for multiple controls?

I have 15 comboBox'es, and I do not want to create an event handler for each. How do I make just one procedure and tie all Combobox'es to it?
private void cbSlots0_SelectedIndexChanged(object sender, EventArgs e)
{
var item = ConfigClass.Slots["0"][cbSlots0.SelectedIndex];
ConfigClass.Slots["0"].Insert(0, item);
ConfigClass.Slots["0"].RemoveAt(cbSlots0.SelectedIndex + 1);
}
private void cbSlots1_SelectedIndexChanged(object sender, EventArgs e)
{
var item = ConfigClass.Slots["1"][cbSlots1.SelectedIndex];
ConfigClass.Slots["1"].Insert(1, item);
ConfigClass.Slots["1"].RemoveAt(cbSlots1.SelectedIndex + 1);
}
Correct answer:
var cb = ((ComboBox)sender);
var tag = int.Parse(cb.Tag.ToString());
var item = ConfigClass.Slots[tag.ToString()][cb.SelectedIndex];
ConfigClass.Slots[tag.ToString()].Insert(tag, item);
ConfigClass.Slots[tag.ToString()].RemoveAt(cb.SelectedIndex + 1);
You can give each ComboBox a distinct Tag, which contains the number of the entry in the ConfigClass, and then use that like so:
private void cbSlots0_SelectedIndexChanged(object sender, EventArgs e)
{
int tag = (int)((ComboBox)sender).Tag;
var item = ConfigClass.Slots[tag.ToString()][cbSlots0.SelectedIndex];
ConfigClass.Slots[tag.ToString()].Insert(tag, item);
ConfigClass.Slots[tag.ToString()].RemoveAt(cbSlots0.SelectedIndex + 1);
}
The tag can contain any data you want, so if you need something more complex stored in there, that's also a possibility.
I would recommend one event handler for all ComboBoxes. Afterwards, within your event handler, use the sender reference to decide which slot to use:
private void allComboBoxesSelectedIndesChanged(object sender, EventArgs e)
{
int index = 0; // Or string as you have shown in your example.
if (sender == cbSlots0)
index = 0;
else if (sender == cbSlots1)
index = 1;
/// And so on for any other comboBox
var item = ConfigClass.Slots[index][((ComboBox) sender).SelectedIndex];
ConfigClass.Slots[index].Insert(index, item);
ConfigClass.Slots[index].RemoveAt(((ComboBox) sender).SelectedIndex +1);
}
This is relatively simple. You create a single SelectedIndexChanged event handler method, and then wire that up to all of the combo box controls.
The way you distinguish between the controls inside of the method at run-time is by checking the value of the sender parameter. You'll have to cast it to a ComboBox control, but that's safe because you know that you didn't wire up any non-combobox controls to that event handler. Then you'll be able to access all the properties of the combobox that raised the event you're handling.
Tie each item in your markup to the same SelectedIndexChangedEvent and cast the sender as your item. So, in your code, look for all of the unique event names (ie. cbSlots0_SelectedIndexChanged, cbSlots1_SelectedIndexChanged, etc) and rename them to the single event name (eg. cbSlotsSelectedIndexChanged).
I think this is right. Verify.
CODE:
private void cbSlotsSelectedIndexChanged(object sender, EventArgs e)
{
ComboBox cBox = (ComboBox) sender;
int tag = (int)cBox.Tag;
var item = ConfigClass.Slots[tag.ToString()][cBox.SelectedIndex];
ConfigClass.Slots[tag.ToString()].Insert(tag, item);
ConfigClass.Slots[tag.ToString()].RemoveAt(item.SelectedIndex + 1);
}
UPDATE:
I revised my post as requested
private void cbSlotsSelectedIndexChanged(object sender, EventArgs e)
{
var cb = ((ComboBox)sender);
var tag = int.Parse(cb.Tag.ToString());
var item = ConfigClass.Slots[tag.ToString()][cb.SelectedIndex];
ConfigClass.Slots[tag.ToString()].Insert(tag, item);
ConfigClass.Slots[tag.ToString()].RemoveAt(cb.SelectedIndex + 1);
}

Reorder a winforms listbox using drag and drop?

Is this a simple process?
I'm only writing a quick hacky UI for an internal tool.
I don't want to spend an age on it.
Here's a quick down and dirty app. Basically I created a Form with a button and a ListBox. On button click, the ListBox gets populated with the date of the next 20 days (had to use something just for testing). Then, it allows drag and drop within the ListBox for reordering:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
this.listBox1.AllowDrop = true;
}
private void button1_Click(object sender, EventArgs e)
{
for (int i = 0; i <= 20; i++)
{
this.listBox1.Items.Add(DateTime.Now.AddDays(i));
}
}
private void listBox1_MouseDown(object sender, MouseEventArgs e)
{
if (this.listBox1.SelectedItem == null) return;
this.listBox1.DoDragDrop(this.listBox1.SelectedItem, DragDropEffects.Move);
}
private void listBox1_DragOver(object sender, DragEventArgs e)
{
e.Effect = DragDropEffects.Move;
}
private void listBox1_DragDrop(object sender, DragEventArgs e)
{
Point point = listBox1.PointToClient(new Point(e.X, e.Y));
int index = this.listBox1.IndexFromPoint(point);
if (index < 0) index = this.listBox1.Items.Count-1;
object data = e.Data.GetData(typeof(DateTime));
this.listBox1.Items.Remove(data);
this.listBox1.Items.Insert(index, data);
}
7 Years Late. But for anybody new, here is the code.
private void listBox1_MouseDown(object sender, MouseEventArgs e)
{
if (this.listBox1.SelectedItem == null) return;
this.listBox1.DoDragDrop(this.listBox1.SelectedItem, DragDropEffects.Move);
}
private void listBox1_DragOver(object sender, DragEventArgs e)
{
e.Effect = DragDropEffects.Move;
}
private void listBox1_DragDrop(object sender, DragEventArgs e)
{
Point point = listBox1.PointToClient(new Point(e.X, e.Y));
int index = this.listBox1.IndexFromPoint(point);
if (index < 0) index = this.listBox1.Items.Count - 1;
object data = listBox1.SelectedItem;
this.listBox1.Items.Remove(data);
this.listBox1.Items.Insert(index, data);
}
private void itemcreator_Load(object sender, EventArgs e)
{
this.listBox1.AllowDrop = true;
}
The first time it takes a few hours if you never implemented drag and drop, want to get it done right and have to read through the docs. Especially the immediate feedback and restoring the list if the user cancels the operation require some thoughts. Encapsulating the behavior into a reusable user control will take some time, too.
If you have never done drag and drop at all, have a look at this drag and drop example from the MSDN. This would be a good starting point and it should take you maybe half a day to get the thing working.
This relies on #BFree's answer above - thanks it helped a lot.
I ran into an error when trying to use the solution because I was using a DataSource for my listbox. Just for completeness, you get this error if you try to remove or add an item to the listbox directly:
// Causes error
this.listBox1.Items.Remove(data);
Error:
System.ArgumentException: 'Items collection cannot be modified when the DataSource property is set.'
Solution: Update the datasource itself, and then rebind to your listbox. Program.SelectedReports is a BindingList.
Code:
private void listboxSelectedReports_DragDrop(object sender, DragEventArgs e)
{
// Get the point where item was dropped.
Point point = listboxSelectedReports.PointToClient(new Point(e.X, e.Y));
// Get the index of the item where the point was dropped
int index = this.listboxSelectedReports.IndexFromPoint(point);
// if index is invalid, put item at the end of the list.
if (index < 0) index = this.listboxSelectedReports.Items.Count - 1;
// Get the item's data.
ReportModel data = (ReportModel)e.Data.GetData(typeof(ReportModel));
// Update the property we use to control sorting within the original datasource
int newSortOrder = 0;
foreach (ReportModel report in Program.SelectedReports) {
// match sorted item on unique property
if (data.Id == report.Id)
{
report.SortOrder = index;
if (index == 0) {
// only increment our new sort order if index is 0
newSortOrder += 1;
}
} else {
// skip our dropped item's index
if (newSortOrder == index) {
newSortOrder += 1;
}
report.SortOrder = newSortOrder;
newSortOrder += 1;
}
}
// Sort original list and reset the list box datasource.
// Note: Tried other things, Reset(), Invalidate(). Updating DataSource was only way I found that worked??
Program.SelectedReports = new BindingList<ReportModel>(Program.SelectedReports.OrderBy(x => x.SortOrder).ToList());
listboxSelectedReports.DataSource = Program.SelectedReports;
listboxSelectedReports.DisplayMember = "Name";
listboxSelectedReports.ValueMember = "ID";
}
Other notes:
BindingList is under this namespace:
using System.ComponentModel;
When dynamically adding items to the list, make sure you populate your sorting property. I used an integer field 'SortOrder'.
When you remove an item, I don't have to worry about updating the Sorting property, as it will just create a number gap which is ok in my situation, YMMV.
To be honest, there could be a better sorting algorithm other than a foreach loop, but in my situation, I am dealing with a very limited number of items.
An alternative is using the list-view control, which is the control Explorer uses to display the contents of folders. It is more complicated, but implements item dragging for you.

Categories