好的……这就像程序员的乐趣一样“有趣”。想弄清楚这件事真的很痛苦,但我脸上露出了灿烂的笑容。(考虑到我自己拍得太用力了,是时候为我的肩膀买些 IcyHot 了!:P)
无论如何,这是一个多步骤的事情,但是一旦你弄清楚了一切,它就会非常简单。简短的版本是您需要同时使用and LostFocus
, LostKeyboardFocus
而不是其中一个。
LostFocus
简单。每当您收到该事件时,设置IsEditing
为 false。做完了。
上下文菜单和丢失的键盘焦点
LostKeyboardFocus
有点棘手,因为控件的上下文菜单可以在控件本身上触发它(即,当控件的上下文菜单打开时,控件仍然具有焦点,但它失去了键盘焦点,因此LostKeyboardFocus
会触发。)
要处理此行为,您需要覆盖ContextMenuOpening
(或处理事件)并设置一个指示菜单正在打开的类级别标志。(我使用bool _ContextMenuIsOpening
。)然后在LostKeyboardFocus
覆盖(或事件)中,您检查该标志,如果已设置,您只需清除它而不做其他任何事情。但是,如果未设置,则意味着除了上下文菜单打开之外的某些内容会导致控件失去键盘焦点,因此在这种情况下,您确实希望设置IsEditing
为 false。
已打开的上下文菜单
现在有一个奇怪的行为,如果控件的上下文菜单打开,因此控件已经失去了键盘焦点,如上所述,如果您单击应用程序中的其他位置,在新控件获得焦点之前,您的控件首先获得键盘焦点,但只是一瞬间,然后它立即将其交给新控件。
这实际上对我们有利,因为这意味着我们还将获得另一个LostKeyboardFocus
事件,但这次 _ContextMenuOpening 标志将设置为 false,就像上面描述的那样,我们的LostKeyboardFocus
处理程序将设置IsEditing
为 false,这正是我们想要的。我喜欢意外收获!
现在只要将焦点简单地转移到您单击的控件上,而无需先将焦点设置回拥有上下文菜单的控件,那么我们就必须做一些事情,例如挂钩ContextMenuClosing
事件并检查接下来将获得焦点的控件,然后IsEditing
如果即将获得焦点的控件不是生成上下文菜单的控件,我们只会设置为 false,因此我们基本上在那里躲过了子弹。
警告:默认上下文菜单
现在还有一点需要注意的是,如果您使用的是文本框之类的东西,并且没有在其上明确设置自己的上下文菜单,那么您将不会收到该ContextMenuOpening
事件,这让我感到惊讶。然而,这很容易解决,只需使用与默认上下文菜单相同的标准命令(例如剪切、复制、粘贴等)创建一个新的上下文菜单并将其分配给文本框。它看起来完全一样,但现在你得到了设置标志所需的事件。
然而,即使你有一个问题,好像你正在创建一个第三方可重用控件并且该控件的用户想要拥有自己的上下文菜单,你可能会不小心将你的优先级设置为更高的优先级,你会覆盖他们的!
解决方法是因为文本框实际上是IsEditing
我的控件模板中的一个项目,所以我只是在外部控件上添加了一个新的 DP ,然后我IsEditingContextMenu
通过内部TextBox
样式绑定到文本框,然后我添加了DataTrigger
那个样式检查IsEditingContextMenu
外部控件上的值,如果它为空,我设置我刚刚在上面创建的默认菜单,该菜单存储在资源中。
这是文本框的内部样式(名为“Root”的元素表示用户实际插入到其 XAML 中的外部控件)...
<Style x:Key="InlineTextbox" TargetType="TextBox">
<Setter Property="OverridesDefaultStyle" Value="True"/>
<Setter Property="FocusVisualStyle" Value="{x:Null}" />
<Setter Property="ContextMenu" Value="{Binding IsEditingContextMenu, ElementName=Root}" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TextBoxBase}">
<Border Background="White" BorderBrush="LightGray" BorderThickness="1" CornerRadius="1">
<ScrollViewer x:Name="PART_ContentHost" />
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<DataTrigger Binding="{Binding IsEditingContextMenu, RelativeSource={RelativeSource AncestorType=local:EditableTextBlock}}" Value="{x:Null}">
<Setter Property="ContextMenu">
<Setter.Value>
<ContextMenu>
<MenuItem Command="ApplicationCommands.Cut" />
<MenuItem Command="ApplicationCommands.Copy" />
<MenuItem Command="ApplicationCommands.Paste" />
</ContextMenu>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
请注意,您必须在样式中设置初始上下文菜单绑定,而不是直接在文本框上,否则样式的 DataTrigger 会被直接设置的值所取代,从而使触发器无用,如果该人使用,您将立即回到原点上下文菜单的“空”。(如果您想禁止菜单,则无论如何都不会使用“null”。您可以将其设置为空菜单,因为 null 表示“使用默认值”)
所以现在用户可以使用常规ContextMenu
属性 when IsEditing
is false... 他们可以使用IsEditingContextMenu
when IsEditing 为 true,如果他们没有指定IsEditingContextMenu
,我们定义的内部默认值将用于文本框。由于文本框的上下文菜单实际上永远不会为空,因此它ContextMenuOpening
总是会触发,因此支持这种行为的逻辑是有效的。
就像我说的……想清楚这一切真的很痛苦,但如果我在这里没有一种很酷的成就感,那该死的。
我希望这可以帮助其他人解决同样的问题。欢迎在这里回复或有问题私信我。
标记