开发者

How to display an "X' in a checked checkbox instead of a checkmark?

开发者 https://www.devze.com 2023-02-20 20:04 出处:网络
The CheckBox component displays a checkmark when checked. I would like to display an \'X开发者_如何学JAVA\' instead.You could do something like this:

The CheckBox component displays a checkmark when checked.

I would like to display an 'X开发者_如何学JAVA' instead.


You could do something like this:

unit CheckboxEx;

interface

uses
  SysUtils, Windows, Messages, Graphics, Classes, Controls, UxTheme;

type
  TCrossType = (ctChar, ctGDI);
  TCheckboxEx = class(TCustomControl)
  private type
    THoverState = (hsNormal = 1, hsHover = 2, hsPushed = 3);
  private const
    DEFAULT_PADDING = 3;
    DEFAULT_CHECK_CHAR = '✘';
    CHECK_LINE_PADDING = 4;
  private
    { Private declarations }
    FCaption: TCaption;
    FChecked: boolean;
    FPadding: integer;
    FCheckWidth, FCheckHeight: integer;
    FCheckRect, FTextRect: TRect;
    theme: HTHEME;
    FHoverState: THoverState;
    FCheckFont: TFont;
    FCheckChar: Char;
    FMouseHover: boolean;
    FCrossType: TCrossType;
    procedure SetCaption(const Caption: TCaption);
    procedure SetChecked(Checked: boolean);
    procedure SetPadding(Padding: integer);
    procedure UpdateMetrics;
    procedure CheckFontChange(Sender: TObject);
    procedure SetCheckChar(const CheckChar: char);
    procedure DetermineState;
    procedure SetCrossType(CrossType: TCrossType);
  protected
    procedure Paint; override;
    procedure MouseMove(Shift: TShiftState; X: Integer; Y: Integer); override;
    procedure MouseDown(Button: TMouseButton; Shift: TShiftState; X: Integer;
      Y: Integer); override;
    procedure MouseUp(Button: TMouseButton; Shift: TShiftState; X: Integer;
      Y: Integer); override;
    procedure WndProc(var Message: TMessage); override;
    procedure Click; override;
    procedure KeyDown(var Key: Word; Shift: TShiftState); override;
    procedure KeyUp(var Key: Word; Shift: TShiftState); override;
    { Protected declarations }
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
    { Public declarations }
  published
    { Published declarations }
    property ParentColor;
    property ParentFont;
    property Color;
    property Visible;
    property Enabled;
    property TabStop default true;
    property TabOrder;
    property OnDblClick;
    property OnEnter;
    property OnExit;
    property OnKeyUp;
    property OnKeyPress;
    property OnKeyDown;
    property OnMouseActivate;
    property OnMouseLeave;
    property OnMouseEnter;
    property OnMouseMove;
    property OnMouseUp;
    property OnMouseDown;
    property OnClick;
    property Font;
    property CheckFont: TFont read FCheckFont write FCheckFont;
    property Caption: TCaption read FCaption write SetCaption;
    property Checked: boolean read FChecked write SetChecked default false;
    property Padding: integer read FPadding write SetPadding default DEFAULT_PADDING;
    property CheckChar: Char read FCheckChar write SetCheckChar default DEFAULT_CHECK_CHAR;
    property CrossType: TCrossType read FCrossType write SetCrossType default ctGDI;
  end;

procedure Register;

implementation

uses Math;

procedure Register;
begin
  RegisterComponents('Rejbrand 2009', [TCheckboxEx]);
end;

var
  Hit: boolean;

function _EnumFontsProcBool(var LogFont: TLogFont; var TextMetric: TTextMetric;
  FontType: Integer; Data: Pointer): Integer; stdcall;
begin
  hit := SameStr(LogFont.lfFaceName, Pstring(Data)^);
  result := IfThen(hit, 0, 1);
end;

function FontInstalled(const FontName: TFontName): boolean;
var
  LF: TLogFont;
  fn: string;
begin
  hit := false;
  FillChar(LF, sizeOf(LF), 0);
  LF.lfCharSet := DEFAULT_CHARSET;
  fn := FontName;
  EnumFontFamiliesEx(GetDC(0), LF, @_EnumFontsProcBool, cardinal(@fn), 0);
  result := hit;
end;

function IsKeyDown(const VK: integer): boolean;
begin
  IsKeyDown := GetKeyState(VK) and $8000 <> 0;
end;

{ TCheckboxEx }

procedure TCheckboxEx.CheckFontChange(Sender: TObject);
begin
  Invalidate;
end;

procedure TCheckboxEx.Click;
begin
  inherited;
  if Enabled then
  begin
    SetChecked(not FChecked);
    SetFocus;
  end;
end;

constructor TCheckboxEx.Create(AOwner: TComponent);
begin
  inherited;
  TabStop := true;
  FMouseHover := false;
  FChecked := false;
  FPadding := DEFAULT_PADDING;
  FCheckChar := DEFAULT_CHECK_CHAR;
  FCrossType := ctGDI;
  theme := 0;
  FHoverState := hsNormal;
  FCheckFont := TFont.Create;
  FCheckFont.Assign(Font);
  if FontInstalled('Arial Unicode MS') then
    FCheckFont.Name := 'Arial Unicode MS';
  FCheckFont.OnChange := CheckFontChange;
end;

destructor TCheckboxEx.Destroy;
begin
  FCheckFont.Free;
  if theme <> 0 then
    CloseThemeData(theme);
  inherited;
end;

procedure TCheckboxEx.DetermineState;
var
  OldState: THoverState;
begin
  inherited;
  OldState := FHoverState;
  FHoverState := hsNormal;
  if FMouseHover then
    FHoverState := hsHover;
  if (csLButtonDown in ControlState) or (IsKeyDown(VK_SPACE) and Focused) then
    FHoverState := hsPushed;
  if (FHoverState <> OldState) and UseThemes then
    Invalidate;
end;

procedure TCheckboxEx.KeyDown(var Key: Word; Shift: TShiftState);
begin
  inherited;
  if Key = VK_SPACE then
    DetermineState;
end;

procedure TCheckboxEx.KeyUp(var Key: Word; Shift: TShiftState);
begin
  inherited;
  if Key = VK_SPACE then
  begin
    Click;
    DetermineState;
  end;
end;

procedure TCheckboxEx.MouseDown(Button: TMouseButton; Shift: TShiftState; X,
  Y: Integer);
begin
  inherited;
  DetermineState;
end;

procedure TCheckboxEx.MouseMove(Shift: TShiftState; X, Y: Integer);
begin
  inherited;
  FMouseHover := true;
  DetermineState;
end;

procedure TCheckboxEx.MouseUp(Button: TMouseButton; Shift: TShiftState; X,
  Y: Integer);
begin
  inherited;
  DetermineState;
end;

procedure TCheckboxEx.Paint;
var
  ext: TSize;
  frect: TRect;
begin
  inherited;
  Canvas.Brush.Color := Self.Color;
  Canvas.FillRect(ClientRect);
  if UseThemes then
  begin
    if theme = 0 then
    begin
      theme := OpenThemeData(Handle, 'BUTTON');
      UpdateMetrics;
    end;
    if Enabled then
      DrawThemeBackground(theme,
        Canvas.Handle,
        BP_CHECKBOX,
        ord(FHoverState),
        FCheckRect,
        nil)
    else
      DrawThemeBackground(theme,
        Canvas.Handle,
        BP_CHECKBOX,
        CBS_UNCHECKEDDISABLED,
        FCheckRect,
        nil);
  end
  else
    if Enabled then
      DrawFrameControl(Canvas.Handle,
        FCheckRect,
        DFC_BUTTON,
        DFCS_BUTTONCHECK)
    else
      DrawFrameControl(Canvas.Handle,
        FCheckRect,
        DFC_BUTTON,
        DFCS_BUTTONCHECK or DFCS_INACTIVE);
  Canvas.TextFlags := TRANSPARENT;
  Canvas.Brush.Style := bsClear;
  Canvas.Font.Assign(Font);
  DrawText(Canvas.Handle,
    PChar(FCaption),
    length(FCaption),
    FTextRect,
    DT_SINGLELINE or DT_VCENTER or DT_LEFT);
  if Focused then
  begin
    ext := Canvas.TextExtent(FCaption);
    frect := Rect(FTextRect.Left,
      (ClientHeight - ext.cy) div 2,
      FTextRect.Left + ext.cx,
      (ClientHeight + ext.cy) div 2);
    Canvas.DrawFocusRect(frect);
  end;
  if FChecked then
    case FCrossType of
      ctChar:
        begin
          Canvas.Font.Assign(FCheckFont);
          DrawText(Canvas.Handle,
            CheckChar,
            1,
            FCheckRect,
            DT_SINGLELINE or DT_VCENTER or DT_CENTER);
        end;
      ctGDI:
        begin
          Canvas.Pen.Width := 2;
          Canvas.Pen.Color := clBlack;
          Canvas.Pen.Mode := pmCopy;
          Canvas.MoveTo(FCheckRect.Left + CHECK_LINE_PADDING, FCheckRect.Top + CHECK_LINE_PADDING);
          Canvas.LineTo(FCheckRect.Right - CHECK_LINE_PADDING, FCheckRect.Bottom - CHECK_LINE_PADDING);
          Canvas.MoveTo(FCheckRect.Right - CHECK_LINE_PADDING, FCheckRect.Top + CHECK_LINE_PADDING);
          Canvas.LineTo(FCheckRect.Left + CHECK_LINE_PADDING, FCheckRect.Bottom - CHECK_LINE_PADDING);
        end;
    end;
end;

procedure TCheckboxEx.SetCaption(const Caption: TCaption);
begin
  if not SameStr(FCaption, Caption) then
  begin
    FCaption := Caption;
    Invalidate;
  end;
end;

procedure TCheckboxEx.SetCheckChar(const CheckChar: char);
begin
  if FCheckChar <> CheckChar then
  begin
    FCheckChar := CheckChar;
    if FChecked then Invalidate;
  end;
end;

procedure TCheckboxEx.SetChecked(Checked: boolean);
begin
  if FChecked <> Checked then
  begin
    FChecked := Checked;
    Invalidate;
  end;
end;

procedure TCheckboxEx.SetCrossType(CrossType: TCrossType);
begin
  if FCrossType <> CrossType then
  begin
    FCrossType := CrossType;
    if FChecked then Invalidate;
  end;
end;

procedure TCheckboxEx.SetPadding(Padding: integer);
begin
  if FPadding <> Padding then
  begin
    FPadding := Padding;
    UpdateMetrics;
    Invalidate;
  end;
end;

procedure TCheckboxEx.UpdateMetrics;
var
  size: TSize;
begin
  FCheckWidth := GetSystemMetrics(SM_CXMENUCHECK);
  FCheckHeight := GetSystemMetrics(SM_CYMENUCHECK);
  if UseThemes then
  begin
    UxTheme.GetThemePartSize(theme, Canvas.Handle, BP_CHECKBOX, CBS_UNCHECKEDNORMAL, nil, TS_DRAW, size);
    FCheckWidth := size.cx;
    FCheckHeight := size.cy;
  end;
  FCheckRect := Rect(0,
                  (ClientHeight - FCheckHeight) div 2,
                  FCheckWidth,
                  (ClientHeight + FCheckHeight) div 2);
  FTextRect := Rect(FCheckWidth + FPadding,
                 0,
                 ClientWidth,
                 ClientHeight);
end;

procedure TCheckboxEx.WndProc(var Message: TMessage);
begin
  inherited;
  case Message.Msg of
    CM_MOUSELEAVE:
      begin
        FMouseHover := false;
        DetermineState;
      end;
    WM_SIZE:
      begin
        UpdateMetrics;
        Invalidate;
      end;
    WM_SETFOCUS, WM_KILLFOCUS:
      Invalidate;
  end;
end;

end.

Now (with CrossType set to ctChar) you can use any Unicode character as the checkmark, the default choice being ✘ (U+2718: HEAVY BALLOT X). The images below illustrate that the control works both with and without visual themes:

How to display an "X' in a checked checkbox instead of a checkmark?

How to display an "X' in a checked checkbox instead of a checkmark?

The following image illustrates that you can choose any character as your checkmark:

How to display an "X' in a checked checkbox instead of a checkmark?

This character is ✿ (U+273F: BLACK FLORETTE).

If you set CrossType to ctGDI instead of ctChar, the control will draw a cross manually and not a character:

How to display an "X' in a checked checkbox instead of a checkmark?

I didn't use double-buffering this time, because there is no noticable flickering with themes enabled. Without themes, however, there is flickering. To remedy this, simply use a FBuffer: TBitmap and draw on FBuffer.Canvas instead of Self.Canvas and then BitBlt at the end of Paint, as I do in my other controls here at SO.


You'll have to write a custom control and paint it yourself.

If this is a real check box then it's a bad idea to avoid the system's default drawing. However, if you want to do something like a voting form then I could see why you might opt to do this.


I would go the opposite way, anyway, select all items by default and let the user remove the ones who should be left out from the list.


Having checkbutton a serious limitation in designs, who want to stay in VCL, can use BitBtn as a check, using "Kind" property to paint the Cancel or Ok images when user click on it. Also delete after every condition change, the "Caption" property, because the BitBtn must have a square layout to simulate a check. Use also a tLabel at left or right hand as you wish.

if lAutoMode = False then
  begin
   lAutoMode := True;
   BitBtn1.Kind := bkOK;
   BitBtn1.Caption := '';
end
else
begin
  lAutoMode := False;
  BitBtn1.Kind := bkAbort;
  BitBtn1.Caption := '';
end;

When create the Form, set the initial state for the BitBtn.

0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号