9
votes

Yes, this is again this question:

How can I change the font color of a TCheckBox (or any handled control) with Delphi7->Delphi2007 on a themes enabled application?

After reading a lot on the internet and on this site, I found 4 kinds of answer:

  1. and Most populare (even from QC): You can't, it's designed like that by Microsoft.
  2. Create a component that let you draw it like you want.
  3. Buy expensive component set that draws like you want.
  4. Do not use themes.

OK, but I am still unhappy with that.

Giving a user colored feedback for the status of a property/data he has on a form, seems legitimate to me.

Then I just installed the MSVC# 2008 Express edition, and what a surprise, they can change the color of the font (property ForeColor of the check box) Then what?

It does not seem to be a "it's designed like that, by Microsoft." then now the question again:

How can I change the font color of a TCheckBox (or any handled control) with Delphi 7 through Delphi 2007 on a theme-enabled application?

5
What makes you think the developers of Visual Studio didn't simply use option 2?Rob Kennedy
Well, I did not have to, while trying to reproduce that "it's designed like that, by Microsoft." on C#. I have set the ForeColor to Red, and get a red caption for my check box.Edouard Westphal

5 Answers

4
votes

This needs some tweak to be perfect solution but worked for me:

Add 2 method to your checkbox component

    FOriginalCaption: string;
    _MySetCap: Boolean;
    procedure WMPaint(var msg: TWMPaint); message WM_PAINT;
    procedure CMTextChanged(var Message: TMessage); message CM_TEXTCHANGED;

and implement this way:

procedure TMyCheckbox.CMTextChanged(var Message: TMessage);
begin
  inherited;
  if _MySetCap then Exit;
  FOriginalCaption := Caption;
end;

procedure TMyCheckbox.WMPaint(var msg: TWMPaint);
var
  BtnWidth: Integer;
  canv: TControlCanvas;
begin
  BtnWidth := GetSystemMetrics(SM_CXMENUCHECK);

  _MySetCap := True;
  if not (csDesigning in ComponentState) then
    Caption := '';
  _MySetCap := False;
  inherited;
  canv := TControlCanvas.Create;
  try
    canv.Control := Self;
    canv.Font := Font;
    SetBkMode(canv.Handle, Ord(TRANSPARENT));
    canv.TextOut(BtnWidth + 1, 2, FOriginalCaption);
  finally
    canv.Free;
  end;
end;
2
votes

Oh, but you can!

Just place this before the declaration of your form :

TCheckBox = class(StdCtrls.TCheckBox)
public
  procedure CNCtlColorStatic(var Message: TWMCtlColorStatic); message CN_CTLCOLORSTATIC;
end;

This re-declaration of TCheckBox is now used at runtime as the type streamed from your form's DFM. Now implement the message like this :

procedure TCheckBox.CNCtlColorStatic(var Message: TWMCtlColorStatic);
begin
  SetTextColor(Message.ChildDC, ColorToRGB(clRed)); // or RGB(255,0,0));
  SetBkMode(Message.ChildDC, TRANSPARENT);
  Message.Result := GetStockObject(NULL_BRUSH);
end;

This traps the WM_CTLCOLORSTATIC message and changes the text color to red. This works in non-themed mode for me (using WinXP classic) - but not in themed mode.

You should know that in order to let themed controls send you this message, the control should supply the DTPB_USECTLCOLORSTATIC flag to the Theme-drawing API's. Sadly, that's not default behaviour, and I don't know how to do it either. Look at this question too.

2
votes

Here's how I solved this in my app:

  1. Remove the caption from the checkbox and make it smaller--just big enough to show the actual checkbox without the caption.
  2. Put a TLabel next to the checkbox to act as the caption.
  3. In the Label's OnClick event, toggle the state of the checkbox.
  4. If the label has an Alt+letter keyboard accelerator, make the form handle the CM_DIALOGCHAR message. Copy the message handler from the TLabel source code and add a line to toggle the state of the checkbox.

It's not a real checkbox, and it's a bit more work than I'd like, but it works well enough in my app (which has only one checkbox that needs this treatment).

0
votes

Option 5. Use the control tyou like as a base option and override all the painting messages in the control (yes you can call it a component but control is the name for visible components so you should rather use that). Simply catch the WM_PAINT, possibly the WM_NCPAINT you can draw the control in your own style. At least you can reuse the whole functionality from the control. As long as you do not change the lay-out, only the colors you won't need to change the hittests mousedowns. up moves etc etc.

Note: I have the experience with overriding TCustomEdit to allow for all kind of colors, background text, extra buttons etc. It took quite some time to get it right and read all the documents from MSDn and KB to make sure the control did what I wanted it to do.

0
votes

this code is improved code from @FLICKER than support BidiMode and the correct position of the text:

interface
  TMSCheckBox = class(TCheckBox)
  private
    FOriginalCaption: string;
    _MySetCap: Boolean;
    procedure WMPaint (var Message: TWMPaint); message WM_PAINT;
    procedure CMTextChanged(var Message: TMessage); message CM_TEXTCHANGED;
  end;

implementation

procedure TMSCheckBox.CMTextChanged(var Message: TMessage);
begin
  inherited;
  if _MySetCap then Exit;
  FOriginalCaption := Caption;
end;

procedure TMSCheckBox.WMPaint(var Message: TWMPaint);
const
  SPACE   :Integer = 2;
var
  txtW, txtH, txtX,
  BtnWidth: Integer;
  canv: TControlCanvas;
begin
  BtnWidth := GetSystemMetrics(SM_CXMENUCHECK);

  _MySetCap := True;
  if not (csDesigning in ComponentState) then
    Caption := '';
  _MySetCap := False;
  inherited;
  canv := TControlCanvas.Create;
  try
    canv.Control := Self;
    canv.Font := Font;
    txtW:= canv.TextWidth(FOriginalCaption);
    txtH:= canv.TextHeight(FOriginalCaption);
    if BiDiMode in [bdRightToLeft, bdRightToLeftReadingOnly] then
      txtX:= Width - BtnWidth - SPACE - txtW
    else
      txtX:= BtnWidth + SPACE;
    SetBkMode(canv.Handle, Ord(TRANSPARENT));
    canv.TextOut(txtX, (Height - txtH) div 2 + 1, FOriginalCaption);
  finally
    canv.Free;
  end;
end;