Allow mouse events to pass through semitransparent

2019-05-16 08:10发布

问题:

I'm trying to do a drag-n-drop operation from a control inside a Popup. The content being dragged will be placed at the position of the drop so when the drag starts I want to make the Popup semitransparent to allow placement under it. But so far I fail to make mouse events pass through my semitransparent Popup.

The following xaml works fine, the Button is still clickable when hovering the Rectangle with the mouse

<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>

But the following xaml fails to allow mouse event to pass through when the mouse hovers the Rectangle inside the Popup.

<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>

I've tried to get the PopupRoot top parent of the Rectangle and set both IsHitTestVisible and Opacity for every element in the visual tree explicitly but it still doesn't work. The only way I can sort of get it to work is by setting Background/Fill to Transparent but it looks weird and hovering the parts that aren't Transparent still fails.

I've read the following link but as far as I can tell, that will only work for a Window and not a Popup. How to create a semi transparent window in WPF that allows mouse events to pass through

Does anyone have any solutions or suggestions? :-)

回答1:

Thanks to the comment made by @NETscape I tried to get a hold of the actual Window for the Popup. It works with the following code (after the Popup is initially shown I might add..)

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

Combining this with the answer to this question I created an attached behavior which adds the property IsPopupEventTransparent which can be set on any child in the Popup. At first, this solution caused a bug which forced the users to click twice to re-activate the Window after the Popup lost its event transparency. I solved this with a "Mouse.Capture-workaround".

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

Usable like this

<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 based on the answer to this 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:

The issue is not with popup but issue is with the color Green when applied to the rectangle this effectively blocks the mouse event to pass through

try the sample below with transparent background, I placed a border to show the presence

<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>

so any color having non zero alpha component will block the mouse events

refer to below for alpha example

  • #FF00FF00 Green Fail
  • #9900FF00 semi transparent Green Fail
  • #0100FF00 almost invisible Green Fail
  • #0000FF00 full transparent Green Pass

so you need to have transparent color in order for mouse event to be passed through

Since popup is a generated as a layered window on top of the app window, so if you need a background you may need to implement the how to create the semi transparent window solution. there seems to be no built solution in wpf