c# WPF - 如何在不阻止其他控件可点击的情况下识别控件外的鼠标单击
c# WPF - How to recognize mouse click outside control without blocking other controls from being clickable
我现在阅读了很多线程并尝试了等等。没看懂。
问题
使用 MouseCapture 时,不会移动任何其他控件。无法单击某些内容。鼠标悬停时没有突出显示。 MouseCaption 正在阻止它。需要点击两次。如何避免这种情况?
基本上
我创建了一个自定义自动完成框,它由一个用于自由输入的文本框和一个作为文本块的下拉列表组成,其中包含给定输入建议的 result-element。这很像一个标准的 ComboBox。
从 Combobox 中我们知道,当它展开并在其他地方单击时,下拉列表会折叠。
我想要与组合框使用的行为完全相同的行为。
因为我不是第一个问这个问题的人,所以我尝试了几件事,但没有让它们完全发挥作用。
我仍然尝试但失败了。
- 向文本框添加
OnLostFocus
事件无法识别对 non-focusable 元素的任何鼠标单击。
- 使用
Mous.Caption(this)
和 PreviewMouseLeftButtonDown
接收鼠标在 window 上任何地方的任何点击。
- 是的,行得通!我可以折叠我的下拉菜单,Un-Capture 再次鼠标。
- 但是:鼠标标题阻止我单击到其他 UIElement。复选框和单选框不会被切换。只需将鼠标悬停在复选框或其他任何地方都不会再突出显示该元素。相反,我现在需要点击两次来检查文本框。
- 我不知道怎么解决。
- 同样没有用的是,当 mouse-capture 事件被触发时,我无法弄清楚点击的位置。
source
以及e.OriginalSource
等于我的自定义控件
- 获得 Mouse-Position 可能是一种选择。但是没有找到获取到我的控件相关位置的mouse-position。控件上的任何属性 return NaN。
- 起初我无法识别
PreviewMouseLeftButtonDown
和 MouseLeftButtonDown
之间的任何区别。
- 我认为第一个,当直接释放 mouse-capture 时,会触发 mouseclick 事件到其原始目的地,而不再捕获鼠标。它没有。
- 我是用Hittest搞定的。是这样吗?
一些代码
XAML 自动完成框
<UserControl x:Class="MyProject.Wpf.Application.Control.AutoCompleteBoxControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Name="AutoCompleteBox"
PreviewMouseLeftButtonDown="AutoComplete_MouseLeftButtonDown">
<Grid>
<StackPanel>
<TextBox Name="AutoCompleteTextBox" Text="{Binding ElementName=AutoCompleteBox, Path=TextBoxText, Mode=TwoWay}" Height="{Binding ElementName=AutoCompleteBox, Path=TextBoxHeight}" Width="{Binding ElementName=AutoCompleteBox, Path=TextBoxWidth}" Padding="5, 3, 5, 3" KeyUp="AutoCompleteTextBoxControl_KeyUp" LostKeyboardFocus="AutoCompleteTextBox_OnLostKeyboardFocus"/>
<Popup Name="ResultStackPopup" IsOpen="True" PlacementTarget="{Binding ElementName=AutoCompleteTextBox}" Placement="Custom">
<Border Name="ResultStackBorder" Width="{Binding ElementName=AutoCompleteBox, Path=SuggestionListWidth}" Height="{Binding ElementName=AutoCompleteBox, Path=SuggestionListHeight}" BorderBrush="Black" BorderThickness="1" Visibility="Collapsed" Background="White" Margin="1,0,1,0" HorizontalAlignment="Left">
<ScrollViewer VerticalScrollBarVisibility="Auto">
<StackPanel Name="ResultStack"></StackPanel>
</ScrollViewer>
</Border>
</Popup>
</StackPanel>
</Grid>
</UserControl>
后面相关代码:
//Whenever the dropdown is expanded, the mouse caption is started:
this.CaptureMouse();
mouse-down 事件:
private void AutoComplete_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
if (this.IsMouseCaptured)
{
if (!this.IsMouseInBounds())
{
//Release MouseCaption
this.ReleaseMouseCapture();
//Collapse SuggestionList
var border = (this.ResultStack.Parent as ScrollViewer)?.Parent as Border;
if (border != null)
{
border.Visibility = Visibility.Collapsed;
}
}
}
}
private bool IsMouseInBounds()
{
Point point = Mouse.GetPosition(this);
// Perform the hit test against a given portion of the visual object tree.
HitTestResult result = VisualTreeHelper.HitTest(this, point);
if (result != null)
{
return true;
}
return false;
}
编辑
不幸的是,Popup 不是 (afaik) visualTree 的成员。所以 Popup 的 hittest 不起作用。所以我试图让 Popup 的位置与 Mouse-position.
检查
TransformToAncestor方法就是大家说的那样。
但这似乎无法正常工作:
以下三个调用return完全相同的点:
Window parentWindow = Window.GetWindow(this);
Point relativePointThis = this.TransformToAncestor(parentWindow).Transform(new Point(0, 0));
Point relativePointPopup = this.ResultStackPopup.TransformToAncestor(parentWindow).Transform(new Point(0, 0));
Point relativePointBorder = this.ResultStackBorder.TransformToAncestor(parentWindow).Transform(new Point(0, 0));
这是一个错误吗?
我没有放弃,向前走了几步
回答:
1)
要么使 Window 可聚焦,要么不这样做。
2)
Mouse.Capture(this, CaptureMode.SubTree);
使用正确的 CaptureMode 至少解决了 DropDown 与鼠标悬停等交互(当然,因为它是捕获控件的子项)。
Window 的其余部分一直被鼠标捕获阻止。人们可能刚刚意识到,这也是标准 ComboBox 的正常行为。所以人们可以接受这一点。
3)
通过鼠标位置获取入站点击检查可以使用以下代码处理。请注意,鼠标位置是相对于给定元素给出的。在这种情况下,AutoCompleteBox 是通过 this
给定的。所以左上角会是{0.0, 0.0}
.
private bool IsMouseInBounds()
{
//Get MousePosition relative to the AutoCompleteBox
Point point = Mouse.GetPosition(this);
//Actual Width and Heigth of AutoCompleteBox
var widthAuto = this.ActualWidth;
var heightAuto = this.ActualHeight;
var upLeftXAuto = 0.0;
var upLeftYAuto = 0.0;
var downRightXAuto = upLeftXAuto + widthAuto;
var downRightYAuto = upLeftYAuto + heightAuto;
//Actual Width and Height of DropDown
var widthDropDown = this.ResultStackBorder.ActualWidth;
var heightDropDown = this.ResultStackBorder.ActualHeight;
double upLeftXDropDown;
double upLeftYDropDown;
//Actual Position of DropDown (may be aligned right or center)
CalculateAlignmentForPopUp(out upLeftXDropDown, out upLeftYDropDown, 0.0, 0.0, widthAuto, heightAuto, widthDropDown);
var downRightXDropDown = upLeftXDropDown + widthDropDown;
var downRightYDropDown = upLeftYDropDown + heightDropDown;
//Calc IsInbound
return (
point.X >= upLeftXAuto && point.X <= downRightXAuto &&
point.Y >= upLeftYAuto && point.Y <= downRightYAuto
) || (
point.X >= upLeftXDropDown && point.X <= downRightXDropDown &&
point.Y >= upLeftYDropDown && point.Y <= downRightYDropDown
);
}
private void CalculateAlignmentForPopUp(out double newX, out double newY, double xAuto, double yAuto, double widthAuto, double heightAuto, double widthPopup)
{
newX = 0.0;
newY = 0.0;
switch (this.ListHorizontalAlignment)
{
case "Right":
newX = xAuto + widthAuto - widthPopup;
newY = yAuto + heightAuto;
break;
case "Center":
newX = xAuto + widthAuto / 2 - widthPopup / 2;
newY = yAuto + heightAuto;
break;
default:
newY = yAuto + heightAuto;
break;
}
}
4)
对此没有答案。
但是当使用 3)
中的解决方案时,这现在已经过时了
编辑)
对此没有答案。
但它以任何方式工作。
我现在阅读了很多线程并尝试了等等。没看懂。
问题 使用 MouseCapture 时,不会移动任何其他控件。无法单击某些内容。鼠标悬停时没有突出显示。 MouseCaption 正在阻止它。需要点击两次。如何避免这种情况?
基本上 我创建了一个自定义自动完成框,它由一个用于自由输入的文本框和一个作为文本块的下拉列表组成,其中包含给定输入建议的 result-element。这很像一个标准的 ComboBox。
从 Combobox 中我们知道,当它展开并在其他地方单击时,下拉列表会折叠。
我想要与组合框使用的行为完全相同的行为。
因为我不是第一个问这个问题的人,所以我尝试了几件事,但没有让它们完全发挥作用。
我仍然尝试但失败了。
- 向文本框添加
OnLostFocus
事件无法识别对 non-focusable 元素的任何鼠标单击。 - 使用
Mous.Caption(this)
和PreviewMouseLeftButtonDown
接收鼠标在 window 上任何地方的任何点击。- 是的,行得通!我可以折叠我的下拉菜单,Un-Capture 再次鼠标。
- 但是:鼠标标题阻止我单击到其他 UIElement。复选框和单选框不会被切换。只需将鼠标悬停在复选框或其他任何地方都不会再突出显示该元素。相反,我现在需要点击两次来检查文本框。
- 我不知道怎么解决。
- 同样没有用的是,当 mouse-capture 事件被触发时,我无法弄清楚点击的位置。
source
以及e.OriginalSource
等于我的自定义控件- 获得 Mouse-Position 可能是一种选择。但是没有找到获取到我的控件相关位置的mouse-position。控件上的任何属性 return NaN。
- 起初我无法识别
PreviewMouseLeftButtonDown
和MouseLeftButtonDown
之间的任何区别。- 我认为第一个,当直接释放 mouse-capture 时,会触发 mouseclick 事件到其原始目的地,而不再捕获鼠标。它没有。
- 我是用Hittest搞定的。是这样吗?
一些代码
XAML 自动完成框
<UserControl x:Class="MyProject.Wpf.Application.Control.AutoCompleteBoxControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Name="AutoCompleteBox"
PreviewMouseLeftButtonDown="AutoComplete_MouseLeftButtonDown">
<Grid>
<StackPanel>
<TextBox Name="AutoCompleteTextBox" Text="{Binding ElementName=AutoCompleteBox, Path=TextBoxText, Mode=TwoWay}" Height="{Binding ElementName=AutoCompleteBox, Path=TextBoxHeight}" Width="{Binding ElementName=AutoCompleteBox, Path=TextBoxWidth}" Padding="5, 3, 5, 3" KeyUp="AutoCompleteTextBoxControl_KeyUp" LostKeyboardFocus="AutoCompleteTextBox_OnLostKeyboardFocus"/>
<Popup Name="ResultStackPopup" IsOpen="True" PlacementTarget="{Binding ElementName=AutoCompleteTextBox}" Placement="Custom">
<Border Name="ResultStackBorder" Width="{Binding ElementName=AutoCompleteBox, Path=SuggestionListWidth}" Height="{Binding ElementName=AutoCompleteBox, Path=SuggestionListHeight}" BorderBrush="Black" BorderThickness="1" Visibility="Collapsed" Background="White" Margin="1,0,1,0" HorizontalAlignment="Left">
<ScrollViewer VerticalScrollBarVisibility="Auto">
<StackPanel Name="ResultStack"></StackPanel>
</ScrollViewer>
</Border>
</Popup>
</StackPanel>
</Grid>
</UserControl>
后面相关代码:
//Whenever the dropdown is expanded, the mouse caption is started:
this.CaptureMouse();
mouse-down 事件:
private void AutoComplete_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
if (this.IsMouseCaptured)
{
if (!this.IsMouseInBounds())
{
//Release MouseCaption
this.ReleaseMouseCapture();
//Collapse SuggestionList
var border = (this.ResultStack.Parent as ScrollViewer)?.Parent as Border;
if (border != null)
{
border.Visibility = Visibility.Collapsed;
}
}
}
}
private bool IsMouseInBounds()
{
Point point = Mouse.GetPosition(this);
// Perform the hit test against a given portion of the visual object tree.
HitTestResult result = VisualTreeHelper.HitTest(this, point);
if (result != null)
{
return true;
}
return false;
}
编辑
不幸的是,Popup 不是 (afaik) visualTree 的成员。所以 Popup 的 hittest 不起作用。所以我试图让 Popup 的位置与 Mouse-position.
检查TransformToAncestor方法就是大家说的那样。 但这似乎无法正常工作:
以下三个调用return完全相同的点:
Window parentWindow = Window.GetWindow(this);
Point relativePointThis = this.TransformToAncestor(parentWindow).Transform(new Point(0, 0));
Point relativePointPopup = this.ResultStackPopup.TransformToAncestor(parentWindow).Transform(new Point(0, 0));
Point relativePointBorder = this.ResultStackBorder.TransformToAncestor(parentWindow).Transform(new Point(0, 0));
这是一个错误吗?
我没有放弃,向前走了几步
回答:
1) 要么使 Window 可聚焦,要么不这样做。
2)
Mouse.Capture(this, CaptureMode.SubTree);
使用正确的 CaptureMode 至少解决了 DropDown 与鼠标悬停等交互(当然,因为它是捕获控件的子项)。
Window 的其余部分一直被鼠标捕获阻止。人们可能刚刚意识到,这也是标准 ComboBox 的正常行为。所以人们可以接受这一点。
3)
通过鼠标位置获取入站点击检查可以使用以下代码处理。请注意,鼠标位置是相对于给定元素给出的。在这种情况下,AutoCompleteBox 是通过 this
给定的。所以左上角会是{0.0, 0.0}
.
private bool IsMouseInBounds()
{
//Get MousePosition relative to the AutoCompleteBox
Point point = Mouse.GetPosition(this);
//Actual Width and Heigth of AutoCompleteBox
var widthAuto = this.ActualWidth;
var heightAuto = this.ActualHeight;
var upLeftXAuto = 0.0;
var upLeftYAuto = 0.0;
var downRightXAuto = upLeftXAuto + widthAuto;
var downRightYAuto = upLeftYAuto + heightAuto;
//Actual Width and Height of DropDown
var widthDropDown = this.ResultStackBorder.ActualWidth;
var heightDropDown = this.ResultStackBorder.ActualHeight;
double upLeftXDropDown;
double upLeftYDropDown;
//Actual Position of DropDown (may be aligned right or center)
CalculateAlignmentForPopUp(out upLeftXDropDown, out upLeftYDropDown, 0.0, 0.0, widthAuto, heightAuto, widthDropDown);
var downRightXDropDown = upLeftXDropDown + widthDropDown;
var downRightYDropDown = upLeftYDropDown + heightDropDown;
//Calc IsInbound
return (
point.X >= upLeftXAuto && point.X <= downRightXAuto &&
point.Y >= upLeftYAuto && point.Y <= downRightYAuto
) || (
point.X >= upLeftXDropDown && point.X <= downRightXDropDown &&
point.Y >= upLeftYDropDown && point.Y <= downRightYDropDown
);
}
private void CalculateAlignmentForPopUp(out double newX, out double newY, double xAuto, double yAuto, double widthAuto, double heightAuto, double widthPopup)
{
newX = 0.0;
newY = 0.0;
switch (this.ListHorizontalAlignment)
{
case "Right":
newX = xAuto + widthAuto - widthPopup;
newY = yAuto + heightAuto;
break;
case "Center":
newX = xAuto + widthAuto / 2 - widthPopup / 2;
newY = yAuto + heightAuto;
break;
default:
newY = yAuto + heightAuto;
break;
}
}
4) 对此没有答案。 但是当使用 3)
中的解决方案时,这现在已经过时了编辑) 对此没有答案。 但它以任何方式工作。