在ListBox中选择一个文本框项目并不会改变ListBox的选定项目。

49

我有一个显示文本框列表的WPF ListBox。当我点击文本框时,ListBox的选择不会改变。我必须在文本框旁边单击才能选择ListBox项。是否需要设置某个属性使文本框将点击事件转发到ListBox?

15个回答

51
我们使用以下样式来设置 PreviewGotKeyboardFocus,它处理 TextBox 控件和 ComboBox 等的所有事件:
    <ListView.ItemContainerStyle>
        <Style TargetType="ListViewItem">
            <EventSetter Event="PreviewGotKeyboardFocus" Handler="SelectCurrentItem"/>
        </Style>
    </ListView.ItemContainerStyle>

然后在代码后台选择该行:

    protected void SelectCurrentItem(object sender, KeyboardFocusChangedEventArgs e)
    {
        ListViewItem item = (ListViewItem) sender;
        item.IsSelected = true;
    }

2
这绝对是最佳方法。我的层次结构嵌套非常深,ListBoxes 嵌套在其他 ListBoxes 中,各种控件就在其中。这个方法非常有效,因为它仅使用 ListBoxItem 而不是包含的控件。不过有一个建议改变 - 直接选择项目更简单: item.IsSelected = true - Niall
2
Grazer在下面有更好的答案。 - Amsakanna
1
我知道你很久以前发布了这个帖子,但是非常感谢你。这刚好解决了我已经努力解决了几个小时的问题!不幸的是,Grazer的方法在我的情况下似乎没有起作用,但是你的方法完美地解决了问题。 - Patrick Reynolds
2
Grazer的回答很好,但并不适用于每个应用程序。我想选择一个项目,然后单击删除按钮以删除所选项目。他的方法在单击删除按钮时会失去对该项目的焦点。这个方法完美地解决了这个问题。 - BenL

40

请确保使用适当的 TargetType:ListViewItem、ListBoxItem 或 TreeViewItem。

<Style TargetType="ListViewItem">
    <Style.Triggers>
        <Trigger Property="IsKeyboardFocusWithin" Value="true">
            <Setter Property="IsSelected" Value="true" />
        </Trigger>
    </Style.Triggers>
</Style>

1
如果 <Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" /> 已经存在,则无法正常工作。 - ender
5
这也存在一个问题,即当 ListView 失去焦点时会导致选择被取消。下面是 Arcturus 更好的回答。 - Tim Rutter

6

我没有足够的声望来评论,所以我将我的评论作为答案发布。Grazer在上面提供的解决方案不能在以下情况下工作:当您拥有另一个控件(如Button)需要SelectedItem时。这是因为根据Style Trigger,当您单击该Button时,IsKeyboardFocusWithin变为false,并且SelectedItem变为null。


4

我将使用类似于Robert的解决方案,但不需要代码后台(使用附加行为)。

具体做法如下:

首先,创建一个名为FocusBehaviour的单独类:


using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;

namespace MyBehaviours
{
    public class FocusBehaviour
    {
        #region IsFocused
        public static bool GetIsFocused(Control control)
        {
            return (bool) control.GetValue(IsFocusedProperty);
        }

        public static void SetIsFocused(Control control, bool value)
        {
            control.SetValue(IsFocusedProperty, value);
        }

        public static readonly DependencyProperty IsFocusedProperty = DependencyProperty.RegisterAttached(
            "IsFocused", 
            typeof(bool),
            typeof(FocusBehaviour), 
            new UIPropertyMetadata(false, IsFocusedPropertyChanged));

        public static void IsFocusedPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
        {
            var control = sender as Control;
            if (control == null || !(e.NewValue is bool))
                return;
            if ((bool)e.NewValue && !(bool)e.OldValue)
                control.Focus();
        }

        #endregion IsFocused

        #region IsListBoxItemSelected

        public static bool GetIsListBoxItemSelected(Control control)
        {
            return (bool) control.GetValue(IsListBoxItemSelectedProperty);
        }

        public static void SetIsListBoxItemSelected(Control control, bool value)
        {
            control.SetValue(IsListBoxItemSelectedProperty, value);
        }

        public static readonly DependencyProperty IsListBoxItemSelectedProperty = DependencyProperty.RegisterAttached(
            "IsListBoxItemSelected", 
            typeof(bool),
            typeof(FocusBehaviour), 
            new UIPropertyMetadata(false, IsListBoxItemSelectedPropertyChanged));

        public static void IsListBoxItemSelectedPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
        {
            var control = sender as Control;
            DependencyObject p = control;
            while (p != null && !(p is ListBoxItem))
            {
                p = VisualTreeHelper.GetParent(p);
            } 

            if (p == null)
                return;

            ((ListBoxItem)p).IsSelected = (bool)e.NewValue;
        }

        #endregion IsListBoxItemSelected
    }
}

其次,在资源部分添加一个样式(我的样式是在焦点上圆角黑色)。请注意FocusBehaviour.IsListBoxItemSelected属性的setter。您应该在xmlns:behave="clr-namespace:MyBehaviours"中引用它。

`

    <Style x:Key="PreviewTextBox" BasedOn="{x:Null}" TargetType="{x:Type TextBox}">
        <Setter Property="BorderThickness" Value="1"/>
        <Setter Property="Padding" Value="1"/>
        <Setter Property="AllowDrop" Value="true"/>
        <Setter Property="Background" Value="White"/>
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type TextBox}">
                    <Border
                        Margin="6,2,0,4"
                        BorderBrush="#FFBDBEBD"
                        BorderThickness="1"
                        CornerRadius="8"
                        Background="White"
                        VerticalAlignment="Stretch"
                        HorizontalAlignment="Stretch"
                        MinWidth="100"
                        x:Name="bg">
                        <ScrollViewer 
                            x:Name="PART_ContentHost" 
                            SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
                    </Border>
                    <ControlTemplate.Triggers>
                        <Trigger Property="IsKeyboardFocusWithin" Value="True">
                            <Setter Property="Background" TargetName="bg" Value="Black"/>
                            <Setter Property="Background" Value="Black"/><!-- we need it for caret, it is black on black elsewise -->
                            <Setter Property="Foreground" Value="White"/>
                            <Setter Property="behave:FocusBehaviour.IsListBoxItemSelected" Value="True"/>
                        </Trigger>

                    </ControlTemplate.Triggers>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>

第三步(可选,针对反向任务)

如果有反向任务——当选择 ListBoxItem 时,集中 TextBox,您将遇到一些问题。我建议使用 Behavior 类的另一个属性,即 IsFocused。以下是 ListBoxItem 的示例模板,请注意 Property="behave:FocusBehaviour.IsFocused"FocusManager.IsFocusScope="True"

    <DataTemplate x:Key="YourKey" DataType="{x:Type YourType}">
            <Border
            Background="#FFF7F3F7"
            BorderBrush="#FFBDBEBD"
            BorderThickness="0,0,0,1"
            FocusManager.IsFocusScope="True"
            x:Name="bd"
            MinHeight="40">
                <TextBox
                    x:Name="textBox"
                    Style="{StaticResource PreviewTextBox}"
                    Text="{Binding Value}" />
        </Border>
        <DataTemplate.Triggers>
            <DataTrigger
                Binding="{Binding IsSelected,RelativeSource={RelativeSource AncestorType=ListBoxItem}}"
                Value="True">
                <Setter
                    TargetName="textBox"
                    Property="behave:FocusBehaviour.IsFocused" 
                    Value="True" />
            </DataTrigger>
        </DataTemplate.Triggers>
    </DataTemplate>

3
我使用一个类处理程序来设置这种行为。以这种方式做将修复应用程序中的所有列表视图。我不知道为什么这不是默认行为。
在您的App.xaml.cs中,添加以下内容到OnStartup:
protected override void OnStartup(StartupEventArgs e)
    {
        EventManager.RegisterClassHandler(typeof (ListViewItem), 
                                          ListViewItem.PreviewGotKeyboardFocusEvent,
                                          new RoutedEventHandler((x,_) => (x as ListViewItem).IsSelected = true));
    }

3

有没有需要设置的属性,使得文本框可以将点击事件转发到列表框?

这不是一个简单的属性,但你可以在你的TextBox上处理GotFocus事件,然后使用VisualTreeHelper查找ListBoxItem并选择它:

private void TextBox_GotFocus(object sender, RoutedEventArgs e)
{
    TextBox myTextBox = sender as TextBox;
    DependencyObject parent = VisualTreeHelper.GetParent(myTextBox);
    while (!(parent is ListBoxItem))
    {
        parent = VisualTreeHelper.GetParent(parent);
    }
    ListBoxItem myListBoxItem = parent as ListBoxItem;
    myListBoxItem.IsSelected = true;
}

1

虽然讨论过时,但也许我的回答能帮助其他人...

Ben的解决方案和Grazer的解决方案有同样的问题。糟糕的是,选择取决于文本框的[键盘]焦点。如果在对话框上有另一个控件(例如按钮),则单击按钮时将失去焦点,并且列表框项将变为未选定状态(SelectedItem == null)。因此,单击该项(文本框外)和单击文本框中的行为不同。这很麻烦,看起来非常奇怪。

我相当确定没有纯XAML解决方案。我们需要代码后台。解决方案接近于Mark建议的方法。

(在我的示例中,我使用ListViewItem而不是ListBoxItem,但解决方案适用于两者)。

代码后台:

private void Element_PreviewMouseDown(object sender, MouseButtonEventArgs e)
    {
        var frameworkElement = sender as FrameworkElement;
        if (frameworkElement != null)
        {
            var item = FindParent<ListViewItem>(frameworkElement);
            if (item != null)
                item.IsSelected = true;
        }
    }

使用 FindParent(取自 http://www.infragistics.com/community/blogs/blagunas/archive/2013/05/29/find-the-parent-control-of-a-specific-type-in-wpf-and-silverlight.aspx):

public static T FindParent<T>(DependencyObject child) where T : DependencyObject
    {
        //get parent item
        DependencyObject parentObject = VisualTreeHelper.GetParent(child);

        //we've reached the end of the tree
        if (parentObject == null) return null;

        //check if the parent matches the type we're looking for
        T parent = parentObject as T;
        if (parent != null)
            return parent;

        return FindParent<T>(parentObject);
    }

在我的数据模板中:
<TextBox Text="{Binding Name}"
        PreviewMouseDown="Element_PreviewMouseDown"/>

1
以下是@Ben的答案的简化版本,无需覆盖DataTemplate即可实现。它甚至可以作为静态样式应用。已在包含GridView > GridViewColumn > TextBox的ListView上进行了测试。
例子:
<ListView.Resources>
    <Style TargetType="{x:Type ListViewItem}">
        <Style.Triggers>
            <Trigger Property="IsKeyboardFocusWithin" Value="True">
                <Setter Property="IsSelected" Value="True"></Setter>
            </Trigger>
        </Style.Triggers>
    </Style>
</ListView.Resources>

那是 Grazers 回答的副本。 - Welcor
@Blechdose 不是故意的,但看起来是一样的。这个解决方案似乎存在潜在的挑战。请参见Grazer解决方案下的评论:https://dev59.com/qXRB5IYBdhLWcg3wWF8H#7990493 - Graeme Wicksted

1
我找到的最简单的方法是使用PreviewMouseDown事件并设置模板化父级的IsSelected属性。由于预览事件向下冒泡,所以当用户点击文本框、组合框或任何其他控件时,ListBoxItem将立即处理该事件。
其中一个好处是,您可以对所有类型的控件使用相同的事件,因为它们都派生自Framework元素。此外,设置IsSelected(而不是设置SelectedItem)将在将listbox的SelectionMode设置为“Extended”时导致选择多个项目,这可能是您要寻找的,也可能不是。
例如:
c#代码
private void Element_PreviewMouseDown(object sender, MouseButtonEventArgs e)
{
    ((sender as FrameworkElement).TemplatedParent as ListBoxItem).IsSelected = true;
}

XAML

    ...
    <ComboBox PreviewMouseDown="Element_PreviewMouseDown"/>
    <TextBox PreviewMouseDown="Element_PreviewMouseDown"/>
    ...


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接