1
votes

SUMMARY: Click on listbox item, textbox in DataTemplate gets focused but listBox Item is not selected.

I'm sure this has something to do with event bubbling but I'm missing something here.

I have ListBox. Each ListBoxItem's ContentTemplate is assigned to a DataTemplate which contains a simple textbox.

This TextBox is designed to appear as a fake editable label.

Problem: When clicking on the textbox, the selectedItem of the ListBox is not being updated. The textbox is swallowing the mousedown event and the listbox is never notified to update to the new item.

I feel like I'm missing something stupid here. Any ideas? Is there a way to force the event to bubble up to the parent ListView?

I've tried everything from making the textbox's background Null to handling the previewmousedown event and setting 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:

<ListView HorizontalAlignment="Stretch" ItemsSource="{Binding Something.Words}" Name="MainListView" SelectedItem="{Binding CurrentItem, Mode=TwoWay}" BorderThickness="0" ItemContainerStyle="{StaticResource ContainerStyle}">
</ListView>
2
I don't see the usage of ItemTempl in your ListView. You also didn't explain what ContainerStyle is.Blachshma

2 Answers

2
votes

I got around this particular problem with a ListView by creating my own listview that handled the preview mouse down event and selected the item, you could adapt this to your situation, would probably be best to do this in an attached property so you don't have to create a new class.

I basically look for the original source of the mouse down which is the textbox, use the visual tree helper to follow it's visual tree all the way back up to the list view item that it's sitting in and select it.

 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);
        }
    }

EDIT: here is the attached property version.

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;
            }
        }
    }

and use it with

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

I changed the event handler to be more general supporting any selector using the ContainerFromElement and ItemContainerGenerator.IndexFromContainer methods.

    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;
                }
            }
        }
    }