ListBox | Выделить элемент списка
Есть ListBox с Images. Пользователь выделяет мышкой изображение в ListBox и стрелкой на клавиатуре перемещает вправо по списку (Из Image используется Source текущего элемента для увеличения). В текущей реализации не устанавливает на позицию текущего индекса. При текущем верном индексе устанавливает выделение на +1 от перемещённого изображения, а если задать жестко ContainerFromIndex(3), то так же устанавливает на +1, на -1 в другую сторону одинаково. Как не видит 0, между +1 и -1.
ListBoxPhotos.Items.Insert(Index + 2, img);
ListBoxPhotos.Items.RemoveAt(Index);
ListBoxPhotos.SelectedItem = null;
ListBoxPhotos.SelectedIndex = Index + 1;
ListBoxItem listBoxItem = ListBoxPhotos.ItemContainerGenerator.ContainerFromIndex(Index) as ListBoxItem;
listBoxItem.Focus();
<ListBox
SelectionChanged="ListBoxItemClick"
SelectionMode="Single"
x:Name="ListBoxPhotos"
ScrollViewer.VerticalScrollBarVisibility="Disabled"
VerticalAlignment="Center"
Background="#FF5CB57A"
BorderBrush="{x:Null}" KeyDown="StackPanel_KeyDown">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" KeyDown="StackPanel_KeyDown"/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
</ListBox>
Этот вариант делает что нужно, только отображается MessageBox, который тут не нужен. Каждый раз отображается и то ли фокус меняется, то ли какое свойство. Не могу понять какое свойство изменяется. Если определить, то можно его вызывать программно без MessageBox.
for (int i = 0; i < ListBoxPhotos.Items.Count; i++)
{
object yourObject = ListBoxPhotos.Items[i];
ListBoxItem lbi = (ListBoxItem)ListBoxPhotos.ItemContainerGenerator.ContainerFromItem(yourObject);
if (lbi.IsFocused)
{
MessageBox.Show("Item at index " + i.ToString() + " has the focus.");
break;
}
ListBoxPhotos.SelectedIndex = Index + 1;
ListBoxItem listBoxItem = ListBoxPhotos.ItemContainerGenerator.ContainerFromIndex(ListBoxPhotos.SelectedIndex - 1) as ListBoxItem;
listBoxItem.Focus();
}
Ответы (2 шт):
Суть в следующем. У ListBox есть стандартное поведение перемещения по списку влево и вправо. И его необходимо перегрузить. Я этого не сделал и происходил конфликт. На данный момент можно решить так:
- У главного
Gridопределить событиеKeyDownс именемOnKeyDownHandler.
private void OnKeyDownHandler(object sender, KeyEventArgs e)
- В этом методе выполнять алгоритм:
if (e.Key == Key.Right)
{
if (ListBoxPhotos.Items.Count > 0)
{
var Path = (ListBoxPhotos.SelectedItem as Image).Source.ToString();
int Index = ListBoxPhotos.SelectedIndex;
Image img = common.PathStringToImage(Path, (ListBoxPhotos.Items.Count));
if (Index != ListBoxPhotos.Items.Count - 1)
{
ListBoxPhotos.Items.Insert(Index + 2, img);
ListBoxPhotos.Items.RemoveAt(Index);
var item = (ListBoxItem)ListBoxPhotos.ItemContainerGenerator.ContainerFromIndex(Index + 1);
item?.Focus();
}
}
}
Финальный код:
private void OnKeyDownHandler(object sender, KeyEventArgs e)
{
if (e.Key == Key.Right)
{
if (ListBoxPhotos.Items.Count > 0)
{
var Path = (ListBoxPhotos.SelectedItem as Image).Source.ToString();
int Index = ListBoxPhotos.SelectedIndex;
Image img = common.PathStringToImage(Path, (ListBoxPhotos.Items.Count));
if (Index != ListBoxPhotos.Items.Count - 1)
{
ListBoxPhotos.Items.Insert(Index + 2, img);
ListBoxPhotos.Items.RemoveAt(Index);
var item = (ListBoxItem)ListBoxPhotos.ItemContainerGenerator.ContainerFromIndex(Index + 1);
item?.Focus();
}
}
}
}
Перемещать элемент коллекции при нажатии кнопок не так сложно, как кажется.
Пример сделаю на базе коллекции ObservableCollection и привязок данных. Как я понял, вы не знакомы с шаблоном проектирования MVVM, и весь код у вас в классе окна. Поэтому покажу с настройкой датаконтекста именно туда.
Для работы кода, представленного ниже, потребуется вспомогательный класс для использования команд (их используют в WPF вместо обработчиков событий).
public class RelayCommand : ICommand
{
private readonly Action<object> _execute;
private readonly Predicate<object> _canExecute;
public event EventHandler CanExecuteChanged
{
add => CommandManager.RequerySuggested += value;
remove => CommandManager.RequerySuggested -= value;
}
public RelayCommand(Action<object> execute, Predicate<object> canExecute = null)
=> (_execute, _canExecute) = (execute, canExecute);
public bool CanExecute(object parameter)
=> _canExecute == null || _canExecute(parameter);
public void Execute(object parameter)
=> _execute(parameter);
}
И еще маленькое перечисление
public enum Direction
{
Backward,
Forward
}
Класс окна надо доработать вот так
public sealed partial class MainWindow : Window, INotifyPropertyChanged
{
private ObservableCollection<string> _items;
private ICommand _moveCommand;
private string _selectedItem;
public ObservableCollection<string> Items
{
get => _items;
set
{
_items = value;
OnPropertyChanged();
}
}
public int SelectedIndex => Items.IndexOf(SelectedItem);
public string SelectedItem
{
get => _selectedItem;
set
{
_selectedItem = value;
OnPropertyChanged();
}
}
public MainWindow()
{
InitializeComponent();
DataContext = this;
}
public ICommand MoveCommand => _moveCommand ?? (_moveCommand = new RelayCommand(parameter =>
{
if (Enum.TryParse(parameter as string, out Direction direction))
{
int index = SelectedIndex;
if (direction == Direction.Backward && index > 0)
{
Items.Move(index, index - 1);
}
else if (direction == Direction.Forward && index < Items.Count - 1)
{
Items.Move(index, index + 1);
}
}
}));
private void Window_Loaded(object sender, RoutedEventArgs e)
{
Items = new ObservableCollection<string>
{
"https://image1_url",
"https://image2_url"
};
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
=> PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
Здесь:
- Реализация интерфейса
INotifyPropertyChanged, чтобы интерфейс мог обновляться динамически. ObservableCollection<string>- коллекция с путями для картинок для изSource.SelectedItem, сюда привязан выбранный элемент листбоксаSelectedIndexконвертируетSelectedItemв ее индекс (предполагается, что все стоки в коллекции уникальны и не повторяются)MoveCommand- команда, которая вызывается при нажатии клавиш
И вот такая получится разметка
<ListBox ItemsSource="{Binding Items}" SelectedItem="{Binding SelectedItem}">
<ListBox.InputBindings>
<KeyBinding Key="Right" Command="{Binding MoveCommand}" CommandParameter="Forward"/>
<KeyBinding Key="Left" Command="{Binding MoveCommand}" CommandParameter="Backward"/>
</ListBox.InputBindings>
<ListBox.ItemTemplate>
<DataTemplate>
<Image Source="{Binding}"/>
</DataTemplate>
</ListBox.ItemTemplate>
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
</ListBox>
При нажатии стрелки на клавиатуре элемент едет право или влево, как задумано. Фокус при этом сохраняется на выделенном элементе.
Клавиши для InputBindings можно задавать с модификаторами, например Key="Right" Modifiers="Ctrl".
Обратите внимание, у контролов даже имён нет x:Name, они здесь без надобности.