I'm trying to achieve the equivalent of a WinForms ListView
with its View
property set to View.List
. Visually, the following works fine. The file names in my Listbox
go from top to bottom, and then wrap to a new column.
Here's the basic XAML I'm working with:
<ListBox Name="thelist"
IsSynchronizedWithCurrentItem="True"
ItemsSource="{Binding}"
ScrollViewer.VerticalScrollBarVisibility="Disabled">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel IsItemsHost="True"
Orientation="Vertical" />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
</ListBox>
However, default arrow key navigation does not wrap. If the last item in a column is selected, pressing the down arrow does not go to the first item of the next column.
I tried handling the KeyDown
event like this:
private void thelist_KeyDown( object sender, KeyEventArgs e ) {
if ( object.ReferenceEquals( sender, thelist ) ) {
if ( e.Key == Key.Down ) {
e.Handled = true;
thelist.Items.MoveCurrentToNext();
}
if ( e.Key == Key.Up ) {
e.Handled = true;
thelist.Items.MoveCurrentToPrevious();
}
}
}
This produces the last-in-column to first-in-next-column behavior that I wanted, but also produces an oddity in the left and right arrow handling. Any time it wraps from one column to the next/previous using the up/down arrows, a single subsequent use of the left or right arrow key moves the selection to the left or right of the item that was selected just before the wrap occured.
Assume the list is filled with strings "0001" through "0100" with 10 strings per column. If I use the down arrow key to go from "0010" to "0011", then press the right arrow key, selection moves to "0020", just to the right of "0010". If "0011" is selected and I use the up arrow key to move selection to "0010", then a press of the right arrow keys moves selection to "0021" (to the right of "0011", and a press of the left arrow key moves selection to "0001".
Any help achieving the desired column-wrap layout and arrow key navigation would be appreciated.
(Edits moved to my own answer, since it technically is an answer.)
It turns out that when it wraps around in my handling of the KeyDown
event, selection changes to the correct item, but focus is on the old item.
Here is the updated KeyDown
eventhandler. Because of Binding, the Items
collection returns my actual items rather than ListBoxItem
s, so I have to do a call near the end to get the actual ListBoxItem
I need to call Focus()
on. Wrapping from last item to first and vice-versa can be achieved by swapping the calls of MoveCurrentToLast()
and MoveCurrentToFirst()
.
private void thelist_KeyDown( object sender, KeyEventArgs e ) {
if ( object.ReferenceEquals( sender, thelist ) ) {
if ( thelist.Items.Count > 0 ) {
switch ( e.Key ) {
case Key.Down:
if ( !thelist.Items.MoveCurrentToNext() ) {
thelist.Items.MoveCurrentToLast();
}
break;
case Key.Up:
if ( !thelist.Items.MoveCurrentToPrevious() ) {
thelist.Items.MoveCurrentToFirst();
}
break;
default:
return;
}
e.Handled = true;
ListBoxItem lbi = (ListBoxItem) thelist.ItemContainerGenerator.ContainerFromItem( thelist.SelectedItem );
lbi.Focus();
}
}
}
You should be able to do it without the event listener using KeyboardNavigation.DirectionalNavigation, e.g.
<ListBox Name="thelist"
IsSynchronizedWithCurrentItem="True"
ItemsSource="{Binding}"
ScrollViewer.VerticalScrollBarVisibility="Disabled"
KeyboardNavigation.DirectionalNavigation="Cycle">
来源:https://stackoverflow.com/questions/134068/listboxwrappanel-arrow-key-navigation