如何以编程方式在WPF ListView中设置选择标记(焦点项)

时间:2013-02-23 21:04:59

标签: wpf wpf-controls

我找不到以编程方式在WPF ListView中设置焦点项的方法。我只能找到所选项目的变体项目|指数|值,但“聚焦”项目与“已选择”项目没有直接关系 - 可以不选择聚焦项目(例如,使用Ctrl + Click取消选择当前项目时)。

要简短 - 我想从以下提供的代码中获取以下behvaiour(它使用虚拟8项填充虚拟列表视图,并在按下X时尝试从最后聚焦第2项):

通缉行为:

  • 使用鼠标 - 选择第二项
  • 按X - 这将结束第二项
  • 按下键盘上的'Down'数组 - 这应该将当前选择移动到最后一项

实际发生了什么:

  • 使用鼠标 - 选择第二项
  • 按X - 从最后选择第2个项目,但焦点仍然是从开始的第2个项目
  • 在键盘上按“向下”数组 - 这应该将当前选择移动到最后一项,但是会选择第3项。

注意:普通的Win32 API(当然,与WPF完全不同)有LVM_SETSELECTIONMARK消息。我在WPF中找不到模拟。它存在吗?

示例XAML:

<Window x:Class="WpfListviewTest.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:sys="clr-namespace:System;assembly=mscorlib"
        Title="MainWindow" Height="350" Width="525">
  <ListView x:Name="List1" KeyDown="List1_KeyDown">
    <ListView.View>
      <GridView>
        <GridViewColumn Width="140" Header="Column 1" />
        <GridViewColumn Width="140" Header="Column 2" />
        <GridViewColumn Width="140" Header="Column 3" />
      </GridView>
    </ListView.View>
    <sys:DateTime>1/2/3</sys:DateTime>
    <sys:DateTime>4/5/6</sys:DateTime>
    <sys:DateTime>7/8/9</sys:DateTime>
    <sys:DateTime>10/11/12</sys:DateTime>
    <sys:DateTime>1/2/3</sys:DateTime>
    <sys:DateTime>4/5/6</sys:DateTime>
    <sys:DateTime>7/8/9</sys:DateTime>
    <sys:DateTime>10/11/12</sys:DateTime>
  </ListView>
</Window>

示例代码隐藏:

public partial class MainWindow : Window {
    public MainWindow() {
        InitializeComponent();
    }

    private void List1_KeyDown(object sender, KeyEventArgs e) {
        if( e.Key == Key.X ) {
            List1.SelectionMode = SelectionMode.Single;
            List1.SelectedIndex = List1.Items.Count - 2;
        }
    }
}

2 个答案:

答案 0 :(得分:2)

感谢Mic的答案中的链接,我得到了更多有用的信息,并为我的案例找到了可行的解决方案。

一些背景资料:

  • 与ListView控件的其他实现(在WinForms或Win32中)不同,WPF的ListView版本没有像FocusedItem那样的东西。似乎MS决定使用UIElement的通用接口将项目集中在listview中,每个可视化的ListViewItem都是它的后代。这导致我的用例在WPF中变得更加复杂。

  • WPF列表视图中的可视项目可以通过ListView.ItemContainerGenerator.ContainerFromIndex(index) as ListViewItem获得,此项目具有Focus()方法,该方法可以执行所需的功能。但是在虚拟化列表视图中,这不适用于当前可见区域之外的项目 - 它们尚未创建,方法返回null :(

  • 这就是为什么,首先,需要使项目聚焦可见。这可以通过ListView中的ScrollViewer使用其ScrollToVerticalOffset()方法完成(您需要知道要滚动到的项目的索引 - 可以使用ListView.Items.IndexOf()完成)。

  • 接下来,在完成程序化滚动后,不会立即创建非正常的listview项目 - 这就是为什么在创建新的可见项目后可以在一段时间后完成实际聚焦的原因。我找到了适当的事件 - ListView.LayoutUpdated。

  • 侧节点:

    • ListView.ScrollIntoView(object item)方法似乎是一个“原生路径”来获得相同的东西,但是当列表在不同的位置包含相对相等的项目时,它不能可靠地工作。 ListView没有类似ListView.ScrollIndexIntoView(int index)

    • 的内容
    • 上面的大多数工作人员似乎都采用单一方法VirtualizingStackPanel.BringIndexIntoView(),但MS决定对其进行保护,因此无法从外部访问...

这是工作解决方案

示例XAML:

<Window x:Class="WpfListviewTest.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:sys="clr-namespace:System;assembly=mscorlib"
        Title="MainWindow" Height="150" Width="525">
  <ListView x:Name="List1" KeyDown="List1_KeyDown" LayoutUpdated="List1_LayoutUpdated">
    <ListView.View>
      <GridView>
        <GridViewColumn Width="140" Header="Column 1" />
        <GridViewColumn Width="140" Header="Column 2" />
        <GridViewColumn Width="140" Header="Column 3" />
      </GridView>
    </ListView.View>
    <sys:DateTime>1/2/3</sys:DateTime>
    <sys:DateTime>4/5/6</sys:DateTime>
    <sys:DateTime>7/8/9</sys:DateTime>
    <sys:DateTime>10/11/12</sys:DateTime>
    <sys:DateTime>1/2/3</sys:DateTime>
    <sys:DateTime>4/5/6</sys:DateTime>
    <sys:DateTime>7/8/9</sys:DateTime>
    <sys:DateTime>10/11/12</sys:DateTime>
  </ListView>
</Window>

示例代码隐藏:

public partial class MainWindow : Window {
    public MainWindow() {
        InitializeComponent();
    }

    private int? _indexToFocus;

    private void List1_KeyDown(object sender, KeyEventArgs e) {
        switch( e.Key ) {
            case Key.S:
                FocusItemByIndex(1); break;
            case Key.X:
                FocusItemByIndex(List1.Items.Count - 2); break;
            case Key.Z:
                List1.ScrollIntoView(List1.Items[List1.Items.Count - 2]); break;
        }
    }

    public void FocusItemByIndex(int index) {
        ScrollViewer sv = FindChild<ScrollViewer>(List1);
        double firstVisible = sv.VerticalOffset;
        double lastVisible = firstVisible + sv.ViewportHeight;

        if( index > lastVisible ) {
            double topVisible = index - sv.ViewportHeight + 1;
            sv.ScrollToVerticalOffset(topVisible);
        }
        else if( index < firstVisible ) {
            sv.ScrollToVerticalOffset(index);
        }

        _indexToFocus = index;
    }

    public static T FindChild<T>(DependencyObject parent, string name = null) 
        where T : DependencyObject 
    {
        if( parent == null )
            return null;

        int cChildren = VisualTreeHelper.GetChildrenCount(parent);
        T result = null;

        for( int i = 0; (result == null) && (i < cChildren); i++ ) {
            DependencyObject child = VisualTreeHelper.GetChild(parent, i);
            T tChild = child as T;

            if( tChild != null ) {
                if( name == null ) {
                    result = (T)child;
                }
                else {
                    FrameworkElement feChild = child as FrameworkElement;

                    if( feChild != null && feChild.Name == name )
                        result = (T)child;
                }
            }

            if( result == null )
                result = FindChild<T>(child, name);
        }

        return result;
    }

    private void List1_LayoutUpdated(object sender, EventArgs e) {
        if( _indexToFocus != null ) {
            ItemContainerGenerator lvItems = List1.ItemContainerGenerator;
            ListViewItem lvitemToFocus = lvItems.ContainerFromIndex(_indexToFocus.Value) as ListViewItem;

            if( lvitemToFocus != null ) {
                lvitemToFocus.Focus();
                _indexToFocus = null;
            }
        }
    }  
}

答案 1 :(得分:1)

看起来您必须通过代码隐藏来执行此操作,并查看/修改IsFocused属性。您可以在此blog post中找到更多信息。

您还可以查看this SO post,它可以准确说明您的需求。