2

Есть 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(&quot;Item at index &quot; + i.ToString() + &quot; has the focus.&quot;);
    break;
}
ListBoxPhotos.SelectedIndex = Index + 1;
ListBoxItem listBoxItem = ListBoxPhotos.ItemContainerGenerator.ContainerFromIndex(ListBoxPhotos.SelectedIndex - 1) as ListBoxItem;
listBoxItem.Focus();

}

  • Эм... А для чего это? Если вам надо чтоб ListBox в ряд размещал изображения, а выделение менялось стрелками вправо и влево, то просто переопределите ему ItemsPanel на <StackPanel Orientation="Horizontal"/> и все. – EvgeniyZ Apr 27 '21 at 02:34
  • @EvgeniyZ Особо ничего не изменилось, так же работает. Прикрепил xaml. – North Face Apr 27 '21 at 03:18
  • Не изменилось что? Какое поведение вы хотите? Стрелками переключать фокус или именно перемещать позицию элемента в списке? Если первое, то я вам решение сказал, если 2-е, то тут надо немного позаморочится, да. Если что-то другое, то я не понял ваш вопрос. – EvgeniyZ Apr 27 '21 at 03:48
  • @EvgeniyZ у меня есть логика перемещения элементов по списку на кнопках. Там всё работает нормально (только там я не определяю ListBoxItem). Здесь нужно сделать то же самое, только на кнопках клавиатуры. Подписываюсь на событие keyDown (StackPanel), получаю текущую кнопку на клавиатуре и пытаюсь перемещать по массиву элемент. Элемент перемещается только один раз, пока есть фокус от мышки, нажатия. После перемещения фокус сбрасывается на нулевой элемент. – North Face Apr 27 '21 at 03:58
  • Вам нужно с клавиатуры при нажатии каких-то кнопок перемещать элемент по списку? Я все правильно понял? – aepot Apr 27 '21 at 10:34
  • @aepot добавил ответ. – North Face Apr 27 '21 at 10:39
  • @NorthFace я немного другой ответ готовил, интересно? – aepot Apr 27 '21 at 10:39
  • 1
    @aepot конечно. – North Face Apr 27 '21 at 10:40

2 Answers2

2

Перемещать элемент коллекции при нажатии кнопок не так сложно, как кажется.

Пример сделаю на базе коллекции ObservableCollection и привязок данных. Как я понял, вы не знакомы с шаблоном проектирования MVVM, и весь код у вас в классе окна. Поэтому покажу с настройкой датаконтекста именно туда.

Для работы кода, представленного ниже, потребуется вспомогательный класс для использования команд (их используют в WPF вместо обработчиков событий).

public class RelayCommand : ICommand
{
    private readonly Action<object> _execute;
    private readonly Predicate<object> _canExecute;
public event EventHandler CanExecuteChanged
{
    add =&gt; CommandManager.RequerySuggested += value;
    remove =&gt; CommandManager.RequerySuggested -= value;
}

public RelayCommand(Action&lt;object&gt; execute, Predicate&lt;object&gt; canExecute = null)
    =&gt; (_execute, _canExecute) = (execute, canExecute);

public bool CanExecute(object parameter)
    =&gt; _canExecute == null || _canExecute(parameter);

public void Execute(object parameter)
    =&gt; _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&lt;string&gt; Items
{
    get =&gt; _items;
    set
    {
        _items = value;
        OnPropertyChanged();
    }
}

public int SelectedIndex =&gt; Items.IndexOf(SelectedItem);

public string SelectedItem
{
    get =&gt; _selectedItem;
    set
    {
        _selectedItem = value;
        OnPropertyChanged();
    }
}

public MainWindow()
{
    InitializeComponent();
    DataContext = this;
}

public ICommand MoveCommand =&gt; _moveCommand ?? (_moveCommand = new RelayCommand(parameter =&gt;
{
    if (Enum.TryParse(parameter as string, out Direction direction))
    {
        int index = SelectedIndex;
        if (direction == Direction.Backward &amp;&amp; index &gt; 0)
        {
            Items.Move(index, index - 1);
        }
        else if (direction == Direction.Forward &amp;&amp; index &lt; Items.Count - 1)
        {
            Items.Move(index, index + 1);
        }
    }
}));

private void Window_Loaded(object sender, RoutedEventArgs e)
{
    Items = new ObservableCollection&lt;string&gt;
    {
        &quot;https://image1_url&quot;,
        &quot;https://image2_url&quot;
    };
}

public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    =&gt; 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, они здесь без надобности.

aepot
  • 49,560
  • _moveCommand можете написать конструкцию ниже 8.0 версии? ??= - неизвестный оператор. – North Face Apr 27 '21 at 11:28
  • @NorthFace готово. – aepot Apr 27 '21 at 11:29
  • Собрал пустой проект, отображается пустой экран. Указывал путь до http://*.jpeg или C:\Users\*.jpeg. – North Face Apr 27 '21 at 11:40
  • @NorthFace вы не забыли добавить в самом верху INotifyPropertyChanged для Window? Так же надо подключить обработчик события у окна Loaded="Window_Loaded" в XAML. – aepot Apr 27 '21 at 11:43
  • @NorthFace using System.ComponentModel; в директивах и можно просто INotifyPropertyChanged потом писать. – aepot Apr 27 '21 at 11:48
  • В Window_Loaded дебагер не заходит. – North Face Apr 27 '21 at 12:00
  • @NorthFace Значит вы его не подключили. Напишите в XAML то, что я вам выше показал, в параметрах окна, там где высота, ширина, заголовок и т.д. – aepot Apr 27 '21 at 12:05
  • @NorthFace не получается? я могу показать, если надо. Просто не ожидал, что подключение обработчика в принципе может вызвать сложности. – aepot Apr 27 '21 at 12:32
1

Суть в следующем. У ListBox есть стандартное поведение перемещения по списку влево и вправо. И его необходимо перегрузить. Я этого не сделал и происходил конфликт. На данный момент можно решить так:

  1. У главного Grid определить событие KeyDown с именем OnKeyDownHandler.
private void OnKeyDownHandler(object sender, KeyEventArgs e)
  1. В этом методе выполнять алгоритм:
 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();
        }
    }
}

}

Ссылка на другой ответ