Как скопировать или воспроизвести градиент фона меню на растровое изображение с помощью Windows API?

#windows #delphi #winapi #menubar

#Windows #delphi #winapi #панель меню

Вопрос:

Я пытаюсь (безуспешно) скопировать / воспроизвести градиент фона строки меню Windows на растровое изображение.

В приведенной ниже функции IconToBitmap fillRect (желаемое за действительное) использует GetSysColorBrush(COLOR_MENU) в попытке нарисовать фон меню таким, какой он есть в Windows (неудивительно, что кисть не является градиентом, но попробовать стоило.)

Приведенный ниже фрагмент текста является попыткой «обмануть». Возьмите часть уже нарисованной строки меню и используйте ее. Это тоже не сработало, и я подозреваю, что причина может быть в том, что функция IconToBitmap вызывается во время WM_CREATE главного окна (я не уверен, что строка меню существует так рано при создании окна.) Мне действительно нужен фон перед тем, как окно впервые станет видимым, вот почему функция вызывается при обработке 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;
  

Комментарии:

1. О каком именно градиенте вы говорите? Старый эффект Aero glass? или эффект, используемый на панелях инструментов?

2. Градиент, который является фоном панели меню. Конечно, это меняется в зависимости от темы и, вообще говоря, предпочтений пользователя, но, что бы это ни было, я хочу либо воспроизвести этот фон, либо сделать его копию в растровом изображении. Чтобы понять, о чем я говорю, запустите notepad, какой бы фон панели меню ни появился в Notepad, это фон, который я хочу скопировать или воспроизвести.

3. @AlexK.: OP, вероятно, использует Windows 7 (или более раннюю версию), где строка меню действительно имеет градиентный фон. Смотрите, например, english.rejbrand.se/rejbrand/pix/screenshots/rte31 /… . Конечно, в Windows 10 фон строки меню является сплошным цветом.

4. Да, в настоящее время я использую Windows 7, но я хочу захватить / скопировать любой фон, который используется в данный момент. Это очень важно. Вот почему я попытался «схитрить» и просто наложил часть фона панели меню на растровое изображение. Это кажется самым простым способом получить «полосу» фона. Я знаю, что могу получить это, если выполню bitblt во время обработки WM_PAINT, но мне это нужно раньше. В идеале во время WM_CREATE, но в любое время до того, как окно станет видимым, это сработало бы.

5. Возможно, стоит обратить внимание на DrawThemeBackground() и friends.

Ответ №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;
  

Комментарии:

1. @ Sertac, я пытаюсь преобразовать опубликованный вами код в обычный Windows API. В сообщении WM_PAINT первое утверждение «Theme := OpenThemeData(Wnd, VSCLASS_MENU);» выдает нарушение доступа. Я не знаком с theme API, что-то не так в моем «переводе» этого утверждения?, спасибо.

2. @Sci — Не могу сказать, не зная, что вы делаете. Я опубликовал пример.

3. @ Sertac, то, что я делаю, это программирую непосредственно в Windows API, как это делают программисты C (не C ). Итак, никаких объектов нигде, только вызовы API и обработка оконных сообщений. Я получил ваше утверждение «Theme := OpenThemeData(Handle, VSCLASS_MENU)»; Я предположил , что параметр «Handle» является дескриптором окна (из документации MSDN), следовательно, утверждение стало «Theme:= OpenThemeData(Wnd, VS_CLASS)»; переменная «Wnd», которую я передаю, является дескриптором окна, которое получило сообщение. Следовательно, это значение допустимо. Все остальное то же самое, но я получаю сообщение о нарушении доступа. Есть идеи, почему?

4. Дескриптор окна в порядке. Функция обычно возвращает 0 при сбое. VSCLASS_MENU — это строка ‘MENU’, MENU_BARBACKGROUND равна 7, MB_ACTIVE равна 1. OpenThemeData экспортируется из uxtheme. dll с соглашением stdcall принимает дескриптор и параметр PWideChar и возвращает дескриптор. Я предлагаю вам задать вопрос, предоставляя пример воспроизведения. Не редактируйте это, потому что это совершенно другой вопрос.

5. Сертак, спасибо за терпение и помощь. Я понял, почему я получал исключение. В Free Pascal есть переменная-указатель на OpenThemeData, которая инициализируется только при вызове InitThemeLibrary (небольшая деталь, которой не будет в документации MSDN). Теперь, когда я знаю, почему такой простой вызов функции не работал, я могу сосредоточиться на остальной части предоставленного вами кода. К сожалению, я не смогу посвятить больше времени этой проблеме до завтра, но я предоставлю обратную связь. Это меньшее, что я могу сделать. Еще раз спасибо.