以编程方式设置ListView.SelectedItem后,箭头键不起作用

Che*_*eso 10 wpf listview observablecollection icollectionview

我有一个WPF ListView控件,ItemsSource设置为以这种方式创建的ICollectionView:

var collectionView = 
  System.Windows.Data.CollectionViewSource.GetDefaultView(observableCollection);
this.listView1.ItemsSource = collectionView;
Run Code Online (Sandbox Code Playgroud)

...其中observableCollection是复杂类型的ObservableCollection.ListView配置为为每个项目显示复杂类型上的一个字符串属性.

用户可以刷新ListView,此时我的逻辑存储当前所选项的"键字符串",重新填充底层的observableCollection.然后将先前的排序和过滤器应用于collectionView.此时我想"重新选择"在刷新请求之前选择的项目.observableCollection中的项是新实例,因此我比较各自的字符串属性,然后选择匹配的字符串属性.像这样:

private void SelectThisItem(string value)
{
    foreach (var item in collectionView) // for the ListView in question
    {
        var thing = item as MyComplexType;
        if (thing.StringProperty == value)
        {
            this.listView1.SelectedItem = thing;
            return;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

这一切都有效.如果选择了第4项,并且用户按下F5,则重构列表,然后选择具有与前4项相同的字符串属性的项.有时这是新的第4项,有时不是,但它提供了" 最不惊讶的行为 ".

当用户随后使用箭头键在ListView中导航时,问题就出现了.刷新后的第一个向上或向下箭头会导致(新)列表视图中的第一个项目被选中,而不管前一个逻辑选择了哪个项目.任何进一步的箭头键按预期工作.

为什么会这样?

这显然违反了"最不惊讶"的规则.我怎么能避免呢?


编辑
进一步搜索,这似乎与未答复的
WPF ListView箭头导航和击键问题描述的相同异常,除了我提供更多细节.

Che*_*eso 15

看起来这是由于ListView(以及其他一些WPF控件)的某种已知但未充分描述的问题行为.Focus()在以编程方式设置SelectedItem之后,它需要在特定ListViewItem上调用app .

但是SelectedItem本身不是UIElement.它是ListView中显示的任何项目,通常是自定义类型.所以你不能打电话this.listView1.SelectedItem.Focus().那不行.您需要获取显示该特定项目的UIElement(或Control).WPF接口的一个黑暗角落叫做ItemContainerGenerator,它可以让你获得在ListView中显示特定项目的控件.

像这样的东西:

this.listView1.SelectedItem = thing;
// *** WILL NOT WORK!
((UIElement)this.listView1.ItemContainerGenerator.ContainerFromItem(thing)).Focus();
Run Code Online (Sandbox Code Playgroud)

但是还有第二个问题 - 它在设置SelectedItem后无法正常工作.ItemContainerGenerator.ContainerFromItem()似乎总是返回null.在googlespace的其他地方,人们已经报告它在GroupStyle设置时返回null.但它在没有分组的情况下表现出这种行为.

ItemContainerGenerator.ContainerFromItem()为列表中显示的所有对象返回null.ItemContainerGenerator.ContainerFromIndex()对所有指标也返回null.只有在ListView被渲染(或其他东西)之后才调用那些东西是必要的.

我尝试直接通过这样做,Dispatcher.BeginInvoke()但这也不起作用.

在一些其他线程的建议下,我Dispatcher.BeginInvoke()StatusChanged事件中使用了ItemContainerGenerator.是的,简单吧?(不)

这是代码的样子.

MyComplexType current;

private void SelectThisItem(string value)
{
    foreach (var item in collectionView) // for the ListView in question
    {
        var thing = item as MyComplexType;
        if (thing.StringProperty == value)
        {
            this.listView1.ItemContainerGenerator.StatusChanged += icg_StatusChanged;
            this.listView1.SelectedItem = thing;
            current = thing;
            return;
        }
    }
}


void icg_StatusChanged(object sender, EventArgs e)
{
    if (this.listView1.ItemContainerGenerator.Status
        == System.Windows.Controls.Primitives.GeneratorStatus.ContainersGenerated)
    {
        this.listView1.ItemContainerGenerator.StatusChanged
            -= icg_StatusChanged;
        Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.Input,
                               new Action(()=> {
                                       var uielt = (UIElement)this.listView1.ItemContainerGenerator.ContainerFromItem(current);
                                       uielt.Focus();}));

    }
}
Run Code Online (Sandbox Code Playgroud)

这是一些丑陋的代码.但是,以这种方式以编程方式设置SelectedItem允许后续箭头导航在ListView中工作.