Allow mouse events to pass through semitransparent Popup

c#, popup, wpf, wpf-controls, xaml

Solution

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();
            }));
        }
    }
}

Problem

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? :-)

Original source

Related problems