background
I have custom control, containing a Popup which contains a ListBox
Requirement
I need to close the popup and do some logic with the selected item when the user chooses an item. User chooses an item when :
he click's on the item with the mouse.
he selected and item through keyboard navigation (up/down keys) and clicks enter
Problem
I have implemented all the above, but my problem is with the events to listen to inorder to execute my logic.
If I execute my logic on the SelectionChanged event, It will not fire when the user clicks on the selected item, so I'm missing my first scenario.
If I execute my logic on the PreviewMouseLeftButtonDown It will fire before the selection changed so I don't know what the user has chosen. This is also why I can't use both.
I thought of listening on the ListBoxItem events to do this (How to capture a mouse click on an Item in a ListBox in WPF?) or firing a command from an implicit ListBoxItem style (WPF Interaction triggers in a Style to invoke commands on View Model) but they didn't work for me.
The best idea that I came up with, is to create some kind of a "post selection" MouseButtonDown event via behaviours or actions, but I am not sure how, or if this is even the way to go.
Any Idea how to create such a thing? Or is there a better solution for this?
The answer is to Bind to the ListBox.SelectedItem property and to handle the PreviewKeyDown event of your control. This way, you'll always know which item is the selected one and when the Enter key is hit:
public static readonly DependencyProperty SelectedItemProperty = DependencyProperty.
Register("SelectedItem", typeof(YourDataType), typeof(YourControl),
new UIPropertyMetadata(null, OnSelectedItemPropertyChanged));
public YourDataType SelectedItem
{
get { return (YourDataType)GetValue(SelectedItemProperty); }
set { SetValue(SelectedItemProperty, value); }
}
private static void OnSelectedItemPropertyChanged(DependencyObject sender,
DependencyPropertyChangedEventArgs e)
{
// User has selected an item
}
...
private void Control_PreviewKeyDown(object sender, KeyEventArgs e)
{
if (e.Key == Key.Enter || e.Key == Key.Return)
{
// User pressed Enter... do something with SelectedItem property here
}
}
UPDATE >>>
Ok, I think I understand your problem a bit better now. The simplest solution would be if you could slightly alter the requirement so that;
An item is selected with either the mouse or keyboard up/down keys
An item is chosen with the Enter key
That way, you'd always know the selected item when the user chooses. However, if you can't do that, can you handle the PreviewMouseLeftButtonUp instead of the PreviewMouseLeftButtonDown event? I'm not 100% sure, but I think that that would occur after the selection has been made.
Related
A am writing a WinRT application which utilises a Gridview to display some data. The Gridview has a SelectionMode of Extended so that as the user navigates the grid with the cursor keys the selected item moves with them (plus I have multi-select functionality)
The problem I'm experiencing is that if you navigate the grid using the cursor keys and have Ctrl pressed down, the selected item remains where is was and only the focus changes. My DataTemplate doesn't show the focused item so it's quite confusing to the user.
Is there anyway I can change this behaviour so that navigating the grid with Ctrl held down works in the same way as if it wasn't being held down?
The solution was quite simple in the end. Just create a GotFocus handler like this one:
private void SdxGridView_GotFocus(object sender, RoutedEventArgs e)
{
if (e.OriginalSource is GridViewItem && !((GridViewItem)e.OriginalSource).IsSelected)
{
SelectedItems.Clear();
((GridViewItem)e.OriginalSource).IsSelected = true;
}
}
I have a WinForm app that has a ListView in it, with a DoubleClick event handler assigned to it.
Theoretically , only items are "clickable" , so it should be impossible to enter the event handler with no selected items, and so it is for 99% of cases.
However, every once in a while, I catch an exception of InvalidAgrument as my handler try to access list_view.SelectedItems[0], and there I see that it does actually empty.
When I try to reproduce, it takes an aggressive clicking session to do it. But it's done, I can sometimes see the cursor in the middle of a valid entity , which makes me suspect it might be some racing condition.
This can certainly go wrong, a double-click doesn't guarantee that an item is selected. It may also de-select an item, your code will crash. Short from adding the test to check that SelectedItems isn't empty, the possible better mouse trap is to find the exact item that was double-clicked. Use the MouseDoubleClick event instead so you get the mouse position, then use the ListView.HitTest() method. Like this:
private void listView1_MouseDoubleClick(object sender, MouseEventArgs e) {
var item = ((ListView)sender).HitTest(e.Location);
if (item != null) {
// etc..
}
}
When I am having a value item selected in my WPf DropDown Combo Box then navigating using keys Left and right arrow keys result in firing of selected changed event for each item.
How to overcome this problem
The most easy and suitable way I found to overcome this problem is as follows:
rather than using SelectedIndexChanged event I used on DropDownClosed event and all code that is wriiten earlier inside selected index changed moved to this event under a if condition that checks whether a item is selected or not. Like this.
private void OnCmbOperatorsListDropDownClosed(object sender, EventArgs e)
{
if (cmbOperatorsList.SelectedIndex != -1)
InsertText(cmbOperatorsList.SelectedValue.ToString());
//Do whatever u want with selected item
}
So in this way when i navigate through Arrow keys SelectedIndexChagned event will not fired or since i haven't used that event so it will not create any problem.
As per my knowledge this is not possible straight away. I could have implemented this in a kind of "selection simulated" manner.
Handle arrow keys on combobox dropdown in PreviewKeyDown event by setting e.Handled = true. So that usual navigation based selection wont happen.
Inthese handlers based on Keys, change the Background and Foreground of the previous or next item from the drop down list so that it will look as if its selected and highlighted.
Then perform selection of the item which curently has the "simulated selection background - foreground" when dropdown closes. After dropdown closure, revert the background and foreground style.
But thats just my way of doing it.
You can use the PreviewKeyDown event like
private void combo_PreviewKeyDown(object sender, KeyEventArgs e)
{
if (e.Key.Equals(Key.Left) || (e.Key.Equals(Key.Right)))
{
((ComboBox)sender).SelectionChanged -= combo_SelectionChanged;
}
}
and if u want to attach that event you can add this PreviewMouseDown event.
This is what i tried and may not be a proper method of doing such cases
If the user click on a item in the listbox, the listboxItems_SelectedIndexChanged is called. But, even if the user miss an item and randomly clicks inside the listbox (not on items) the listboxItems_SelectedIndexChanged is still called.
How can I change this? I only want action on item click.
Note: removing the ability to navigate the application with keyboard is not a option.
I guess that in some cases you don't have enough list items in your control, therefore you have some space that you can click on and then SelectedIndexChanged is fired.
I guess you cannot dynamically resize the control to always fit the number of list items or else you wouldn't be asking this question.
Now, what should happen when the user click (selects) the same list item? Should some logic happen even though the selected index is the same (so when it was clicked the first time the same logic happend)?
If you require that selecting the same index more than once should be ignored then you could use the following hack:
Keep a variable at the form scope (the form containing the listbox control) and each time the selection index changes set that variable. Then use it later to check if the same selection has been made to ignore handling the event. Here is an example:
private int _currSelIdx = -1; // Default value for the selected index when no selection
private void listBox1_SelectedIndexChanged(object sender, EventArgs e)
{
if (listBox1.SelectedIndex == _currSelIdx)
return;
Console.WriteLine(listBox1.SelectedIndex);
_currSelIdx = listBox1.SelectedIndex;
}
It ain't pretty, but hey...whatever works!
Maybe SelectedIndexChanged is not the right place to put your logic, since it is triggered even when you change the selection with the keyboard.
I would use MouseClick instead, checking if the click occurred over the selected item, i.e. something like this:
private void listBox1_MouseClick(object sender, MouseEventArgs e)
{
if (listBox1.SelectedIndex < 0 || !listBox1.GetItemRectangle(listBox1.SelectedIndex).Contains(e.Location))
MessageBox.Show("no click");
else
MessageBox.Show("click on item " + listBox1.SelectedIndex.ToString());
}
This link may help, instead of double click, implement the same for single click
i want to detect an item double click in a winforms listbox control. [how to handle click on blank area?]
The tricky part is that each item has a ContextMenu that I still want to open when it is right-clicked (I just don't want it selecting it).
In fact, if it makes things any easier, I don't want any automatic selection at all, so if there's some way I can disable it entirely that would be just fine.
I'm thinking of just switching to an ItemsControl actually, so long as I can get virtualization and scrolling to work with it.
If you don't want selection at all I would definitely go with ItemsControl not ListBox. Virtualization and scrolling both can be used with a plain ItemsControl as long as they are in the template.
On the other hand, if you need selection but just don't want the right click to select, the easiest way is probably to handle the PreviewRightMouseButtonDown event:
void ListBox_PreviewRightMouseButtonDown(object sender, MouseButtonEventArgs e)
{
e.Handled = true;
}
The reason this works is that ListBoxItem selection happens on mouse down but context menu opening happens on mouse up. So eliminating the mouse down event during the preview phase solves your problem.
However this does not work if you want mouse down to be handled elsewhere within your ListBox (such as in a control within an item). In this case the easiest way is probably to subclass ListBoxItem to ignore it:
public class ListBoxItemNoRightClickSelect : ListBoxItem
{
protected override void OnMouseRightButtonDown(MouseButtonEventArgs e)
{
}
}
You can either explicitly construct these ListBoxItems in your ItemsSource or you can also subclass ListBox to use your custom items automatically:
public class ListBoxNoRightClickSelect : ListBox
{
protected override DependencyObject GetContainerForItemOverride()
{
return new ListBoxItemNoRightClickSelect();
}
}
FYI, here are some solutions that won't work along with explanations why they won't work:
You can't just add a MouseRightButtonDown handler on each ListBoxItem because the registered class handler will get called before yours
You can't handle MouseRightButtonDown on ListBox because the event is directly routed to each control individually