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;
}
}
Related
I am having a bit of an issue with the datagridview's CellEndEdit event. While I understand the concept of what the issue actually is, any attempt at circumventing it seems to fail.
Basically, I have a datagridview, in the CellEndEdit event, I make a check against the database to make sure the entry is not a duplicate. If it is, I prompt the user with a messagebox to tell them they can't enter duplicates, I then change the value back to its original state/value programmatically, and return the cell to an "Edit" state.
My understanding is that the fact that i'm changing the value programatically is why the event fires twice. To circumvent, I set a flag upon first entering the event, then prompt + set + re-edit, then set the flag to false. This does not work... can anyone tell me why or how I can make this happen?
Here's the event code:
private void dataGridView1_CellEndEdit(object sender, DataGridViewCellEventArgs e)
{
if(e.ColumnIndex == this.dataGridView1.Columns["Name"].ColumnIndex)
{
if(!this.CellBeingEdited)
{
string NewName = this.dataGridView1.Rows[e.RowIndex].Cells["Name"].Value.ToString();
//-== DATABASE CODE REMOVED ==-
bool IsDuplicate = ...;
if(IsDuplicate)
{
MessageBox.Show("Cannot have duplicate item names at this level!");
this.dataGridView1.CurrentCell = this.dataGridView1.Rows[e.RowIndex].Cells["Name"];
this.CellBeingEdited = true;
this.dataGridView1.CurrentCell.Value = this.LastEditedRowName;
this.CellBeingEdited = false;
this.dataGridView1.BeginEdit(false);
return;
}
}
}
}
This bit of code does not fire twice when I edit a value in a row :
private void dataGridView1_CellEndEdit(object sender, DataGridViewCellEventArgs e)
{
string test = "test";
if (this.dataGridView1.Rows[e.RowIndex].Cells[0].Value.ToString() == test)
{
this.dataGridView1.CurrentCell = this.dataGridView1.Rows[e.RowIndex].Cells[0];
this.dataGridView1.CurrentCell.Value = "not test";
this.dataGridView1.BeginEdit(false);
return;
}
}
Perhaps are you calling the event elsewhere?
I'd like to implement a scenario that requires me to know if the user selects an item or deselects everything by clicking on an empty space. Basically, I need to keep the first item (which always exists) selected. It should be possible to select a different item, but as soon as the user clicks on empty space and when normally selection will be removed it should jump back to the first (default) item.
I tried to employ the selection changed events of the listview control, but those are triggered in a sequence which is not very helpful: first the currently selected item loses the selection. Then there's a separate event to indicate that a new item is selected.
The question is: when the event ItemSelectionChanged is triggered for the first time to say that a previously selected item is not selected any more, is it possible to know if that item was de-selected because another item is being selected or because nothing is selected?
Try this to see if it can solve your problem. We remove the event callback to prevent the event to be called again, apply our logic, and re-assign the callback.
private void listView1_ItemSelectionChanged(object sender, ListViewItemSelectionChangedEventArgs e)
{
listView1.ItemSelectionChanged -= listView1_ItemSelectionChanged;
if (!e.IsSelected)
{
listView1.Items[0].Selected = true;
listView1.Items[0].Focused = true;
}
else
{
listView1.Items[0].Selected = false;
listView1.Items[e.ItemIndex].Selected = true;
}
listView1.ItemSelectionChanged += listView1_ItemSelectionChanged;
}
Thanks for the answers.
I solved it altogether differently: upon a selection change event I enable a short timer which, when ticked, checks whether or not there's a selected item in the ListView, and if not, it selects the first one. And it disables itself in any case. Seems that it works for me, so I am stopping there.
This will remember previous selection and keep it. (I needed it for my project and got some help here).
private void listView1_ItemSelectionChanged(object sender, ListViewItemSelectionChangedEventArgs e)
{
int temp = 0;
listView1.ItemSelectionChanged -= listView1_ItemSelectionChanged;
if (!e.IsSelected)
{
temp = e.ItemIndex;
listView1.Items[temp].Selected = true;
listView1.Items[temp].Focused = true;
}
else
{
for (int y = listView1.Items.Count - 1; y >= 0; y--)
{
listView1.Items[y].Selected = false;
}
listView1.Items[e.ItemIndex].Selected = true;
}
listView1.ItemSelectionChanged += listView1_ItemSelectionChanged;
}
I have a LookupEdit in the grid that needs to be able to accept new values. When I enter the new value and press "Enter" or "Tab", it saves normally via the ProcessNewValue event, however when I enter a value and click elsewhere in the grid, on another cell or just in white space, the value vanishes completely. By implementing several other events and setting breakpoints all I figured out was that the value disappears before the "CloseUp" event fires. Validating, EditValueChanged, EditValueChanging, ProcessNewValue, Closed, Leave, and even GetNotInListValue never even get called because of the empty value.
Can anyone think of some setting I haven't found yet, or any other reason why this value would disappear...And how I might stop it from happening?
Found a valid Workaround
I implemented the following 3 events, in sequence, to solve this issue. I still have no idea what caused it, or how to go about preventing it. This is a Workaround, not a solution, and should be treated as such. I end up having to manually call the ProcessNewValue method, as well as forcing the value to equal the text field, and the text field back into the value later on. Not the smoothest of operations, but it does work.
private void repPatchNum1_EditValueChanging(object sender, DevExpress.XtraEditors.Controls.ChangingEventArgs e)
{
string surfaceSoftware = vwSurfaceSoftware.GetRowCellDisplayText(vwSurfaceSoftware.FocusedRowHandle, "SurfaceSoftware");
if (string.Compare(surfaceSoftware, SOFTWARE_CHECK, true) == 0)
{
string version = vwSurfaceSoftware.GetRowCellDisplayText(vwSurfaceSoftware.FocusedRowHandle, "Version");
if (version.ToLower().Contains(VERSION_CHECK))
{//now we are certain we are in the right place
LookUpEdit editor = sender as LookUpEdit;
if (!((RPickListCollection)((BindingSource)editor.Properties.DataSource).DataSource).OfType<RPickList>().Any(a => a.RValue.Equals(e.NewValue)))
{
repPatchNum1_ProcessNewValue(sender, new DevExpress.XtraEditors.Controls.ProcessNewValueEventArgs(e.NewValue));
vwSurfaceSoftware.SetRowCellValue(vwSurfaceSoftware.FocusedRowHandle, colPatchNum, e.NewValue);
}
}
}
}
private void repPatchNum1_CloseUp(object sender, DevExpress.XtraEditors.Controls.CloseUpEventArgs e)
{
string surfaceSoftware = vwSurfaceSoftware.GetRowCellDisplayText(vwSurfaceSoftware.FocusedRowHandle, "SurfaceSoftware");
if (string.Compare(surfaceSoftware, SOFTWARE_CHECK, true) == 0)
{
string version = vwSurfaceSoftware.GetRowCellDisplayText(vwSurfaceSoftware.FocusedRowHandle, "Version");
if (version.ToLower().Contains(VERSION_CHECK))
{//now we are certain we are in the right place
LookUpEdit editor = sender as LookUpEdit;
if (!((RPickListCollection)((BindingSource)editor.Properties.DataSource).DataSource).OfType<RPickList>().Any(a => a.RValue.Equals(e.Value)))
{
e.Value = ((LookUpEdit)sender).Text;
}
}
}
}
private void repPatchNum1_Closed(object sender, DevExpress.XtraEditors.Controls.ClosedEventArgs e)
{
string surfaceSoftware = vwSurfaceSoftware.GetRowCellDisplayText(vwSurfaceSoftware.FocusedRowHandle, "SurfaceSoftware");
if (string.Compare(surfaceSoftware, SOFTWARE_CHECK, true) == 0)
{
string version = vwSurfaceSoftware.GetRowCellDisplayText(vwSurfaceSoftware.FocusedRowHandle, "Version");
if (version.ToLower().Contains(VERSION_CHECK))
{//now we are certain we are in the right place
LookUpEdit editor = sender as LookUpEdit;
string patch = vwSurfaceSoftware.GetRowCellValue(vwSurfaceSoftware.FocusedRowHandle, colPatchNum).ToString();
if (String.IsNullOrEmpty(editor.Text) && !String.IsNullOrEmpty(patch))
{
editor.Text = vwSurfaceSoftware.GetRowCellValue(vwSurfaceSoftware.FocusedRowHandle, colPatchNum).ToString();
vwSurfaceSoftware.UpdateCurrentRow();
}
}
}
}
As to the original question: Please post an answer if you know why this might be happening or how to prevent it.
Thanks all :-)
I think I found a simpler workaround, tested on DevExpress 13.
When user presses Tab/Enter, event sequence is ProcessNewValue -> CloseUp
However if user finishes lookup by clicking somewhere else, events are reversed: CloseUp -> ProcessNewValue and entered value is lost. We can use PopupCloseMode.Immediate (specifies that the dropdown window was closed because an end-user clicked outside the editor) to detect this case, manually take entered value from editor, set it to event Value field and manually call ProcessNewValue. No need for other events.
private void myLookUp_CloseUp( object sender, CloseUpEventArgs e )
{
var lookUpEdit = sender as LookUpEdit;
if( lookUpEdit != null )
{
var enteredLookUpText = lookUpEdit.Text;
if( e.CloseMode == PopupCloseMode.Immediate )
{
e.Value = enteredLookUpText;
myLookUp_ProcessNewValue( sender, new ProcessNewValueEventArgs( enteredLookUpText ) );
}
}
// Rest of event handler
}
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();
}
}
}
I have a CheckedListBox control on my windows form application, which gives a list of items to select from.
I want to count and show only checked (not selected) items while the user is still checking them. I mean count as you check them.
I have tried to use ItemCheck event and CheckedListBox.CheckedItems.Count but the problem is that it counts every other click even if the item is unchecked. When I check something it counts and if I unchecked it again, it counts that too.
I think it has to do with the remark given on MSDN "The check state is not updated until after the ItemCheck event occurs." I do not completely understand the problem here.
Thank you.
The ItemCheckEventArgs parameter has the Property (NewValue) which tells you whether the change is a check, uncheck or neither.
If CheckedItems.Count is not updated until after the event fires (which is what I'm understanding from that remark) - then you can add that count, and see whether the ItemChecckEventArgs is a check (+1) or an uncheck (-1) and you can get the correct total.
(Unless I'm understanding the remark wrongly, its very vague).
Haedrian answered this correctly, but i think raw code goes a long ways too. So here's the C# code:
private void CheckedListBox_ItemCheck(Object sender, ItemCheckEventArgs e)
{
int sCount = checkedListBox.CheckedItems.Count;
if (e.NewValue == CheckState.Checked ) { ++sCount; }
if (e.NewValue == CheckState.Unchecked) { --sCount; }
// now sCount is count of selected items in checkedListBox.
}
I had the same issue, and this question and answer gave me the solution. Thanks for the question and already existing answers!
Add event handler for SelectedIndexChanged and get the count from CheckedItems.Count.
With ItemCheck you don't have the actual value, but value before the last change is processed and you would need to adjust the count according to EventArgs as Haedrian proposed.
Since we won't get the updated value in the CheckedItems.Count immediately, we use e.NewValue to get the updated value to get whether the state is Checked or not.
Declare a global variable for getting the count
int chkListCount = 0;
In the ItemCheck event give the following code
private void chkList_ItemCheck(object sender, ItemCheckEventArgs e)
{
if (e.NewValue == CheckState.Checked)
{
chkListCount++;
}
else if(e.NewValue == CheckState.Unchecked)
{
chkListCount--;
}
}
Its the simple logic to get the count of selected items
It is not really a wise and intelligent work as a beginner but at the end solved my problem.
private void CheckedListBox_ItemCheck(Object sender, ItemCheckEventArgs e)
{
int count = 0;
if (e.NewValue.ToString() == "Checked")
{
// first get all the checked items
foreach (object checkeditem in CheckedListBox.CheckedItems)
{
string checkItem = CheckedListBox.GetItemCheckState(CheckedListBox.Items.IndexOf(checkeditem)).ToString();
if (checkItem == "Checked")
{
count = count + 1;
}
}
// Now, below is the most important part considering the remark on MSDN
// "The check state is not updated until after the ItemCheck event occurs."
count = count + 1; // Plus 1 as the NewValue was Checked
labelCount.Text = "You have selected " + count + "Items.";
}
else if (e.NewValue.ToString() == "Unchecked")
{
// first get all the checked items
foreach (object checkeditem in CheckedListBox.CheckedItems)
{
string checkItem = CheckedListBox.GetItemCheckState(CheckedListBox.Items.IndexOf(checkeditem)).ToString();
if (checkItem == "Checked")
{
count = count + 1;
}
}
// Now, below is the most important part considering the remark on MSDN
// "The check state is not updated until after the ItemCheck event occurs."
count = count - 1; // minus 1 as the NewValue was Unchecked,
labelCount.Text = "You have Selected " + count + "Items.";
}
}
I would really appreciate comments on this code.
I know this has been answered long ago, but I found it easier to just handle the MouseUp and KeyUp events. The CheckedItems.Count property is accurate when those events are fired. Since they both do the same thing, I created a method to to the work and called that method from both event handlers.
private void clbFolders_KeyUp(object sender, KeyEventArgs e) { Update(); }
private void clbFolders_MouseUp(object sender, MouseEventArgs e) { Update(); }
private void Update()
{
btnDelete.Enabled = clbFolders.CheckedItems.Count > 0;
}