Search code examples
c#wpfaero-glass

How to give a glass appearance to the control under an adorner?


I am attempting to "blurr" a windows 10 user control when an adorner is placed over it.

More exactly, I have a datagrid control in a wpf user window. When a cell in the datagrid is selected, an editing adorner is created over the datagrid control.

public DataGridAnnotationAdorner(DataGrid adornedDataGrid, IVisit visit, DateTime TableDate)
            : base(adornedDataGrid)
        {
            Control = new DataGridAnnotationControl(visit, TableDate);

            AddLogicalChild(Control);
            AddVisualChild(Control);

            Loaded += DataGridAnnotationAdorner_Loaded;    
        }

        private void DataGridAnnotationAdorner_Loaded(object sender, RoutedEventArgs e)
        {
            EnableBlur(AdornedElement);
        }

The loaded event then calls (a pretty standard) Blurr/glass effect method:

   internal void EnableBlur(UIElement dataGrid)
    {
        HwndSource source = (HwndSource)PresentationSource.FromVisual(dataGrid);
        IntPtr hwnd = source.Handle;

        var accent = new AccentPolicy();
        accent.AccentState = AccentState.ACCENT_ENABLE_BLURBEHIND;

        var accentStructSize = Marshal.SizeOf(accent);

        var accentPtr = Marshal.AllocHGlobal(accentStructSize);
        Marshal.StructureToPtr(accent, accentPtr, false);

        var data = new WindowCompositionAttributeData();
        data.Attribute = WindowCompositionAttribute.WCA_ACCENT_POLICY;
        data.SizeOfData = accentStructSize;
        data.Data = accentPtr;

        SetWindowCompositionAttribute(hwnd, ref data);

        Marshal.FreeHGlobal(accentPtr);
    }

No error is reported....But is does not work.

So, how can I give a "glass" appearance to the control under the adorner?

TIA


Solution

  • I'm not sure I can give you a whole solution, but I can at least explain why your attempt doesn't work and maybe point you in the right direction.

    In WinForms (and C++ I think), each control is sort of its own "mini-window". By which I mean each control is given its own "handle" by the OS, and the OS treats them as individual entities on the screen. WPF is different, the illusion of "separate controls" in a window is one created by the WPF framework. As far as the OS knows, there is only one entity: the Window and all further logic is handled internally by WPF. This is only mostly true, though, as I'll bring up later.

    The reason I bring this up at all is because your line of code PresentationSource.FromVisual(dataGrid) is going to give you the handle of the Window in which the DataGrid is being hosted, not a handle unique to the DataGrid itself (since the DataGrid has no handle). And so everything else you do using that handle is actually being targeted to the entire WPF Window as a whole.

    All of the rendering inside a WPF Window is handled internally by the WPF framework (plus your code), not directly by the OS (as in WinForms and C++), so using a native method that tells the OS how to render something wont work on a specific element inside the Window.

    As I see it there are two routes you can take in trying to create this effect:

    • You could try to recreate the "glassy" effect inside WPF. This would involve recreating or getting ahold of whatever algorithm is used to visually distort an area to produce that effect, and then somehow implementing that into a control. I wouldn't really know where to start with that other than just Google.

    • You could try to take advantage of other WPF elements that do have their own handles.

      • The two main WPF elements I know of that have their own handles are Window and Popup. You might be able to create one of those and position it where you need it, just like you're currently doing with your adorer. This will get tricky though because these elements are actually outside the Window that spawned them and aren't really linked to it. So when you move the main Window across the desktop, the Popup or sub-Window won't move with it. You might also be able to drag other windows between your WPF window and the popup you create (not sure about that, though).

      • There is maybe one other possibility under this heading, and that is the WindowsFormsHost control. This is a WPF control that hosts a WinForms control in a WPF window. The WinForms controls hosted inside presumably are given their own handle by the OS (since that's how WinForms operates). This one might be your best bet, except I've never tried it and I have no idea how the WindowsFormsHost will interact with other elements that are around or underneath it. So it also might not work at all for what you need.

    Good luck.