11

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

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

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

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

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

所以......有人知道我如何成功地编码这种行为吗?

标记

4

6 回答 6

10

好的……这就像程序员的乐趣一样“有趣”。想弄清楚这件事真的很痛苦,但我脸上露出了灿烂的笑容。(考虑到我自己拍得太用力了,是时候为我的肩膀买些 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 IsEditingis false... 他们可以使用IsEditingContextMenuwhen IsEditing 为 true,如果他们没有指定IsEditingContextMenu,我们定义的内部默认值将用于文本框。由于文本框的上下文菜单实际上永远不会为空,因此它ContextMenuOpening总是会触发,因此支持这种行为的逻辑是有效的。

就像我说的……想清楚这一切真的很痛苦,但如果我在这里没有一种很酷的成就感,那该死的。

我希望这可以帮助其他人解决同样的问题。欢迎在这里回复或有问题私信我。

标记

于 2011-05-02T00:54:08.740 回答
3

不幸的是,您正在寻找一个复杂问题的简单解决方案。简单地说的问题是拥有智能自动提交用户界面控件,当您“切换”它们时需要最少的交互并“做正确的事情”。

它之所以复杂,是因为正确的事情取决于应用程序的上下文。WPF 采用的方法是为您提供逻辑焦点和键盘焦点概念,并让您决定如何在您的情况下为您做正确的事情。

如果上下文菜单打开了怎么办?如果打开应用程序菜单会发生什么?如果焦点切换到另一个应用程序怎么办?如果打开属于本地控件的弹出窗口怎么办?如果用户按下回车键关闭对话框怎么办?所有这些情况都可以处理,但如果你有一个提交按钮或者用户必须按 Enter 提交,它们都会消失。

所以你有三个选择:

  • 让控件在拥有逻辑焦点时保持在编辑状态
  • 添加显式提交或应用机制
  • 处理当您尝试支持自动提交时出现的所有混乱情况
于 2011-05-01T22:17:25.093 回答
1

难道不是更容易:

    void txtBox_LostKeyboardFocus(object sender, KeyboardFocusChangedEventArgs e)
    {
        TextBox txtBox = (sender as TextBox);

        if (e.NewFocus is ContextMenu && (e.NewFocus as ContextMenu).PlacementTarget == txtBox)
        {
            return;
        }

        // Rest of code for existing edit mode here...
    }
于 2016-05-11T04:53:15.900 回答
0

我不确定上下文菜单问题,但我试图做类似的事情,发现使用鼠标捕获给你(几乎)你所追求的行为:

在此处查看答案:控件如何处理该控件之外的鼠标单击?

于 2013-02-01T11:02:36.720 回答
0

不确定,但这可能会有所帮助。我对可编辑组合框有类似的问题。我的问题是我使用的 OnLostFocus 覆盖方法没有被调用。修复是我已将回调附加到 LostFocus 事件并且一切正常。

于 2015-02-05T01:03:57.110 回答
0

我通过这里寻找类似问题的解决方案:我有一个ListBox在打开时失去焦点ContextMenu,我不希望这种情况发生。

我的简单解决方案是为 the和它的 s 设置Focusable为:FalseContextMenuMenuItem

<ContextMenu x:Key="QueryResultsMenu" Focusable="False">
    <ContextMenu.Resources>
        <Style TargetType="MenuItem">
            <Setter Property="Focusable" Value="False"/>
        </Style>
    </ContextMenu.Resources>
    <MenuItem ... />
</ContextMenu>

希望这对未来的求职者有所帮助...

于 2015-06-23T18:05:36.733 回答