Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Selecting a Textbox Item in a Listbox does not change the selected item of the listbox

We use the following style to set a PreviewGotKeyboardFocus which handles all events of TextBox control and ComboBoxes and such:

    <ListView.ItemContainerStyle>
        <Style TargetType="ListViewItem">
            <EventSetter Event="PreviewGotKeyboardFocus" Handler="SelectCurrentItem"/>
        </Style>
    </ListView.ItemContainerStyle>

And then we select the row in code behind:

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

Be sure to use appropriate TargetType: ListViewItem, ListBoxItem or TreeViewItem.

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

I don't have enough reps to comment, so I am posting my comment as answer. Grazer's solution above does not work in cases where you have another control such as a Button that needs the SelectedItem. This is because as per the Style Trigger, the IsKeyboardFocusWithin becomes false when you click on that Button, and the SelectedItem becomes null.


I used similar to Robert's solution, but without code behind (using attached behavior).

To do so,

First. Create separate class 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
    }
}

Second. Add a style in resources section (my style is rounded black on focus). Notice setter for FocusBehaviour.IsListBoxItemSelected property. You should reference it in 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>

`

Third. (optional, for reverse task)

You will meet, if not any, reverse task - focusing on TextBox when ListBoxItem get selected. I recommend using another property of Behaviour class, IsFocused. Here is a sample template for ListBoxItem, please notice Property="behave:FocusBehaviour.IsFocused" and 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>

I use a class handler to set this behavior. Doing it this way will fix all of the list views in the application. I don't know why this is not the default behavior.

In your App.xaml.cs, add the following to OnStartup:

protected override void OnStartup(StartupEventArgs e)
    {
        EventManager.RegisterClassHandler(typeof (ListViewItem), 
                                          ListViewItem.PreviewGotKeyboardFocusEvent,
                                          new RoutedEventHandler((x,_) => (x as ListViewItem).IsSelected = true));
    }