ListView ItemSelectionChanged fires more often than expected - winforms

This isn't the same as this question which boiled down to the "deselect" and "select" events. What I see is not two fires per selection change but four. Simplified code:
System::Void myclass::handleSelectionChange(Object ^ sender, ListViewItemSelectionChangedEventArgs ^ args) {
if(myListView->SelectedIndices->Count == 1) { // This also serves to filter out "deselect" events (MultiSelect = false)
if(myUnsavedChanges && myListView->SelectedIndices[0] != myPreviousSelection) { // No need for this bit if the newly-selected item is the "dirty" item
Windows::Forms::DialogResult response = MessageBox::Show("Save your changes?", "Unsaved changes", MessageBoxButtons::YesNoCancel, MessageBoxIcon::Warning);
if(response == Windows::Forms::DialogResult::Cancel) {
// re-select the edited item
myListView->SelectedIndices->Clear();
myListView->SelectedIndices->Add(myPreviousSelection);
} else if(response == Windows::Forms::DialogResult::Yes) {
// save the changes, don't reselect the edited item
} else if(response == Windows::Forms::DialogResult::No) {
// let the changes be lost
}
}
}
}
I've got a lot of debugging messages clearly showing me the following course of events when I click an item after making a change in the form:
handleSelectionChange enters (and exits) with the deselection of the "dirty" item
handleSelectionChange enters with the selection of the clicked item
The MessageBox appears and I click "Cancel"
handleSelectionChange enters (and exits) with the deselection of the clicked item
handleSelectionChange enters with the selection of the "dirty" item
...then it gets interesting:
handleSelectionChange enters (and exits) with the deselection of the "dirty" item again
handleSelectionChange enters with the selection of the clicked item again
The MessageBox appears and I click "Cancel" again
handleSelectionChange enters (and exits) with the deselection of the clicked item again
handleSelectionChange enters with the selection of the "dirty" item again
Then everything settles down and the ListView shows the previously-selected item (the "dirty" item) selected.
More curiously, if I have a breakpoint anywhere within the delegate, the problem doesn't occur (only events 1-5 take place).
I've even tried doing a BeginInvoke to change the selection so that the programmatic selection change occurs after the delegate exits, but no no avail. The behavior is still the same. If I eliminate the MessageBox and just make the code take the "Cancel" path, the behavior includes everything up to (including) step 6, and I wind up with nothing selected. So it's not completely the MessageBox's fault, though that affects the behavior.
Why is this happening? Why doesn't the sequence of events stop after step 5? Where are the additional ItemSelectionChanged events coming from?

Related

How to know if the AutoComplete listbox on an TextBox is open?

I have a form with 2 textboxes. The first one has a customsource for its autocompletion set like this :
textBox1.AutoCompleteMode = AutoCompleteMode.SuggestAppend;
textBox1.AutoCompleteSource = AutoCompleteSource.CustomSource;
textBox1.AutoCompleteCustomSource = GetUserNames();
The GetUserNames() method retuns an AutoCompleteCustomSource and all this works very well.
When this form opens the focus in on the first textbox, the user can type in or choose from the autocomplete and that works well.
Both textboxes have an onKeyDown event and in that event they should do some validations using the values of both textboxes.
I want to keydown to only do its validations when ENTER is pressed when the autocomplete listbox is closed.
Look at it this way:
the user starts typing, a list appears, the user chooses an item from the list and presses enter to confirm his choice, and then he wants to press TAB to go to the next textbox.
But when he presses ENTER after choosing an item in the autocomplete list the keyDown event already fires. At this stage the keydown event should not fire, the ENTER should only confirm the choice from the autocomplete list.
Is there a way to detect in the keydown that ENTER was pressed while the autocomplete list was still open ?
Or is there a way to disable the keydown event while the autocomplete list is open ?
EDIT:
from the comments I tried the answer in this link https://stackoverflow.com/a/40915048/3110834
Unfortunate it does not works in this case but it has teached me that pressing enter on the autocomplete suggestion does 2 things:
close the autocomplete window
fire the keydown event of the textbox
So I need to find a way to stop the keydown event for the textbox to fire when pressing enter on the autocomplete window.
Edit:
Things are far worse than I thought.
When you open the autosuggest box and then click on a suggested item to select it, the keydown event also fires and it has Keys.Enter in its KeyCode ! Since when is a click equal to a keystroke ?
How do I stop this ? Is this a bug ?
I ran into this same problem, I followed the approach in this link https://stackoverflow.com/a/40915048/3110834
if you try to evaluate if there is a list open during the Keydown event, it will always return a false, since the event already closed the list.
instead I observe the PreviewKeyDown event to evaluate if the AutoComplete list is open or not, if it is Open, i unsubscribe from the KeyDown event (which only uses the Enter key on my case) and if the list is close, i re-subscribe to it back again
private void tbxAND_PreviewKeyDown(object sender, PreviewKeyDownEventArgs e)
{
if (e.KeyCode == Keys.Enter)
{
if (IsAutoCompleteOpen())
{
tbxAND.KeyDown -= tbxAND_KeyDown;
}
else
{
tbxAND.KeyDown += tbxAND_KeyDown;
}
}
}
notice that IsAutoCompleteOpen() starts the code to enumerate the openlist.
this may not be the full solution for this particular case if you still need to catch other keys, but i wanted to leave the hint in case someone else run into this problem.

WPF, MVVM and combobox

I am having some trouble with my comboBox logic in my viewModel. The viewModel populates the comboBox and the user can select items.
When an item is selected, some editable info appears in the view and they can make changes. Now, I want to prompt the user to save if changes were made and not saved and they are trying to select another item in the drop down (a "Save?" yes/no/cancel messageBox).
Right now I need to remember the previously selected item and handle this myself by resetting the selected item if the user selects cancel. Since the comboBox does not have a PreviewSelectionChanged event, this is the only way I can think of of handling this scenario. It works but it gets a little messy hwen there are multiple comboBoxes, etc.
Am I missing anything or is this just the way it needs to be done?
You essentially have to make a flag in your view model called isDirty or something along those lines.
public class EditorViewModel
{
private bool _isDirty = false;
private long _editableProperty;
public long EditableProperty
{
get { return _editableProperty; }
set
{
_editableProperty = value;
// We've detected a change so mark this view model as dirty.
_isDirty = true;
}
}
}
Note that you will have to have to jump through a few more hoops if you want ensure that the data is in fact different from your original. So say someone accidently adds a space in EditableProperty and removes it your view model will think it's dirty and prompt the user.
The Windows Forms ComboBox provided a SelectionChangeCommitted event, but for the WPF ComboBox control you correct in that there is no event that will notify before the selection change occurs that will provide you with a means of cancelling the event.
If you are going to take a change tracking/editable approach, I would recommend considering implementing IChangeTracking and IEditableObject on the items in your combobox items source.
You will probably have to handle the SelectionChanged event, inspect the removed items to determine if the item that was previously selected was modified and then display a dialog requesting confirmation. If no/cancel was indicated, you can then set the selected index back to that of the previously selected item.
What about making the Editable item a copy of an item instead of the actual item?
So your ViewModel would contain
ObservableCollection<MyModel> ComboBoxItems;
int SelectedComboBoxIndex;
MyModel EditingItem;
Whenever the PropertyChange event occurs on SelectedComboBoxIndex, you check and see if EditingItem is null or not. If it is null, it means you're safe to switch and you set
EditingItem = ComboBoxItem[SelectedComboBoxIndex]).Copy();
If the EditingItem is not null, then you throw up a prompt asking if the user wants to save changes or cancel.
When the user hits Save, it takes the EditingItem and applies the changes to the data store and updates the item in the ComboBoxItems list.
If they hit Cancel, the EditingItem is simply discarded.

Control.Leave Triggered by MouseDown, not MouseUp

I have a C# .NET WinForm. In the form, I allow a user to add an item to a ListView by double-clicking in the ListView. This adds a TextBox control to the ListView and places the key cursor in the TextBox so a user can type.
I detect that a user is done with an item in a couple of ways (e.g. pressing Enter, Esc, Tab...), but also when they Leave (TextBox.Leave) the TextBox.
The problem is this set of steps:
User triggers TextBox.Leave by mousing down outside of the TextBox.
I add the new item to the ListView.
I select the the new item in the ListView.
Mouse up occurs and the new item that I just selected, loses focus and is unselected.
What I would like is for TextBox.Leave to be triggered by MouseUp, not MouseDown. How can I accomplish this?
Edit: Cody suggests using the ListView.LabelEdit property. Here are my results trying that:
listView_DoubleClick(...) {
listView.LabelEdit = true;
if(double clicked on existing listViewItem) {
listViewItem.BeginEdit(); //this works as expected
} else {
var newItem = listView.Items.Add("");
newItem.BeginEdit(); //this doesn't work, see below
}
}
The call to newItem.BeginEdit() only works when the user double clicks where the new item will show up. If they double click on any other blank area in the listview the new item is added, but it does not enter edit mode. What's going on here?
Pressing the mouse down on another control is causing that other control to request the focus and so the focus moving causes the TextBox.Leave event to occur. Preventing ever other possible control from requesting the focus is not a very viable option. But luckly you only need to prevent the ListView from using the MouseDown to shift focus. So you need to override the WndProc of your ListView and when the MouseDown windows message occurs and you are currently showing a TextBox you eat the message. In order words you do not allow the base class to process it.

How to prevent Enter press from closing menu

I'm embedding a Colour Picker into a Context Menu using the Windows.Forms.ToolStripControlHost class. The picker displays fine and handles all mouse events properly:
The problem arises when one of the channel sliders is double clicked. This causes the control to add a Windows.Forms.TextBox into the parent control with the same dimensions as the slider so users can enter numeric values. When Enter is pressed while the TextBox has focus, it should assign the value and hide the textbox (which it does), but it also closes the entire menu structure. So, how do I keep the menu alive?
There's an awful lot of code involved but I'll post it if needed.
Somehow, you'll need to eat the Enter key presses before they reach your context menu. Obviously, it's default behavior is to "select" the currently highlighted item when the user presses Enter, just like every other menu control known to man.
You would do that by subclassing the ContextMenuStrip control (if you're not doing so already), and overriding its ProcessCmdKey method. Watch for a keyData value corresponding to Keys.Enter, and when you detect that value, return True to indicate that the character was already processed by the control and prevent it from being passed on for any further processing. Everything else, of course, you'll let the base class process so the behavior of other keys (such as the arrow keys) is unchanged.
For example (I just tested this and it works fine):
public class CrazyContextMenuStrip : ContextMenuStrip
{
protected override bool ProcessCmdKey(ref Message m, Keys keyData)
{
if (keyData == Keys.Enter)
{
// Eat it when the user presses Enter to
// prevent the context menu from closing
return true;
}
// Let the base class handle everything else
return base.ProcessCmdKey(m, keyData);
}
}
And of course, you could add extra checks to the above code so that the Enter key presses are only eaten when your color picker is visible, allowing things to work as expected all the rest of the time,

WPF toplevel MenuItem enable/disable based on task

I have a top-level menu item which is responsible for refreshing a datagrid in the same window. My current control flow is:
User clicks on refresh
In the click event handler, I:
Disable the menuitem, by setting oMenuItem.IsEnabled = false.
Dispatch an action to refresh the grid and in that action, I re-enable the menuitem, by setting IsEnabled = true
The problem is that the user can click refresh even when it's disabled and it's as if the clicks get queued up. When the action returns it goes on to process the remaining, "queued-up" clicks. What I expect is: all clicks while the menuitem is disabled are ignored and only when it's enabled, the clicks are acknowledged.
The weird thing is that if I just disable it and never enable it it stays that way, i.e., it is disabled.wpf,
"Dispatch an action" you mean by calling Dispatcher.BeginInvoke() or some other kind of async opetation?
Anyway, in both cases you can get a "handle" to the operation (DispatcherOperation or IAsyncResult) and store it as a field when you dispatch your operation. When it completes - set this field to null.
In the click event handler of the menu-item check this field. If it's null it means it is safe to start the operation. If it is not null - return immediately and do nothing.
And something not related to your question but important - why not use Commands? That way you don't need to play with event handling and enabling/disabling. And of course commands can be invoked by multiple means (for example - the user selected the command from the menu using the keyboard and pressed Enter. No mouse clicks involved, but should do the same as clicking the menu item).
Alex.

Resources