Мы ответственны за рисование заголовка при использовании Windows API для рисования кнопки?

Введение

Я немного поиграл с Windows API и Parts and States и посмотрел, как элементы управления могут быть нарисованы на холсте.

С большим количеством проб и ошибок мне удалось придумать эту процедуру, которая нарисовала бы кнопку на холсте:

type
  TButtonState = (bsDefault, bsDisabled, bsHot, bsNormal, bsPressed);

procedure DrawButton(ACanvas: TCanvas; X, Y, AWidth, AHeight: Integer;
  AFont: TFont; Caption: string; ButtonState: TButtonState);
var
  Size: TSize;
  R: TRect;
  H: HTHEME;
begin
  Size.cx := AWidth;
  Size.cy := AHeight;

  R := Rect(X, Y, X + AWidth, Y + AHeight);

  if Winapi.uxTheme.UseThemes then
  begin
    H := OpenThemeData(0, 'BUTTON');
    if H <> 0 then
    try
      ACanvas.Brush.Style := bsClear;
      if AFont <> nil then
      begin
        ACanvas.Font.Assign(AFont);
      end;

      case ButtonState of
        bsDefault:
        begin
          GetThemePartSize(H, ACanvas.Handle, BP_PUSHBUTTON, PBS_DEFAULTED, nil, TS_DRAW, Size);
          DrawThemeBackground(H, ACanvas.Handle, BP_PUSHBUTTON, PBS_DEFAULTED, R, nil);
        end;

        bsDisabled:
        begin
          GetThemePartSize(H, ACanvas.Handle, BP_PUSHBUTTON, PBS_DISABLED, nil, TS_DRAW, Size);
          DrawThemeBackground(H, ACanvas.Handle, BP_PUSHBUTTON, PBS_DISABLED, R, nil);
          //ACanvas.Font.Color := $00838383; //todo get actual disabled font color
        end;

        bsHot:
        begin
          GetThemePartSize(H, ACanvas.Handle, BP_PUSHBUTTON, PBS_HOT, nil, TS_DRAW, Size);
          DrawThemeBackground(H, ACanvas.Handle, BP_PUSHBUTTON, PBS_HOT, R, nil);
        end;

        bsNormal:
        begin
          GetThemePartSize(H, ACanvas.Handle, BP_PUSHBUTTON, PBS_NORMAL, nil, TS_DRAW, Size);
          DrawThemeBackground(H, ACanvas.Handle, BP_PUSHBUTTON, PBS_NORMAL, R, nil);
        end;

        bsPressed:
        begin
          GetThemePartSize(H, ACanvas.Handle, BP_PUSHBUTTON, PBS_PRESSED, nil, TS_DRAW, Size);
          DrawThemeBackground(H, ACanvas.Handle, BP_PUSHBUTTON, PBS_PRESSED, R, nil);
        end;
      end;

      // draw the button caption
      DrawText(ACanvas.Handle, PChar(Caption), Length(Caption), R, DT_CENTER or DT_VCENTER or DT_SINGLELINE);
    finally
      CloseThemeData(H);
    end
  end
  else
  begin
    // draw button in classic theme?
  end;
end;

Если я вызываю эту процедуру следующим образом:

procedure TForm1.FormPaint(Sender: TObject);
begin
  DrawButton(Image1.Canvas, 10, 10, 75, 25, Form1.Font, 'Normal', bsNormal);
  DrawButton(Image1.Canvas, 10, 40, 75, 25, Form1.Font, 'Default', bsDefault);
  DrawButton(Image1.Canvas, 10, 70, 75, 25, Form1.Font, 'Disabled', bsDisabled);
  DrawButton(Image1.Canvas, 10, 100, 75, 25, Form1.Font, 'Hot', bsHot);
  DrawButton(Image1.Canvas, 10, 130, 75, 25, Form1.Font, 'Pressed', bsPressed);
end;

В результате я ожидал, что это будет выглядеть так, как любой элемент управления кнопкой Windows:

enter image description here

Вопрос

Пока я играл с процедурой, я скоро понял, что кнопка не имеет надписи, и я не мог видеть способ добавления надписи, кроме того, что сам рисовал поверх кнопки. Как вы можете видеть, кнопка "Отключено" по-прежнему показывает цвет шрифта по умолчанию, отключенные элементы управления обычно имеют другой цвет, чтобы показать, что элемент управления отключен. Отключенный цвет шрифта под стандартной темой Windows - $00838383 (найденный с помощью выбора цвета экрана), но жестко закодированные значения никогда не являются хорошей идеей, поскольку эти значения обычно уникальны для каждой темы.

Есть несколько частей к моему вопросу, при рисовании кнопки с использованием Winows API мы сами должны вручную нарисовать подпись? Если да, то как я могу обеспечить правильное имя шрифта, стиль и размер и т.д., Чтобы кнопка была такой же, как кнопки с широким масштабом системы?

Bonus

Когда темы не включены, как я должен нарисовать кнопку в классическом стиле Windows?

Ответ 1

Вы должны нарисовать текст самостоятельно. Вы также можете использовать тему api для рисования текста. Например:

...
 bsDisabled:
        begin
          GetThemePartSize(H, ACanvas.Handle, BP_PUSHBUTTON, PBS_DISABLED, nil, TS_DRAW, Size);
          DrawThemeBackground(H, ACanvas.Handle, BP_PUSHBUTTON, PBS_DISABLED, R, nil);
          DrawThemeText(H, ACanvas.Handle, BP_PUSHBUTTON, PBS_DISABLED, PChar(Caption),
              Length(Caption), DT_CENTER or DT_VCENTER or DT_SINGLELINE, 0, R);
          //ACanvas.Font.Color := $00838383; //todo get actual disabled font color
        end;
...

Поскольку вы вызываете api с соответствующим состоянием, включите вызов в ветки case и удалите вызов DrawText.

Вы можете использовать DrawFrameControl для классического стиля. Например:

DrawFrameControl(ACanvas.Handle, R, DFC_BUTTON, DFCS_BUTTONPUSH);