您如何以编程方式将焦点设置到已具有焦点的 WPF ListBox 中的 SelectedItem?
Posted
技术标签:
【中文标题】您如何以编程方式将焦点设置到已具有焦点的 WPF ListBox 中的 SelectedItem?【英文标题】:How do you programmatically set focus to the SelectedItem in a WPF ListBox that already has focus? 【发布时间】:2012-05-13 17:50:50 【问题描述】:我们希望以编程方式设置ListBox
的SelectedItem
,并希望该项目具有焦点,以便箭头键相对于该选定项目起作用。看起来很简单。
但问题是,如果 ListBox
在以编程方式设置 SelectedItem
时已经拥有键盘焦点,虽然它确实正确更新了 ListBoxItem
上的 IsSelected
属性,但它没有为其设置键盘焦点,因此,箭头键相对于列表中先前聚焦的项目移动,而不是预期的新选择的项目。
这会让用户感到非常困惑,因为它使选择在使用键盘时看起来会跳来跳去,因为它会快速回到编程选择发生之前的位置。
注意:正如我所说,这只有在您以编程方式将SelectedItem
属性设置在本身已经具有键盘焦点的ListBox
上时才会发生。如果没有(或者如果有但你离开了,那么马上回来),当键盘焦点返回到ListBox
时,正确的项目现在将按预期获得键盘焦点。
这里有一些示例代码显示了这个问题。为了演示这个,运行代码,使用鼠标在列表中选择“Seven”(从而将焦点放在ListBox
),然后单击“Test”按钮以编程方式选择第四项。最后,点击键盘上的“Alt”键以显示焦点矩形。您会看到它仍在“七”上,而不是您所期望的“四”,因此,如果您使用向上和向下箭头,它们是相对的“七”行,而不是“四”,更加混乱用户,因为他们在视觉上看到的内容和实际关注的内容不同步。
重要提示:请注意,我将按钮上的
Focusable
设置为false
。如果我没有,当您单击它时,它会获得焦点,而ListBox
会失去它,从而掩盖问题,因为当ListBox
重新获得焦点时,它会正确聚焦选定的ListBoxItem
。问题是当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>
代码隐藏:
using System.Collections.ObjectModel;
using System.Windows;
namespace Test
public partial class MainWindow : Window
public MainWindow()
InitializeComponent();
MainListBox.ItemsSource = new string[]
"One", "Two", "Three", "Four", "Five", "Six", "Seven", "Eight"
;
private void Button_Click(object sender, RoutedEventArgs e)
MainListBox.SelectedItem = MainListBox.Items[3];
注意:有些人建议使用IsSynchronizedWithCurrentItem
,但该属性将ListBox
的SelectedItem
与关联视图的Current
属性同步。与焦点无关,因为这个问题仍然存在。
我们的解决方法是暂时将焦点设置在其他位置,然后设置所选项目,然后将焦点设置回ListBox
,但这会产生不希望的效果,我们必须让ViewModel
意识到ListBox
本身,然后根据它是否具有焦点等执行逻辑(即,如果'这里'没有焦点,你不想简单地说'焦点在别处然后回到这里')您会从其他地方窃取它。)另外,您不能简单地通过声明性绑定来处理它。不用说这很丑。
再说一次,“丑陋”的船,就是这样。
【问题讨论】:
【参考方案1】:这是几行代码。如果您不想在代码隐藏中使用它,我相信它可以打包在附加的行为中。
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();
【讨论】:
ContainerFromItem 如果尚未为其生成容器,则将返回 Null,这是虚拟化列表的情况,并且项目不在屏幕上。另外,如果您尝试从绑定中设置值,它会崩溃,因为您无权访问 ListBox,也不应该。 (下面继续...) 即使是附加的行为也会带来问题,因为您不希望控件盲目地将键盘焦点设置为 ListBoxItem,除非 a) 控件已经具有焦点(易于测试),并且 b) 发生了变化从代码隐藏(没有子类不容易测试。)否则,在多选模式和取消选择时,您可能会无意中从另一个控件中窃取焦点(案例 a)或弄乱当前控件中的焦点(案例 b)一行,通常应该保留键盘焦点,但会设置为新的 SelectedItem,如果有的话,会导致奇怪的行为。 实际上,再想一想,我认为我有解决上述案例“B”的方法。您确实创建了您所说的附加行为,但您没有直接在 XAML 中设置它。相反,您在 ViewModel 上创建一个属性并将行为绑定到该属性。这样你就不需要知道(或关心)谁在听。然后,就在您从后面的代码设置所选项目之前,启用该行为,选择该项目,然后再次禁用该行为。这解决了上面的“B”。 (当然,你仍然需要'A'。)投票给你作为答案,因为虽然不完整,但确实让我走上了这条路。 在调用ContainerFromItem()
之前,我已经编辑到 UpdateLayout()
,这应该会预先生成项目容器并避免意外的 null 结果。
如果使用 listBox.ScrollIntoView(listBox.SelectedItem) 比 ListboxItem.Focus() 会更好,不需要使用 ContainerFromItem();【参考方案2】:
也许有附加行为?类似的东西
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>
【讨论】:
这是我最初的目标,但问题是如果你有一个虚拟列表,这些项目还不存在,所以附加的行为还没有被连接起来响应。您仍然必须以某种方式首先执行 ScrollIntoView 方法。其次,如果你严格响应 IsSelected,在多选模式下事情可能会变得有点疯狂,但话又说回来,这可能被认为是期望的行为。虚拟化(默认开启)才是真正的杀手。【参考方案3】:你只需要使用 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);
【讨论】:
【参考方案4】:在您的 XAML 中,您尝试过这个但没有用?
<ListBox IsSynchronizedWithCurrentItem="True" ItemsSource="Binding Path=YourCollectionView" SelectedItem="Binding SelectedItem"></ListBox>
还有SelectedItem
属性:
private YourObject _SelectedItem;
public YourObject SelectedItem
get
return _SelectedItem;
set
if (_SelectedItem == value)
return;
_SelectedItem = value;
OnPropertyChanged("SelectedItem");
现在您可以在代码中执行以下操作:
SelectedItem = theItemYouWant;
对我来说,这种方法总是有效的。
【讨论】:
我会打电话给你的。你试过了吗?加载你的列表框,比如 100 个项目。用鼠标单击第 50 个项目。然后在表单上添加一个按钮,以编程方式将SelectedItem
设置为第 10 项。选择确实发生了变化,但是 a) 所选项目不会滚动到视图中,因为 b) 它没有聚焦。您可以通过点击“Alt”键看到这一点,您仍然会看到您点击的项目仍然具有键盘焦点。
顺便说一句...为了确定,您必须先使用鼠标或键盘物理选择另一行。
@MarquelV 我可能对你想要什么感到困惑。我以为您想以编程方式设置选定的行。选定的行是焦点行。您必须使用 ScrollIntoView 才能移动到那里。是的,我的方法不适用于多项选择。
您的语句“选定的行是焦点行”实际上是不正确的,这是我要解决的问题。即使在您上面的代码中,如果您按照我的 cmets 中的步骤操作,您也会看到这一点。另请注意:当前项目是第一个选择的项目,而不是焦点项目。这就是我认为你感到困惑的地方。我添加了一个代码 sn-p 来显示这一点。【参考方案5】:
首先) 您必须使用 ListBox.Items.IndexOf() 在列表框中找到选定的项目。 Second) 现在使用 ListBox.SelectedItems.Add() 添加项目。
这是我的代码:
DataRow[] drWidgetItem = dtItemPrice.Select(widgetItemsFilter);
lbxWidgetItem.SelectedItems.Clear(); foreach(DataRow drvItem in
drWidgetItem)
lbxWidgetItem.SelectedItems.Add(lbxWidgetItem.Items[dtItemPrice.Rows.IndexOf(drvItem)]);
如果你想在 ListBox 中选择一个项目,你可以这样使用: ListBox.SelectedItem = (你的 ListBoxItem); 如果您想在 ListBox 中选择某些项目,您必须使用这种方式: ListBox.SelectedItems.Add(你的 ListBoxItem);
【讨论】:
我不是 100% 确定,但我认为您可能误解了我的问题。选择不是问题。重点是。您的代码确实选择了项目,但它没有设置焦点,这是我要解决的问题。 对不起!所以,我认为如果你集中你的控制之后你的项目必须集中。 (对不起我的英语不好) 但是如果控件已经有了焦点,就不能重新设置了。您必须先关注其他事情。以上是关于您如何以编程方式将焦点设置到已具有焦点的 WPF ListBox 中的 SelectedItem?的主要内容,如果未能解决你的问题,请参考以下文章
Xamarin iOS - 我们如何以编程方式将可访问性焦点设置为按钮