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();
}
}
}