Функциональность всплывающего ключа ListView - PullRequest
1 голос
/ 07 января 2020

Я реализовал просмотр списка в виде всплывающего списка. Теперь я хотел бы добавить к нему ключевые функции, например, если всякий раз, когда в текстовом поле нажимается стрелка вверх, он должен выбирать элемент в моем представлении списка, и если продолжение нажатия KEY_UP / DOWN продолжается, он должен продолжать изменять свой индекс соответственно.

Это EditMessageTextBox и связанный EditMessageTagPopup enter image description here

Используется код XAML:

<Grid x:Name="EditGrid"
      Grid.Row="1"
      Visibility="{Binding EditMessageControlVisibility}"
      FocusManager.IsFocusScope="False"
      VerticalAlignment="Center"
      Grid.Column="1"
      HorizontalAlignment="Stretch">
  <Grid.RowDefinitions>
    <RowDefinition Height="Auto" />
    <RowDefinition Height="Auto" />
  </Grid.RowDefinitions>
  <Border x:Name="EditMessageBorder"
          Grid.Row="0"
          BorderThickness="1"
          CornerRadius="1"
          Margin="0,10,0,0"
          BorderBrush="Gray">
    <Grid>
      <TextBlock FontSize="16"
                 Margin="10,0,0,3"
                 VerticalAlignment="Center"
                 HorizontalAlignment="Left"
                 Text="Edit message"
                 Foreground="{StaticResource brushWatermarkForeground}"
                 Visibility="{Binding ElementName=EditMessageTextBox, Path=Text.IsEmpty, Converter={StaticResource BooleanToVisibilityConverter}}" />
      <TextBox Name="EditMessageTextBox"
               Text="{Binding MessageToEdit, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
               BorderBrush="Transparent"
               BorderThickness="0"
               Foreground="Black"
               FontSize="16"
               Margin="8,1,1,1"
               VerticalContentAlignment="Center"
               HorizontalContentAlignment="Left"
               MinHeight="35"
               ScrollViewer.VerticalScrollBarVisibility="Auto"
               TextWrapping="Wrap"
               AcceptsReturn="False"
               KeyUp="OnEditMessage_KeyUp"
               SpellCheck.IsEnabled="true" />
    </Grid>
  </Border>
  <StackPanel Grid.Row="1"
              Margin="0,10"
              Orientation="Horizontal">
    <Button Background="Transparent"
            VerticalContentAlignment="Center"
            Padding="5,2,5,3"
            Foreground="Black"
            BorderBrush="Gray"
            BorderThickness="0.8"
            Width="100"
            materialDesign:ShadowAssist.ShadowDepth="Depth0"
            Click="EditMessageCancelButton_Clicked">Cancel</Button>
    <Button Name="EditMessageButton"
            VerticalContentAlignment="Center"
            Padding="5,2,5,3"
            Background="#007a5a"
            Foreground="White"
            BorderBrush="#007a5a"
            Margin="15,0,0,0"
            materialDesign:ShadowAssist.ShadowDepth="Depth0"
            BorderThickness="0.8"
            IsEnabled="True"
            Width="140"
            Content="Save Changes"
            Click="EditMessageSaveButton_Clicked" />
  </StackPanel>    

  <Popup x:Name="EditMessageTagPopup"
         AllowsTransparency="True"
         IsOpen="{Binding IsOpenTagPopUp}"
         StaysOpen="False"
         Placement="Top"
         PlacementTarget="{Binding ElementName=EditMessageTextBox}">
    <Border materialDesign:ShadowAssist.ShadowDepth="Depth5"
            CornerRadius="5"
            Background="White"
            BorderBrush="Black"
            BorderThickness="0.8"
            MaxHeight="200">
      <ListView x:Name="EditTaggedUsers"
                Focusable="True"
                IsSynchronizedWithCurrentItem="True"
                ItemsSource="{Binding Source={StaticResource UserListForTag}}"
                SelectionChanged="EditMessageTagList_SelectionChanged">
        <ListView.ItemContainerStyle>
          <Style TargetType="ListViewItem">
            <Setter Property="Template">
              <Setter.Value>
                <ControlTemplate TargetType="ListViewItem">
                  <Border Name="_Border"
                          Padding="8">
                    <ContentPresenter />
                  </Border>
                  <ControlTemplate.Triggers>
                    <Trigger Property="IsMouseOver"
                             Value="True">
                      <Setter TargetName="_Border"
                              Property="Background"
                              Value="#FF3BD38E" />
                      <Setter Property="Foreground"
                              Value="White" />
                    </Trigger>
                    <Trigger Property="IsSelected"
                             Value="True">
                      <Setter TargetName="_Border"
                              Property="Background"
                              Value="#FF205B4B" />
                      <Setter Property="Foreground"
                              Value="White" />
                    </Trigger>
                  </ControlTemplate.Triggers>
                </ControlTemplate>
              </Setter.Value>
            </Setter>
          </Style>
        </ListView.ItemContainerStyle>
        <ListView.ItemTemplate>
          <DataTemplate>
            <Grid Margin="-15,0,0,0"
                  Width="500">
              <Grid.ColumnDefinitions>
                <ColumnDefinition Width="70" />
                <ColumnDefinition Width="*" />
              </Grid.ColumnDefinitions>
              <Rectangle Grid.Column="0"
                         RadiusY="5"
                         RadiusX="5"
                         Height="20"
                         Width="20">
                <Rectangle.Fill>
                  <ImageBrush ImageSource="{Binding ProfileImage}"
                              Stretch="UniformToFill" />
                </Rectangle.Fill>
              </Rectangle>
              <TextBlock Grid.Column="1"
                         Text="{Binding FullName}"
                         Margin="-10,0,0,0" />
            </Grid>
          </DataTemplate>
        </ListView.ItemTemplate>
      </ListView>
    </Border>
  </Popup>
</Grid>

и вот код позади:

 ApplicationContext.StoredEditingMessage = (String)ApplicationContext.EditMessageText;
                var messageData = ((TextBox)sender).DataContext as ChatsModel;
                var EditMessagePopup = FindEditMessagePopup(MessageList);
                Border EditEessageBorder = EditMessagePopup.Child as Border;
                ListView EditMessageTagList = EditEessageBorder.Child as ListView;

                Dispatcher?.Invoke(() =>
                {
                    if (_contactsViewModel.GroupedChatByDate
                        .Find(x => messageData != null && x.MessageGuid == messageData.MessageGuid)
                        .IsOpenTagPopUp == false) return;
                    var index = _contactsViewModel.UsersListForTag.IndexOf(_contactsViewModel.UsersListForTag.FirstOrDefault(x => x.Selected == true));


                    switch (e.Key)
                    {
                        case Key.Up:
                            if (EditMessageTagList.SelectedIndex > 0)
                            {
                                EditMessageTagList.SelectedIndex -= 1;
                                EditMessageTagList.ScrollIntoView(EditMessageTagList.Items[EditMessageTagList.SelectedIndex]);
                            }
                            else
                            {
                                EditMessageTagList.SelectedIndex = _contactsViewModel.UsersListForTag.Count - 1;
                                EditMessageTagList.ScrollIntoView(EditMessageTagList.Items[EditMessageTagList.SelectedIndex]);
                            }
                            break;
                        case Key.Down:
                            if (EditMessageTagList.SelectedIndex + 1 == _contactsViewModel.UsersListForTag.Count)
                            {
                                EditMessageTagList.SelectedIndex = 0;
                                _contactsViewModel.UsersListForTag[index].Selected = true;
                                EditMessageTagList.ScrollIntoView(EditMessageTagList.Items[EditMessageTagList.SelectedIndex]);
                            }
                            else
                            {
                                EditMessageTagList.SelectedIndex += 1;
                                _contactsViewModel.UsersListForTag[index].Selected = true;
                                EditMessageTagList.ScrollIntoView(EditMessageTagList.Items[EditMessageTagList.SelectedIndex]);
                            }
                            break;
                    }
                    _contactsViewModel.UsersListForTag.ForEach(x => x.Selected = false);
                    if (index != -1)
                    {
                        _contactsViewModel.UsersListForTag[index].Selected = true;
                    }
                });

Я пытался добавить элемент в прокрутке в view () вместо выбранного индекса, но без обновления

, когда сделан идеальный выбор, эта функция вызывается из кода позади

private void EditMessageTagList_SelectionChanged(object sender, SelectionChangedEventArgs e)
    {
        try
        {
            var messageModel = ((ListView)sender).DataContext as ChatsModel;

            if (((ListView)sender).SelectedItem is UserModel selectedUserForTag)
            {
               // _contactsViewModel.GroupedChatByDate.Find(x => messageModel != null && x.MessageGuid == messageModel.MessageGuid) .IsOpenTagPopUp = false;
                string SelectedTag = (selectedUserForTag.Id == ApplicationContext.CurrentLoggedInUserGuid) ? $"{selectedUserForTag.UserName.Replace("(you) ", "")} " : $"{selectedUserForTag.UserName} ";
                _contactsViewModel.GroupedChatByDate.Find
                    (x => messageModel != null && x.MessageGuid == messageModel.MessageGuid)
                .MessageToEdit = "@" + SelectedTag;
            }

            // ((ListView) sender).SelectedItem = null;
        }
        catch (Exception exception)
        {
            LoggingManager.Error(exception);
        }
    }

Вот экранная запись о проблеме

и

Вот рабочая функциональность

1 Ответ

0 голосов
/ 07 января 2020

Проблема в том, что после каждой навигации к следующему элементу ListView вы хотите установить фокус на выделение TextBox, которое связывается с SelectedItem. В противном случае навигация по элементам ListView с помощью клавиш со стрелками уже является поведением по умолчанию ListView.

Самое простое решение - захватить ввод с клавиатуры, используя UIElement.InputBinding для выбора TextBox (который позволяет обрабатывать клавиши в модели представления), а затем

  1. Выберите следующий / предыдущий элемент
  2. Прокрутка выбранного элемента в поле зрения
  3. Перемещение фокуса на выделение TextBox
  4. Перемещение курсора выделения TextBox в конец

DataItem.cs

class DataItem
{
  public string FullName { get; set; }

  public DataItem(string fullName) => this FullName = fullName;
}

ViewModel.cs

class ViewModel : INotifyPropertyChanged
{
  public ObservableCollection<DataItem> DataItems { get; set; }
  public ICommand SelectNextCommand => new AsyncRelayCommand(SelectNextItem);
  public ICommand SelectPreviousCommand => new AsyncRelayCommand(SelectPreviousItem);  

  private bool IsSelectedItemChangeInternal { get; set; }

  private DataItem selectedDataItem;
  public DataItem SelectedDataItem 
  {
    get => this.selectedDataItem; 
    set
    {
      this.selectedDataItem = value;
      OnPropertyChanged();

      // Do not filter the list when the selected item was set by the user
      // e.g. by using arrow keys
      if (!this.IsSelectedItemChangeInternal)
      {
        UpdateSearchFilter();
      }
    }
  }         

  private string filterKey;
  public string FilterKey
  {
    get => this.filterKey; 
    set
    {
      this.filterKey = value;
      OnPropertyChanged();

      // Only apply filters when the FilterKey was changed by the user
      // e.g. by editing the edit TextBox that binds to this property
      if (!this.IsSelectedItemChangeInternal)
      {
        ApplySearchFilter();
      }
    }        
  }         

  public ViewModel()
  {
    this.DataItems = new ObservableCollection<DataItems>();
    for (var index = 0; index < 100; index++)
    {
      this.DataItems.Add(new DataItem("name " + index.ToString());
    }
  }      

  private void ApplySearchFilter()
  {
    ICollectionView collectionView = CollectionViewSource.GetDefaultView(this.Games);
    this.IsSelectedItemChangeInternal = true;

    collectionView.Filter = item => 
      string.IsNullOrWhiteSpace(this.FilterKey) || (item as DetailItem).FullName.StartsWith(this.FilterKey);

    // pre-select the first match
    collectionView.MoveCurrentToFirst();

    this.IsSelectedItemChangeInternal = false;
  }  

  private void UpdateSearchFilter()
  {        
    this.IsSelectedItemChangeInternal = true;
    this.FilterKey = this.SelectedDataItem.FullName;
    this.IsSelectedItemChangeInternal = false;
  }

  private void SelectNextItem()
  {
    ICollectionView collectionView = CollectionViewSource.GetDefaultView(this.DataItems);
    collectionView.MoveCurrentToNext();

    // Loop
    if (collectionView.IsCurrentAfterLast)
    {
      collectionView.MoveCurrentToFirst();
    }
  }

  private void SelectPreviousItem()
  {
    ICollectionView collectionView = CollectionViewSource.GetDefaultView(this.DataItems);
    collectionView.MoveCurrentToPrevious();

    // Loop
    if (collectionView.IsCurrentBeforeFirst)
    {
      collectionView.MoveCurrentToLast();
    }
  }

  public event PropertyChangedEventHandler PropertyChanged;
  private void OnPropertyChanged([CallerMemberName] string propertyName = null)
  {
    this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName);
  }
}

MainWindow.xaml. cs

private void AdjustFocus_OnSelectionChanged(object sender, SelectionChangedEventArgs e)
{
  var listView = sender as ListView;
  listView.ScrollIntoView(listView.SelectedItem);

  Application.Current.Dispatcher.InvokeAsync(() =>
  {
    Keyboard.Focus(this.EditMessageTextBox);
    this.EditMessageTextBox.CaretIndex = this.EditMessageTextBox.Text.Length;
  });
}

private void AdjustFocus_OnOpened(object sender, EventArgs e)
{
  this.EditTaggedUsers.Focus();
}

MainWindow.xaml

<Window>
  <Window.DataContext>
    <ViewModel />
  </Window.DataContex>

  <Grid>
    <TextBox x:Name="EditMessageTextBox"
             Text="{Binding FilterKey}">
      <TextBox.InputBindings>
        <KeyBinding Key="Down"
                    Command="{Binding SelectNextCommand}" />
        <KeyBinding Key="Up"
                    Command="{Binding SelectPreviousCommand}" />
      </TextBox.InputBindings>
    </TextBox>

    <Popup IsOpen="True"
           Opened="AdjustFocus_OnOpened"
           StaysOpen="False"
           Placement="Top"
           PlacementTarget="{Binding ElementName=EditMessageTextBox}">
      <ListView IsSynchronizedWithCurrentItem="True"
                Height="400"
                SelectedItem="{Binding SelectedDataItem}"
                ItemsSource="{Binding DataItems}"
                SelectionChanged="AdjustFocus_OnSelectionChanged">
        <ListView.ItemTemplate>
          <DataTemplate DataType="{x:Type DataItem}">
            <TextBox Text="{Binding FullName}" />
          </DataTemplate>
        </ListView.ItemTemplate>
      </ListView>
    </Popup>
  </Grid>
</Window>

Примечания

Поскольку навигация выполняется с помощью CollectionView ItemsSource свойство ListView.IsSynchronizedWithCurrentItem должно быть установлено на true. В противном случае навигация CollectionView не повлияет на вид.

...