Как использовать IsKeyboardFocusWithin и IsSelected вместе? - PullRequest
5 голосов
/ 11 июня 2010

У меня есть стиль, определенный для моего ListBoxItems, с триггером для установки цвета фона, когда IsSelected - True:

    <Style x:Key="StepItemStyle" TargetType="{x:Type ListBoxItem}">
        <Setter Property="SnapsToDevicePixels" Value="true"/>
        <Setter Property="OverridesDefaultStyle" Value="true"/>
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="ListBoxItem">
                    <Border Name="Border" Padding="0" SnapsToDevicePixels="true">
                        <ContentPresenter />
                    </Border>
                    <ControlTemplate.Triggers>
                        <Trigger Property="IsSelected" Value="True">
                            <Setter TargetName="Border" Property="Background" Value="#40a0f5ff"/>
                        </Trigger>
                    </ControlTemplate.Triggers>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>

Этот стиль поддерживает выбранный элемент, даже когда ListBox и ListBoxItem теряют фокус, что в моем случае является абсолютной необходимостью. Проблема в том, что я также хочу, чтобы ListBoxItem был выбран, когда один из его дочерних элементов TextBox сфокусирован. Чтобы добиться этого, я добавляю триггер, который устанавливает IsSelected в true, когда IsKeyboardFocusWithin - true:

<Trigger Property="IsKeyboardFocusWithin" Value="True">
    <Setter Property="IsSelected" Value="True" />
</Trigger>

Когда я добавляю этот триггер, элемент выбирается, когда фокус находится на дочернем элементе TextBox, но первое поведение исчезает. Теперь, когда я нажимаю за пределами ListBox, элемент отменяется.

Как я могу сохранить оба поведения?

Ответы [ 3 ]

6 голосов
/ 11 июня 2010

Когда ваш список теряет фокус, из-за вашего триггера выбранный элемент становится пустым.Вы можете выбрать фокус, используя какой-то код позади, который не будет отменен, когда вы потеряете фокус.

5 голосов
/ 05 октября 2012

"Когда я добавляю этот триггер, элемент выбирается, когда фокус находится на дочернем TextBox, но первое поведение исчезает. Теперь, когда я щелкаю за пределами ListBox, элемент отменяется."

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

Вы можете проверить это, принудительно выбрав ListBoxItem, щелкнув непосредственно по нему (примечание: вы всегда должны давать ему фон, даже если он просто «прозрачный», чтобы он мог получать щелчки мыши, чего не будет если он нулевой) или даже просто нажмите Shift-Tab, чтобы установить фокус там, обратно из текстового поля.

Однако это не решает вашу проблему, а именно то, что TextBox получает фокус, но не сообщает об этом базовому ListBoxItem.

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

Первый - это триггер события IsKeyboardFocusWithinChanged, в котором для IsSelected установлено значение true, если фокус клавиатуры изменился на true. (Примечание: ответ Шеридана делает ложное уведомление об изменении, но его не следует использовать в случаях, когда вы можете выбрать несколько элементов в списке, потому что все становится выбранным.) Но даже триггер события вызывает проблемы, потому что вы теряете варианты выбора нескольких элементов. такие как переключение или щелчок по диапазону и т. д.

Другой (и мой предпочтительный подход) заключается в написании присоединенного поведения, которое вы устанавливаете в ListBoxItem, либо напрямую, либо через стиль, если хотите.

Вот прикрепленное поведение. Примечание. Если вы захотите реализовать это, вам снова потребуется обработать несколько элементов. Также обратите внимание, что, хотя я прикрепляю поведение к ListBoxItem, внутри я приводлю к UIElement. Таким образом, вы также можете использовать его в ComboBoxItem, TreeViewItem и т. Д. В основном любой ContainerItem в элементе управления на основе селектора.

public class AutoSelectWhenAnyChildGetsFocus
{
    public static readonly DependencyProperty EnabledProperty = DependencyProperty.RegisterAttached(
        "Enabled",
        typeof(bool),
        typeof(AutoSelectWhenAnyChildGetsFocus),
        new UIPropertyMetadata(false, Enabled_Changed));

    public static bool GetEnabled(DependencyObject obj){ return (bool)obj.GetValue(EnabledProperty); }
    public static void SetEnabled(DependencyObject obj, bool value){ obj.SetValue(EnabledProperty, value); }

    private static void Enabled_Changed(DependencyObject sender, DependencyPropertyChangedEventArgs e)
    {
        var attachEvents = (bool)e.NewValue;
        var targetUiElement = (UIElement)sender;

        if(attachEvents)
            targetUiElement.IsKeyboardFocusWithinChanged += TargetUiElement_IsKeyboardFocusWithinChanged;
        else
            targetUiElement.IsKeyboardFocusWithinChanged -= TargetUiElement_IsKeyboardFocusWithinChanged;
    }

    static void TargetUiElement_IsKeyboardFocusWithinChanged(object sender, DependencyPropertyChangedEventArgs e)
    {
        var targetUiElement = (UIElement)sender;

        if(targetUiElement.IsKeyboardFocusWithin)
            Selector.SetIsSelected(targetUiElement, true);
    }

}

... и вы просто добавляете это как установщик свойства в стиле вашего ListBoxItem

<Setter Property="behaviors:AutoSelectWhenAnyChildGetsFocus.Enabled" Value="True" />

Это, конечно, предполагает, что вы импортировали пространство имен XML, называемое «поведения», которое указывает на пространство имен, в котором содержится класс. Вы можете поместить сам класс в общую библиотеку «Помощник», что мы и делаем. Таким образом, везде, где мы этого хотим, его простое свойство устанавливается в XAML, а все остальное заботится о поведении.

4 голосов
/ 24 сентября 2012

Я понял, что IsKeyboardFocusWithin не лучшее решение.

В этом случае я установил стиль для всех элементов управления, используемых в качестве DataTemplate, для отправки GotFocus -эвентубыть обработан в коде позади.Затем в коде позади я искал визуальное дерево (используя VisualTreeHelper), чтобы найти ListViewItem и установил IsSelected в true.Таким образом, он не «касается» DataContext и работает только с элементами View.

<Style TargetType="{x:Type Control}" x:Key="GridCellControlStyle">
...
<EventSetter Event="GotFocus" Handler="SelectListViewItemOnControlGotFocus"/>
...

private void SelectListViewItemOnControlGotFocus(object sender, RoutedEventArgs e)
{
var control = (Control)sender;
FocusParentListViewItem(control);
}

private void FocusParentListViewItem(Control control)
{
var listViewItem = FindVisualParent<ListViewItem>(control);
if (listViewItem != null)
    listViewItem.IsSelected = true;
}

public static T FindVisualParent<T>(UIElement element) where T : UIElement
{
UIElement parent = element; 

while (parent != null)
{
    var correctlyTyped = parent as T; 

    if (correctlyTyped != null)
    {
        return correctlyTyped;
    }

    parent = VisualTreeHelper.GetParent(parent) as UIElement;
} 

return null;
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...