【问题标题】:Allow mouse events to pass through semitransparent Popup允许鼠标事件通过半透明Popup
【发布时间】:2017-09-18 22:56:51
【问题描述】:

我正在尝试从Popup 中的控件执行拖放操作。被拖动的内容将被放置在放置的位置,所以当拖动开始时,我想让 Popup 半透明以允许放置在它下面。但到目前为止,我无法让鼠标事件通过我的半透明Popup

以下 xaml 工作正常,当用鼠标悬停 Rectangle 时,Button 仍然可点击

<Grid>
    <Rectangle Panel.ZIndex="2"
               Width="200"
               Height="200"
               Fill="Green"
               IsHitTestVisible="False"
               Opacity="0.5"/>
    <Button Content="Click Me"
            FontSize="22"
            FontWeight="Bold"/>
</Grid>

但是当鼠标悬停在Popup 内的Rectangle 时,以下xaml 无法让鼠标事件通过。

<Grid>
    <Popup Placement="Center"
           AllowsTransparency="True"
           IsHitTestVisible="False"
           IsOpen="True">
        <Rectangle Width="100"
                   Height="50"
                   Fill="Green"
                   IsHitTestVisible="False"
                   Opacity="0.5"/>
    </Popup>
    <Button Content="Click Me"
            FontSize="22"
            FontWeight="Bold"/>
</Grid>

我尝试获取 Rectangle 的顶级父级 PopupRoot 并为可视树中的每个元素显式设置 IsHitTestVisibleOpacity,但它仍然不起作用。我可以让它工作的唯一方法是将Background/Fill 设置为Transparent,但它看起来很奇怪并且悬停在不是Transparent 的部分仍然失败。

我已阅读以下链接,但据我所知,这仅适用于 Window 而不是 PopupHow to create a semi transparent window in WPF that allows mouse events to pass through

有没有人有任何解决方案或建议? :-)

【问题讨论】:

  • 在我看来,这要么是 WPF 中的错误,要么是 IsHitTestVisible 的文档错误或不完整。我建议您在Microsoft Connect 上提交错误报告。
  • @NETscape:我想试试,但我不知道如何获得Window。我能用 snoop 找到的只有PopupRoot,它源自FrameworkElement,而不是Window。对如何找到Window 有任何建议吗?
  • @NETscape:我设法获得了实际的Window,你说得对,它有效:-) 谢谢!

标签: c# wpf xaml popup wpf-controls


【解决方案1】:

感谢@NETscape 的评论,我试图获取Popup 的实际Window。它适用于以下代码(在最初显示 Popup 之后我可能会添加..)

// FromVisual can take any Visual inside the Popup..
HwndSource popupHwndSource = HwndSource.FromVisual(rectangle) as HwndSource;

将此与this question 的答案结合起来,我创建了一个附加行为,它添加了属性IsPopupEventTransparent,可以在Popup 中的任何子项上设置该属性。起初,这个解决方案导致了一个错误,在Popup 失去其事件透明性后,迫使用户单击两次以重新激活Window。我用“Mouse.Capture-workaround”解决了这个问题。

elementInPopup.Dispatcher.BeginInvoke(new Action(() =>
{
    Keyboard.Focus(elementInPopup);
    Mouse.Capture(elementInPopup);
    elementInPopup.ReleaseMouseCapture();
}));

可以这样使用

<Popup AllowsTransparency="True"
       ...>
    <Border inf:PopupBehavior.IsPopupEventTransparent="{Binding SomeProperty}"
            BorderThickness="1"
            BorderBrush="Black"
            Background="White"
            Margin="0 0 8 8">
        <Border.Effect>
            <DropShadowEffect ShadowDepth="2.25" 
                              Color="Black"
                              Opacity="0.4"
                              Direction="315"
                              BlurRadius="4"/>
        </Border.Effect>
        <!--...-->
    </Border>
</Popup>

PopupBehavior

public class PopupBehavior
{
    public static readonly DependencyProperty IsPopupEventTransparentProperty =
        DependencyProperty.RegisterAttached("IsPopupEventTransparent",
                                            typeof(bool),
                                            typeof(PopupBehavior),
                                            new UIPropertyMetadata(false, OnIsPopupEventTransparentPropertyChanged));

    public static bool GetIsPopupEventTransparent(DependencyObject obj)
    {
        return (bool)obj.GetValue(IsPopupEventTransparentProperty);
    }
    public static void SetIsPopupEventTransparent(DependencyObject obj, bool value)
    {
        obj.SetValue(IsPopupEventTransparentProperty, value);
    }
    private static void OnIsPopupEventTransparentPropertyChanged(DependencyObject target, DependencyPropertyChangedEventArgs e)
    {
        FrameworkElement element = target as FrameworkElement;
        if ((bool)e.NewValue == true)
        {
            FrameworkElement topParent = VisualTreeHelpers.GetTopParent(element) as FrameworkElement;
            topParent.Opacity = 0.4;
            HwndSource popupHwndSource = HwndSource.FromVisual(element) as HwndSource;
            WindowHelper.SetWindowExTransparent(popupHwndSource.Handle);
        }
        else
        {
            FrameworkElement topParent = VisualTreeHelpers.GetTopParent(element) as FrameworkElement;
            topParent.Opacity = 1.0;
            HwndSource popupHwndSource = HwndSource.FromVisual(element) as HwndSource;
            WindowHelper.UndoWindowExTransparent(popupHwndSource.Handle, element);
        }
    }
}

WindowHelper 基于对此this question的答案@

public static class WindowHelper
{
    private static Dictionary<IntPtr, int> _extendedStyleHwndDictionary = new Dictionary<IntPtr, int>();

    const int WS_EX_TRANSPARENT = 0x00000020;
    const int GWL_EXSTYLE = (-20);

    [DllImport("user32.dll")]
    static extern int GetWindowLong(IntPtr hwnd, int index);

    [DllImport("user32.dll")]
    static extern int SetWindowLong(IntPtr hwnd, int index, int newStyle);

    public static void SetWindowExTransparent(IntPtr hwnd)
    {
        int extendedStyle = GetWindowLong(hwnd, GWL_EXSTYLE);
        SetWindowLong(hwnd, GWL_EXSTYLE, extendedStyle | WS_EX_TRANSPARENT);
        if (_extendedStyleHwndDictionary.Keys.Contains(hwnd) == false)
        {
            _extendedStyleHwndDictionary.Add(hwnd, extendedStyle);
        }
    }

    public static void UndoWindowExTransparent(IntPtr hwnd, FrameworkElement elementInPopup)
    {
        if (_extendedStyleHwndDictionary.Keys.Contains(hwnd) == true)
        {
            int extendedStyle = _extendedStyleHwndDictionary[hwnd];
            SetWindowLong(hwnd, GWL_EXSTYLE, extendedStyle);
            // Fix Focus problems that forces the users to click twice to
            // re-activate the window after the Popup loses event transparency
            elementInPopup.Dispatcher.BeginInvoke(new Action(() =>
            {
                Keyboard.Focus(elementInPopup);
                Mouse.Capture(elementInPopup);
                elementInPopup.ReleaseMouseCapture();
            }));
        }
    }
}

【讨论】:

    【解决方案2】:

    问题不在于弹出窗口,而在于颜色 Green 当应用于矩形时,这有效地阻止了鼠标事件通过

    用透明背景试试下面的示例,我放置了一个边框来显示存在

    <Grid>
        <Popup Placement="Center"
               AllowsTransparency="True"
               IsHitTestVisible="False"
               IsOpen="True">
            <Border Width="100"
                    Height="50"
                    Background="Transparent"
                    BorderBrush="Black"
                    BorderThickness="4"
                    IsHitTestVisible="False"
                    Opacity="0.5" />
        </Popup>
        <Button Content="Click Me"
                FontSize="22"
                FontWeight="Bold" />
    </Grid>
    

    所以任何具有non zero alpha 组件的颜色都会阻止鼠标事件

    请参阅下面的 alpha 示例

    • #FF00FF00 绿色Fail
    • #9900FF00 半透明绿色Fail
    • #0100FF00 几乎看不见的绿色Fail
    • #0000FF00 全透明绿色Pass

    所以你需要有透明的颜色才能让鼠标事件通过

    由于弹出窗口是在应用程序窗口顶部生成的分层窗口,因此如果您需要背景,您可能需要实现如何创建半透明窗口的解决方案。 wpf 中似乎没有内置的解决方案

    【讨论】:

    • 这与我在问题末尾写的类似,将Background/Fill 设置为Transparent 之类的作品,但如果您将鼠标悬停在边界线(BorderThickness="4" )。是的,我正在考虑将Window 作为替代解决方案。谢谢:-)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-01-04
    • 1970-01-01
    • 2013-03-26
    相关资源
    最近更新 更多