如何以编程方式将焦点设置为已具有焦点的WPF列表框中的SelectedItem?

如何以编程方式将焦点设置为已具有焦点的WPF列表框中的SelectedItem?,wpf,listbox,selecteditem,Wpf,Listbox,Selecteditem,我们希望以编程方式设置列表框的SelectedItem,并希望该项具有焦点,以便箭头键相对于该选定项工作。看起来很简单 但是,问题是如果以编程方式设置SelectedItem时,ListBox已经具有键盘焦点,而它确实正确更新了ListBoxItem上的IsSelected属性,但它没有将键盘焦点设置为它,因此,箭头键相对于列表中以前关注的项目移动,而不是像预期的那样相对于新选择的项目移动 这对于用户来说是非常混乱的,因为当使用键盘时,它会使选择看起来像是跳转,因为它会弹回到编程选择发生之前的位

我们希望以编程方式设置
列表框的
SelectedItem
,并希望该项具有焦点,以便箭头键相对于该选定项工作。看起来很简单

但是,问题是如果以编程方式设置
SelectedItem
时,
ListBox
已经具有键盘焦点,而它确实正确更新了
ListBoxItem
上的
IsSelected
属性,但它没有将键盘焦点设置为它,因此,箭头键相对于列表中以前关注的项目移动,而不是像预期的那样相对于新选择的项目移动

这对于用户来说是非常混乱的,因为当使用键盘时,它会使选择看起来像是跳转,因为它会弹回到编程选择发生之前的位置

注意:正如我所说的,只有当您以编程方式在已具有键盘焦点的
列表框
上设置
SelectedItem
属性时,才会发生这种情况。如果键盘焦点返回到
列表框
,则正确的项目现在将具有预期的键盘焦点

下面是一些显示此问题的示例代码。要演示这一点,请运行代码,使用鼠标在列表中选择“七”(从而将焦点放在
列表框上),然后单击“测试”按钮以编程方式选择第四项。最后,轻触键盘上的“Alt”键以显示焦点矩形。你会看到它仍然在“七”,而不是你所期望的“四”,正因为如此,如果你使用上下箭头,它们是相对的“七”行,而不是“四”,进一步混淆了用户,因为他们在视觉上看到的和实际聚焦的是不同步的

重要提示:请注意,我已将按钮上的
Focusable
设置为
false
。如果我没有,当您单击它时,它将获得焦点,
ListBox
将丢失它,从而掩盖问题,因为当
ListBox
重新获得焦点时,它会正确聚焦所选的
ListBoxItem
。问题是当一个
列表框
已经有焦点,并且您以编程方式选择了一个
列表框项

XAML文件:

<Window x:Class="Test.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Width="525" Height="350" WindowStartupLocation="CenterScreen"
    Title="MainWindow" x:Name="Root">

    <DockPanel>

        <Button Content="Test"
            DockPanel.Dock="Bottom"
            HorizontalAlignment="Left"
            Focusable="False"
            Click="Button_Click" />

        <ListBox x:Name="MainListBox" />

    </DockPanel>

</Window>
注意:有些人建议使用
IsSynchronizedWithCurrentItem
,但该属性将
列表框的
SelectedItem
与关联视图的
Current
属性同步。这与焦点无关,因为这个问题仍然存在

我们的解决方法是暂时在其他地方设置焦点,然后设置所选项目,然后将焦点设置回
列表框
,但这会产生不希望出现的效果,即我们必须让我们的
视图模型
知道
列表框
本身,然后根据它是否具有焦点来执行逻辑,等等。(也就是说,如果“here”没有焦点,你就不想简单地说“Focus-otherbefore”,然后回到这里,因为你会从其他地方窃取它。)此外,你不能简单地通过声明性绑定来处理这个问题。不用说,这很难看


再说一次,“丑陋”的飞船,就是这样。

可能有附加行为?比如

public static DependencyProperty FocusWhenSelectedProperty = DependencyProperty.RegisterAttached(
            "FocusWhenSelected", 
            typeof(bool), 
            typeof(FocusWhenSelectedBehavior), 
            new PropertyMetadata(false, new PropertyChangedCallback(OnFocusWhenSelectedChanged)));

private static void OnFocusWhenSelectedChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
    {
        var i = (ListBoxItem)obj;
        if ((bool)args.NewValue)
           i.Selected += i_Selected;
        else
           i.Selected -= i_Selected;
    }

static void i_Selected(object sender, RoutedEventArgs e)
{
    ((ListBoxItem)sender).Focus();
}
在xaml中

       <Style TargetType="ListBoxItem">
            <Setter Property="local:FocusWhenSelectedBehavior.FocusWhenSelected" Value="True"/>
        </Style>

在您的XAML中,您尝试了这个,但没有成功

<ListBox IsSynchronizedWithCurrentItem="True" ItemsSource="{Binding Path=YourCollectionView}" SelectedItem="{Binding SelectedItem}"></ListBox>
现在,您可以在代码中执行以下操作:

SelectedItem = theItemYouWant;

对我来说,这种方法总是有效的。

它只需要几行代码。如果您不想在代码隐藏中使用它,我相信它可以打包在附加的行为中

private void Button_Click(object sender, RoutedEventArgs e)
{
    MainListBox.SelectedItem = MainListBox.Items[3];
    MainListBox.UpdateLayout(); // Pre-generates item containers 

    var listBoxItem = (ListBoxItem) MainListBox
        .ItemContainerGenerator
        .ContainerFromItem(MainListBox.SelectedItem);

    listBoxItem.Focus();
}
首先)必须使用listbox.items.IndexOf()在listbox中找到所选项目。
Second)现在使用列表框添加项目。选择editems.add()

这是我的代码:

DataRow[] drWidgetItem = dtItemPrice.Select(widgetItemsFilter);
lbxWidgetItem.SelectedItems.Clear(); foreach(DataRow drvItem in
drWidgetItem)
lbxWidgetItem.SelectedItems.Add(lbxWidgetItem.Items[dtItemPrice.Rows.IndexOf(drvItem)]);
如果要在列表框中选择项目,可以使用以下方法:
ListBox.SelectedItem=(您的ListBoxItem);

如果要选择列表框中的某些项目,必须使用以下方法:

ListBox.SelectedItems.Add(您的ListBoxItem)

您只需使用ListBox.SelectedItem,然后使用ListBox.ScrollIntoView(ListBox.SelectedItem)

示例代码:

        private void textBox2_TextChanged(object sender, TextChangedEventArgs e)
    {

        var comparision = StringComparison.InvariantCultureIgnoreCase;
        string myString = textBox2.Text;
        List<dynamic> index = listBox.Items.SourceCollection.OfType<dynamic>().Where(x=>x.Nombre.StartsWith(myString,comparision)).ToList();
        if (index.Count > 0) { 
        listBox.SelectedItem= index.First();


            listBox.ScrollIntoView(listBox.SelectedItem);


        }

    }
private void textBox2\u TextChanged(对象发送者,textchangedventargs e)
{
var Comparison=StringComparison.InvariantCultureInogoreCase;
字符串myString=textBox2.Text;
List index=listBox.Items.SourceCollection.OfType().Where(x=>x.Nombre.StartsWith(myString,comparison)).ToList();
如果(index.Count>0){
listBox.SelectedItem=index.First();
listBox.ScrollIntoView(listBox.SelectedItem);
}
}

我会就此给你打电话。你试过了吗?在列表框中加载100个项目。用鼠标单击第50项。然后在表单上添加一个按钮,以编程方式将
SelectedItem
设置为第10项。所选内容确实会更改,但a)所选项目不会滚动到视图中,因为b)它没有聚焦。您可以通过点击“Alt”键看到,您单击的项目仍然具有键盘焦点。顺便说一下。。。只是为了确定,你必须先用鼠标或键盘实际选择另一行。@markelv我可能对你想要什么感到困惑。我以为您希望以编程方式设置所选行。选定的行是焦点行。您必须使用ScrollIntoView才能移动到那里。是的,我的方法不适用于多重选择。您的声明“所选行是重点行”实际上是不正确的,这是我试图解决的问题。即使在上面的代码中,如果您按照我的注释中的步骤操作,您也会看到这一点。另请注意:当前项目是第一个选定的项目,而不是重点项目。这就是我认为你困惑的地方。我已经添加了一个代码片段来展示这一点。这就是我的原版
DataRow[] drWidgetItem = dtItemPrice.Select(widgetItemsFilter);
lbxWidgetItem.SelectedItems.Clear(); foreach(DataRow drvItem in
drWidgetItem)
lbxWidgetItem.SelectedItems.Add(lbxWidgetItem.Items[dtItemPrice.Rows.IndexOf(drvItem)]);
        private void textBox2_TextChanged(object sender, TextChangedEventArgs e)
    {

        var comparision = StringComparison.InvariantCultureIgnoreCase;
        string myString = textBox2.Text;
        List<dynamic> index = listBox.Items.SourceCollection.OfType<dynamic>().Where(x=>x.Nombre.StartsWith(myString,comparision)).ToList();
        if (index.Count > 0) { 
        listBox.SelectedItem= index.First();


            listBox.ScrollIntoView(listBox.SelectedItem);


        }

    }