WPF中类似Windows资源管理器的LIstView键盘导航

WPF中类似Windows资源管理器的LIstView键盘导航,wpf,mvvm,keyboard-navigation,Wpf,Mvvm,Keyboard Navigation,我希望在Windows资源管理器的图标视图中有键盘导航,即如果移动到屏幕宽度的末尾,则选择的内容应移动到下一行 <ListView Name="lv" Grid.Row="1" Width="Auto" Height="Auto" IsTextSearchEnabled="True" ItemsSource="{Binding Path=Per

我希望在Windows资源管理器的图标视图中有键盘导航,即如果移动到屏幕宽度的末尾,则选择的内容应移动到下一行

    <ListView Name="lv"
              Grid.Row="1"
              Width="Auto"
              Height="Auto"
              IsTextSearchEnabled="True"
              ItemsSource="{Binding Path=Persons}"
              KeyboardNavigation.DirectionalNavigation="Continue"
              SelectedItem="{Binding Path=SelectedPerson}"
              SelectionMode="Single"
              View="{StaticResource ResourceKey=plainView}">
        <ListView.Resources>
            <Style TargetType="{x:Type ListViewItem}" BasedOn="{StaticResource {x:Type ListViewItem}}">
                <Style.Triggers>
                    <DataTrigger Binding="{Binding Path=IsSelected}" Value="True">
                        <Setter Property="IsEnabled"  Value="False"></Setter>    
                    </DataTrigger>
                </Style.Triggers>
            </Style>
        </ListView.Resources>
    </ListView>


在Generic.xaml中定义ListView默认样式,如下所示:

<Style TargetType="{x:Type ListView}">
        <Setter Property="SnapsToDevicePixels" Value="true" />        
        <Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Visible" />
        <Setter Property="ScrollViewer.VerticalScrollBarVisibility" Value="Visible" />
        <Setter Property="ScrollViewer.CanContentScroll" Value="True" />
        <Setter Property="HorizontalContentAlignment" Value="Stretch" />
        <Setter Property="VerticalContentAlignment" Value="Center" />
        <Setter Property="FontFamily" Value="Trebuchet MS" />
        <Setter Property="FontSize" Value="12" />
        <Setter Property="BorderBrush" Value="{DynamicResource ControlBorderBrush}" />
        <Setter Property="BorderThickness" Value="1" />
        <Setter Property="Padding" Value="1" />
        <Setter Property="IsTabStop" Value="False" />
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type ListView}">
                    <Grid>
                        <Border x:Name="Border"
                                Background="{DynamicResource ControlBackgroundBrush}"
                                BorderBrush="{TemplateBinding BorderBrush}"
                                BorderThickness="{TemplateBinding BorderThickness}"
                                CornerRadius="1">
                            <ScrollViewer Margin="{TemplateBinding Padding}" IsTabStop="False">                                
                                    <ItemsPresenter/>
                            </ScrollViewer>
                        </Border>
                        <Border x:Name="DisabledVisualElement"
                                Background="#A5FFFFFF"
                                BorderBrush="#66FFFFFF"
                                BorderThickness="1"
                                IsHitTestVisible="false"
                                Opacity="0" />
                    </Grid>
                    <ControlTemplate.Triggers>
                        <Trigger Property="IsEnabled" Value="false">
                            <Setter TargetName="DisabledVisualElement" Property="Opacity" Value="1" />
                        </Trigger>
                        <Trigger Property="IsGrouping" Value="true">
                            <Setter Property="ScrollViewer.CanContentScroll" Value="false" />
                        </Trigger>
                    </ControlTemplate.Triggers>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
        <Setter Property="ItemsPanel">
            <Setter.Value >
                <ItemsPanelTemplate>
                    <WrapPanel Height="{Binding ActualHeight,
                                                            RelativeSource={RelativeSource AncestorType=Border}}"
                                           MinWidth="{Binding (ListView.View).MinWidth,
                                                              RelativeSource={RelativeSource Mode=FindAncestor,
                                                                                             AncestorType={x:Type ListView}}}"
                                           Focusable="False"
                                           IsItemsHost="True"
                               KeyboardNavigation.DirectionalNavigation="Contained"
                                           ItemWidth="{Binding (ListView.View).ItemWidth,
                                                               RelativeSource={RelativeSource Mode=FindAncestor,
                                                                                              AncestorType={x:Type ListView}}}"                                           
                                           Orientation="Vertical" />
                </ItemsPanelTemplate>
            </Setter.Value>
        </Setter>
    </Style>


小说明:您的
WrapPanel
占用了您的
ScrollViewer
的所有可用大小,这实际上是无限的。如果你想让你的项目在垂直包装中滚动,你应该在水平宽度中限制你的高度。

我发现实现这一点的唯一方法是手动解释 PreviewKeyDown事件并设置selectedindex。 您必须将handled设置为true,否则listview也将解释键,这将导致错误的键导航

下面是一个带有两个键的示例:

    private void listView_PreviewKeyDown(object sender, KeyEventArgs e)
    {
      if (listView.SelectedIndex >= 0)
      {
        if (e.Key == Key.Right)
        {
          listView.SelectedIndex++;
        }
        if (e.Key == Key.Left)
        {
          listView.SelectedIndex--;
        }
      e.Handled = true;
      }
   }
编辑:使用MVVMLight工具包的100%纯MVVM方式

XAML:

xmlns:mvvmLight=“clr命名空间:GalaSoft.mvvmLight.Command;assembly=GalaSoft.mvvmLight.Extras.WPF4”
xmlns:i=“clr命名空间:System.Windows.Interactivity;assembly=System.Windows.Interactivity”
>
视图模型:

public ICommand PreviewKeyDownCommand
{
  get
  {
    return new RelayCommand<Object>(x => this.PreviewKeyDown(x as KeyEventArgs));
  }
}

private void PreviewKeyDown(KeyEventArgs e)
{
  if (SelectedIndex >= 0)
  {
    if (e.Key == Key.Right)
    {
      SelectedIndex++;
    }
    if (e.Key == Key.Left)
    {
      SelectedIndex--;
    }
  }
  e.Handled = true;
}

private int _selectedIndex;

public int SelectedIndex
{
  get { return _selectedIndex; }
  set
  {
    _selectedIndex = value;
    NotifyPropertyChanged("SelectedIndex");
  }
}
public ICommand previewkeydown命令
{
得到
{
返回新的RelayCommand(x=>this.PreviewKeyDown(x为KeyEventArgs));
}
}
私有void PreviewKeyDown(KeyEventArgs e)
{
如果(已选择索引>=0)
{
如果(e.Key==Key.Right)
{
选择dex++;
}
如果(e.Key==Key.Left)
{
选择索引--;
}
}
e、 已处理=正确;
}
私有int_选择的索引;
公共整数选择索引
{
获取{return\u selectedIndex;}
设置
{
_selectedIndex=值;
NotifyPropertyChanged(“SelectedIndex”);
}
}

我也尝试了
键盘导航。DirectionalNavigation=“Cycle”
通过这样做,我的导航现在包含在。。。也就是说,如果我到达宽度的末尾(通过使用键盘的右键),它会停在那里,我希望它移动到下一行……这是参与导航场景的
FocusNavigationDirection
enum,如果是WrapPanel,当你按键盘上的光标键时,你将只在一行的边界内导航(左、右)或一列(上、下)除了编写你自己的聚焦场景提供商之外,没有其他解决方案了。是的,看起来……那么你能给我一些关于编写这种导航提供商的指导吗?谢谢你的回答,但是我使用的是MVVM,所以我不希望有代码隐藏……我们能为你的上述方法做些什么吗???好的,我编辑了我的帖子并补充道MVVM方法。我使用MVVM light toolkit将Keydown事件传递给VM,并将SelectedIndex绑定到ListView。更好的方法是创建一个行为正确的CustomControl。除此之外,MVVM完全可以使用Keydown事件并将其传递给ViewModel。感谢您的编辑……但我在这里遇到的问题是m垂直包装,所以当我按下右键时,我希望转到Adcent columns对象,但在您的情况下,它不起作用,因为它会移动到上一个或下一个选定的项目。另外,通过
附加行为可以避免将KeyEventArgs带到ViewModel中
我编写的代码已经在使用您的代码了…如果您愿意,我会发布,但我的上述行为仍然有效t陈述了问题所在
public ICommand PreviewKeyDownCommand
{
  get
  {
    return new RelayCommand<Object>(x => this.PreviewKeyDown(x as KeyEventArgs));
  }
}

private void PreviewKeyDown(KeyEventArgs e)
{
  if (SelectedIndex >= 0)
  {
    if (e.Key == Key.Right)
    {
      SelectedIndex++;
    }
    if (e.Key == Key.Left)
    {
      SelectedIndex--;
    }
  }
  e.Handled = true;
}

private int _selectedIndex;

public int SelectedIndex
{
  get { return _selectedIndex; }
  set
  {
    _selectedIndex = value;
    NotifyPropertyChanged("SelectedIndex");
  }
}