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 转换为 hRgnhRgn 传递给 DwmEnableBlurBehindWindow,并将透明部分剪切到 Region。在这种情况下,我使用了一个圆圈。然后 XAML 只是重音边框。值得注意的是,出于某种原因,将 Window.Background 设置为 Transparent 不会启用 hit-testing,而此处 AllowsTransparency 为真。不知道为什么,但它可能与 code-behind.

有关

这只是两个试图模拟 DWM 的无边界 windows。阴影和 "fishy" 关闭按钮的主题不一致以及右侧渐变的中断证明了这一点 "subwindow." DWM 的简单查询会给出正确的提示来呈现 windows正确。

没有 "sorcery involved," 因为两个 windows 都是正方形。 主 window 有一个与之关联的形状,导致插入按钮出现,而底部的其余部分则显示为半透明。 使用 GDI+ 或 WPF 都可以达到同样的效果。

我的目的是澄清原始效果是如何创建的,而不是复制它的方法。之前的答案已经成功证明了这一点。