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.