ListView ItemChanged puzzle with MessageBoxes - ItemSelectionChanged called twice - c#

Here is the problem: I have a Windows Forms application that I'm developing, and in one segment I'm using a ListView control.
What I'm trying can be simply stated as: on event ListViewItemSelectionChange show a MessageBox for user to confirm the change, if not confirmed change to let's say the first item. This change to the first item would again fire ListViewItemSelecionChange, so I unregister and re-register the event handler method, so everything should be good, right?
What actually happens is that the handler method is called twice (actually ListView should fire two events on Selection change, one for deselect, other for newly selected item, but I have an e.IsSelected statement at the beginning to catch only selected items, so actually you could say that there are four events fired).
The problem is, if I generated the first event with mouse click on ListView item, and I've unsubscribed before programatically changing to the first item, what generates the second event firing? Is it some focus change because of the MessageBox call? Is there any way to prevent the second event to fire?
I have a simple example solution here, it can't be more simlified (25 SLOC), so if you can, please take a look. Note that commenting the line "if (ShowMessageBox())" stops the second event from firing, is this some focus change problem?
http://www.filedropper.com/listviewtestwithmsgbox
Edit: the relevant code:
private void listViewWithSelection1_ItemSelectionChanged(object sender, ListViewItemSelectionChangedEventArgs e)
{
// listview actually generates two ItemSelectionChanged events,
// one for deselect of a item, and another event for a newly selected item (which we want here).
if (e.IsSelected)
{
if (ShowMessageBox())
Button1_Click(null, EventArgs.Empty);
label1.Text += "item selected ";
}
}
private bool ShowMessageBox()
{
return MessageBox.Show("Change to first item instead?", "test", MessageBoxButtons.YesNo) == DialogResult.Yes;
}
private void Button1_Click(object sender, EventArgs e)
{
// change ti first ListView item
listView1.ItemSelectionChanged -= listViewWithSelection1_ItemSelectionChanged;
listView1.Items[0].Selected = true;
listView1.ItemSelectionChanged += listViewWithSelection1_ItemSelectionChanged;
}

Hmm, can you describe how the selection is being changed to begin with? If it's by the user clicking to select an item, perhaps catch the Click or DoubleClick event rather than the ItemSelectionChanged event? I have this snippet I'm using on a program currently. If the user double clicks the list box (listView, in your case), do something with the selected item.
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private bool ShowMessageBox()
{
return MessageBox.Show("Change to first item instead?", "test", MessageBoxButtons.YesNo) == DialogResult.Yes;
}
private void listView1_Click(object sender, EventArgs e)
{
if (ShowMessageBox())
listView1.TopItem.Selected = true;
label1.Text += "item selected ";
}
}
Edited to include relevant code.

One way to do this is to have a flag which says should the on change code run.
In your ListViewItemSelecionChange code you check the value of the flag and run code accordingly.

Related

Winforms ListView MouseUp event firing more than once

In my .NET 4,5 Winforms application, the ListView control's MouseUp event is firing multiple times when I open a file from that event as follows:
private void ListView1_MouseUp(object sender, MouseEventArgs e)
{
ListViewHitTestInfo hit = ListView1.HitTest(e.Location);
if (hit.SubItem != null && hit.SubItem == hit.Item.SubItems[1])
{
System.Diagnostics.Process.Start(#"C:\Folder1\Test.pdf");
MessageBox.Show("A test");
}
}
Note: When clicking on the SubItem1 of the listview, the file opens but the message box appears at least twice. But, when I comment out the line that opens the file the message box appears only once (as it should). I do need to open the file whose name is clicked by the user in the listview. How can I achieve this without the MoueUp event firing multiple times?
Please also note that the MouseClick event for listview does not always work as also stated here. So, I have to use MouseUp event.
EDIT: I should mention the ListView is in Details mode.
Avoid HitTest() and use ListView's native function GetItemAt(). An example from MSDN looks like this:
private void ListView1_MouseDown(object sender, MouseEventArgs e)
{
ListViewItem selection = ListView1.GetItemAt(e.X, e.Y);
// If the user selects an item in the ListView, display
// the image in the PictureBox.
if (selection != null)
{
PictureBox1.Image = System.Drawing.Image.FromFile(
selection.SubItems[1].Text);
}
}

C# combobox appropriate event type

I have a combobox and with this I have a associated event
private void comboBox8_SelectedIndexChanged(object sender, EventArgs e)
{
}
My comobox is populated with two items a and b
I am setting combobox8.selectedItem = x where x= a or b. My event only fires if I select a from b or b from a. It does not fire if I again select a from a.
How can I do it and what's the appropriate event to deal with it ?
Moreover I am doing it all programmatically.
It makes sense that the event doesn't fire again. The selected item doesn't change. Depending on what you actually want, there are a lot of events you can utilize. You might start with Click, or DropDown, or DropDownClosed, for instance.
It won't fire because Selected Index did not change...
Take a look at msdn documentation for a list of comboBox events:
http://msdn.microsoft.com/en-us/library/system.windows.forms.combobox_events.aspx
You'll find out you can use more one depending on what you want to achieve (leave,lostfocus, [...])
Because the index did not change, the event is not fired. Since you need this processing when you are refreshing the form programmatically, call the appropriate code programmatically as well:
private void comboBox8_SelectedIndexChanged(object sender, EventArgs e)
{
ProcessComboBoxInput();
}
private void RefreshFormProgrammatically()
{
// Refresh the form here...
ProcessComboBoxInput();
}
private void ProcessComboBoxInput()
{
// Process the comboBox8 here...
}
Because its selected index changed event. From a to a nothing has been changed. You can try onclick event.

List View Selected Index Changed in a multi-selectable list view

I have a windows form with a listview control. I set the MultiSelect property to true and I added a selected_index changed event.
I get the event fired when I click the same index as the current selected index.
My expectation is that I will not get the event fired. The strange thing is that the event fired 1 second after I click the index.
I appreciate for any reply to explain why this is happening.
Edited:
Sample Code:
private void Form1_Load(object sender, EventArgs e)
{
listView1.View = View.Details;
listView1.MultiSelect = true;
listView1.FullRowSelect = true;
listView1.Columns.Add("Number");
listView1.Items.Add("1");
listView1.Items.Add("2");
listView1.Items.Add("3");
listView1.Items.Add("4");
}
private void listView1_SelectedIndexChanged(object sender, EventArgs e)
{
if (listView1.SelectedItems.Count > 0)
{
MessageBox.Show("Selected Index Changed event fired: ");
}
}
Follow these steps to see the problem:
Try to select one item, for instance: select number 3
expected result: listview1_SelectedIndexChanged is fired
Result: It is fired.
Try to click the number 3 again.
expected result: listview1_SelectedIndexChanged is NOT fired
Result: It is fired with one second delay.
From the MSDN documention on ListView.SelectedIndexChangedEvent:
In a multiple selection ListView control, this event occurs whenever an item is removed or added to the list of selected items. To determine which items are selected in the ListView control, use the SelectedItems property to access the ListView.SelectedListViewItemCollection.
As for why the event waited so long to fire: I can only imagine the processor was tied up doing something else. Do you have more details regarding what you're seeing, exactly (sample code would help)?

C# ComboBox GotFocus

I have a C# ComboBox using WPF. I have code that executes when the ComboBox's GotFocus is activated. The issue is that the GotFocus event is executed every time a selection is made from the ComboBox. For example, the GotFocus is executed when you first click on the ComboBox and then when you make a selection even though you have not click on any other control.
Is it possible to prevent this event from firing if a selection is being made in the list or is there a flag or something else in the event handler that can be used to determine if the GotFocus event handler was fired as a result of the user selecting an item in the list?
You can solve this problem with next verification:
private void myComboBox_GotFocus(object sender, RoutedEventArgs e)
{
if (e.OriginalSource.GetType() == typeof(ComboBoxItem))
return;
//Your code here
}
This code will filter all focus events from items (because they use bubble routing event). But there is another problem - specific behaviour of WPF ComboBox focus: when you open drop-down list with items your ComboBox losing focus and items get. When you select some item - item losing focus and ComboBox get back. Drop-down list is like another control. You can see this by simple code:
private void myComboBox_GotFocus(object sender, RoutedEventArgs e)
{
if (e.OriginalSource.GetType() != typeof(ComboBoxItem))
{
Trace.WriteLine("Got " + DateTime.Now);
}
}
private void myComboBox_LostFocus(object sender, RoutedEventArgs e)
{
if (e.OriginalSource.GetType() != typeof(ComboBoxItem))
{
Trace.WriteLine("Lost " + DateTime.Now);
}
}
So you will get anyway atleast two focus events: when you select ComboBox and when you selecting something in it (focus will return to ComboBox).
To filter returned focus after selecting item, you can try to use DropDownOpened/DropDownClosed events with some field-flag.
So the final code with only 1 event of getting focus:
private bool returnedFocus = false;
private void myComboBox_GotFocus(object sender, RoutedEventArgs e)
{
if (e.OriginalSource.GetType() != typeof(ComboBoxItem) && !returnedFocus)
{
//Your code.
}
}
private void myComboBox_LostFocus(object sender, RoutedEventArgs e)
{
if (e.OriginalSource.GetType() != typeof(ComboBoxItem))
{
ComboBox cb = (ComboBox)sender;
returnedFocus = cb.IsDropDownOpen;
}
}
Choose from this examples what you actually need more for your application.
I'm not too hot on WPF; but if you're trying to detect changes to the list (click on new value etc) you can use SelectedIndexChanged events..
On the other hand, if you really do want to know simply when the control is focussed, can you filter it by saying something like;
if (combo1.Focused && combo1.SelectedIndex == -1)
{
...
}
.. ? It really depends on what youre trying to detect, exactly.
Another solution is used is to determine whether the new focused element is an existing item in the combobox. If true then the LostFocus event should not be performed, because the combobox still has focus. Otherwise an element outside the combobox received focus.
In the code snipplet below I added the functionality in a custom combobox class
public class MyComboBox : System.Windows.Controls.Combobox
{
protected override void OnLostFocus(RoutedEventArgs e)
{
//Get the new focused element and in case this is not an existing item of the current combobox then perform a lost focus command.
//Otherwise the drop down items have been opened and is still focused on the current combobox
var focusedElement = FocusManager.GetFocusedElement(FocusManager.GetFocusScope(this));
if (!(focusedElement is ComboBoxItem && ItemsControl.ItemsControlFromItemContainer(focusedElement as ComboBoxItem) == this))
{
base.OnLostFocus(e);
/* Your code here... */
}
}
}

DataGridView's bound table only generates one RowChanged event on a double-click. How do I make it do two?

I have a DataGridView whose DataSource is a DataTable.
This DataTable has a boolean column, which is interpreted as a checkbox in the DataGridView.
employeeSelectionTable.Columns.Add("IsSelected", typeof(bool));
...
employeeSelectionTable.RowChanged += selectionTableRowChanged;
dataGridViewSelectedEmployees.DataSource = employeeSelectionTable;
...
private void selectionTableRowChanged(object sender, DataRowChangeEventArgs e)
{
if ((bool)e.Row["IsSelected"])
{
Console.Writeline("Is Selected");
}
else
{
Console.Writeline("Is Not Selected");
}
break;
}
When the user single-clicks on a checkbox, it gets checked, and selectionTableRowChanged will output "Is Selected."
Similarly, when the user checks it again, the box gets cleared, and selectionTableRowChanged outputs "Is Not Selected."
Here's where I have the problem:
When the user double-clicks on the checkbox, the checkbox gets checked, the RowChanged event gets called ("Is Selected"), and then the checkbox is cleared, and no corresponding RowChanged event gets called. Now the subscriber to the the RowChanged event is out of sync.
My solution right now is to subclass DataGridView and override WndProc to eat WM_LBUTTONDBLCLICK, so any double-clicking on the control is ignored.
Is there a better solution?
The reason that making an empty DoubleClick event method would not help would be that is executed in addition to the other operations that happen when a double click occurs.
If you look at the windows generated code or examples of programatically adding event handlers, you use += to assign the event handler. This means you are adding that event handler in addition to the others that already exist, you could have multiple event handlers being triggered on the save event.
My instinct would have been to override the DataGridView class, then override the OnDoubleClick method and not call the base OnDoubleClick method.
However, I have tested this real quick and am seeing some interesting results.
I put together the following test class:
using System;
using System.Windows.Forms;
namespace TestApp
{
class DGV : DataGridView
{
private string test = "";
protected override void OnDoubleClick(EventArgs e)
{
MessageBox.Show(test + "OnDoubleClick");
}
protected override void OnCellMouseDoubleClick(System.Windows.Forms.DataGridViewCellMouseEventArgs e)
{
MessageBox.Show(test + "OnCellMouseDoubleClick");
}
protected override void OnCellMouseClick(System.Windows.Forms.DataGridViewCellMouseEventArgs e)
{
if (e.Clicks == 1)
{
// Had to do this with a variable as using a MessageBox
// here would block us from pulling off a double click
test = "1 click ";
base.OnCellMouseClick(e);
}
else
{
MessageBox.Show("OnCellMouseClick");
}
}
}
}
Then inserted this into a windows form, adding a checkbox column and ran the program.
On a fresh run, double clicking on the checkbox causes the messagebox display to say "1 click OnDoubleClick".
This means that OnCellMouseClick executed on the first part of the double click and then OnDoubleClick executed on the second click.
Also, unfortunately, the removal of the call to the base methods doesn't seem to be preventing the checkbox from getting the click passed to it.
I suspect that for this approach to work it may have to be taken further and override the DataGridViewCheckBoxColumn and DataGridViewCheckBoxCell that ignores the double click. Assuming this works, you would be able to stop double click on the checkbox but allow it still on your other column controls.
I have posted an answer on another question that talks about creating custom DataGridView columns and cells at here.
In case you want a checkbox column inside a DataGridView, create something like this:
DataGridViewCheckBoxCell checkBoxCell = new MyDataGridViewCheckBoxCell();
...
DataGridViewColumn col = new DataGridViewColumn(checkBoxCell);
...
col.Name = "colCheckBox";
...
this.dgItems.Columns.Add(col);
where dgItems is DataGridView instance.
As you can see I have a MyDataGridViewCheckBoxCell class which is a subclass of the DataGridViewCheckBoxCell class. In this subclass I define:
protected override void OnContentDoubleClick(DataGridViewCellEventArgs e)
{
//This the trick to keep the checkbox in sync with other actions.
//base.OnContentDoubleClick(e);
}
When the user now double clicks a checkbox in the checkbox column the checkbox will behave as if it was single clicked. This should solve your sync problem.
This isn't exactly an elegant solution, but why not simply do the following?
private void dgv_CellDoubleClick(object sender, DataGridViewCellEventArgs e)
{
dgv_CellClick(sender, e);
}
This would explicitly force the second CellClick event and avoid out of sync.
Is there some reason it needs to be done that low level? Can the DoubleClick Method just be an empty method that eats it?
I already tried overriding the OnDoubleClick method in the DataGridView subclass to do nothing, but it still allowed the checkbox to be changed a second time.
Ideally I was looking to have the DataTable's RowChanged event get called twice.
Is there a way to affect the underlying DataTable via the overridden OnDoubleClick method?
Why not just leave IsSelected column unbounded? Use CellContentClick event to push the data to underlying datatable and leave CellDoubleClick or RowChange event alone.
private void dgv_CellContentClick(object sender, DataGridViewCellEventArgs e)
{
if(e.ColumnIndex == <columnIndex of IsSelected>)
{
string value = dgv[e.ColumnIndex, e.RowIndex].EditedFormattedValue;
if( value == null || Convert.ToBoolean(value) == false)
{
//push false to employeeSelectionTable
}
else
{
//push true to employeeSelectionTable
}
}
}
Don't know why i had to, but i was able to put in a timer tick event attached to a datagridview refresh that just refreshed the dgv after the second click.
In cell click event
** _RefreshTimer = new Timer();
_RefreshTimer.Tick += new EventHandler(RefreshTimer_Tick);
_RefreshTimer.Interval = 100;
_RefreshTimer.Start();
}
}
}
void RefreshTimer_Tick(object sender, EventArgs e)
{
dgv.Refresh();
_RefreshTimer.Stop();
_RefreshTimer = null;
}**

Categories