Search code examples
c#.net-corewinui-3winui

Can i create borderless transparent overlay window in WinUI 3?


I want to create empty window - transparent and borderless, and make it overlay. To create overlay window, I use AppWindow.SetPresenter(AppWindowPresenterKind.CompactOverlay), to make it borderless i subclass window and using following WndProc:

 private unsafe LRESULT SubclassProc(HWND hWnd, uint uMsg, WPARAM wParam, LPARAM lParam, nuint uIdSubclass, nuint dwRefData)
 {
     if (uMsg == PInvoke.WM_NCCALCSIZE)
     {
         return new LRESULT(0);
     }

     return PInvoke.DefSubclassProc(hWnd, uMsg, wParam, lParam);
 }

And for make window transparent i use following code:

ICompositionSupportsSystemBackdrop composition = this.As<ICompositionSupportsSystemBackdrop>();
CompositionColorBrush compositionColorBrush = new Compositor().CreateColorBrush(Color.FromArgb(0, 255, 255, 255));
composition.SystemBackdrop = compositionColorBrush;

But i can't hide default close/minimize/maxmimize buttons. I'm using Windows 11.

Full code:

using Microsoft.UI;
using Microsoft.UI.Composition;
using Microsoft.UI.Windowing;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Media;
using Sentry;
using System;
using System.Linq;
using Windows.System;
using Windows.UI;
using Windows.Win32;
using Windows.Win32.Foundation;
using Windows.Win32.Graphics.Dwm;
using Windows.Win32.Graphics.Gdi;
using Windows.Win32.System.WinRT;
using Windows.Win32.UI.Shell;
using Windows.Win32.UI.Controls;
using WinRT;
using WinRT.Interop;
using CompositionColorBrush = Windows.UI.Composition.CompositionColorBrush;
using Compositor = Windows.UI.Composition.Compositor;
using System.IO;
using Windows.Win32.UI.WindowsAndMessaging;
using Windows.ApplicationModel.Core;

namespace Widget.UI.Windows
{
    internal abstract class OverlayWindow : Window
    {
        private Grid _rootGrid
        {
            get
            {
                if (base.Content is Grid rootGrid)
                {
                    return rootGrid;
                }

                throw new InvalidOperationException("TopWindow: root isn't initalized");
            }
        }

        public new UIElement? Content
        {
            get
            {
                return _rootGrid.Children.FirstOrDefault();
            }
            set
            {
                _rootGrid.Children.Clear();
        
                Grid contentGrid = new();
        
                Grid.SetColumn(contentGrid, 0);
                Grid.SetRow(contentGrid, 1);
        
                contentGrid.Children.Add(value);
        
                _rootGrid.Children.Add(contentGrid);
            }
        }

        protected HorizontalAlignment HorizontalAlignment
        {
            set
            {
                AppWindow.Move(new global::Windows.Graphics.PointInt32()
                {
                    X = value switch
                    {
                        HorizontalAlignment.Left => 0,
                        HorizontalAlignment.Center => (_displayArea.WorkArea.Width - AppWindow.Size.Width) / 2,
                        HorizontalAlignment.Right => _displayArea.WorkArea.Width - AppWindow.Size.Width,
                        _ => throw new ArgumentException($"Invalid value (note: HorizontalAlignment.Stretch not supported)")
                    },
                    Y = AppWindow.Position.Y
                });
            }
        }

        protected VerticalAlignment VerticalAlignment
        {
            set
            {
                AppWindow.Move(new global::Windows.Graphics.PointInt32()
                {
                    X = AppWindow.Position.X,
                    Y = value switch
                    {
                        VerticalAlignment.Top => 0,
                        VerticalAlignment.Center => (_displayArea.WorkArea.Height - AppWindow.Size.Height) / 2,
                        VerticalAlignment.Bottom => _displayArea.WorkArea.Height - AppWindow.Size.Height,
                        _ => throw new ArgumentException($"Invalid value (note: VerticalAlignment.Stretch not supported)")
                    }
                });
            }
        }

        private DisplayArea _displayArea
        {
            get
            {
                return DisplayArea.GetFromWindowId(Win32Interop.GetWindowIdFromWindow(WindowNative.GetWindowHandle(this)), DisplayAreaFallback.Nearest);
            }
        }

        private SUBCLASSPROC? _subclassProc;

        public OverlayWindow()
        {
            AppWindow.SetPresenter(AppWindowPresenterKind.CompactOverlay);
            this.InitializeWindow();
            this.InitializeDwmBlur();
            this.InitializeComposition();
            this.InitializeSubclass();
        }

        private void InitializeWindow()
        {
            Grid root = new();

            root.ColumnDefinitions.Add(new ColumnDefinition() { Width = new GridLength(1, GridUnitType.Star) });

            root.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(0) });
            root.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(1, GridUnitType.Star) });

            Grid titleBar = new();

            Grid.SetColumn(titleBar, 0);
            Grid.SetRow(titleBar, 0);

            root.Children.Add(titleBar);

            root.Loaded += (s, e) =>
            {
                if (VisualTreeHelper.GetParent(base.Content) is not DependencyObject contentPresenter)
                {
                    return;
                }
                 
                if (VisualTreeHelper.GetParent(contentPresenter) is not DependencyObject layoutRoot)
                {
                    return;
                }
                    
                     if (VisualTreeHelper.GetChild(layoutRoot, 1) is not Grid titleBar)
                     {
                         return;
                     }
                 
                     if (VisualTreeHelper.GetChild(titleBar, 0) is not Grid buttonContainer)
                     {
                         return;
                     }
                 
                     foreach (UIElement child in buttonContainer.Children)
                     {
                         child.Visibility = Visibility.Collapsed;
                     }

                // For correct drawing background after creation
                PInvoke.SendMessage(_currentHWND, PInvoke.WM_ACTIVATE, new WPARAM(0), new LPARAM(0));
            };

            this.ExtendsContentIntoTitleBar = true;
            this.SetTitleBar(titleBar);

            base.Content = root;

        }

        private void InitializeComposition()
        {
            EnsureDispatcherQueueExists();
             
            ICompositionSupportsSystemBackdrop composition = this.As<ICompositionSupportsSystemBackdrop>();
            CompositionColorBrush compositionColorBrush = new Compositor().CreateColorBrush(Color.FromArgb(0, 255, 255, 255));
            composition.SystemBackdrop = compositionColorBrush;
        }

        private void InitializeDwmBlur()
        {
            PInvoke.DwmExtendFrameIntoClientArea(_currentHWND, new MARGINS());
            PInvoke.DwmEnableBlurBehindWindow(_currentHWND, new DWM_BLURBEHIND()
            {
                fEnable = true,
                dwFlags = PInvoke.DWM_BB_ENABLE | PInvoke.DWM_BB_BLURREGION,
                hRgnBlur = PInvoke.CreateRectRgn(-2, -2, 1, -1)
            });
        }

        private void InitializeSubclass()
        {
            _subclassProc = SubclassProc;

            PInvoke.SetWindowSubclass(_currentHWND, _subclassProc, 1000, 0);
        }

        private unsafe LRESULT SubclassProc(HWND hWnd, uint uMsg, WPARAM wParam, LPARAM lParam, nuint uIdSubclass, nuint dwRefData)
        {
            if (uMsg == PInvoke.WM_NCCALCSIZE)
            {
                return new LRESULT(0);
            }

            return PInvoke.DefSubclassProc(hWnd, uMsg, wParam, lParam);
        }

        private unsafe void EnsureDispatcherQueueExists()
        {
            DispatcherQueue dispatcherQueue = global::Windows.System.DispatcherQueue.GetForCurrentThread();

            if (dispatcherQueue == null)
            {
                DispatcherQueueOptions options = new()
                {
                    apartmentType = DISPATCHERQUEUE_THREAD_APARTMENTTYPE.DQTAT_COM_STA,
                    threadType = DISPATCHERQUEUE_THREAD_TYPE.DQTYPE_THREAD_CURRENT,
                    dwSize = (uint)sizeof(DispatcherQueueOptions)
                };

                PInvoke.CreateDispatcherQueueController(options, out DispatcherQueueController _);
            }
        }
    }
}

Solution

  • Hide buttons with

    ((OverlappedPresenter)AppWindow.Presenter).SetBorderAndTitleBar(false, false);
            ((OverlappedPresenter)AppWindow.Presenter).IsMinimizable = false;
            ((OverlappedPresenter)AppWindow.Presenter).IsMaximizable = false;
            ((OverlappedPresenter)AppWindow.Presenter).IsResizable = false;
            AppWindow.IsShownInSwitchers = false;
    

    For removing border around window use p/invoke:

    public static void SetWindowCornerRadius(IntPtr hwnd, NativeValues.DWM_WINDOW_CORNER_PREFERENCE cornerPreference)
        {
            var attribute = NativeValues.DWMWINDOWATTRIBUTE.DWMWA_WINDOW_CORNER_PREFERENCE;
            var preference = (uint)cornerPreference;
            NativeMethods.DwmSetWindowAttribute(hwnd, attribute, ref preference, sizeof(uint));
        }
    
    [DllImport(ExternDll.DwmApi, CharSet = CharSet.Unicode, PreserveSig = false)]
        public static extern int DwmSetWindowAttribute(IntPtr hwnd, DWMWINDOWATTRIBUTE dwAttribute, ref uint pvAttribute, uint cbAttribute);
    
    [Flags]
        public enum DWMWINDOWATTRIBUTE : uint
        {
            DWMWA_WINDOW_CORNER_PREFERENCE = 33,
            DWMWA_BORDER_COLOR,
            DWMWA_VISIBLE_FRAME_BORDER_THICKNESS
        }
    
    public enum DWM_WINDOW_CORNER_PREFERENCE
        {
            DWMWCP_DEFAULT = 0,
            DWMWCP_DONOTROUND = 1,
            DWMWCP_ROUND = 2,
            DWMWCP_ROUNDSMALL = 3
        }
    
    SetWindowCornerRadius(hwnd, NativeValues.DWM_WINDOW_CORNER_PREFERENCE.DWMWCP_DONOTROUND);