I have a WPF window that manages sets of configurations and it allows users to edit a configuration set (edit button) and to remove a configuration set (remove button). The window has a ListBox control that lists the configuration sets by name and its ItemsSource has a binding set to a list of configuration sets.
I'm trying to remove the item in the code behind file for the window..
private void RemoveButton_Click(object sender, RoutedEventArgs e)
{
var removedItems = configSetListBox.SelectedItems;
foreach(ConfigSet removedItem in removedItems)
{
configSetListBox.Items.Remove(removedItem);
}
}
My code yields an invalid operation exception stating "Access and modify elements with ItemsControl.ItemsSource instead." What property should I be accessing to properlyremove items from the ListBox? Or is there possibly a more elegant way to handle this in WPF? My implementation is a bit WinForm-ish if you will :)
Solution
private void RemoveButton_Click(object sender, RoutedEventArgs e)
{
foreach(ConfigSet removedItem in configSetListBox.SelectedItems)
{
(configSetListBox.ItemsSource as List<ConfigSet>).Remove(removedItem);
}
configSetListBox.Items.Refresh();
}
In my case I had a List as the ItemSource binding type so I had to cast it that way. Without refreshing the Items collection, the ListBox doesn't update; so that was necessary for my solution.
use:
private void RemoveButton_Click(object sender, RoutedEventArgs e)
{
foreach(ConfigSet item in this.configSetListBox.SelectedItems)
{
this.configSetListBox.ItemsSource.Remove(item); // ASSUMING your ItemsSource collection has a Remove() method
}
}
Note: my use of this. is just so it as it is more explicit - it also helps one see the object is in the class namespace as opposed to variable in the method we are in - though it is obvious here.
This is because , you are modifying a collection while iterating over it.
if you have binded item source of listbox than try to remove the items from the source
this was answered here already.
WPF - Best way to remove an item from the ItemsSource
You will need to implement an ObservableCollection and then whatever you do to it will be reflected in your listbox.
I Used this logic to preceed. And it worked.
you may want to try it.
private void RemoveSelectedButton_Click(object sender, RoutedEventArgs e) {
if (SelectedSpritesListBox.Items.Count <= 0) return;
ListBoxItem[] temp = new ListBoxItem[SelectedSpritesListBox.SelectedItems.Count];
SelectedSpritesListBox.SelectedItems.CopyTo(temp, 0);
for (int i = 0; i < temp.Length; i++) {
SelectedSpritesListBox.Items.Remove(temp[i]);
}
}
for (int i = lstAttachments.SelectedItems.Count - 1; i >= 0; i--)
{
lstAttachments.Items.Remove(lstAttachments.SelectedItems[i]);
}
Simplest way to remove items from a list you iterate through is going backwards because it does not affect the index of items you are moving next to.
Related
Thanks to Peter Duniho's tip on how a Stack Overflow question has to look like, I converted my original question and its title hopefully to something more appropriate.
So I'm currently working on a conversation editor for a chatbot which consists of a treeview and two listviews. I store each treenode as a key(int) in a dictionary. The dictionary structure looks like this:
Dictionary<selectedNode(int), Tuple<List<listView1ItemLabels(string)>, List<listView2ItemLabels(string)>>>
Each list in the Tuple holds the labels of the items which are dynamically added to the two listviews at runtime using this custom function:
void AddItemToListView1(string itemName)
{
// add new Item to listView1 and
dictionary[selectedNodeID].Item1.Add(itemName);
// add its Text to the dictionary Tuple first generic list
listView1.Items.Add(itemName);
}
When I click a node (or rather "the corresponding dictionary key"), both listviews will be repopulated from the lists in the Tuple via the AfterSelect event of the treeView, which looks like this:
private void treeView_AfterSelect(object sender, TreeViewEventArgs e)
{
foreach(string str in dictionary[selectedNodeID].Item1)
{
listView1.Items.Add(str);
}
foreach(string str in dictionary[selectedNodeID].Item2)
{
listView2.Items.Add(str);
}
}
What I want to achieve is to change the string in the tuple's lists according to the change that happens to the listview item inside the AfterLabelEdit event. My approach below is obviously incorrect, even though the methods are valid:
private void listView1_BeforeLabelEdit(object sender, LabelEditEventArgs e)
{
// Capture the yet unedited label of the item
oldLabel = e.Label;
}
private void listView1_AfterLabelEdit(object sender, LabelEditEventArgs e)
{
if(e.Label == null)
return;
if(e.Label == "")
e.CancelEdit = true;
// remove the selected listview item label, which was previously added using the custom AddItem() function
dictionary[selectedNodeID].Item1.Add(e.Label);
dictionary[selectedNodeID].Item1.Remove(oldLabel);
}
I really don't see any reason why this won't work. What am I missing?
EDIT: Here's a picture of the GUI. May be it helps people understand what this is all about. :-)
I'm having some problem to get the index of the selected row in a listview. I wonder why this code isn't working? I get a red line below the SelectedIndex
private void lvRegAnimals_SelectedIndexChanged(object sender, EventArgs e)
{
int index = lvRegAnimals.SelectedIndex;
string specialData = motelManager.GetInfoFromList(index);
UppdateSpecialData(specialData);
}
Help is preciated. Thanks!
EDIT:
For some strange reason I get two messages when I click on one of the lines in the listView!? First I get the previous number and then the number for the last clicked line. What could be wrong?
private void lvRegAnimals_SelectedIndexChanged(object sender, EventArgs e)
{
int index = lvRegAnimals.FocusedItem.Index;
MessageBox.Show(Convert.ToString(index));
}
It's working now when I added a check like this:
if(lvRegAnimals.SelectedIndices.Count > 0)
Because ListView doesn't contain any SelectedIndex, instead there is a property of SelectedIndices.
var indices = lvRegAnimals.SelectedIndices;
//indices[0] you can use that to access the first selected index
ListView.SelectedIndices
When the MultiSelect property is set to true, this property returns a
collection containing the indexes of all items that are selected in
the ListView. For a single-selection ListView, this property returns a
collection containing a single element containing the index of the
only selected item in the ListView.
private void listView1_SelectedIndexChanged(object sender, EventArgs e)
{
// Acquire SelectedItems reference.
var selectedItems = listView1.SelectedItems;
if (selectedItems.Count > 0)
{
// Display text of first item selected.
this.Text = selectedItems[0].Text;
}
else
{
// Display default string.
this.Text = "Empty";
}
}
Try :
listView1.FocusedItem.Index
This give you the index of the selected row.
There is another thread like this one, but here it goes again.
It can return NULL. Also the SelectedIndexChanged event can be FIRED TWICE. And the first time, there nothing selected yet.
So the only safe way to find it is like this:
private void lv1_SelectedIndexChanged(object sender, EventArgs e)
{
if (lv1.FocusedItem == null) return;
int p = lv1.FocusedItem.Index;
... now int p has the correct value...
The ListView is a darn hassle to work with sometimes.
A simple solution i've used is a for loop that checks for the
selected Item.
I've put my solution in the "When index change trigger" within the ListView.
Example:
int sel_item = 0; //an int to store the selected item index.
private void listView1_SelectedIndexChanged(object sender, EventArgs e)
{
for (int i = 0; i < listView1.Items.Count; i++)
{
if (listView1.Items[i].Selected == true)
{
sel_item = i;
}
}
}
This would ofcourse only work correctly with the "Multiselection" option set as false.
I have this subtle program regarding the behavior of listbox. My listbox is binded with an observable list in the viewmodel. There are 2 ways in addding an item in the listbox. First is ADD a single item then that item would be selected directly. This works fine.
The second way was LOAD which by its name will be adding more than 1 item in the lisbox. Now the problem is when loading items more than the listbox can accomodate in the view, those items that are not in view (items at the bottom thus need to be scrolled in order for it to be viewed) was not automatically selected...
Only the items that are by default viewed are the ones selected:
private void ItemContainerGenerator_StatusChanged(object sender, EventArgs e)
{
if (listBoxAddresses.ItemContainerGenerator.Status != GeneratorStatus.ContainersGenerated) return;
for (int i = 0; i < TestSetting.DeviceSettings.Count; i++)
{
ListBoxItem myListBoxItem = (ListBoxItem)(listBoxAddresses.ItemContainerGenerator.ContainerFromItem(TestSetting.DeviceSettings[i]));
if (myListBoxItem != null)
{
myListBoxItem.IsSelected = true;
}
}
listBoxAddresses.ItemContainerGenerator.StatusChanged -= ItemContainerGenerator_StatusChanged;
}
I wonder if this is just a natural behavior for listbox.
I just realize this now...setting my listbox to :
VirtualizingStackPanel.IsVirtualizing="False"
did all the trick. Thanks to Dr.WPF for the idea. Though there are consequences for turning off virtualization (performance) but it won't matter that much.
I have a ListBox in my Silverlight project.And,when to remove and add ListItem from a ListBox,I got the following error.
Operation not supported on read-only collection.
Code:
public void btnUp_Click(object sender, RoutedEventArgs e)
{
if (lbChoices.SelectedItem != null)
{
ListBoxItem selectedItem = new ListBoxItem();
selectedItem.Content = lbChoices.SelectedItem;
selectedItem.IsSelected = true;
int selectedIndex = lbChoices.SelectedIndex;
if (lbChoices.Items.Count > 1)
{
if (selectedIndex > 0)
{
lbChoices.Items.Remove(lbChoices.SelectedItem);
lbChoices.Items.Insert(selectedIndex - 1, selectedItem);
}
}
}
}
When you are using ItemsControl with an ItemsSource, you can not add/remove elements using the Items collection. You should modify your underlying collection instead.
"The problem stems from the fact that I’d bound my ListBox to an ObservableCollection, once bound the Items collection becomes read-only."
I guess you added items by binding the ItemsSource? If so, remove the item from the collection you are binding to.
You need to remove the item from the source that your ListBox is bound to not the ListBox itself. As soon as your remove it from the source, the ListBox will automatically refresh to not display the item.
Change your code like this:
private void button1_Click(object sender, RoutedEventArgs e)
{
if (lbChoices.SelectedItem != null)
{
ListBoxItem selectedItem = (ListBoxItem)lbChoices.SelectedItem;
int selectedIndex = lbChoices.SelectedIndex;
if (lbChoices.Items.Count > 1)
{
if (selectedIndex > 0)
{
lbChoices.Items.Remove(lbChoices.SelectedItem);
lbChoices.Items.Insert(selectedIndex - 1, selectedItem);
}
}
}
}
It seems that your are moving up the selected item in the list box.
I created two RadioButton (Weight and Height). I will do switch between the two categories. But the they share the same ListBox Controllers (listBox1 and listBox2).
Is there any good method to clear all the ListBox items simpler? I didn't find the removeAll() for ListBox. I don't like my complex multi-lines style which I posted here.
private void Weight_Click(object sender, RoutedEventArgs e)
{
// switch between the radioButton "Weith" and "Height"
// Clear all the items first
listBox1.Items.Remove("foot");
listBox1.Items.Remove("inch");
listBox1.Items.Remove("meter");
listBox2.Items.Remove("foot");
listBox2.Items.Remove("inch");
listBox2.Items.Remove("meter");
// Add source units items for listBox1
listBox1.Items.Add("kilogram");
listBox1.Items.Add("pound");
// Add target units items for listBox2
listBox2.Items.Add("kilogram");
listBox2.Items.Add("pound");
}
private void Height_Click(object sender, RoutedEventArgs e)
{
// switch between the radioButton "Weith" and "Height"
// Clear all the items first
listBox1.Items.Remove("kilogram");
listBox1.Items.Remove("pound");
listBox2.Items.Remove("kilogram");
listBox2.Items.Remove("pound");
// Add source units items for listBox1
listBox1.Items.Add("foot");
listBox1.Items.Add("inch");
listBox1.Items.Add("meter");
// Add target units items for listBox2
listBox2.Items.Add("foot");
listBox2.Items.Add("inch");
listBox2.Items.Add("meter");
}
isn't the same as the Winform and Webform way?
listBox1.Items.Clear();
I think it would be better to actually bind your listBoxes to a datasource, since it looks like you are adding the same elements to each listbox. A simple example would be something like this:
private List<String> _weight = new List<string>() { "kilogram", "pound" };
private List<String> _height = new List<string>() { "foot", "inch", "meter" };
public Window1()
{
InitializeComponent();
}
private void Weight_Click(object sender, RoutedEventArgs e)
{
listBox1.ItemsSource = _weight;
listBox2.ItemsSource = _weight;
}
private void Height_Click(object sender, RoutedEventArgs e)
{
listBox1.ItemsSource = _height;
listBox2.ItemsSource = _height;
}
Write the following code in the .cs file:
ListBox.Items.Clear();
while (listBox1.Items.Count > 0){
listBox1.Items.Remove(0);
}
You should be able to use the Clear() method.
I made on this way, and work properly to me:
if (listview1.Items.Count > 0)
{
for (int a = listview1.Items.Count -1; a > 0 ; a--)
{
listview1.Items.RemoveAt(a);
}
listview1.Refresh();
}
Explaining: using "Clear()" erases only the items, do not
removes then from object, using RemoveAt() to removing an item of beginning position
just realocate the others [if u remove item[0], item[1] turns into [0] triggering a new internal event],
so removing from the ending no affect de others position,
its a Stack behavior, this way we can Stack over all items, reseting the object.
VB
ListBox2.DataSource = Nothing
C#
ListBox2.DataSource = null;