UWP:使用键盘箭头禁用*特定* ListViewItem 的选择
UWP: Disable *specific* ListViewItem's selection with keyboard arrows
我有一个 AutoSuggestBox。当我输入内容时,它会给我一个 "suggestions" 的列表。该列表是 Popup 中的 ListView。无论哪种方式,我都希望列表中的某些项目被禁用,因此用户无法选择它们。
我做的还不错,我的实现如下:
<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" 和其他逻辑相关的东西都不起作用。
经过几个小时的努力,我找到了解决问题的办法。该解决方案的工作方式与我在问题中发布的有所不同。它不会跳过禁用的项目(在遇到带有箭头键的禁用项目时跳到第一个启用的项目)。相反,它允许用户像其他任何项目一样突出显示禁用的项目,但不允许他使用 "Enter" 键 select 它。无论哪种方式,用户都可以理解该项目被禁用,首先是因为它是灰色的(因为它的 "IsEnabled" 设置为 false),其次,我将禁用的 ItemListView 中的文本前景设为红色.
它不会通过简单地在 KeyDown 方法中捕获 "Enter" 并返回 "without doing nothing" 让用户 select 具有 "Enter" 的项目。问题是从哪里获得所需的 KeyDown 方法。
我有自己的 AutoSuggestBox 样式(主要是原始 Windows' AutoSuggestBox,我不记得有人更改过它),如下所示:
<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,一个与之同级的Popup。根据其上方 TextBox 中的文本,Popup 包含 "suggestions" 的 ListView。他们的名字是 "TextBox" & "SuggestionsList".
TextBox(原名 "TextBox")是您要捕获 "KeyDown" 事件的主要元凶。这个想法是,当您使用 up-down 箭头键浏览建议列表时,您的焦点始终停留在同一个文本框上,而不是列表视图或其他任何地方。
所以我所做的就是在 TextBox(上面样式中的那个)中添加一个 Behavior,如下所示:
<TextBox x:Name="TextBox"
... >
<interactivity:Interaction.Behaviors>
<TheNamespaceContainingClass:BehaviorClassName />
</interactivity:Interaction.Behaviors>
</TextBox>
行为class代码:
我添加了几条评论来进一步解释重要部分
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,整个问题可以用更简单的方式解决 and/or 不关心质量,但主要思想保持不变。
此外,我在问题中发布的 SetterValueBindingHelper 取自 this 博客。非常感谢它的作者 SuperJMN。
我有一个 AutoSuggestBox。当我输入内容时,它会给我一个 "suggestions" 的列表。该列表是 Popup 中的 ListView。无论哪种方式,我都希望列表中的某些项目被禁用,因此用户无法选择它们。
我做的还不错,我的实现如下:
<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" 和其他逻辑相关的东西都不起作用。
经过几个小时的努力,我找到了解决问题的办法。该解决方案的工作方式与我在问题中发布的有所不同。它不会跳过禁用的项目(在遇到带有箭头键的禁用项目时跳到第一个启用的项目)。相反,它允许用户像其他任何项目一样突出显示禁用的项目,但不允许他使用 "Enter" 键 select 它。无论哪种方式,用户都可以理解该项目被禁用,首先是因为它是灰色的(因为它的 "IsEnabled" 设置为 false),其次,我将禁用的 ItemListView 中的文本前景设为红色.
它不会通过简单地在 KeyDown 方法中捕获 "Enter" 并返回 "without doing nothing" 让用户 select 具有 "Enter" 的项目。问题是从哪里获得所需的 KeyDown 方法。
我有自己的 AutoSuggestBox 样式(主要是原始 Windows' AutoSuggestBox,我不记得有人更改过它),如下所示:
<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,一个与之同级的Popup。根据其上方 TextBox 中的文本,Popup 包含 "suggestions" 的 ListView。他们的名字是 "TextBox" & "SuggestionsList".
TextBox(原名 "TextBox")是您要捕获 "KeyDown" 事件的主要元凶。这个想法是,当您使用 up-down 箭头键浏览建议列表时,您的焦点始终停留在同一个文本框上,而不是列表视图或其他任何地方。
所以我所做的就是在 TextBox(上面样式中的那个)中添加一个 Behavior,如下所示:
<TextBox x:Name="TextBox"
... >
<interactivity:Interaction.Behaviors>
<TheNamespaceContainingClass:BehaviorClassName />
</interactivity:Interaction.Behaviors>
</TextBox>
行为class代码: 我添加了几条评论来进一步解释重要部分
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,整个问题可以用更简单的方式解决 and/or 不关心质量,但主要思想保持不变。
此外,我在问题中发布的 SetterValueBindingHelper 取自 this 博客。非常感谢它的作者 SuperJMN。