6

NB: THIS IS NOT A QUESTION ABOUT A BORDERLESS WINDOW.

So, I stumbled upon this program while I was exploring my Start menu the other day on Windows 7:

Math input panel

It's a native Windows program, called "Math Input Panel." Now, I'm curious about the window shape. I know that it's not completely drawn by DWM, because the borders and Close button look fishy and the window has no drop shadow (I have drop shadows enabled). My first guess as to how this was made would be using DwmEnableBlurBehindWindow, but I can't imagine that works on irregular window shapes, does it? (Or is there another way to do this, or is it just completely Microsoft sorcery?)

rookie1024
  • 612
  • 7
  • 18
  • This ain't wpf, unless you can prove otherwise. But then, if you tried (by using snoop, for example) you'd have your answer. –  Jan 05 '15 at 21:15
  • 2
    I know it's not WPF. It's probably within the realm of Winapi, but I would prefer a solution that works with WPF. In fact, I think hRgn might be the answer... – rookie1024 Jan 05 '15 at 21:17
  • It's sorcery. There is no wpf solution. Also, asking for libraries (like specialized window chrome libraries) is verboten. Hacking window chrome is a royal pain in the ass, with little benefit (the only chrome hack I've ever seen that didn't suck was ... Chrome). –  Jan 05 '15 at 21:18
  • As I said before, I know it's not WPF. I think using `DwmEnableBlurBehindWindow` and complex `hRgn`s might be the solution, but I must conduct further testing. – rookie1024 Jan 05 '15 at 21:21
  • If you don't care about WPF, why has that tag? –  Jan 05 '15 at 21:22
  • Not actually sure why I did that. Lemme see if I can remove it. – rookie1024 Jan 05 '15 at 21:26
  • @rookie1024 do not remove that tag if you'd prefer a WPF solution. – Lucas Trzesniewski Jan 05 '15 at 21:27
  • @LucasTrzesniewski I figure it shouldn't be too hard to convert the solution to WPF. Besides, I think I'm onto something. – rookie1024 Jan 05 '15 at 21:29
  • 2
    @rookie1024 The `DWM_BLURBEHIND` structure has a `hRgnBlur` field. Just apply a non-rectangular region to it. Wrap everything in a nice attached property or behavior to make it MVVM-friendy and you'll have your WPF solution. – Lucas Trzesniewski Jan 05 '15 at 21:32
  • @Will *"It's sorcery. There is no wpf solution"* never say never :P – Lucas Trzesniewski Jan 05 '15 at 22:10
  • Keep in mind that the Aero Glass effect is Windows Vista/7 only. Starting with Windows 8, the Aero Glass effect is gone. – IInspectable Jan 06 '15 at 00:07
  • @IInspectable Yeah, this was more of a proof-of-concept question. Not sure how practical this actually is, anyway. – rookie1024 Jan 06 '15 at 00:31

3 Answers3

8

Here's a quickly hacked together WPF solution. It uses the hRgnBlur of the DWM_BLURBEHIND structure, and some interop.

This example will apply an ellipse-shaped background blur on the window.

You can easily convert this to an attached property or behavior for MVVM-friendliness. It's also a good idea to listen to the WM_DWMCOMPOSITIONCHANGED message and reapply the blur if needed.

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();

        WindowStyle = WindowStyle.None;
        AllowsTransparency = true;

        SourceInitialized += OnSourceInitialized;
    }

    private void OnSourceInitialized(object sender, EventArgs eventArgs)
    {
        if (!NativeMethods.DwmIsCompositionEnabled())
            return;

        var hwnd = new WindowInteropHelper(this).Handle;

        var hwndSource = HwndSource.FromHwnd(hwnd);
        var sizeFactor = hwndSource.CompositionTarget.TransformToDevice.Transform(new Vector(1.0, 1.0));

        Background = System.Windows.Media.Brushes.Transparent;
        hwndSource.CompositionTarget.BackgroundColor = Colors.Transparent;

        using (var path = new GraphicsPath())
        {
            path.AddEllipse(0, 0, (int)(ActualWidth * sizeFactor.X), (int)(ActualHeight * sizeFactor.Y));

            using (var region = new Region(path))
            using (var graphics = Graphics.FromHwnd(hwnd))
            {
                var hRgn = region.GetHrgn(graphics);

                var blur = new NativeMethods.DWM_BLURBEHIND
                {
                    dwFlags = NativeMethods.DWM_BB.DWM_BB_ENABLE | NativeMethods.DWM_BB.DWM_BB_BLURREGION | NativeMethods.DWM_BB.DWM_BB_TRANSITIONONMAXIMIZED,
                    fEnable = true,
                    hRgnBlur = hRgn,
                    fTransitionOnMaximized = true
                };

                NativeMethods.DwmEnableBlurBehindWindow(hwnd, ref blur);

                region.ReleaseHrgn(hRgn);
            }
        }
    }

    [SuppressUnmanagedCodeSecurity]
    private static class NativeMethods
    {
        [StructLayout(LayoutKind.Sequential)]
        public struct DWM_BLURBEHIND
        {
            public DWM_BB dwFlags;
            public bool fEnable;
            public IntPtr hRgnBlur;
            public bool fTransitionOnMaximized;
        }

        [Flags]
        public enum DWM_BB
        {
            DWM_BB_ENABLE = 1,
            DWM_BB_BLURREGION = 2,
            DWM_BB_TRANSITIONONMAXIMIZED = 4
        }

        [DllImport("dwmapi.dll", PreserveSig = false)]
        public static extern bool DwmIsCompositionEnabled();

        [DllImport("dwmapi.dll", PreserveSig = false)]
        public static extern void DwmEnableBlurBehindWindow(IntPtr hwnd, ref DWM_BLURBEHIND blurBehind);
    }
}

Used with the following XAML:

<Window x:Class="WpfTest.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="200" Width="300" WindowStartupLocation="CenterScreen">

    <Border Background="#800000FF" Margin="30">
        <TextBlock Text="Hello, world!" VerticalAlignment="Center" HorizontalAlignment="Center" />
    </Border>
</Window>

The result is:

blur example

Lucas Trzesniewski
  • 50,214
  • 11
  • 107
  • 158
  • Looks similar enough to [my answer](http://stackoverflow.com/a/27789036/1852105) that I'll use that, although I may have forgotten to dispose some stuff. Oh, well, that should be dealt with by garbage collection (I hope). Do you think that's an issue? – rookie1024 Jan 05 '15 at 22:35
  • 1
    @rookie1024 it would be an issue if this was called often, but as it's called only once you should be fine. The wrappers' finalizers will take care of everything. But what I'd add to your solution is what I did with `sizeFactor` - this takes into account the user's DPI settings (control panel -> display). And I suggest handling `WM_DWMCOMPOSITIONCHANGED` too if you don't want your app to look ugly after an Aero on/off switch. – Lucas Trzesniewski Jan 05 '15 at 23:46
  • Is there any way to set the color of the blurred background. My Areo settings on my desktop are orange? However I would like the app to be white regardless of the users Windows Color Settings. – Mike Nov 07 '16 at 16:30
  • @Mike AFAIK there's no option for this, at least not in `DWM_BLURBEHIND` – Lucas Trzesniewski Nov 07 '16 at 16:50
4

So, unbeknownst to me, hRgn can take an irregular shape (and DwmEnableBlurBehindWindow takes an hRgn, but I knew that). So, here's my solution that's (more or less) compatible with WPF:

My own custom-shaped glass window

...and source code:

MainWindow.xaml:

<Window x:Class="IrregularGlassWindow.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow"
        Height="500"
        Width="500"
        Background="#01FFFFFF"
        AllowsTransparency="True"
        WindowStyle="None"
        ResizeMode="NoResize">
  <Window.Clip>
    <PathGeometry>
      <PathFigure StartPoint="250,0">
        <ArcSegment Point="250,500"
                    RotationAngle="180"
                    Size="250,250"
                    SweepDirection="Clockwise" />
        <ArcSegment Point="250,0"
                    RotationAngle="180"
                    Size="250,250"
                    SweepDirection="Clockwise" />
      </PathFigure>
    </PathGeometry>
  </Window.Clip>
  <Grid>
    <Ellipse Margin="1"
             Width="498"
             Height="498"
             Stroke="#8FFF"
             StrokeThickness="1.25" />
    <Ellipse Width="500"
             Height="500"
             Stroke="#C000"
             StrokeThickness="1"/>
  </Grid>
</Window>

MainWindow.xaml.cs:

public partial class MainWindow : Window {
  public MainWindow() {
    InitializeComponent();

    this.SourceInitialized += MainWindow_SourceInitialized;
    this.KeyDown += MainWindow_KeyDown;
  }

  void MainWindow_KeyDown(object sender, KeyEventArgs e) {
    if (e.Key == Key.Escape) this.Close();
  }

  void MainWindow_SourceInitialized(object sender, EventArgs e) {
    var helper = new WindowInteropHelper(this);
    var hwnd = helper.Handle;
    var src = HwndSource.FromHwnd(hwnd);

    src.CompositionTarget.BackgroundColor = Colors.Transparent;

    WindowChrome.SetWindowChrome(this, new WindowChrome {
      CaptionHeight = 500,
      CornerRadius = new CornerRadius(0),
      GlassFrameThickness = new Thickness(0),
      NonClientFrameEdges = NonClientFrameEdges.None,
      ResizeBorderThickness = new Thickness(0),
      UseAeroCaptionButtons = false
    });

    GraphicsPath path = new GraphicsPath(FillMode.Alternate);
    path.StartFigure();
    path.AddArc(new RectangleF(0, 0, 500, 500), 0, 360);
    path.CloseFigure();

    var dbb = new DwmBlurBehind(true);
    dbb.SetRegion(Graphics.FromHwnd(hwnd), new Region(path));
    DwmApi.DwmEnableBlurBehindWindow(hwnd, ref dbb);
  }
}

I think somebody else beat me to it, but here's how my solution works:

When the window's SourceInitialized event is fired, that means that we have a handle for our window. So in the handler of this function, I get the window handle. Then I make a call to a function I imported from dwmapi.dll called DwmEnableBlurBehindWindow. This basically turns transparent areas of the window into glass for a certain region. The DwmBlurBehind struct I got from pinvoke.net, and it converts a GDI+ System.Drawing.Region into an hRgn. The hRgn is passed to DwmEnableBlurBehindWindow, and it clips the transparent parts to the Region. In this case, I used a circle. Then the XAML is just the accent borders. It's worth noting that, for some reason, setting Window.Background to Transparent doesn't enable hit-testing when AllowsTransparency is true here. No idea why, but it probably has something to do with the code-behind.

rookie1024
  • 612
  • 7
  • 18
  • 1
    How to implement this in winform? – IlPADlI Dec 20 '16 at 09:33
  • @IlPADlI Well, I don't know much about the behind-the-scenes stuff for WinForms, and this solution is probably specific to WPF since it uses a special WPF helper (`WindowChrome`) to achieve it, but a possible solution might be getting the `HWND` of your window and using P/Invoke to run the DWMAPI methods. You'll probably want to ask that as a separate question, though. – rookie1024 Dec 21 '16 at 21:50
  • 1
    Thanks you for reply! I found the way to implement it. OnPaintBackground to clear black or alpha color,OnSizeChanged to create Gdi Rgn and DwmEnableBlurBehindWindow. – IlPADlI Dec 22 '16 at 03:45
  • 1
    @IlPADlI I've posted an old article on CodeProject to achieve that: https://www.codeproject.com/Articles/38780/Glass-Effect-Extender-Library-for-your-Application – Ragheed Al-Tayeb Mar 18 '23 at 19:44
0

This is simply two borderless windows attempting to emulate the DWM. This is evidenced with the inconsistencies in the theme with shadows and the "fishy" close button and the break in the gradient of the right "subwindow." A simple query of the DWM would have given the proper hints to render the windows correctly.

There is no "sorcery involved," since both of the windows are perfectly square. The main window has a shape associated with it, causing the insert button to appear while the rest of the bottom appears translucent. The same effect could be achieved using either GDI+ or WPF.

I am purposing a clarification of how the original effect was created, and not a way to duplicate it. That has been successfully demonstrated by previous answers.