WPF ComboBox:在弹出窗口外单击抑制鼠标单击
WPF ComboBox: click outside of popup suppress mouse click
我使用标准的 WPF ComboBox 控件。当弹出窗口打开并且用户单击外部某处时,弹出窗口将关闭。但是,如果 window 上有按钮并且用户单击它(弹出窗口仍然打开),则不会执行按钮的单击处理程序。弹出窗口已关闭,但用户必须再点击一次按钮才能引发点击事件。
我知道这是此控件的标准行为。你有什么想法如何绕过这种行为吗?谢谢!
您可以尝试在ComboBox获得一个后立即释放鼠标捕获:
在 XAML:
中的 ComboBox 属性中
GotMouseCapture="ComboBox_OnGotMouseCapture"
在代码隐藏中:
private void ComboBox_OnGotMouseCapture(object sender, MouseEventArgs e)
{
ComboBox.ReleaseMouseCapture();
}
您可以为 ComboBox DropDownClosed 创建一个事件,并使用 hittest 函数,找到用户单击的其他控件。
private void ComboBox_DropDownClosed(object sender, EventArgs e)
{
Point m = Mouse.GetPosition(this);
VisualTreeHelper.HitTest(this, this.FilterCallback, this.ResultCallback, new PointHitTestParameters(m));
}
private HitTestFilterBehavior FilterCallback(DependencyObject o)
{
var c = o as Control;
if ((c != null) && !(o is MainWindow))
{
if (c.Focusable)
{
if (c is ComboBox)
{
(c as ComboBox).IsDropDownOpen = true;
}
else
{
var mouseDevice = Mouse.PrimaryDevice;
var mouseButtonEventArgs = new MouseButtonEventArgs(mouseDevice, 0, MouseButton.Left)
{
RoutedEvent = Mouse.MouseDownEvent,
Source = c
};
c.RaiseEvent(mouseButtonEventArgs);
}
return HitTestFilterBehavior.Stop;
}
}
return HitTestFilterBehavior.Continue;
}
private HitTestResultBehavior ResultCallback(HitTestResult r)
{
return HitTestResultBehavior.Continue;
}
然后在找到该控件后的 FilterCallback 函数中,在该控件上引发鼠标按下事件。
我发现了 raise 事件,它在组合框上不起作用,所以为了单击它,我只是将 IsDropDownOpen 设置为 true。
找到here中的代码,稍微修改一下
我使用了一个简单的解决方案:
在 PreviewMouseLeftButtonDown 事件中,如果鼠标位置在组合框之外,则关闭下拉列表。这将允许其他控件获得鼠标点击:
Dim p = Mouse.GetPosition(combo)
If p.X < 0 OrElse p.Y < 0 OrElse p.X > combo.Width OrElse p.Y > combo.Height Then
cmb.IsDropDownOpen = False
End If
我用@Eng 修复了一些错误。 M.Hamdy 并在 C# 中完成,还将其应用于应用程序范围内的所有组合框。
应用挂钩:
EventManager.RegisterClassHandler(typeof(ComboBox), UIElement.PreviewMouseLeftButtonDownEvent, new MouseButtonEventHandler(FixComboBoxOutClick));
处理程序代码:
private void FixComboBoxOutClick(object sender, MouseButtonEventArgs e) {
if (sender is ComboBox combo) {
Point comboRelativePoint = Mouse.GetPosition(combo);
if (comboRelativePoint.X < 0 || comboRelativePoint.Y < 0 || comboRelativePoint.X > combo.ActualWidth || comboRelativePoint.Y > combo.ActualHeight) {
UIElement popupContent = combo.FindChild<Popup>(null).Child;
Point popupRelativePoint = Mouse.GetPosition(popupContent);
if (popupRelativePoint.X < 0 || popupRelativePoint.Y < 0 || popupRelativePoint.X > popupContent.RenderSize.Width || popupRelativePoint.Y > popupContent.RenderSize.Height) {
combo.IsDropDownOpen = false;
}
}
}
}
您可以查找 FindChild<T>()
实现 here。
我使用标准的 WPF ComboBox 控件。当弹出窗口打开并且用户单击外部某处时,弹出窗口将关闭。但是,如果 window 上有按钮并且用户单击它(弹出窗口仍然打开),则不会执行按钮的单击处理程序。弹出窗口已关闭,但用户必须再点击一次按钮才能引发点击事件。
我知道这是此控件的标准行为。你有什么想法如何绕过这种行为吗?谢谢!
您可以尝试在ComboBox获得一个后立即释放鼠标捕获: 在 XAML:
中的 ComboBox 属性中GotMouseCapture="ComboBox_OnGotMouseCapture"
在代码隐藏中:
private void ComboBox_OnGotMouseCapture(object sender, MouseEventArgs e)
{
ComboBox.ReleaseMouseCapture();
}
您可以为 ComboBox DropDownClosed 创建一个事件,并使用 hittest 函数,找到用户单击的其他控件。
private void ComboBox_DropDownClosed(object sender, EventArgs e)
{
Point m = Mouse.GetPosition(this);
VisualTreeHelper.HitTest(this, this.FilterCallback, this.ResultCallback, new PointHitTestParameters(m));
}
private HitTestFilterBehavior FilterCallback(DependencyObject o)
{
var c = o as Control;
if ((c != null) && !(o is MainWindow))
{
if (c.Focusable)
{
if (c is ComboBox)
{
(c as ComboBox).IsDropDownOpen = true;
}
else
{
var mouseDevice = Mouse.PrimaryDevice;
var mouseButtonEventArgs = new MouseButtonEventArgs(mouseDevice, 0, MouseButton.Left)
{
RoutedEvent = Mouse.MouseDownEvent,
Source = c
};
c.RaiseEvent(mouseButtonEventArgs);
}
return HitTestFilterBehavior.Stop;
}
}
return HitTestFilterBehavior.Continue;
}
private HitTestResultBehavior ResultCallback(HitTestResult r)
{
return HitTestResultBehavior.Continue;
}
然后在找到该控件后的 FilterCallback 函数中,在该控件上引发鼠标按下事件。
我发现了 raise 事件,它在组合框上不起作用,所以为了单击它,我只是将 IsDropDownOpen 设置为 true。
找到here中的代码,稍微修改一下
我使用了一个简单的解决方案: 在 PreviewMouseLeftButtonDown 事件中,如果鼠标位置在组合框之外,则关闭下拉列表。这将允许其他控件获得鼠标点击:
Dim p = Mouse.GetPosition(combo)
If p.X < 0 OrElse p.Y < 0 OrElse p.X > combo.Width OrElse p.Y > combo.Height Then
cmb.IsDropDownOpen = False
End If
我用@Eng 修复了一些错误。 M.Hamdy
应用挂钩:
EventManager.RegisterClassHandler(typeof(ComboBox), UIElement.PreviewMouseLeftButtonDownEvent, new MouseButtonEventHandler(FixComboBoxOutClick));
处理程序代码:
private void FixComboBoxOutClick(object sender, MouseButtonEventArgs e) {
if (sender is ComboBox combo) {
Point comboRelativePoint = Mouse.GetPosition(combo);
if (comboRelativePoint.X < 0 || comboRelativePoint.Y < 0 || comboRelativePoint.X > combo.ActualWidth || comboRelativePoint.Y > combo.ActualHeight) {
UIElement popupContent = combo.FindChild<Popup>(null).Child;
Point popupRelativePoint = Mouse.GetPosition(popupContent);
if (popupRelativePoint.X < 0 || popupRelativePoint.Y < 0 || popupRelativePoint.X > popupContent.RenderSize.Width || popupRelativePoint.Y > popupContent.RenderSize.Height) {
combo.IsDropDownOpen = false;
}
}
}
}
您可以查找 FindChild<T>()
实现 here。