【问题标题】:How do I create a blurred-glass effect on a border-less Form?如何在无边框表单上创建模糊玻璃效果?
【发布时间】:2013-06-19 18:32:15
【问题描述】:

如何在无边框表单上绘制平滑的模糊玻璃效果?我已经尝试了Image Processing for Dummies with C and GDI+ 页面上列出的代码,但我确定这不是我应该使用的。再多的玩弄它并没有产生任何我所追求的结果。

这基本上是我想要实现的目标:

【问题讨论】:

  • 很奇怪你最初的问题是关于实体框架的,现在它是关于一个带有 +400 赏金的 gui 界面。你为什么不把它写成一个新问题呢?
  • @LarsTech,我最初确实在几天前写了一篇新文章,但是当我刷新页面时它说它不存在。所以我又试了一次,当我完成并单击“”时,它只是一直在加载,从来没有到达任何地方,所以我决定只编辑一个基本上是废话并且有一段时间没有任何活动的旧问题,然后悬赏这个新问题。
  • 无论发生什么其他事情,请不要在未来再次完全改变问题 - 它会造成一些混乱并不必要地混淆事情。
  • (仅供参考,您将其作为一个新问题提出的尝试效果很好:stackoverflow.com/questions/25720906/…
  • 你关心语言吗?我有一个 C 示例

标签: c# .net winforms drawing gdi+


【解决方案1】:

我假设您正在谈论 Windows 7 / Vista,并且您希望以相同的方式实现某些 MS 程序具有的模糊透明区域。对于一般情况,您确实需要一些我不会介绍的图像处理。

对于我上面提到的情况,您不应该自己做这件事 - 这有点重新发明轮子。基本上你可以使用窗口管理器来实现这个效果(叫做aero glass),使用本文介绍的方法:http://msdn.microsoft.com/en-us/magazine/cc163435.aspx

我目前只有一台 Windows 8 机器(默认情况下他们取消了这种模糊和透明度),所以我没有测试环境来检查这一点。我将在本周晚些时候找到一个并设置一个示例代码来执行此操作,

使用桌面窗口管理器后,如果您只想模糊顶部(如图像中的部分),请使用 DwmExtendFrameIntoClientArea 将框架(默认为 aero 模糊)扩展到您的窗口中。对于自定义区域,使用 DwmEnableBlurBehindWindow

所以,如果这确实是您正在寻找的东西(Windows 7 / vista 的解决方案,与现有 MS 程序的工作方式相同),请告诉我,我稍后会更新代码。否则,如果您正在寻找通用解决方案(不仅仅是 windows vista / 7),请告诉我省去我编写此代码的工作...


编辑:鉴于您选择手动制作效果,这里有一些基本代码可以帮助您入门

// Code was burrowed from:
//   http://stackoverflow.com/questions/19867402/how-can-i-use-enumwindows-to-find-windows-with-a-specific-caption-title
//   http://www.dotnetframework.org/default.aspx/4@0/4@0/DEVDIV_TFS/Dev10/Releases/RTMRel/ndp/fx/src/CommonUI/System/Drawing/NativeMethods@cs/1305376/NativeMethods@cs
//   http://stackoverflow.com/questions/7292757/how-to-get-screenshot-of-a-window-as-bitmap-object-in-c
//   http://stackoverflow.com/questions/798295/how-can-i-use-getnextwindow-in-c */

public static class WinAPI
{
   public delegate bool EnumWindowsProc(IntPtr hWnd, IntPtr lParam);

    [StructLayout(LayoutKind.Sequential)]
    public struct RECT
    {
        public int left;
        public int top;
        public int right;
        public int bottom;
    };

    public enum GW
    {
        GW_HWNDFIRST = 0,
        GW_HWNDLAST = 1,
        GW_HWNDNEXT = 2,
        GW_HWNDPREV = 3,
        GW_OWNER = 4,
        GW_CHILD = 5,
        GW_ENABLEDPOPUP = 6
    }

    [DllImport("User32.dll")]
    public static extern bool EnumWindows(EnumWindowsProc lpEnumFunc, IntPtr lParam);

    [DllImport("User32.dll", CharSet = CharSet.Unicode)]
    public static extern int GetWindowText(IntPtr hWnd, StringBuilder strText, int maxCount);

    [DllImport("User32.dll")]
    public static extern int GetWindowTextLength(IntPtr hWnd);

    [DllImport("User32.dll")]
    public static extern bool IsWindowVisible(IntPtr hWnd);

    [DllImport("User32.dll")]
    public static extern bool IsIconic(IntPtr hWnd);

    [DllImport("User32.dll")]
    public static extern bool GetClientRect(IntPtr hWnd, ref RECT lpRect);

    [DllImport("User32.dll")]
    public static extern bool GetWindowRect(IntPtr hWnd, ref RECT lpRect);

    [DllImport("User32.dll")]
    public static extern IntPtr GetDesktopWindow();

    [DllImport("User32.dll")]
    public static extern IntPtr GetTopWindow(IntPtr hWnd);

    public static IntPtr GetNextWindow(IntPtr hWnd, GW wCmd)
    {
        return GetWindow(hWnd, wCmd);
    }

    public static IntPtr GetWindow(IntPtr hWnd, GW wCmd)
    {
        return GetWindow(hWnd, (uint)wCmd);
    }

    [DllImport("User32.dll")]
    private static extern IntPtr GetWindow(IntPtr hWnd, uint wCmd);

    [DllImport("User32.dll")]
    public static extern bool PrintWindow(IntPtr hWnd, IntPtr hdcBlt, uint nFlags);

    [DllImport("User32.dll")]
    public static extern IntPtr GetDC(IntPtr hWnd);

    [DllImport("Gdi32.dll")]
    public static extern bool DeleteDC(IntPtr hdc);

    [DllImport("User32.dll")]
    public static extern IntPtr GetWindowDC(IntPtr hWnd);

    [DllImport("User32.dll")]
    public static extern int ReleaseDC(IntPtr hWnd, IntPtr hdc);

    [DllImport("User32.dll")]
    public static extern int GetWindowRgn(IntPtr hWnd, IntPtr hRgn);

    [DllImport("Gdi32.dll")]
    public static extern IntPtr CreateCompatibleDC(IntPtr hWnd);

    [DllImport("Gdi32.dll")]
    public static extern IntPtr CreateCompatibleBitmap(IntPtr hdc, int width, int height);

    [DllImport("Gdi32.dll")]
    public static extern IntPtr CreateBitmap(int width, int height, uint cPlanes, uint cBitsPerPel, IntPtr lpvBits);

    [DllImport("Gdi32.dll")]
    public static extern bool DeleteObject(IntPtr hGdiObj);

    [DllImport("Gdi32.dll")]
    public static extern bool SelectObject(IntPtr hdc, IntPtr hGdiObj);

    [DllImport("Gdi32.dll")]
    public static extern IntPtr CreateRectRgn(int nLeftRect, int nTopRect, int nRightRect, int nBottomRect);

    public static StringBuilder GetWindowText(IntPtr hWnd)
    {
        int length = GetWindowTextLength(hWnd);
        // Add another place to allow null terminator
        StringBuilder text = new StringBuilder(length + 1);
        GetWindowText(hWnd, text, length + 1);
        return text;    
    }

    public static bool IsWindowReallyVisible(IntPtr hWnd)
    {
        if (!IsWindowVisible(hWnd) || IsIconic(hWnd))
            return false;
        RECT area = new RECT();
        if (!GetWindowRect(hWnd, ref area))
            return true;
        if (area.left == area.right || area.bottom == area.top)
            return false;
        return true;
    }

    public enum RegionFlags
    {
        ERROR = 0,
        NULLREGION = 1,
        SIMPLEREGION = 2,
        COMPLEXREGION = 3,
    } 

}

public class ScreenShot
{
    public static List<IntPtr> GetAllWindows(Func<IntPtr, bool> filter, Func<IntPtr, bool> stop)
    {
        List<IntPtr> result = new List<IntPtr>();

        WinAPI.EnumWindows((wnd, param) =>
        {
            bool relevant = filter(wnd);
            if (relevant)
                result.Add(wnd);
            bool shouldStop = stop(wnd);
            if (shouldStop)
                Console.WriteLine("Stop");
            return !shouldStop;
        }, IntPtr.Zero);

        return result;
    }

    public static IEnumerable<IntPtr> GetWindowsByOrder(Func<IntPtr, bool> filter)
    {
        List<IntPtr> skip = new List<IntPtr>();
        List<IntPtr> result = new List<IntPtr>();
        IntPtr desktop = WinAPI.GetDesktopWindow();

        for (IntPtr wnd = WinAPI.GetTopWindow(IntPtr.Zero); wnd != IntPtr.Zero; wnd = WinAPI.GetNextWindow(wnd, WinAPI.GW.GW_HWNDNEXT))
        {
            if (result.Contains(wnd) || skip.Contains(wnd))
                break;
            else if (filter(wnd))
                result.Add(wnd);
            else
                skip.Add(wnd);
        }

        result.Add(desktop);
        return result;
    }

    public static Image GetScreenshot(IntPtr hWnd)
    {
        IntPtr hdcScreen = WinAPI.GetDC(hWnd);
        IntPtr hdc = WinAPI.CreateCompatibleDC(hdcScreen);
        WinAPI.RECT area = new WinAPI.RECT();

        WinAPI.GetWindowRect(hWnd, ref area);

        IntPtr hBitmap = WinAPI.CreateCompatibleBitmap(hdcScreen, area.right - area.left, area.bottom - area.top);
        WinAPI.SelectObject(hdc, hBitmap);

        WinAPI.PrintWindow(hWnd, hdc, 0);

        Image resultOpaque = Image.FromHbitmap(hBitmap);

        WinAPI.DeleteObject(hBitmap);
        WinAPI.DeleteDC(hdc);

        IntPtr hRegion = WinAPI.CreateRectRgn(0, 0, 0, 0);
        WinAPI.RegionFlags f = (WinAPI.RegionFlags) WinAPI.GetWindowRgn(hWnd, hRegion);
        Region region = Region.FromHrgn(hRegion);
        WinAPI.DeleteObject(hRegion);

        Bitmap result = new Bitmap(resultOpaque.Width, resultOpaque.Height, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
        Graphics g = Graphics.FromImage(result);
        g.Clear(Color.Transparent);
        if (f != WinAPI.RegionFlags.ERROR)
            g.SetClip(region, System.Drawing.Drawing2D.CombineMode.Replace);
        if (f != WinAPI.RegionFlags.NULLREGION)
            g.DrawImageUnscaled(resultOpaque, 0, 0);

        g.Dispose();
        region.Dispose();
        resultOpaque.Dispose();

        return result;
    }

/* And now for the actual code of getting screenshots of windows */
var windows = ScreenShot.GetWindowsByOrder(this.WindowFilter).Intersect(ScreenShot.GetAllWindows(this.WindowFilter, (wnd) => false)).ToList();

int index = windows.IndexOf((IntPtr)this.Handle); /* Remove all the windows behind your windows */
if (index != -1)
    windows.RemoveRange(index, windows.Count - index + 1);

windows.Reverse();

/* Get the images of all the windows */
for (int i = 0; i < windows.Count; ++i )
{
    var window = windows[i];
    using (var img = ScreenShot.GetScreenshot(window))
    {
        // Get the actual position of the window
        // Draw it's image overlayed on the other windows 9have an accumulating image)
    }
}

这在哪里仍然有错误(在我的 Windows 8.1 测试套件上)?

  • 我仍然在透明度方面遇到问题 - 在其剪切区域内为半透明的窗口仍会被漆成黑色
  • 我有一些未知的黑色窗口(可能是透明的)仍然出现
  • 任务栏无法正确绘制

【讨论】:

  • 谢谢@Lightning,感谢您的努力。我讨厌迂腐,但这真的不是我想要的。我不想要“Aero”……我想要自己的杯子。在 Windows 8 和 8.1 中,您无法获得 Aero 玻璃效果,它只是保持不透明,这就是我问这个问题的原因。我想要一个自定义绘制的玻璃效果。
  • @baeltazor:好的,我怀疑 400+ 的赏金并不容易。在这种情况下,您基本上需要收集您自己窗口下所有窗口的屏幕截图并将它们组合为位图。完成后,模糊位图并将其作为您自己窗口的背景。这确实更难 - 我会试一试,但我怀疑我知道的足够多
  • 非常感谢@Lightning,我正在考虑对窗口正下方的桌面区域进行“截图”,但我不确定它是否只抓取壁纸或者它是否也胸罩 a 那里的所有其他东西(就像你说的,其他窗户等)
  • @baeltazor:我添加了所有代码供您试验。我已经实现了 70% 的屏幕截图概念。希望这会有所帮助;)
【解决方案2】:

Winform

使用像 DwmEnableBlurBehindWindow 这样的 Win API

    [DllImport("gdi32")]
    private static extern IntPtr CreateEllipticRgn(int nLeftRect, int nTopRect, int nRightRect, int nBottomRect);
    [DllImport("dwmapi")]
    private static extern int DwmEnableBlurBehindWindow(IntPtr hWnd, ref DwmBlurbehind pBlurBehind);
    public struct DwmBlurbehind
    {
        public int DwFlags;
        public bool FEnable;
        public IntPtr HRgnBlur;
        public bool FTransitionOnMaximized;
    }
    public Form1()
    {
        InitializeComponent();
    }
    private void Form1_Load(object sender, EventArgs e)
    {
        var hr = CreateEllipticRgn(0, 0, Width, Height);
        var dbb = new DwmBlurbehind {FEnable = true, DwFlags = 1, HRgnBlur = hr, FTransitionOnMaximized = false};
        DwmEnableBlurBehindWindow(Handle, ref dbb);
    }
    protected override void OnPaint(PaintEventArgs e)
    {
        e.Graphics.FillRectangle(new SolidBrush(Color.Black), new Rectangle(0, 0, Width, Height));
    }

示例 1:

示例 2:您可以更改画笔颜色(例如:DarkRed)以获得漂亮的效果

这里要注意的是,您可以选择一个适用的区域,并且您可以拥有超过 1 个。

Wpf

您可以使用same approach

Wpf 对着色器有更好的支持,您可以添加模糊等效果,但性能更重。此外,您的图像很可能来自 WPF 和 Blend 创建的程序。

如果我没记错的话,你不能在纯色画笔上使用像模糊这样的着色器。因此,以下不会产生相同的效果。将背景设置为 50% 透明(Window 属性 AllowsTransparency="True"WindowStyle="None"

<Window.Background>
    <SolidColorBrush Opacity="0.5" Color="White"/>
</Window.Background>

原生玻璃窗

此处描述了 Wpf 支持:http://www.paulrohde.com/native-glass-with-wpf/

我用稍微不同的方法得到了以下结果:

【讨论】:

猜你喜欢
  • 1970-01-01
  • 2012-07-21
  • 1970-01-01
  • 2019-04-16
  • 2021-11-12
  • 2011-02-26
  • 1970-01-01
  • 2015-02-19
  • 2013-06-10
相关资源
最近更新 更多