UWP: отключить * определенный * выбор ListViewItem с помощью стрелок на клавиатуре

У меня есть AutoSuggestBox. Когда я что-то набираю, это дает мне список "предложений". Список представляет собой ListView внутри Popup. В любом случае, я хотел, чтобы некоторые элементы в списке были отключены, чтобы пользователь не мог их выбрать.

Я сделал это просто отлично, моя реализация выглядит следующим образом:

<AutoSuggestBox x:Name="AutoSuggest" ...
                ItemContainerStyle="{StaticResource 
                MyPopUpListViewItemStyle}"
/>


<Style x:Key="MyPopUpListViewItemStyle" TargetType="ListViewItem">
...
        <Setter Property="helpers:SetterValueBindingHelper.PropertyBinding">
            <Setter.Value>
                <helpers:SetterValueBindingHelper
                    Property="IsEnabled"
                    Binding="{Binding Path=Content.IsItemEnabled, RelativeSource={RelativeSource Self}}" />
            </Setter.Value>
        </Setter>
...
</Style>

У меня были некоторые проблемы с обязательным свойством внутри стиля. Но теперь все работает нормально, все свойства ListViewItems "IsEnabled" привязаны к свойству внутри его содержимого. Теперь пользователь не может выбирать конкретные предметы мышью.

Моя проблема в том! Хотя пользователь не может выбрать элемент мышью, он все же может выбрать его с помощью стрелок вверх ↑ и вниз ↓ (и не только установить их выбранными, но фактически выбрать с помощью Enter). Я хочу, чтобы пользователь пропускал отключенные элементы (все еще имея возможность использовать стрелки для выбора обычных элементов).

Я долго искал, я нашел красивое решение, чтобы связать свойство "Focusable" из ListViewItem с любым моим собственным свойством, но это только WPF, так как для моего ListViewItem нет свойства "Focusable".

Все возможные свойства ListViewItem, включая: "AllowFocusOnInteraction", "IsTabStop", "AllowFocusWhenDisabled", "IsHitTestVisible" и другие логически релевантные вещи, не работали.

1 ответ

Решение

Я нашел решение этой проблемы после нескольких часов борьбы. Решение работает несколько иначе, чем я написал в вопросе. Он не пропускает отключенные элементы (переход к первому включенному элементу при обнаружении отключенного элемента с помощью клавиш со стрелками). Вместо этого он позволяет пользователю выделять отключенный элемент, как и любой другой, но не позволяет ему выбирать его клавишей "Ввод". В любом случае, пользователь может понять, что элемент отключен, во-первых, потому что он неактивен (так как его "IsEnabled" установлен в false), а во-вторых, я сделал передний план текста внутри отключенного ItemListView красным.

Он не позволяет пользователю выбрать элемент с помощью "Enter", просто перехватывая "Enter" в методе KeyDown и возвращая "не делая ничего". Проблема в том, где взять необходимый метод KeyDown.

У меня есть свой собственный стиль для AutoSuggestBox (который в основном является исходным AutoSuggestBox для Windows, я не припоминаю, чтобы кто-нибудь что-либо менял в этом), который выглядит следующим образом:

<Style TargetType="AutoSuggestBox">
    ...
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="AutoSuggestBox">
                <Grid>
                    ...
                    <TextBox x:Name="TextBox" 
                             ...
                             />
                    <Popup x:Name="SuggestionsPopup">
                        <Border x:Name="SuggestionsContainer">
                            <Border.RenderTransform>
                                <TranslateTransform x:Name="UpwardTransform" />
                            </Border.RenderTransform>
                            <ListView
                                x:Name="SuggestionsList"
                                ...
                                >
                                <ListView.ItemContainerTransitions>
                                    <TransitionCollection />
                                </ListView.ItemContainerTransitions>
                            </ListView>
                        </Border>
                    </Popup>
                </Grid>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

Как видно, идея AutoSuggestBox - это TextBox с поиском и всплывающим контекстом на одном уровне с ним. Всплывающее окно содержит ListView "предложений" в соответствии с текстом в TextBox над ним. Они называются "TextBox" и "SuggestionsList".

TextBox (который называется "TextBox") является основным преступником, на которого вы хотите поймать событие "KeyDown". Идея состоит в том, что когда вы просматриваете список предложений с помощью клавиш со стрелками вверх-вниз, ваш фокус всегда остается на том же текстовом поле, а не на просмотре списка или чем-либо еще.

Итак, я добавил в TextBox поведение (как в вышеприведенном стиле) следующим образом:

    <TextBox x:Name="TextBox"
             ... >
        <interactivity:Interaction.Behaviors>
            <TheNamespaceContainingClass:BehaviorClassName />
        </interactivity:Interaction.Behaviors>
    </TextBox>

Код класса поведения: я добавил несколько комментариев для дальнейшего объяснения важных частей

public class BehaviorClassName: DependencyObject, IBehavior
{
    private TextBox _associatedObject;
    private ListView _listView;

    public DependencyObject AssociatedObject
    {
        get
        {
            return _associatedObject;
        }
    }

    public void Attach(DependencyObject associatedObject)
    {
        _associatedObject = associatedObject as TextBox;

        if (_associatedObject != null)
        {
            _associatedObject.KeyDown -= TextBox_OnKeyDown;
            _associatedObject.KeyDown += TextBox_OnKeyDown;
        }
    }

    private void TextBox_OnKeyDown(object sender, KeyRoutedEventArgs e)
    {
        if (_associatedObject != null)
        {
            if (_listView == null)
            {
                // Gets ListView through visual tree. That's a hack of course. Had to put it here since the list initializes only after the textbox itself
                _listView = (ListView)((Border)((Popup)((Grid)_associatedObject.Parent).Children[1]).Child).Child;
            }
            if (e.Key == VirtualKey.Enter && _listView.SelectedItem != null)
            {
                // Here I had to make sure the Enter key doesn't work only on specific (disabled) items, and still works on all the others
                // Reflection I had to insert to overcome the missing reference to the needed ViewModel
                if (!((bool)_listView.SelectedItem.GetType().GetProperty("PropertyByWhichIDisableSpecificItems").GetValue(_listView.SelectedItem, null)))
                    e.Handled = true;
            }
        }
    }

    public void Detach()
    {
        _associatedObject.KeyDown -= TextBox_OnKeyDown;
    }
}

Возможно, это не самое простое объяснение и решение, но проблема не совсем простая. Надеюсь, что вы сможете понять всю идею, если столкнетесь с этой конкретной проблемой. Вся проблема может быть решена более простым способом, если вы не следите за MVVM и / или не заботитесь о качестве, но основная идея остается прежней.

Кроме того, SetterValueBindingHelper, который я разместил в вопросе, взят из этого блога. Большое спасибо его автору, SuperJMN.

Другие вопросы по тегам