delphi - 使用Windows API绘制Button时,我们需要负责绘制标题吗?

标签 delphi winapi delphi-xe7

简介

我一直在使用 Windows API 和 Parts and States并了解如何将控件绘制到 Canvas 上。

经过大量的试验和错误,我设法想出了这个程序,它将在 Canvas 上绘制一个按钮:

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 绘制按钮时,我们是否必须自己手动绘制标题?如果是这样,我如何确保绘制正确的字体名称、样式和大小等,以确保按钮与系统范围绘制的按钮相同?

奖金

未启用主题时,如何以经典 Windows 样式绘制按钮?

最佳答案

您应该自己绘制文本。您也可以使用主题 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);

关于delphi - 使用Windows API绘制Button时,我们需要负责绘制标题吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/31270821/

相关文章:

delphi - 额外的 HTTP header 会影响 MWS Feed API 提交吗?

c++ - 操作 Windows 注册表

c++ - FindFirstFile/FindNextFile API 对缓存结果是否返回?

delphi - 如何在使用 ShellExecute 创建的电子邮件中包含换行符?

android - 我可以在哪些用途中找到 SharedActivityContext?

windows - 如何在 Delphi 中枚举另一个进程的窗口?

SQL Server 2008 中 Delphi TClientDataSet "Trying to modify read-only field"错误,2000 中正常

c# - 当注册名称不唯一时,有什么方法可以从 ROT 获取对象?

delphi - 阻止 Delphi 自动错误地添加单位

delphi - 如何在ShowMessage中显示表格?