[WPF]如何正确地用代码设置ListBox的当前选中项

有人可能会说这有什么好写的。不就是一行代码就能搞定的吗?而且为什么需要用代码设置SelectedItem呢?用户所点的Item不就自动是SelectedItem吗?在这里将要讨论我们的,就是ListBox自己没有能自己把SelectedItem设置正确的情况。本来想当作一个WPF Bug清单的一篇文章的,但是又感觉也许就是有这样变态的需求呢。

 

我们用一个非常简单的代码的XAML就可以重现这个问题。

 

ContractedBlock.gif ExpandedBlockStart.gif Demo Window
 1<Window x:Class="SelectListBoxItem.DemoWindow"
 2    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
 3    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
 4    xmlns:s="clr-namespace:System;assembly=mscorlib"    
 5    Title="ListBox Selection Problem"
 6    SizeToContent="Height"
 7    Width="300">
 8    <ListBox>
 9        <ListBox.ItemTemplate>
10            <DataTemplate>
11                <TextBox Text="{Binding .}" Width="100"/>
12            </DataTemplate>
13        </ListBox.ItemTemplate>
14        <s:String>a</s:String>
15        <s:String>b</s:String>
16        <s:String>c</s:String>
17    </ListBox>
18</Window>

 

运行的效果如下。

1. TextBox得到焦点

 其实这就是个问题,一个ListBoxItem已经被MouseDown了,可是没有被选中。MouseDown已经被TextBox吃了。结果有可能出现下面的状况。 

2. 焦点与选中项不一致 

这个问题在WPF里的其它控件也有,在智者千虑【WPF】如何让TreeView实现右键选中的功能里就描述了TreeView上的相似问题。感觉很恶心。

 

一开始使用的是PreviewMouseDown解决,在MouseDown的时候,通过DataContext也好,通过FindAncestor也好,反正是在获得焦点的同时选择上了。

 

但是随着项目的进行,这种方法造成DataBindingValidation出现了问题。Validation应该是在LostFocus是对DataContext进行验证;但是使用PreviewMouseDown更改选中项,这个LostFocus就是在别的项被选中之后发现,结果就是用一个无关的数据在新的DataContext上进行验证。

 

这个问题又普遍存在于项目各个DataBinding中,分别修改肯定是不行的。只能是不用PreviewMouseDown。用GotFocus,用它的ItemGotFocus来设置选中项。

 

为了在现有系统中方便应用,使用了AttachedProperty来实现这个功能。代码如下:

 

ContractedBlock.gif ExpandedBlockStart.gif ListBoxService
 1using System.Diagnostics;
 2using System.Windows;
 3using System.Windows.Controls;
 4
 5namespace SelectListBoxItem
 6ExpandedBlockStart.gifContractedBlock.gif{
 7ExpandedSubBlockStart.gifContractedSubBlock.gif    /**//// <summary>
 8    /// 
 9    /// </summary>

10    public class ListBoxService
11ExpandedSubBlockStart.gifContractedSubBlock.gif    {
12ContractedSubBlock.gifExpandedSubBlockStart.gif        AutoSelect Property#region AutoSelect Property
13
14        public static readonly DependencyProperty AutoSelectProperty = DependencyProperty.RegisterAttached("AutoSelect"typeof(bool), typeof(ListBoxService), new PropertyMetadata(OnAutoSelectPropertyChanged));
15
16        public static bool GetAutoSelect(DependencyObject element)
17ExpandedSubBlockStart.gifContractedSubBlock.gif        {
18            if (element == null)
19                return false;
20
21            return (bool)element.GetValue(AutoSelectProperty);
22        }

23
24        public static void SetAutoSelect(DependencyObject element, bool value)
25ExpandedSubBlockStart.gifContractedSubBlock.gif        {
26            if (element == null)
27                return;
28
29            element.SetValue(AutoSelectProperty, value);
30        }

31
32        #endregion

33
34        private static void OnAutoSelectPropertyChanged(DependencyObject element, DependencyPropertyChangedEventArgs e)
35ExpandedSubBlockStart.gifContractedSubBlock.gif        {
36            if (!(element is UIElement))
37                return;
38
39            if ((bool)e.NewValue)
40                (element as UIElement).GotFocus += new RoutedEventHandler(OnElementGotFocus);
41            else
42                (element as UIElement).GotFocus -= new RoutedEventHandler(OnElementGotFocus);
43        }

44
45        private static void OnElementGotFocus(object sender, RoutedEventArgs e)
46ExpandedSubBlockStart.gifContractedSubBlock.gif        {
47            Debug.Assert(e.OriginalSource is DependencyObject);
48
49            ListBoxItem item = (e.OriginalSource as DependencyObject).FindAncestor<ListBoxItem>();
50            if (item != null)
51                item.IsSelected = true;
52            else
53                Debug.WriteLine(string.Format("Cannot find ListBoxItem from {0}", sender));
54        }

55    }

56}

57

 

其中FindAncestor是自己定义的一个方法,因为单纯地使用VisualTreeHelper是不足以在所有情况下找到Parent的。代码可参见源代码 

写好了怎么用呢?我们说了,要以对现有代码最小的变动实现这个功能。可能有人已经想到了,用Style,那个Window的代码根本不用动。只要在App.xaml里加上一个ResourceOK了。代码如下,简单吧。 

ContractedBlock.gif ExpandedBlockStart.gif App Resource
 1<Application x:Class="SelectListBoxItem.App"
 2    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
 3    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
 4    xmlns:l="clr-namespace:SelectListBoxItem"
 5    StartupUri="DemoWindow.xaml">
 6    <Application.Resources>
 7        <Style TargetType="{x:Type ListBox}">
 8            <Setter Property="l:ListBoxService.AutoSelect" Value="True"/>
 9        </Style>
10    </Application.Resources>
11</Application>
12

 

到此,ListBox的行为算是正常些了。正常的运行截图就不发了。

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值