好的,我有一个具有IsEditing属性的控件,出于参数的考虑,该控件具有默认模板,该模板通常是文本块,但是当IsEditing为true时,它将在文本框中交换以进行就地编辑。现在,当控件失去焦点时,如果仍在编辑,则应该退出编辑模式,并换回到TextBlock模板中。很直截了当,对吧?

想一下在Windows资源管理器或桌面上重命名文件的行为(这是我所知道的...),这就是我们想要的行为。

问题是您不能使用LostFocus事件,因为当您切换到另一个窗口(或作为FocusManager的元素)时,由于控件仍然具有逻辑焦点,因此不会触发LostFocus,因此它将无法正常工作。

如果您改用LostKeyboardFocus,虽然确实解决了“其他FocusManager”问题,那么现在您有了一个新问题:编辑时,您右键单击文本框以显示上下文菜单,因为上下文菜单现在具有键盘焦点,控件将失去键盘焦点,退出编辑模式并关闭上下文菜单,使用户感到困惑!

现在,我尝试设置一个标志以在菜单打开之前忽略LostKeyboardFocus,然后在LostKeyboardFocus事件中使用该错误来确定是否将其踢出编辑模式,但是如果菜单已打开并且我单击了菜单中的其他位置应用程序,因为控件本身不再具有键盘焦点(菜单具有菜单焦点),因此该控件再也不会收到另一个LostKeyboardFocus事件,因此它仍处于编辑模式。 (我可能不得不在菜单关闭时添加检查,以查看具有焦点的内容,然后如果不是控件,则将其手动踢出EditMode。这似乎很有希望。)

所以...有人知道我如何成功编写此行为?

标记

最佳答案

好的,这是像Programmer-fun一样的“有趣”。弄清楚keester的确很痛苦,但脸上却挂满了漂亮的笑容。 (考虑到我自己这么努力拍拍,是时候让我的肩膀得到一些IcyHot了::P)

无论如何,这是一个多步骤的事情,但是一旦您弄清楚了一切,它就会非常简单。简短的版本是您需要同时使用LostFocusLostKeyboardFocus,而不是一个或另一个。
LostFocus很简单。每当您收到该事件时,请将IsEditing设置为false。做完了。

上下文菜单和失去的键盘焦点
LostKeyboardFocus有点棘手,因为控件的上下文菜单可以在控件本身上触发它(即,当控件的上下文菜单打开时,控件仍然具有焦点,但是失去了键盘焦点,因此LostKeyboardFocus被触发。)

若要处理此行为,请覆盖ContextMenuOpening(或处理事件),并设置一个表示菜单正在打开的类级别标志。 (我使用bool _ContextMenuIsOpening。)然后在LostKeyboardFocus覆盖(或事件)中,检查该标志,如果已设置该标志,则只需清除该标志即可,而无需执行其他任何操作。但是,如果未设置它,则意味着上下文菜单打开以外的其他原因导致控件失去键盘焦点,因此在这种情况下,您确实希望将IsEditing设置为false。

已打开上下文菜单

现在有一个奇怪的行为,如果打开控件的上下文菜单,因此控件如上所述已经失去了键盘焦点,如果您单击应用程序中的其他位置,则在新控件获得焦点之前,您的控件将首先获得键盘焦点,但只有一瞬间,它会立即将其交给新控件。

这实际上对我们有利,因为这意味着我们还将获得另一个LostKeyboardFocus事件,但是这次_ContextMenuOpening标志将设置为false,就像上面所描述的,我们的LostKeyboardFocus处理程序将IsEditing设置为false,这正是我们想要。我喜欢偶然!

现在,只需将焦点转移到您单击的控件上,而无需先将焦点重新设置为拥有上下文菜单的控件,那么我们就必须执行类似 Hook ContextMenuClosing事件并检查下一个控件将获得焦点的操作,那么如果即将成为焦点的控件不是产生上下文菜单的控件,则仅将IsEditing设置为false,因此我们基本上在此处避开了一个项目符号。

警告:默认上下文菜单

现在还需要注意的是,如果您正在使用文本框之类的东西,并且没有在其上显式设置自己的上下文菜单,那么您不会得到ContextMenuOpening事件,这令我感到惊讶。但是,只需使用与默认上下文菜单相同的标准命令(例如,剪切,复制,粘贴等)创建一个新的上下文菜单,然后将其分配给文本框,即可轻松解决此问题。它看起来完全一样,但是现在您需要设置标志。

但是,即使您遇到问题,好像要创建第三方可重用控件,并且该控件的用户希望拥有自己的上下文菜单,也可能会不小心将其设置为更高的优先级,并且您将覆盖它们的优先级!

解决方法是,因为文本框实际上是控件的IsEditing模板中的一个项目,所以我只是在外部控件上添加了一个名为IsEditingContextMenu的新DP,然后通过内部TextBox样式将其绑定(bind)到文本框,然后添加了DataTrigger以这种样式检查外部控件上IsEditingContextMenu的值,如果它为null,则设置上面刚刚创建的默认菜单,该菜单存储在资源中。

这是文本框的内部样式(名为“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>

请注意,您必须在样式中设置初始上下文菜单绑定(bind),而不是直接在文本框上设置,否则样式的DataTrigger将被直接设置的值取代,从而使触发器无用,并且如果该人使用上下文菜单为“null”。 (如果您想取消显示菜单,则无论如何都不会使用'null'。您可以将其设置为空菜单,因为null表示'使用默认值')

因此,现在当ContextMenu为false时,用户可以使用常规的IsEditing属性...当IsEditing为true时,他们可以使用IsEditingContextMenu,并且如果他们未指定IsEditingContextMenu,那么我们定义的内部默认值将用于文本框。由于文本框的上下文菜单实际上永远不会为空,因此它的ContextMenuOpening始终会触发,因此支持这种行为的逻辑起作用。

就像我说的那样……真正的痛苦可以解决所有这些问题,但是如果我在这里没有很酷的成就感,那该死的。

我希望这对这里的其他人有帮助。请随时在此处回复或向我提问。

标记

10-05 22:42