ListBox SelectedIndexChanged event not fired when bound list updates - c#

I'd like to have a basic ListBox with associated Add and Delete buttons. The Delete button be enabled if and only if there is a selection in the ListBox, and the Add button should append an item to the end of the ListBox.
My first attempt was to just make a List<string> items field, like so:
public partial class Form1 : Form {
public Form1 {
InitializeComponent();
this.listBox1.DataSource = this.items;
}
private void addButton_Click(object sender, EventArgs e) {
this.items.Add("item {this.items.Count + 1}");
}
private void deleteButton_Click(object sender, EventArgs e) {
this.items.RemoveAt(this.listBox1.SelectedIndex);
}
private void listBox1_SelectedIndexChanged(object sender, EventArgs e) {
this.deleteButton.enabled = (this.listBox1.SelectedIndex != -1);
}
List<string> items = new List<string>();
}
But, it turns out that the ListBox does not seem to update automatically as the items in the list change. Here's a related question about it.
Most advice suggested just running listBox1.DataSource = null; this.listBox1.DataSource = this.items; every time I updated the list, but that to me defeats the purpose of binding.
Another answer suggested using an ObservableCollection for my list's data type, but that did nothing different.
Finally I found the BindingList class and it seemed to do what I want; the ListBox automatically reflects the contents of the items field. (If there is another solution that is actually a best practice and not some piece of cargo cult folklore pasted around the Internet, please let me know.)
However, when the first item I add to items appears in the ListBox, the row becomes selected. And I can see that listBox1.SelectedIndex changes from -1 to 0. But, the listBox1_SelectedIndexChanged event does not fire. Hence, the state of the Delete button doesn't get updated properly.
(As an aside, this UI pattern is extremely commonplace, and it seems disastrous that there's so much conflicting information on implementing such a simple thing.)

I also ran into the same problem. Since this was the first question I came across, maybe my answer will save someone some time.
When DataSource is assigned an empty BindingList and items are added to that BindingList later, like so:
private BindingList<MyType> _myList = new BindingList<MyType>();
public MyForm()
{
listBox1.DataSource = _myList;
}
private void LoadData()
{
foreach (MyType item in GetDataFromSomewhere())
{
_myList.Add(item);
}
}
then after adding the first item, that will be selected in the ListBox, and listBox1.SelectedIndex will be 0 - but the SelectedIndexChanged event will not have been fired.
Looks like this could be a bug in ListBox as suggested here and here.
A workaround could be to derive from ListBox as Juan did, or just check if the BindingList contains any items before adding:
bool isFirstItem = (0 == _myList.Count);
_myList.Add(item);
if (isFirstItem)
{
// Your choice: set listBox1.SelectedIndex to -1 and back to 0,
// or call the event handler yourself.
}

Try manually selecting the last element, instead of relying on the default behavior. This will trigger the event.
public partial class Form1 : Form
{
BindingList<string> items = new BindingList<string>();
public Form1()
{
InitializeComponent();
this.listBox1.DataSource = items;
}
private void listBox1_SelectedIndexChanged(object sender, EventArgs e)
{
this.deleteButton.Enabled = (this.listBox1.SelectedIndex != -1);
}
private void addButton_Click(object sender, EventArgs e)
{
this.items.Add($"item {this.items.Count + 1}");
this.listBox1.SelectedIndex = -1;
this.listBox1.SelectedIndex = this.listBox1.Items.Count - 1;
}
private void deleteButton_Click(object sender, EventArgs e)
{
this.items.RemoveAt(this.listBox1.SelectedIndex);
}
}

Related

Combobox not firing SelectedIndexChanged when programmatically selecting index 0 (from -1)

I have a combobox for wich I am using the SelectIndexChanged event to capture both user and programmatically changes.
Clearing and reloading the list bound to the combobox will fire the eventhandler with index -1 naturally.
But then with selectedindex=-1
combobox1.SelectedIndex = 0 ; // will NOT fire the event.
but
combobox1.SelectedIndex = 1 ; // or higher number WILL fire the event.
In both cases I AM programmatically changing the selextedindex and expect the event to be fired.
I verified the behavior in a simple form.
namespace cmbTest
{
public partial class Form1 : Form
{
private BindingList<string> items = new BindingList<string>();
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
comboBox1.DataSource = items;
loadItems();
}
private void loadItems()
{
items.Add("chair");
items.Add("table");
items.Add("coffemug");
}
private void comboBox1_SelectedIndexChanged(object sender, EventArgs e)
{
MessageBox.Show("Fired with selected item index:" + comboBox1.SelectedIndex);
}
private void button1_Click(object sender, EventArgs e)
{
int index = comboBox1.SelectedIndex;
// Clear and reload items for whatever reason.
items.Clear();
loadItems();
// try select old item index; if it doesnt exists, leave it.
try { comboBox1.SelectedIndex = index; }
catch { }
}
}
}
The form has a combobox1 and a button1.
EDIT for clarity (I hope):
Run program
Select 'chair'. Message "Fired with selected item index:0"
Hit button. Message "Fired with selected item index:-1"
Select 'table'. Message "Fired with selected item index:1"
Hit button. Messages "Fired with selected item index:-1" AND
"Fired with selected item index:1".
I expect to get two messages when hitting button when "chair" is selected too, since I programmatically changes the index to 0.
So, why is this not working as I expect it to do, and what will be an acceptable workaround?
When your first item is added to the items collection the index is automatically changed to 0. That's why when your previous index is saved as 0, and then you set it again using this line comboBox1.SelectedIndex = index;, the index is not changed. That's why the event is not fired.
Looking at the source code for the ComboBox, the event is not fired in 2 cases : Either an expcetion is thrown, or the index is set to the same value that it is was.
If you really want some hack around this, you can do it this way:
int index = comboBox1.SelectedIndex;
// Clear and reload items for whatever reason.
items.Clear();
loadItems();
if(comboBox1.SelectedIndex == index)
{
comboBox1.SelectedIndexChanged -= comboBox1_SelectedIndexChanged;
comboBox1.SelectedIndex = -1;
comboBox1.SelectedIndexChanged += comboBox1_SelectedIndexChanged;
}
// try select old item index; if it doesnt exists, leave it.
try { comboBox1.SelectedIndex = index; }
catch
{
}

RadContextMenu.DropDownOpened called before RadGridView.CurrentRow is changed

I'm trying to change an item on my RadContextMenu depending on the currently selected row in my RadGridView (edit: OrderList). I want the item to be enabled if the databound item in the current row has the correct property value.
The problem is that when I directly rightclick a row to open the RadContextmenu the CurrentRow has not yet been updated, so DropDownOpened is called with the old row.
If I left click or double right click it works fine.
Here's a bit of the code:
OrderMenu.DropDownOpened += OrderMenu_DropDownOpened;
And the method
private void OrderMenu_DropDownOpened(object sender, EventArgs e)
{
GoToParentOrderBtn.Enabled = GetSelectedOrder()?.ParentOrderId != null;
}
private OrderViewModel GetSelectedOrder()
{
return (OrderViewModel)OrderList.CurrentRow.DataBoundItem;
}
Use dataGridView.EndEdit(); This function commits and ends the edit operation on the current cell being edited.
More info here
Sorry for not specifying that I'm using a radgridview.
I found a related answer which helped me solve my problem.
I ended up making an extension (so I can use it all around the application) to RadGridView which fire an event on mousedown:
public partial class RadExtendedGridViewController : RadGridView
{
public RadExtendedGridViewController()
{
InitializeComponent();
base.MouseDown += RadExtendedGridViewController_MouseDown;
}
private void RadExtendedGridViewController_MouseDown(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Right)
{
var element = this.ElementTree.GetElementAtPoint(e.Location);
GridDataCellElement cell = element as GridDataCellElement;
if (cell?.RowElement is GridDataRowElement)
{
Rows[cell.RowIndex].IsSelected = true;
}
}
}
}
I then changed my GetSelectedOrder to using SelectedRows instead of Current:
private OrderViewModel GetSelectedOrder()
{
return (OrderViewModel)OrderList.SelectedRows.FirstOrDefault()?.DataBoundItem;
}
And now it works as intended. Thanks for taking your time trying to help me :-)

Winforms - Stop Dropdown of Combobox during DropDown event

I need to implement a ComboBox, which acts as follows:
When Click on the ComboBox, the client calling API method and updates the combobox items with the response.
My problem is, when I have 0 results - I want the ComboBox not to open (It has 0 items).
Is there a way to do that?
This is my current code:L
private void Combo_DropDown(object sender, EventArgs e)
{
// Private method which addes items to the combo, and returns false if no itmes were added
if (!AddItemsToComboBox())
{
// This is not working
Combo.DroppedDown = false;
}
}
You can make the DropDownHeight as small as possible (1). For example:
int iniHeight;
private void Form1_Load(object sender, EventArgs e)
{
iniHeight = Combo.DropDownHeight;
}
private void Combo_DropDown(object sender, EventArgs e)
{
Combo.DropDownHeight = (AddItemsToComboBox() ? iniHeight : 1);
}

How to sync selection index between two ListBoxes

I have two listboxes: listBox1, listBox2.
If i select the item in first listBox1, item of the same index must be automatically selected in listBox2.
So, If i select item 1 in listbox1 then, item 1 selected automatically in listbox2 and so on.
Not: I found some examples but not work.
private void listBoxControl2_SelectedIndexChanged(object sender, EventArgs e)
{ listBoxControl5.SelectedIndex = listBoxControl2.SelectedIndex; }
Edit:
I solved it using the selected index code in This answer in SelectedValueChanged Event.
private void listBoxControl2_SelectedValueChanged(object sender, EventArgs e)
{
listBoxControl5.SelectedIndex = listBoxControl2.SelectedIndex;
}
Here's a sample that you may want to explore more, try to add ListBoxto your form (in this sample 3 listboxes) it should look like the following:
And here's the source that would select the same index whenever you click on an item on it:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
InitializeListBoxes();
}
private void InitializeListBoxes()
{
//Populate listboxes
listBox1.Items.Add("Apple");
listBox1.Items.Add("Orange");
listBox1.Items.Add("Mango");
listBox2.Items.Add("Milk");
listBox2.Items.Add("Cheese");
listBox2.Items.Add("Butter");
listBox3.Items.Add("Coffee");
listBox3.Items.Add("Cream");
listBox3.Items.Add("Sugar");
//Subscribe to same events
listBox1.SelectedIndexChanged += listBox_SelectedIndexChanged;
listBox2.SelectedIndexChanged += listBox_SelectedIndexChanged;
listBox3.SelectedIndexChanged += listBox_SelectedIndexChanged;
}
void listBox_SelectedIndexChanged(object sender, EventArgs e)
{
ListBox listBox = (ListBox)sender;
listBox1.SelectedIndex = listBox.SelectedIndex;
listBox2.SelectedIndex = listBox.SelectedIndex;
listBox3.SelectedIndex = listBox.SelectedIndex;
}
}
What happens is on the InitializeListBoxes you subscribe to the same event which would trigger the SelectedIndexChanged event, and select appropriate item from each of the ListBox.
to solve your problem, you can use a pattern called Observer: https://msdn.microsoft.com/en-us/library/ee850490(v=vs.110).aspx
Basically, you will have to create a notifier method in the listboxes that you want to notify. When you select an item in listBox1, you will call the notifier method of listBox2.
I solved it using the selected index code in This answer in SelectedValueChanged Event.
private void listBoxControl2_SelectedValueChanged(object sender, EventArgs e)
{
listBoxControl5.SelectedIndex = listBoxControl2.SelectedIndex;
}
Fastest and easiest way can be via MouseDown event:
private void lstBoxes_MouseDown(object sender, MouseEventArgs e)
{
ListBox lstBox = (ListBox)sender;
lstBx1.SelectedIndex = lstBox.SelectedIndex;
lstBx2.SelectedIndex = lstBox.SelectedIndex;
lstBx3.SelectedIndex = lstBox.SelectedIndex;
}

Knowing which ListView has the focus on a form

C# WinForms:
In some application like this:
I want to write a code for "Select All" button.
If I go and check which listview's "SelectedIndex" or "selected Item" property is greater than zero, then it won't work because what if user just has clicked inside the white area of them?
And also form.ActiveControl won't work either because when we click on "SelectAll" button, it is too late! ActiveControl is that SelectAll button.
maybe I could create a class level variable to remember which control has been clicked,etc..but I think there should be a better way....But what?!
Thanks
You could assign the 'GotFocus' event to a method like so, and record the 'last focused' control this way. Then in your SelectAll_CLick handler, if the listview is assigned, selectall, else - don't!
private ListView mLastSelectedListView;
private void ListViews_GotFocus(object sender, EventArgs e)'
{
ListView lv = sender as ListView;
if (null == lv) return;
mLastSelectedListView = lv;
}
private void SelectAll_Click(object sender, EventArgs e)
{
if (null == mLastSelectedListView) return;
mLastSelectedListView.SelectAll();
}
Here's a quick 'SelectAll' extension method to support the above;
public static class ListViewExtensions
{
public static void SelectAll(this ListView lv)
{
foreach (ListViewItem item in lv.Items)
item.Selected = true;
}
}

Categories

Resources