WPF - текстовое поле ListBox ContentTemplate MouseDown, в результате чего элемент списка не будет выбран

РЕЗЮМЕ: Нажмите на элемент списка, текстовое поле в DataTemplate будет сфокусировано, но элемент listBox не выбран.

Я уверен, что это как-то связано с буллингом событий, но мне здесь чего-то не хватает.

У меня есть ListBox. ContentTemplate каждого ListBoxItem присваивается DataTemplate, который содержит простое текстовое поле.

Этот TextBox разработан как поддельная редактируемая метка.

Проблема: при щелчке по текстовому полю выбранный элемент списка не обновляется. Текстовое поле проглатывает событие mousedown, и в списке никогда не появляется уведомление об обновлении до нового элемента.

Я чувствую, что мне здесь не хватает чего-то глупого. Есть идеи? Есть ли способ заставить событие пузыриться до родительского ListView?

Я перепробовал все, от создания фона текстового поля Null до обработки события previewmousedown и установки e.handled = false;.

DataTemplate:

<DataTemplate x:Key="ItemTempl">
            <TextBox Height="20" Width="200" Name="tbox" Text="{Binding WordText}" HorizontalAlignment="Stretch">
                <TextBox.Style>
                    <Style TargetType="TextBox">
                        <Setter Property="BorderThickness" Value="0"/>
                        <Setter Property="Background" Value="{x:Null}"/>
                        <Style.Triggers>
                            <DataTrigger Binding="{Binding Path=IsFocused, ElementName=tbox}" Value="True">
                                <Setter Property="BorderThickness" Value="1"/>
                                <Setter Property="Background" Value="White"/>                                    
                            </DataTrigger>
                        </Style.Triggers>
                    </Style>
                </TextBox.Style>
            </TextBox>
</DataTemplate>

Посмотреть список:

<ListView HorizontalAlignment="Stretch" ItemsSource="{Binding Something.Words}" Name="MainListView" SelectedItem="{Binding CurrentItem, Mode=TwoWay}" BorderThickness="0" ItemContainerStyle="{StaticResource ContainerStyle}">
</ListView>

2 ответа

Решение

Я обошел эту конкретную проблему с ListView, создав свой собственный просмотр списка, который обрабатывал событие предварительного просмотра мыши вниз и выбрал элемент, вы могли бы адаптировать это к вашей ситуации, вероятно, было бы лучше сделать это в присоединенном свойстве, чтобы вы не должны создать новый класс.

Я в основном ищу исходный источник мыши, который является текстовым полем, использую помощник визуального дерева, чтобы проследить его визуальное дерево до самого элемента представления списка, в котором он находится, и выбрать его.

 public class MyListView : ListView
    {
        protected override void OnPreviewMouseDown(System.Windows.Input.MouseButtonEventArgs e)
        {
            DependencyObject listViewItem = (DependencyObject)e.OriginalSource;
            while (listViewItem != null && !(listViewItem is ListViewItem))
                listViewItem = VisualTreeHelper.GetParent(listViewItem);

            SelectedItem = ((ListViewItem)listViewItem).Content;

            base.OnPreviewMouseDown(e);
        }
    }

РЕДАКТИРОВАТЬ: вот прикрепленная версия свойства.

public class ListViewExtras : DependencyObject
    {
        public static bool GetWillAlwaysSelect(DependencyObject obj)
        {
            return (bool)obj.GetValue(WillAlwaysSelectProperty);
        }

        public static void SetWillAlwaysSelect(DependencyObject obj, bool value)
        {
            obj.SetValue(WillAlwaysSelectProperty, value);
        }

        // Using a DependencyProperty as the backing store for WillAlwaysSelect.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty WillAlwaysSelectProperty =
            DependencyProperty.RegisterAttached("WillAlwaysSelect", typeof(bool), typeof(ListViewExtras), new PropertyMetadata(false, new PropertyChangedCallback((s, e) =>
            {
                ListView listView = s as ListView;
                if (listView != null)
                {
                    if ((bool)e.NewValue) listView.PreviewMouseDown += listView_PreviewMouseDown;
                    if (!(bool)e.NewValue && (bool)e.OldValue) listView.PreviewMouseDown -= listView_PreviewMouseDown;
                }
            })));

        static void listView_PreviewMouseDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
        {
            ListView listView = sender as ListView;
            if (listView != null)
            {
                DependencyObject listViewItem = (DependencyObject)e.OriginalSource;
                while (listViewItem != null && !(listViewItem is ListViewItem))
                    listViewItem = VisualTreeHelper.GetParent(listViewItem);
                listView.SelectedItem = ((ListViewItem)listViewItem).Content;
            }
        }
    }

и использовать его с

<ListView HorizontalContentAlignment="Stretch" local:ListViewExtras.WillAlwaysSelect="True">

Я изменил обработчик событий на более общий, поддерживая любой селектор, используя методы ContainerFromElement и ItemContainerGenerator.IndexFromContainer.

    private static void OnPreviewListBoxMouseDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
    {
        var listBox = sender as Selector;
        if (listBox != null)
        {
            DependencyObject mouseItem = e.OriginalSource as DependencyObject;
            if (mouseItem != null)
            {
                // Get the container based on the element
                var container = listBox.ContainerFromElement(mouseItem);
                if (container != null)
                {
                    var index = listBox.ItemContainerGenerator.IndexFromContainer(container);
                    Debug.Assert(index >= 0);
                    listBox.SelectedIndex = index;
                }
            }
        }
    }
Другие вопросы по тегам