【问题标题】:How to programmatically set selection mark (focused item) in WPF ListView [duplicate]如何以编程方式在 WPF ListView 中设置选择标记(焦点项目)[重复]
【发布时间】:2013-02-23 21:04:58
【问题描述】:

我找不到在 WPF ListView 中以编程方式设置焦点项目的方法。我只能找到 Selected Item | 的变体项目 |索引 |值,但 'Focused' 项目与 'selected' 项目没有直接关系 - 可以不选择焦点项目(例如,当使用 Ctrl+Click 取消选择当前项目时)。

简而言之 - 我想从下面提供的代码中获得以下行为(它用 8 个虚拟项目填充虚拟列表视图,并在按下 X 时尝试从末尾开始关注第二个项目):

想要的行为:

  • 使用鼠标 - 选择第二项
  • 按 X - 这将聚焦从最后的第二个项目
  • 按键盘上的“向下”数组 - 这应该会将当前选择移动到最后一项

实际发生了什么:

  • 使用鼠标 - 选择第二项
  • 按 X - 这会选择从末尾开始的第二个项目,但焦点仍然在从开始的第二个项目上
  • 按键盘上的“向下”数组 - 这应该将当前选择移动到最后一项,但改为选择第 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;
        }
    }
}

【问题讨论】:

    标签: wpf wpf-controls


    【解决方案1】:

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

    一些背景资料:

    • 与 ListView 控件的其他实现不同(在 WinForms 或 Win32 中),WPF 的 ListView 版本没有像 FocusedItem 这样的东西。似乎 MS 决定使用 UIElement 的通用接口将项目集中在列表视图中,每个视觉 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;
                }
            }
        }  
    }
    

    【讨论】:

      【解决方案2】:

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

      您还可以查看this SO post,它准确地解释了您的需求。

      【讨论】:

        猜你喜欢
        • 2010-11-07
        • 1970-01-01
        • 2012-07-29
        • 1970-01-01
        • 2013-12-25
        • 1970-01-01
        • 2011-08-13
        • 2010-10-08
        • 1970-01-01
        相关资源
        最近更新 更多