listView ItemSelectionChanged 'Is Selected' not working as expected? - c#

I'm having a problem with a ListView ItemSelectionChanged event being called twice. From searching I've found it's because the event is being called when an item is selected and again when an item is deselected. I only want it to fire when an item is selected, not when it's deselected. The solution seems to have an if(e.IsSelected) at the beginning of the event so it's only called when an item is selected, but that's not working for me in this case. Here's the basic structure of my code:
private void SelectionChanged(object sender, ListViewItemSelectionChangedEventArgs e)
{
if (e.IsSelected)
{
DialogResult GetDialogResult =
MessageBox.Show("Keep this item selected?",
"Keep Selected",
MessageBoxButtons.YesNo);
if (GetDialogResult == DialogResult.No)
listView1.SelectedItems[0].Selected = false;
}
}
MultiSelect is disabled so the selected item index will always be 0. My problem is I want the selected item to be deselected if the DialogResult is No, but when you click no, the SelectionChanged event is firing again, and e.IsSelected is apparently still true, so the Dialog Box pops up a second time. I imagine it has something to do with the fact that the first event hasn't completely finished executing when the item is being deselected, but I'm not sure how to solve that and make it so the Dialog Box only shows up once. Any suggestions?
Edit: Something else I've tried now is instead of Deselecting the item in the listBox, clearing all items in the listbox and recreating them. If the items are all cleared, the dialog box doesn't come up a second time. However if the items are immediately recreated, the same item becomes selected again and the dialog box still comes up the 2nd time.

If the behaviour you want is:
User clicks an item
Dialog pops up
If they click 'no' on the popup, selection doesn't happen
If they click 'yes', it does
then one approach is to unhook your event handler before you set the Selected property to false, then rehook afterwards, as follows:
if (GetDialogResult == DialogResult.No)
{
listView1.ItemSelectionChanged -= SelectionChanged;
e.Item.Selected = false;
listView1.ItemSelectionChanged += SelectionChanged;
}
This will stop the event handler from triggering again until your operation has completed.

ItemSelectionChanged (like several other events on other controls) gets fired when you change the value from the program as well. So when you run this line of code, that counts as a "selection changed"...
listView1.SelectedItems[0].Selected = false;
One way to handle this is by setting a flag:
private bool _programChangingSelection;
private void SelectionChanged(object sender, EventArgs e)
{
if (_programChangingSelection)
{
_programChangingSelection = false;
return;
}
if (e.IsSelected)
{
DialogResult GetDialogResult =
MessageBox.Show("Keep this item selected?",
"Keep Selected",
MessageBoxButtons.YesNo);
if (GetDialogResult == DialogResult.No)
{
_programChangingSelection = true;
listView1.SelectedItems[0].Selected = false;
}
}
}
Although, personally I think the add/remove handler method is probably a bit more elegant.

I've found a solution that works. Calling a function to deselect the item asynchronously in a separate thread seems to have solved the issue for me. Without doing this, the second SelectionChanged event is called and has to finish executing before the first one can finished executing, which apparently causes the problem I was seeing. Here is the code I came up with that has worked:
delegate void DeselectDelegate();
public void DeselectItem()
{
if (this.listView1.InvokeRequired)
{
DeselectDelegate del = new DeselectDelegate(DeselectItem);
this.Invoke(del);
}
else
{
listView1.SelectedItems[0].Selected = false;
}
}
private void SelectionChanged(object sender, ListViewItemSelectionChangedEventArgs e)
{
if (e.IsSelected)
{
DialogResult GetDialogResult =
MessageBox.Show("Keep this item selected?",
"Keep Selected",
MessageBoxButtons.YesNo);
if (GetDialogResult == DialogResult.No)
{
Thread thread = new Thread(DeselectItem);
thread.Start();
}
}
}

Related

Treeview - Unable to restore previous selection on clicking cancel on a message pop up that is placed on a NodeMouseClick event

Consider a TreeView control containing multiple nodes. On selecting a node, certain information is displayed on a text box. This information can be modified and saved. Accidentally, if a user navigates to a different node without saving, a pop is displayed asking the user to save or cancel the action. If cancel is clicked, selection should remain on the previously selected node instead of a new node. The logic to display pop up is written in NodeMouseClick() event of TreeView. Is there any way to achieve this ?. Below is the sample code snippet.
private void TreeView1_NodeMouseClick(object sender,
TreeNodeMouseClickEventArgs e)
{
DialogResult dr = MessageBox.Show("Cancel
Clicked......!!!","Information", MessageBoxButtons.OKCancel,
MessageBoxIcon.Information);
if(dr == DialogResult.Cancel)
{
//Need to handle previous node selection
}
}
NodeMouseClick() event doesn't seem to be providing any information to cancel the event. I have already tried BeforeSelect() event of TreeView to cancel the event using below code snippet.
private void TreeView1_BeforeSelect(object sender, TreeViewCancelEventArgs e)
{
e.Cancel = true;
}
But the above code removes the selection on all the nodes and doesn't suffice the need. Is there any way we can retain previous selection?
You would do this in the TreeView's BeforeSelect event.
If you added an event handler for the BeforeSelect event that literally just does e.Cancel = true you will never be able to select a node as the selection will be canceled every time. Instead, you need to show your message box inside of the BeforeSelect event, and set e.Cancel based on the result of the message box.
private void treeView1_BeforeSelect(object sender, TreeViewCancelEventArgs e)
{
if (DataIsDirty())
{
if (MessageBox.Show(
"Unsaved changes detected. Press OK to switch nodes and lose the change, or Cancel to stay on the current node.",
"Unsaved Changes Detected",
MessageBoxButtons.OKCancel) == DialogResult.Cancel)
{
e.Cancel = true;
}
}
}

WPF DataGrid SelectionChanged event cancelation cell focus issue

I am performing the check inside the datagrid selection changed event.
If my condition is satisfied the code should get executed and the selected item should be highlighted (or gets focus), if the condition is not satisfied the control should return and
the previously selected item should remain selected.
What's happening here is if condition is not met the selected item is not changing (which is working as desired) but the focus still gets shifted to the cell selected now, so the cell selected item is the previous cell while the one in focus is the cell that fired the event.
I've tried datagrid.dispatcher.invoke approach but that doesn't seem to be working.
Also I've tried to set the datagrid.selectedindex=e.removeditem[0] which leads the control again into selection changed event thus by putting into a continuous loop.
Please suggest something.
EDIT:
dataGrid_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (!IsDirty)
{
if (e.AddedItems.Count > 0)
{
SelectedProfile = e.AddedItems[0] as profile;
}
if (e.RemovedItems.Count > 0)
{
}
}
else
{
MessageBox.Show("Save the profile selected", MessageBoxButton.OK, MessageBoxImage.Warning);
return;
}
}
Prevent the loop.
void OnSelectionChanged(Object sender, SelectionChangedEventArgs e)
{
// condition code
if (conditionFailed)
{
datagrid.SelectionChanged -= OnSelectionChanged;
datagrid.Selectedindex = e.Removeditem[0];
datagrid.SelectionChanged += OnSelectionChanged;
}
}
Solved my problem.
What need to be done was to enable the dirty flag at textbox_PreviewKeyDown() event
and then performing the dirty check at datagrid_PreviewMouseLeftButtonDown() event.
If dirty is found set then setting e.handled=true so that control skips code execution for this flow.

ListView Checkbox Behavior

I have a Winform application I'm modifying for a friend. It has a listview and wants me to add a checkbox to each row and make them mutually exclusive. So in my testing of how to make it work I found a strange behavior and as hopping someone could tell me what I'm missing.
If I display the List view with no checkbox checked. When I click right on the checkbox I cannot get it to check, but the row dose get selected. If I click on the item (the name in this case) in the column it does get checked and selected.
No matter where I click on a row, any checkboxes in rows not selected will be uncheck. Here is my little test program. I’m using .NET 4
public partial class Form1 : Form {
public Form1() {
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e) {
TestListView.Items.Add("Bob");
TestListView.Items.Add("Ann");
TestListView.Items.Add("Frank");
}
void TestListView_MouseClick(object sender, System.Windows.Forms.MouseEventArgs e) {
ListViewItem currentItem = TestListView.GetItemAt(e.X, e.Y);
if (currentItem != null) {
foreach (ListViewItem item in TestListView.Items) {
if (item.Text == currentItem.Text) {
item.Checked = true;
item.Selected = !currentItem.Selected;
}
else
item.Checked = false;
}
}
}
}
Seems that WinForms is checking the checkbox when you click on the checkbox directly, and then your code immediately undoes the checking, so you never see it.
Perhaps instead of MouseClick you should use the ItemCheck or ItemChecked event. The first is fired before the Checked property changes, the second after.
I'm currently having a very similar issue as well, however in response to Timwi it's not the code doing the unchecking. I've been stepping through it very slowly and as the code fires when clicking on a checkbox, it states that it has checked it. But when the form resumes, it is unchecked again. After reading Timwi post, he lead me onto the answer. It's not the code doing the unchecked, but the winforms event firing afterwards that unchecks the box. This fixes it:
My code is:
private bool allowCheck = false;
private bool preventOverflow = true;
private void lstvwRaiseLimitStore_MouseClick(object sender, MouseEventArgs e)
{
preventOverflow = false;
ListViewItem item = lstvwRaiseLimitStore.HitTest(e.X, e.Y).Item;
if (item.Checked)
{
allowCheck = true;
item.Checked = false;
}
else
{
allowCheck = true;
item.Checked = true;
}
}
private void lstvwRaiseLimitStore_ItemChecked(object sender, ItemCheckedEventArgs e)
{
if (!preventOverflow)
{
if (!allowCheck)
{
preventOverflow = true;
e.Item.Checked = !e.Item.Checked;
}
else
allowCheck = false;
}
}
So what it is doing, first I have to set a flag to prevent overflow, otherwise when the form is built or when you uncheck windows checking the box, it keeps looping the code and will eventually stack overflow. Next flag is the actual allow checking of the checkbox via your code and not via another method.
Clicking on them item, it locates where the click was and then sets the flag to allow a check to be done. The code then checks the box and the item checked section kicks off, becuase it was done by our code, it does nothing but reset the allowcheck flag.
If you clicked on a line, it does nothing else, however if it was a checkbox, at the end of our code, the Windows function kicks off and tries to check the box, becuase the allowcheck flag is false, the code first sets a flag to say I'm already preventing a check and then resets the check back to it's intial status. Becuase this is done, the itemchecked event kicks off again, but the code has set the flag to prevent it from doing anything. Then the code finishes and it has prevented windows from checking the check box and only allowed our code.
Hope it helps!

ListView ItemChanged puzzle with MessageBoxes - ItemSelectionChanged called twice

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.

Cancelling ListBox SelectedIndexChange Event

Is it possible to cancel the SelectedIndexChange event for a listbox on a winforms application? This seems like such a logical thing to have that I must be overlooking some easy feature. Basically, I have been popping up a message box asking if the user really wants to move to another item, as this will change the UI and I don't want their changes to be lost. I'd like to be able to cancel the event in case the user has not saved what they are working on. Is there a better way of doing this?
You cannot cancel it.
What I did just a couple of days ago was to have a variable with the latest selected index. Then when the event fires, you ask the user if he wants to save, this is done in the eventhandler. If the user selected "Cancel" you change the id again.
The problem is that this will make the event fire once again. So what i've used is a bool just saying "Inhibit". And at the top of the eventhandler I have:
if(Inhibit)
return;
Then below this where you ask the question you do something like this:
DialogResult result = MessageBox.Show("yadadadad", yadada cancel etc);
if(result == DialogResult.Cancel){
Inhibit = true; //Make sure that the event does not fire again
list.SelectedIndex = LastSelectedIndex; //your variable
Inhibit = false; //Enable the event again
}
LastSelectedIndex = list.SelectedIndex; // Save latest index.
This is exactly #Oskar Kjellin 's method, but with a twist. That is, one variable less and with a selected index changed event that really behaves like a selected index changed event. I often wonder why is selected index changed event getting fired even if I click on the exact same selected item. Here it doesn't. Yes it's a deviation, so be doubly sure if you want this to be there.
int _selIndex = -1;
private void listBox1_SelectedIndexChanged(object sender, EventArgs e)
{
if (listBox1.SelectedIndex == _selIndex)
return;
if (MessageBox.Show("") == DialogResult.Cancel)
{
listBox1.SelectedIndex = _selIndex;
return;
}
_selIndex = listBox1.SelectedIndex;
// and the remaining part of the code, what needs to happen when selected index changed happens
}
I just ran into this exact problem. What I did is when the user makes changes, I set ListBox.Enabled = false; This disallows them to select a different index. Once they either save or discard their changes, I set ListBox.Enabled = true; Probably not as slick as a prompt, but it gets the job done.
More elegant, use the Tag property:
if ((int)comboBox.Tag == comboBox.SelectedIndex)
{
// Do Nothing
}
else
{
if (MessageBox.Show("") == DialogResult.Cancel)
{
comboBox.SelectedIndex = (int)comboBox.Tag;
}
else
{
// Reset the Tag
comboBox.Tag = comboBox.SelectedIndex;
// Do what you have to
}
}
The SelectedIndexChanged cannot be cancelled. So you only have one real option:
private int? currentIndex;
public void ListBox_SelectedIndexChanged(sender, EventArgs args) {
if (currentIndex.HasValue && currentIndex.Value != listBox1.SelectedIndex) {
var res = MessageBox.Show("Do you want to cancel edits?", "Cancel Edits", MessageBoxButtons.YesNo);
if (res == DialogResult.Yes) {
currentIndex = (listBox1.SelectedIndex == -1 ? null : (int?) listBox1.SelectedIndex);
} else {
listBox1.SelectedIndex = currentIndex.Value;
}
}
}
This is my way to cancel SelectionChange for ComboBox. I think it could also fit to ListBox.
private bool comboBox_CancelSelection = false;
private int comboBox_LastSelectedIndex = -1;
private void comboBox_SelectedIndexChanged(object sender, EventArgs e) {
if (comboBox_CancelSelection) {
comboBox_CancelSelection = false;
return ;
}
// Handle Event
if (!comoBox_CancelSelection) {
comboBox_LastSelectedIndex = comboBox.SelectedIndex;
} else {
comboBox.SelectedIndex = comboBox_LastSelectedIndex;
}
}

Categories