如何使用 Windows API 将菜单背景渐变复制或再现到位图上?

Posted

技术标签:

【中文标题】如何使用 Windows API 将菜单背景渐变复制或再现到位图上?【英文标题】:How to copy or reproduce the menu background gradient onto a bitmap using the Windows API? 【发布时间】:2019-03-25 12:18:53 【问题描述】:

我正在尝试(未成功)将 Windows 菜单栏的背景渐变复制/再现到位图上。

在下面的 IconToBitmap 函数中,FillRect (希望)使用 GetSysColorBrush(COLOR_MENU) 尝试让它在 Windows 中绘制菜单背景(不出所料,画笔不是渐变,但值得试一试。)

它下面的BitBlt是企图“作弊”。抓住已经绘制的菜单栏的一部分并使用它。那也不起作用,我怀疑原因可能是因为在主窗口的 WM_CREATE 期间调用了函数 IconToBitmap (我不确定菜单栏在窗口创建的早期是否存在。)我确实需要背景窗口首先可见,这就是在处理 WM_CREATE 时调用该函数的原因(但在窗口可见之前工作的任何其他方式都是完美的。)

在这一点上,我没有想法。

如果有人知道如何抓取该菜单背景或在位图上重现它,那就太好了。

谢谢。

PS:函数中的硬编码值将在最终工作版本中删除(希望会有一个。)此外,对于 Delphi,数据类型 ptrint 必须更改为 NativeInt。

function IconToBitmap(Wnd : HWND; Icon : HICON) : HBITMAP;
var
  Bitmap      : HBITMAP;
  BitmapDc    : HDC;
  BitmapRect  : TRECT;

  OldBitmap   : HBITMAP;

  dc          : HDC;

  MenuHeight  : ptrint;
  MenuY       : ptrint;

  WindowDc    : HDC;

begin
  Bitmap      := 0;
  BitmapDc    := 0;
  OldBitmap   := 0;
  dc          := 0;

  MenuY       := 0;
  MenuHeight  := 0;

  WindowDc    := 0;


  MenuY       := GetSystemMetrics(SM_CYSIZEFRAME) +
                 GetSystemMetrics(SM_CYCAPTION);
  MenuHeight  := GetSystemMetrics(SM_CYMENUSIZE);

  WindowDc    := GetWindowDC(Wnd);


  dc          := GetDC(0);
  BitmapDc    := CreateCompatibleDC(dc);

  Bitmap      := CreateCompatibleBitmap(dc, 16, 16);
  OldBitmap   := SelectObject(BitmapDc, Bitmap);

  with BitmapRect do
  begin
    Left      := 0;
    Top       := 0;
    Right     := 16;
    Bottom    := 16;
  end;

  FillRect(BitmapDc, BitmapRect, GetSysColorBrush(COLOR_MENU));
  BitBlt(BitmapDc, 0, 0, 16, 16, WindowDc, 20, MenuY, SRCCOPY);

  DrawIconEx(BitmapDc,
             0,
             0,
             Icon,
             16,
             16,
             0,
             0,
             DI_NORMAL);

  SelectObject(BitmapDc, OldBitmap);
  DeleteDC(BitmapDc);
  ReleaseDC(0, dc);

  IconToBitmap := Bitmap;
end;

【问题讨论】:

你到底在说什么渐变?旧的航空玻璃效应?还是工具栏上使用的效果? 菜单栏背景的渐变。当然,它会根据主题和用户偏好而变化,但无论它是什么,我都想重现该背景或在位图中复制它。要查看我在说什么,请启动记事本,记事本出现的任何菜单栏背景都是我想要复制或重现的背景。 @AlexK.: OP 可能使用的是 Windows 7(或更早版本),其中菜单栏确实具有渐变背景。例如,参见english.rejbrand.se/rejbrand/pix/screenshots/rte31/…。当然,在 Windows 10 中,菜单栏背景是纯色的。 是的,我目前使用的是 Windows 7,但是我想抓取/复制当前正在使用的任何背景。这是非常重要的。这就是为什么我试图“作弊”并将菜单栏背景的一部分简单地bitblt到位图上。这似乎是获得背景“条带”的最简单方法。我知道如果我在处理 WM_PAINT 时执行 bitblt 可以得到它,但我需要它更早。理想情况下,在 WM_CREATE 期间,但在窗口可见之前的任何时间都可以工作。 DrawThemeBackground() 和朋友可能值得一看。 【参考方案1】:

使用visual styles API 绘制主题部分。下面的示例在窗体的客户区顶部绘制菜单栏背景。您可以调整它以在位图画布上绘制。

uses
  uxtheme, types;

procedure TForm1.FormPaint(Sender: TObject);
var
  Theme: HTHEME;
  Size: TSize;
  Rect: TRect;
begin
  Theme := OpenThemeData(Handle, VSCLASS_MENU);
  GetThemePartSize(Theme, Canvas.Handle, MENU_BARBACKGROUND, MB_ACTIVE, nil,
      TS_TRUE, Size);
  Rect.Create(0, 0, ClientWidth, Size.cy);
  DrawThemeBackground(Theme, Canvas.Handle, MENU_BARBACKGROUND, MB_ACTIVE,
      Rect, nil);
  CloseThemeData(Theme);
end;

WM_PAINT 处理程序中,这可能如下所示。

procedure TForm1.WMPaint(var Message: TWMPaint);
var
  DC: HDC;
  PS: TPaintStruct;

  Theme: HTHEME;
  Size: TSize;
  Rect: TRect;
begin
  if Message.DC = 0 then
    DC := BeginPaint(Handle, PS)
  else
    DC := Message.DC;

  Theme := OpenThemeData(Handle, VSCLASS_MENU);
  GetThemePartSize(Theme, DC, MENU_BARBACKGROUND, MB_ACTIVE, nil,
      TS_TRUE, Size);
  Rect.Create(0, 0, ClientWidth, Size.cy);
  DrawThemeBackground(Theme, DC, MENU_BARBACKGROUND, MB_ACTIVE,
      Rect, nil);
  CloseThemeData(Theme);

  if Message.DC = 0 then begin
    Message.DC := DC;
    inherited;
    EndPaint(Handle, PS);
  end else
    inherited;
end;

【讨论】:

@Sertac,我正在尝试将您发布的代码转换为直接的 Windows API。在 WM_PAINT 消息中,第一条语句“Theme := OpenThemeData(Wnd, VSCLASS_MENU);”给我一个访问冲突。我对主题 API 不熟悉,我对该语句的“翻译”有什么问题吗?谢谢。 @Sci - 不知道你在做什么就无法判断。我发布了一个例子。 @Sertac,我正在做的是像 C 程序员(不是 C++)那样直接对 Windows API 进行编程。 IOW,任何地方都没有对象,只有 API 调用和 Window 消息处理。我的声明,“主题:= OpenThemeData(Handle,VSCLASS_MENU);”我猜测“Handle”参数是一个窗口句柄(来自 MSDN 文档),因此语句变成了“Theme := OpenThemeData(Wnd, VS_CLASS);”我传递的“Wnd”变量是接收消息的窗口句柄。因此该值是有效的。其他一切都是一样的,但我遇到了访问冲突。任何想法为什么? Handle 是窗口句柄好吗。该函数通常在失败时返回 0。 VSCLASS_MENU 是字符串 'MENU',MENU_BARBACKGROUND 是 7,MB_ACTIVE 是 1。OpenThemeData 从 uxtheme.dll 中导出,带有 stdcall 约定,接受句柄和 PWideChar 参数并返回句柄。我建议你问一个提供复制案例的问题。不要编辑这个,因为这是完全不同的事情。 Sertac,感谢您的耐心和帮助。我弄清楚了为什么我得到了一个例外。 Free Pascal 有一个指向 OpenThemeData 的指针变量,该变量仅在调用 InitThemeLibrary 时才被初始化(MSDN 文档中没有的一些细节)。现在我知道为什么这样一个简单的函数调用不起作用,我可以专注于您提供的其余代码。不幸的是,直到明天我才能再花时间解决这个问题,但我会提供反馈。这是我能做的最少的事情。再次感谢您。

以上是关于如何使用 Windows API 将菜单背景渐变复制或再现到位图上?的主要内容,如果未能解决你的问题,请参考以下文章

带有背景渐变的CSS子菜单覆盖内容

如何在我的 Flutter 应用中添加渐变背景?

如何将一块div为黑色背景 渐变成上黑下部透明的?

IText - 如何将渐变设置为PDF文档的背景?

如何将多个线性渐变添加到 CSS 背景?如果

如何将渐变设置为 QML TreeView 背景?