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
{
}
Related
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);
}
}
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;
}
I have a form that only contains an empty ComboBox.
I set the DataSource to an empty BindingList.
When I add something to the BindingList, it is selected and combobox1.SelectedIndex changes, but the event comboBox1_SelectedIndexChanged is not raised even tough it should in my opinion. Why is it not raised? When the single item is removed, the comboBox1_SelectedIndexChanged is fired correctly.
public partial class Form1 : Form
{
public Form1()
{
var test_ = new BindingList<int>();
InitializeComponent();
comboBox1.DataSource = test_;
Console.WriteLine(comboBox1.SelectedIndex); // -1
test_.Add(42); // BUG? no comboBox1_SelectedIndexChanged -> 0
Console.WriteLine(comboBox1.SelectedIndex); // 0
test_.Remove(42); // comboBox1_SelectedIndexChanged -> -1
Console.WriteLine(comboBox1.SelectedIndex); // -1
}
private void comboBox1_SelectedIndexChanged(object sender, EventArgs e)
{
Console.WriteLine("index changed " + comboBox1.SelectedIndex);
}
}
You are not correct with your logic.
comboBox1.SelectedIndex is -1 doesn't mean that you have an item selected in -1 position!
It means there is no item selected in comboBox1.
Adding an item, the SelectedIndex becomes 0. No change on the selection has happened because there was no item been selected in the first place (SelectedIndex = -1).
One way to work around the bug is to tap into the ListChanged event of the BindingList collection you are using:
var test_ = new BindingList<int>();
comboBox1.DataSource = test_;
test_.ListChanged += (sender, e) => {
if (e.ListChangedType == ListChangedType.ItemAdded && test_.Count == 1) {
comboBox1_SelectedIndexChanged(comboBox1, EventArgs.Empty);
}
};
test_.Add(42);
I have this event code of the listBox:
I tried ot do it this way and it's almost working good.
private void listBox1_SelectedIndexChanged(object sender, System.EventArgs e)
{
if (recentItems.Contains(listBox1.SelectedItem))
{
itemExist = true;
item = listBox1.SelectedItem.ToString();
this.f1.PlayLightnings();
f1.pdftoolsmenu();
}
else
{
itemExist = false;
item = listBox1.SelectedItem.ToString();
recentItems.Add(listBox1.SelectedItem.ToString());
this.f1.PlayLightnings();
f1.pdftoolsmenu();
}
}
Im using a new bool variable itemExist and check and if the List recentItems wich is don't contain the selectedItem add it.
And if it does exist set the flag to true.
Then in the other code in Form1 im doing:
if (Lightnings_Extractor.Lightnings_Mode.itemExist == true)
{
if (!pdf1.Lightnings.Contains(Lightnings_Extractor.Lightnings_Mode.item))
{
pdf1.Lightnings.Add(Lightnings_Extractor.Lightnings_Mode.item);
}
}
So it's working as i wanted but the problem is that each new item i select in the listBox click on it i have to click on it twice since first time it's not in the recentItems and only on the second click it does in the recentItems and only on the second click it's changing the flag to true.
So how can i solve this problem in the SelectedIndexChanged event ?
I saw now i don't need the code part in Form1 only this code:
private void listBox1_SelectedIndexChanged(object sender, System.EventArgs e)
{
item = listBox1.SelectedItem.ToString();
this.f1.PlayLightnings();
f1.pdftoolsmenu();
if (item != null && !pdf1.Lightnings.Contains(item.ToString()))
{
pdf1.Lightnings.Add(item.ToString());
}
}
Is there any possibility to get a value of doubleclicked row in ListView?
I registered an event:
private void lvLista_DoubleClick(object sender, EventArgs e)
{
MessageBox.Show(lvLista.SelectedItems.ToString());
}
But on message, when i doubleclick some row in listview i get:
System.Windows.Forms.ListView+SelectedListViewItemCollection
What is more, I have got 2 columns in listview:
lvLista.Columns.Add("ID");
lvLista.Columns.Add("Tilte");
And i want to show in messagebox the "ID" of doubleclicked row.
How to do it? How to get a values from this event?
If you handle the MouseDown and/or MouseDoubleClick events of the ListView control, and use the HitTest method to determine the target of the mouse action, you will know which item has been double clicked. This is also a good means to determine if NO item was clicked (for example, clicking on the empty area in a partially filled list.
The following code will display the clicked item in a textbox if a single click occurs, and will pop up a message box with the name of the double-clicked item if a double click occurs.
If the click or double click occur in an area of the list view not populated by an item, the text box or message box inform yopu of that fact.
This is a trivial example, and depending on your needs, you will have to mess with it a little.
UPDATE: I added some code which clears the SelectedItems property of the Listview control when an empty area of the list is clicked or double-clicked.
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
listView1.MouseDown += new MouseEventHandler(listView1_MouseDown);
listView1.MouseDoubleClick += new MouseEventHandler(listView1_MouseDoubleClick);
this.Load += new EventHandler(Form1_Load);
}
void Form1_Load(object sender, EventArgs e)
{
this.SetupListview();
}
private void SetupListview()
{
ListView lv = this.listView1;
lv.View = View.List;
lv.Items.Add("John Lennon");
lv.Items.Add("Paul McCartney");
lv.Items.Add("George Harrison");
lv.Items.Add("Richard Starkey");
}
void listView1_MouseDoubleClick(object sender, MouseEventArgs e)
{
ListViewHitTestInfo info = listView1.HitTest(e.X, e.Y);
ListViewItem item = info.Item;
if (item != null)
{
MessageBox.Show("The selected Item Name is: " + item.Text);
}
else
{
this.listView1.SelectedItems.Clear();
MessageBox.Show("No Item is selected");
}
}
void listView1_MouseDown(object sender, MouseEventArgs e)
{
ListViewHitTestInfo info = listView1.HitTest(e.X, e.Y);
ListViewItem item = info.Item;
if (item != null)
{
this.textBox1.Text = item.Text;
}
else
{
this.listView1.SelectedItems.Clear();
this.textBox1.Text = "No Item is Selected";
}
}
}
Try this:
private void lvLista_DoubleClick(object sender, EventArgs e)
{
MessageBox.Show(lvLista.SelectedItems[0].SubItems[0].Text);
}
I know this thread is old but nobody here answered the question properly in my opinion. For those in the future, try this, from MSDN:
// User must double-click to activate item
myListView.Activation = System.Windows.Forms.ItemActivation.Standard;
// Add event handler
myListView.ItemActivate += new
System.EventHandler(this.myListView_ItemClick);
Since the accepted answer didn't help me i thought that I would share my solution to the same problem: getting data from a specific column in a listview in the double click event.
The following line returns the data of the second column in the row that I've double clicked on as a string:
private void listViewOutput_DoubleClick(object sender, EventArgs e)
{
string content = listViewOutput.Items[listViewOutput.SelectedIndices[0]].SubItems[1].Text
}
Thanks; this is what I needed. I thought I'd also mention one could set up the local info variable more generally as:
ListViewHitTestInfo info = ((ListView)sender).HitTest(e.X, e.Y);
Try this
private void listView1_MouseClick(object sender, MouseEventArgs e)
{
ListViewHitTestInfo hit = listView1.HitTest(e.Location);
Rectangle rowBounds = hit.SubItem.Bounds;
Rectangle labelBounds = hit.Item.GetBounds(ItemBoundsPortion.Label);
int leftMargin = labelBounds.Left - 1;
string x = hit.Item.Text;
}