WINAPI / DWMAPI Blurred window with irregular shape
NB: THIS IS NOT A QUESTION ABOUT A SAFE WINDOW.
So, I stumbled upon this program when I was looking into my Start Menu the other day in Windows 7:
It is a native Windows program called "Math Input Panel". Now I am curious about the shape of the window. I know it is not fully drawn by DWM because the borders and the Close button look suspicious and there is no shadow in the window (I have shadows to cast). My first guess on how this was done would be using DwmEnableBlurBehindWindow
, but I can't imagine what works on irregular window shapes, can I? (Or is there another way to do this, or is it just Microsoft magic?)
source to share
So, unknowingly, it hRgn
may have the wrong shape (and DwmEnableBlurBehindWindow
does hRgn
, but I knew it). So here's my solution, which is (more or less) WPF compatible:
... and the 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 someone beat me up, but this is how my solution works:
When the window event is SourceInitialized
fired, it means we have a handle for our window. Therefore, in the handler of this function, I get the window handle. Then I call a function that I imported from dwmapi.dll called DwmEnableBlurBehindWindow
. This basically turns the transparent areas of the window into glass for a specific region. The structure DwmBlurBehind
I got from pinvoke.net and converts GDI + System.Drawing.Region
to hRgn
. hRgn
is transmitted DwmEnableBlurBehindWindow
and it clamps the transparent parts to Region
. In this case, I used a circle. Then the XAML is just emphasis boundaries. It's worth noting that for some reason setting Window.Background
to Transparent
does not enable hit testing if AllowsTransparency
true here. Not sure why, but it probably has something to do with the code.
source to share
Here's a quick hacked WPF solution. It uses hRgnBlur
structures DWM_BLURBEHIND
and some interaction.
This example will blur the background in an ellipse shape.
You can easily convert this to an attached property or behavior for MVVM convenience. It's also a good idea to listen to the message WM_DWMCOMPOSITIONCHANGED
and reapply the blur as 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>
Result:
source to share