C# WPF弹出窗口中的控件有时无法接收输入

C# WPF弹出窗口中的控件有时无法接收输入,c#,wpf,input,popup,treeview,C#,Wpf,Input,Popup,Treeview,我在弹出窗口中有一个WPF复选框,我发现如果它在树视图的项目模板中,那么复选框不会响应用户输入。如果它在树视图之外,则没有问题 我在这里创建了一个相对最小的模型: 有人知道为什么从树视图中弹出的复选框控件不能被选中吗?我认为这是树视图设计中的一个疏忽。看看这个: 注意:为了避免包装,对一些代码摘录进行了整理 此方法是从TreeViewItem.OnMouseButtonDown调用的,我们可以看到它是一个类级处理程序,配置为接收已处理事件: 我已使用调试器验证,在事件进入树视图项时,已将处理的

我在
弹出窗口中有一个WPF
复选框
,我发现如果它在
树视图
的项目模板中,那么
复选框
不会响应用户输入。如果它在
树视图
之外,则没有问题

我在这里创建了一个相对最小的模型:


有人知道为什么从
树视图中弹出的
复选框
控件不能被选中吗?

我认为这是
树视图
设计中的一个疏忽。看看这个:

注意:为了避免包装,对一些代码摘录进行了整理

此方法是从
TreeViewItem.OnMouseButtonDown
调用的,我们可以看到它是一个类级处理程序,配置为接收已处理事件:

我已使用调试器验证,在事件进入
树视图项时,已将
处理的
设置为

当您在
复选框
上按下鼠标左键时,
复选框
开始推测性的“单击”操作,并将事件标记为已处理。通常,祖先元素不会看到已处理的事件冒泡,但在本例中,它显式地请求它们

TreeView
看到
this.IsKeyboardFocusWithin
解析为
false
,因为聚焦元素位于另一个可视树(弹出窗口)中。然后它将焦点返回到
树视图项

现在,如果您查看
按钮库

protected override void OnLostKeyboardFocus(KeyboardFocusChangedEventArgs e)
{
    base.OnLostKeyboardFocus(e);

    if (ClickMode == ClickMode.Hover)
    {
        // Ignore when in hover-click mode.
        return;
    }

    if (e.OriginalSource == this)
    {
        if (IsPressed)
        {
            SetIsPressed(false);
        }

        if (IsMouseCaptured)
            ReleaseMouseCapture();

        IsSpaceKeyDown = false;
    }
}
我们看到当焦点丢失时,
IsPressed
被设置为
false
。如果我们随后转到MouseLeftButtonUp的
,我们会看到:

bool shouldClick = !IsSpaceKeyDown && IsPressed && ClickMode == ClickMode.Release;

如果
IsPressed
now false,则单击操作永远不会完成,这一切都是因为当您尝试单击按钮时,
treevieItem
将焦点从您身上偷走。

作为一种解决方法,到目前为止,我已经成功地使用了NuGet library Ryder(它看起来像是一个免费使用的开源(MIT许可证)Microsoft Detours的版本)截取
TreeView
中的
HandleMouseButdown
方法

Ryder库可在NuGet库中找到,其背后的代码可在此处找到:

钩住
HandleMouseButtonDown
方法非常简单:

        var realMethod = typeof(System.Windows.Controls.TreeView).GetMethod("HandleMouseButtonDown", BindingFlags.Instance | BindingFlags.NonPublic);
        var replacementMethod = typeof(Program).GetMethod(nameof(TreeView_HandleMouseButtonDown_shim), BindingFlags.Static | BindingFlags.NonPublic);

        Redirection.Redirect(realMethod, replacementMethod);
替代该方法的垫片基本上可以做真实方法所做的事情,但有一个检测交叉视觉树焦点情况的修复:

    static void TreeView_HandleMouseButtonDown_shim(TreeView @this)
    {
        // Fix as seen in: https://developercommunity.visualstudio.com/content/problem/190202/button-controls-hosted-in-popup-windows-do-not-wor.html
        if (!@this.IsKeyboardFocusWithin)
        {
            // BEGIN NEW LINES OF CODE
            var keyboardFocusedControl = Keyboard.FocusedElement;

            var focusPathTrace = keyboardFocusedControl as DependencyObject;

            while (focusPathTrace != null)
            {
                if (ReferenceEquals(@this, focusPathTrace))
                    return;

                focusPathTrace = VisualTreeHelper.GetParent(focusPathTrace) ?? LogicalTreeHelper.GetParent(focusPathTrace);
            }
            // END NEW LINES OF CODE

            var selectedContainer = (System.Windows.Controls.TreeViewItem)TreeView_selectedContainer_field.GetValue(@this);

            if (selectedContainer != null)
            {
                if (!selectedContainer.IsKeyboardFocused)
                    selectedContainer.Focus();
            }
            else
            {
                // If we don't have a selection - just focus the treeview
                @this.Focus();
            }
        }
    }

需要进行一些反射,因为它与一个私有字段进行交互,而这个私有字段不是从
TreeView
类中公开的,但是随着解决方法的进行,这比我最初尝试的入侵性要小得多,它导入了整个
TreeView
类(和相关类型)从引用源到我的项目中,以更改一个成员。:-)

您是否做了任何事情来确认该复选框未获得输入(例如创建单击事件)?如果它没有得到任何东西,你能找出什么是得到点击事件吗?嗯,我可以看到它的风格变化时,鼠标经过它,所以它得到一些输入。我会在上面放一些事件处理程序,看看会出现什么。但有一件事让人困惑,那就是我认为一个
弹出窗口
有效地代表了可视化树的一个新的逻辑根——它是一个独立的顶层窗口,位于其所有者之上。这难道不意味着事件不能从树的更上层连接到
弹出窗口
?不过我还没有测试过这个。当您在Visual Studio中调试时,您可以通过查看Live Visual树来验证您的假设(也可以查看Live Property Explorer)。这有助于查看窗口的完整视图。除
弹出窗口
元素外,实时可视化树非常有用。树中似乎没有包含它们。我看到了
复选框
控件的相同事件序列,这些控件起作用了,而那些控件没有起作用:一个
PreviewMouseDown
,然后是一个
PreviewMouseUp
。这些事件的处理程序显然将它们标记为已处理,因为主
MouseDown
MouseUp
事件没有被引发。我还注意到
CheckBox
ToggleButton
的子类,而
TreeView
在内部使用
ToggleButton
,因此,我想知道它是否会设置
ToggleButton
s的样式,但我通过将
CheckBox
放入
TreeView
的项目模板中,但在
弹出窗口之外,排除了这种情况,
CheckBox
工作正常。
弹出窗口
似乎是导致问题的一个组成部分。我正试图找到一种解决方法,包括可能调用类的非公共成员,但到目前为止,我还没有找到任何有效的方法。我看不到任何“注销”类处理程序的方法,这就是
TreeViewItem
钩住
MouseDown
事件的方式。事件的顺序似乎意味着我无法完成任何技巧,比如试图说服
TreeView
MouseDown
事件发生时,
IsKeyboardFocusWithin
是正确的——看起来它比我先得到事件。你有什么想法吗?我已经为此记录了一份错误报告,如果你认为它值得的话,可以通过Microsoft开发人员社区进行“投票”:我也通过Microsoft Connect记录了该报告,尽管我听说Microsoft最近基本上忽略了Connect。这是链接,值得一提的是:谢谢你,乔纳森!我把两者都投了赞成票,但我不希望有人会阅读它们,更不用说实施修复了;)。作为一种变通方法,到目前为止,我已经成功地使用NuGet library Ryder(它看起来像是免费使用的Microsoft Detours开源版本)拦截了
TreeVi中的
HandleMouseButtonDown
方法
        var realMethod = typeof(System.Windows.Controls.TreeView).GetMethod("HandleMouseButtonDown", BindingFlags.Instance | BindingFlags.NonPublic);
        var replacementMethod = typeof(Program).GetMethod(nameof(TreeView_HandleMouseButtonDown_shim), BindingFlags.Static | BindingFlags.NonPublic);

        Redirection.Redirect(realMethod, replacementMethod);
    static void TreeView_HandleMouseButtonDown_shim(TreeView @this)
    {
        // Fix as seen in: https://developercommunity.visualstudio.com/content/problem/190202/button-controls-hosted-in-popup-windows-do-not-wor.html
        if (!@this.IsKeyboardFocusWithin)
        {
            // BEGIN NEW LINES OF CODE
            var keyboardFocusedControl = Keyboard.FocusedElement;

            var focusPathTrace = keyboardFocusedControl as DependencyObject;

            while (focusPathTrace != null)
            {
                if (ReferenceEquals(@this, focusPathTrace))
                    return;

                focusPathTrace = VisualTreeHelper.GetParent(focusPathTrace) ?? LogicalTreeHelper.GetParent(focusPathTrace);
            }
            // END NEW LINES OF CODE

            var selectedContainer = (System.Windows.Controls.TreeViewItem)TreeView_selectedContainer_field.GetValue(@this);

            if (selectedContainer != null)
            {
                if (!selectedContainer.IsKeyboardFocused)
                    selectedContainer.Focus();
            }
            else
            {
                // If we don't have a selection - just focus the treeview
                @this.Focus();
            }
        }
    }