c# WPF - 如何在不阻止其他控件可点击的情况下识别控件外的鼠标单击

c# WPF - How to recognize mouse click outside control without blocking other controls from being clickable

我现在阅读了很多线程并尝试了等等。没看懂。

问题 使用 MouseCapture 时,不会移动任何其他控件。无法单击某些内容。鼠标悬停时没有突出显示。 MouseCaption 正在阻止它。需要点击两次。如何避免这种情况?

基本上 我创建了一个自定义自动完成框,它由一个用于自由输入的文本框和一个作为文本块的下拉列表组成,其中包含给定输入建议的 result-element。这很像一个标准的 ComboBox。

从 Combobox 中我们知道,当它展开并在其他地方单击时,下拉列表会折叠。

我想要与组合框使用的行为完全相同的行为。

因为我不是第一个问这个问题的人,所以我尝试了几件事,但没有让它们完全发挥作用。

我仍然尝试但失败了。

  1. 向文本框添加 OnLostFocus 事件无法识别对 non-focusable 元素的任何鼠标单击。
  2. 使用 Mous.Caption(this)PreviewMouseLeftButtonDown 接收鼠标在 window 上任何地方的任何点击。
    • 是的,行得通!我可以折叠我的下拉菜单,Un-Capture 再次鼠标。
    • 但是:鼠标标题阻止我单击到其他 UIElement。复选框和单选框不会被切换。只需将鼠标悬停在复选框或其他任何地方都不会再突出显示该元素。相反,我现在需要点击两次来检查文本框。
    • 我不知道怎么解决。
  3. 同样没有用的是,当 mouse-capture 事件被触发时,我无法弄清楚点击的位置。
    • source以及e.OriginalSource等于我的自定义控件
    • 获得 Mouse-Position 可能是一种选择。但是没有找到获取到我的控件相关位置的mouse-position。控件上的任何属性 return NaN。
  4. 起初我无法识别 PreviewMouseLeftButtonDownMouseLeftButtonDown 之间的任何区别。
    • 我认为第一个,当直接释放 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)

中的解决方案时,这现在已经过时了

编辑) 对此没有答案。 但它以任何方式工作。