WINAPI / DWMAPI不规则形状的模糊窗口

时间:2015-01-05 21:11:15

标签: c# winapi dwm

注意:这不是关于无边窗的问题。

所以,当我在Windows 7上探索我的“开始”菜单时,我偶然发现了这个程序:

Math input panel

它是一个原生Windows程序,名为"数学输入面板。"现在,我对窗户的形状感到好奇。我知道它并没有被DWM完全绘制,因为边框和关闭按钮看起来很腥,窗口没有阴影(我启用了阴影)。我对这是如何制作的第一个猜测是使用DwmEnableBlurBehindWindow,但是我无法想象它是否适用于不规则的窗口形状,是吗? (或者还有另一种方法可以做到这一点,还是完全是微软的巫术?)

3 个答案:

答案 0 :(得分:4)

这是一个快速入侵的WPF解决方案。它使用DWM_BLURBEHIND结构的hRgnBlur和一些互操作。

此示例将在窗口上应用椭圆形背景模糊。

您可以轻松地将其转换为MVVM友好性的附加属性或行为。听取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>

结果是:

blur example

答案 1 :(得分:2)

所以,我不知道,hRgn可以采取不规则的形状(而DwmEnableBlurBehindWindow需要hRgn,但我知道这一点。所以,这是我的解决方案(或多或少)与WPF兼容:

My own custom-shaped glass window

...和源代码:

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);
  }
}

我认为有人打败了我,但这就是我的解决方案的工作原理:

当窗口的SourceInitialized事件被触发时,这意味着我们有一个窗口的句柄。所以在这个函数的处理程序中,我得到了窗口句柄。然后我调用从dwmapi.dll导入的函数DwmEnableBlurBehindWindow。这基本上将窗户的透明区域变成某个区域的玻璃。我从pinvoke.net获得的DwmBlurBehind结构,它将GDI + System.Drawing.Region转换为hRgnhRgn传递给DwmEnableBlurBehindWindow,并将透明部分剪切为Region。在这种情况下,我用了一个圆圈。然后XAML只是重音边框。值得注意的是,出于某种原因,将Window.Background设置为Transparent时,如果AllowsTransparency为真,则无法启用命中测试。不知道为什么,但它可能与代码隐藏有关。

答案 2 :(得分:0)

这只是两个试图模拟DWM的无边界窗口。这可以通过带有阴影和“腥”关闭按钮的主题不一致以及右“子窗口”的渐变中断来证明。一个简单的DWM查询将给出正确的提示,以正确显示窗口。

因为两个窗口都完全是正方形,所以没有“涉及到的魔法”。 主窗口具有与之关联的形状,导致插入按钮出现,而底部的其余部分显示为半透明。 使用GDI +或WPF可以达到相同的效果。

我要澄清原始效果是如何产生的,而不是复制它的方法。先前的答案已成功证明了这一点。