WINAPI/DWMAPI Blur-behind window 形状不规则
WINAPI/DWMAPI Blur-behind window with irregular shape
注意:这不是关于无边框的问题 WINDOW。
所以,前几天 Windows 7:
我在浏览“开始”菜单时偶然发现了这个程序
这是一个原生的 Windows 程序,叫做 "Math Input Panel." 现在,我对 window 的形状很好奇。我知道它不是完全由 DWM 绘制的,因为边框和关闭按钮看起来很可疑,而且 window 没有阴影(我启用了阴影)。关于这是如何制作的,我的第一个猜测是使用 DwmEnableBlurBehindWindow
,但我无法想象它适用于不规则的 window 形状,是吗? (或者有其他方法可以做到这一点,还是完全是微软的魔法?)
这是一个快速整合的 WPF 解决方案。它使用 DWM_BLURBEHIND
结构的 hRgnBlur
和一些互操作。
此示例将在 window 上应用 ellipse-shaped 背景模糊。
您可以轻松地将其转换为附加的 属性 或 MVVM-friendliness 的行为。听取 WM_DWMCOMPOSITIONCHANGED
消息并在需要时重新应用模糊也是一个好主意。
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);
}
}
与以下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>
结果是:
所以,在我不知道的情况下,hRgn
可以采用不规则形状(DwmEnableBlurBehindWindow
采用 hRgn
,但我知道这一点)。所以,这是我的(或多或少)与 WPF 兼容的解决方案:
...和源代码:
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);
}
}
我认为有人先于我,但这是我的解决方案的工作原理:
当 window 的 SourceInitialized
事件被触发时,这意味着我们有了 window 的句柄。所以在这个函数的处理程序中,我得到了 window 句柄。然后我调用了一个从 dwmapi.dll 导入的函数 DwmEnableBlurBehindWindow
。这基本上将 window 的透明区域变成特定区域的玻璃。我从 pinvoke.net 获得的 DwmBlurBehind
结构,它将 GDI+ System.Drawing.Region
转换为 hRgn
。 hRgn
传递给 DwmEnableBlurBehindWindow
,并将透明部分剪切到 Region
。在这种情况下,我使用了一个圆圈。然后 XAML 只是重音边框。值得注意的是,出于某种原因,将 Window.Background
设置为 Transparent
不会启用 hit-testing,而此处 AllowsTransparency
为真。不知道为什么,但它可能与 code-behind.
有关
这只是两个试图模拟 DWM 的无边界 windows。阴影和 "fishy" 关闭按钮的主题不一致以及右侧渐变的中断证明了这一点 "subwindow." DWM 的简单查询会给出正确的提示来呈现 windows正确。
没有 "sorcery involved," 因为两个 windows 都是正方形。
主 window 有一个与之关联的形状,导致插入按钮出现,而底部的其余部分则显示为半透明。
使用 GDI+ 或 WPF 都可以达到同样的效果。
我的目的是澄清原始效果是如何创建的,而不是复制它的方法。之前的答案已经成功证明了这一点。
注意:这不是关于无边框的问题 WINDOW。
所以,前几天 Windows 7:
我在浏览“开始”菜单时偶然发现了这个程序这是一个原生的 Windows 程序,叫做 "Math Input Panel." 现在,我对 window 的形状很好奇。我知道它不是完全由 DWM 绘制的,因为边框和关闭按钮看起来很可疑,而且 window 没有阴影(我启用了阴影)。关于这是如何制作的,我的第一个猜测是使用 DwmEnableBlurBehindWindow
,但我无法想象它适用于不规则的 window 形状,是吗? (或者有其他方法可以做到这一点,还是完全是微软的魔法?)
这是一个快速整合的 WPF 解决方案。它使用 DWM_BLURBEHIND
结构的 hRgnBlur
和一些互操作。
此示例将在 window 上应用 ellipse-shaped 背景模糊。
您可以轻松地将其转换为附加的 属性 或 MVVM-friendliness 的行为。听取 WM_DWMCOMPOSITIONCHANGED
消息并在需要时重新应用模糊也是一个好主意。
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);
}
}
与以下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>
结果是:
所以,在我不知道的情况下,hRgn
可以采用不规则形状(DwmEnableBlurBehindWindow
采用 hRgn
,但我知道这一点)。所以,这是我的(或多或少)与 WPF 兼容的解决方案:
...和源代码:
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);
}
}
我认为有人先于我,但这是我的解决方案的工作原理:
当 window 的 SourceInitialized
事件被触发时,这意味着我们有了 window 的句柄。所以在这个函数的处理程序中,我得到了 window 句柄。然后我调用了一个从 dwmapi.dll 导入的函数 DwmEnableBlurBehindWindow
。这基本上将 window 的透明区域变成特定区域的玻璃。我从 pinvoke.net 获得的 DwmBlurBehind
结构,它将 GDI+ System.Drawing.Region
转换为 hRgn
。 hRgn
传递给 DwmEnableBlurBehindWindow
,并将透明部分剪切到 Region
。在这种情况下,我使用了一个圆圈。然后 XAML 只是重音边框。值得注意的是,出于某种原因,将 Window.Background
设置为 Transparent
不会启用 hit-testing,而此处 AllowsTransparency
为真。不知道为什么,但它可能与 code-behind.
这只是两个试图模拟 DWM 的无边界 windows。阴影和 "fishy" 关闭按钮的主题不一致以及右侧渐变的中断证明了这一点 "subwindow." DWM 的简单查询会给出正确的提示来呈现 windows正确。
没有 "sorcery involved," 因为两个 windows 都是正方形。 主 window 有一个与之关联的形状,导致插入按钮出现,而底部的其余部分则显示为半透明。 使用 GDI+ 或 WPF 都可以达到同样的效果。
我的目的是澄清原始效果是如何创建的,而不是复制它的方法。之前的答案已经成功证明了这一点。