Reputation: 1870
I have a WPF ComboBox
I need to change default behaivor in popup list.
Now, by pressing up
and down
keys SelectedItem
changes automatically.
I need change SelectedItem
only by pressing Enter
key, or clicking by mouse.
How can it be done?
I've subclassed ComboBox
:
protected override void OnPreviewKeyDown(System.Windows.Input.KeyEventArgs e)
{
Debug.Write("Pressed " + e.Key+ " ");
if (e.Key == System.Windows.Input.Key.Up || e.Key == System.Windows.Input.Key.Down)
{
// ???
e.Handled = true;
return;
}
base.OnPreviewKeyDown(e);
}
This code doesn't work - no popup is shown and user can't select items. What I shoud write and where? :)
Thanks.
UPD1:
I need same functionality as ComboBox's
popup is open and user can select items by mouse.
Each item can be hovered by mouse, but not selected. Selection become only by pressing mouse button. I need the same. 'Up' and 'Down' only highlights items in popup, but SelectedItem
will be changed only by pressing Enter
or mouse clicking.
UPD2: If I press by mouse on button, that opens Popup in ComboCox, I can highlight items in Popup by mouse, but SelectedItem will change only if I click on item.
I need same functionality by keyboard. If I start typing somewhat in ComboBox, Popup opens. And I have to hightlight items by keyboard Up
and Down
. TextBox in ComboBox must not change during highlighting and SelectedItem must change only if I press Enter
(or mouse click)
UPD3: Link to demo solution: download
Upvotes: 6
Views: 15269
Reputation: 967
Install a nuget package System.Windows.Interactivity.WPF, create a class like following (NB: left/right arrows also change selection in a closed focused ComboBox):
public class ComboBoxCustomBehaviour: Behavior<ComboBox>
{
private readonly ISet<Key> _dropdownBlockedKeys = new HashSet<Key>{Key.Up, Key.Down, Key.Left, Key.Right};
protected override void OnAttached()
{
AssociatedObject.PreviewKeyDown += AssociatedObject_KeyDown;
}
protected override void OnDetaching()
{
AssociatedObject.PreviewKeyDown -= AssociatedObject_KeyDown;
}
private void AssociatedObject_KeyDown(object sender, KeyEventArgs e)
{
if (_dropdownBlockedKeys.Contains(e.Key))
e.Handled = true;
// Use following line, when you need to stop selection only on closed ComboBox
// e.Handled = !((ComboBox)sender).IsDropDownOpen;
}
}
Add this behavior class to the xaml:
<ComboBox>
<ComboBoxItem>Item 1</ComboBoxItem>
<ComboBoxItem>Item 2</ComboBoxItem>
<ComboBoxItem>Item 3</ComboBoxItem>
<i:Interaction.Behaviors>
<local:ComboBoxCustomBehaviour />
</i:Interaction.Behaviors>
</ComboBox>
Upvotes: 0
Reputation: 359
Better use the ComboBox.OnDropDownClosed(EventArgs) Method
PS: For Infracistics Controls (UltraComboEditor) it´s the OnAfterCloseUp Event.
Upvotes: 0
Reputation: 132648
The code you have seems to work fine, just add a check to see if the DropDown is open before cancelling the Key Event
protected override void OnPreviewKeyDown(KeyEventArgs e)
{
Debug.Write("Pressed " + e.Key + " ");
if (!base.IsDropDownOpen && (e.Key == Key.Up || e.Key == Key.Down))
{
e.Handled = true;
return;
}
base.OnPreviewKeyDown(e);
}
Upvotes: 3
Reputation: 79
Here is the solution which worked for me -
public class CustomComboBox : ComboBox
{
private int _currentItemIndex;
private string _rowColor = "#E7E7E7";
private string _selectedRowColor = "#FFFFC6";
protected override void OnDropDownOpened(EventArgs e)
{
_currentItemIndex = base.SelectedIndex;
base.OnDropDownOpened(e);
}
protected override void OnPreviewKeyDown(KeyEventArgs e)
{
if (base.IsDropDownOpen)
{
if (e.Key == Key.Up || e.Key == Key.Down)
{
ComboBoxItem currentItem;
var colorConverter = new BrushConverter();
if (_currentItemIndex > -1 && _currentItemIndex != base.SelectedIndex)
{
currentItem = (ComboBoxItem)base.ItemContainerGenerator.ContainerFromIndex(_currentItemIndex);
currentItem.Background = (Brush)colorConverter.ConvertFromString(_rowColor);
}
if (e.Key == Key.Up)
{
_currentItemIndex -= 1;
if (_currentItemIndex < 0)
{
_currentItemIndex = 0;
}
}
else if (e.Key == Key.Down)
{
_currentItemIndex += 1;
if (_currentItemIndex > base.Items.Count - 1)
{
_currentItemIndex = base.Items.Count - 1;
}
}
currentItem = (ComboBoxItem)base.ItemContainerGenerator.ContainerFromIndex(_currentItemIndex);
currentItem.Background = (Brush)colorConverter.ConvertFromString(_selectedRowColor);
currentItem.BringIntoView();
e.Handled = true;
return;
}
else if (e.Key == Key.Enter)
{
base.SelectedItem = base.Items[_currentItemIndex];
}
}
base.OnPreviewKeyDown(e);
}
protected override void OnDropDownClosed(EventArgs e)
{
if (_currentItemIndex > -1 && base.Items[_currentItemIndex] != base.SelectedItem)
{
var colorConverter = new BrushConverter();
ComboBoxItem currentItem = (ComboBoxItem)base.ItemContainerGenerator.ContainerFromIndex(_currentItemIndex);
currentItem.Background = (Brush)colorConverter.ConvertFromString(_rowColor);
}
base.OnDropDownClosed(e);
}
}
Upvotes: 1
Reputation: 11
In my opinion, you should create property - IsKeyNavigation (like IsMouseOver)
protected override void OnPreviewKeyDown(System.Windows.Input.KeyEventArgs e)
{
Debug.Write("Pressed " + e.Key+ " ");
if (e.Key == System.Windows.Input.Key.Up || e.Key == System.Windows.Input.Key.Down)
{
VisualStateManager.GoToState(this, "KeyNavigation", true);
e.Handled = true;
return;
}
base.OnPreviewKeyDown(e);
}
And if Key.Up or Key.Down is pressed you should Define navigated element and change the VisualState.
Upvotes: 0
Reputation: 19895
You should have this event handled on all the ComboBoxItem
s in a combobox.
<ComboBox.Resources>
<Style TargetType="{x:Type ComboBoxItem}">
<EventSetter Event="PreviewKeyDown" Handler="OnPreviewKeyDown" />
</Style>
</ComboBox.Resources>
EDIT:
In code behind, you can add folowing code in MyComboBox's constructor after InitializeComponent()
do this...
var comboBoxItemstyle = new Style(typeof (ComboBoxItem));
comboBoxItemstyle.Setters.Add(
new EventSetter(PreviewKeyDownEvent,
new KeyEventHandler(OnPreviewKeyDown)));
this.Resources.Add(typeof (ComboBoxItem), comboBoxItemstyle);
Hope this helps.
Upvotes: 3