1
0
Files
applications
bindings
components
Comba_Animation
aboutcomponent
acs
beepfp
callite
chelper
chemtext
cmdline
cmdlinecfg
colorpalette
cryptini
csvdocument
epiktimer
everettrandom
examplecomponent
exctrls
examples
images
source
checkctrlsreg.res
exbuttons.pas
excheckctrls.pas
excombo.pas
exctrlsreg.pas
exctrlsreg.res
exeditctrls.pas
exctrlspkg.lpk
exctrlspkg.pas
extrasyn
fpexif
fpsound
fpspreadsheet
fractions
freetypepascal
geckoport
gradcontrols
grid_semaphor
industrialstuff
iosdesigner
iphonelazext
jujiboutils
jvcllaz
kcontrols
lazautoupdate
lazbarcodes
lazmapviewer
lclextensions
longtimer
manualdock
mbColorLib
mplayer
multithreadprocs
nvidia-widgets
onguard
orpheus
playsoundpackage
poweredby
powerpdf
rgbgraphics
richmemo
richview
rtfview
rx
scrolltext
smnetgradient
spktoolbar
splashabout
svn
systools
tdi
thtmlport
tparadoxdataset
tvplanit
xdev_toolkit
zlibar
zmsql
examples
image_sources
lclbindings
wst
lazarus-ccr/components/exctrls/source/excheckctrls.pas

2179 lines
60 KiB
ObjectPascal

{ Extended checked controls (radiobutton, checkbox, radiogroup, checkgroup)
Copyright (C) 2020 Lazarus team
This library is free software; you can redistribute it and/or modify it
under the same terms as the Lazarus Component Library (LCL)
See the file COPYING.modifiedLGPL.txt, included in the Lazarus distribution,
for details about the license.
}
unit ExCheckCtrls;
{$mode objfpc}{$H+}
interface
uses
LCLType, LCLIntf, LCLProc, LMessages,
Graphics, Classes, SysUtils, Types, Themes, Controls,
StdCtrls, ExtCtrls, ImgList;
type
TGetImageIndexEvent = procedure (Sender: TObject; AHover, APressed, AEnabled: Boolean;
AState: TCheckboxState; var AImgIndex: Integer) of object;
{ TCustomCheckControlEx }
TCustomCheckControlEx = class(TCustomControl)
private
type
TCheckControlKind = (cckCheckbox, cckRadioButton);
private
FAlignment: TLeftRight;
FAllowGrayed: Boolean;
FThemedBtnSize: TSize;
FBtnLayout: TTextLayout;
FDistance: Integer; // between button and caption
FDrawFocusRect: Boolean;
FFocusBorder: Integer;
FGroupLock: Integer;
FHover: Boolean;
FImages: TCustomImageList;
FImagesWidth: Integer;
FKind: TCheckControlKind;
FPressed: Boolean;
FReadOnly: Boolean;
FState: TCheckBoxState;
FTextLayout: TTextLayout;
FThemedCaption: Boolean;
// FTransparent: Boolean;
FWordWrap: Boolean;
FOnChange: TNotifyEvent;
FOnGetImageIndex: TGetImageIndexEvent;
function GetCaption: TCaption;
function GetChecked: Boolean;
procedure SetAlignment(const AValue: TLeftRight);
procedure SetBtnLayout(const AValue: TTextLayout);
procedure SetCaption(const AValue: TCaption);
procedure SetChecked(const AValue: Boolean);
procedure SetDrawFocusRect(const AValue: Boolean);
procedure SetImages(const AValue: TCustomImageList);
procedure SetImagesWidth(const AValue: Integer);
procedure SetState(const AValue: TCheckBoxState);
procedure SetTextLayout(const AValue: TTextLayout);
procedure SetThemedCaption(const AValue: Boolean);
//procedure SetTransparent(const AValue: Boolean);
procedure SetWordWrap(const AValue: Boolean);
protected
procedure AfterSetState; virtual;
procedure CalculatePreferredSize(var PreferredWidth, PreferredHeight: Integer;
{%H-}WithThemeSpace: Boolean); override;
procedure CMBiDiModeChanged(var {%H-}Message: TLMessage); message CM_BIDIMODECHANGED;
procedure CreateHandle; override;
procedure DoAutoAdjustLayout(const AMode: TLayoutAdjustmentPolicy;
const AXProportion, AYProportion: Double); override;
procedure DoClick;
procedure DoEnter; override;
procedure DoExit; override;
procedure DrawBackground;
procedure DrawButton(AHovered, APressed, AEnabled: Boolean; AState: TCheckboxState);
procedure DrawButtonText(AHovered, APressed, AEnabled: Boolean;
AState: TCheckboxState);
function GetBtnSize: TSize; virtual;
function GetDrawTextFlags: Cardinal;
function GetTextExtent(const ACaption: String): TSize;
function GetThemedButtonDetails(AHovered, APressed, AEnabled: Boolean;
AState: TCheckboxState): TThemedElementDetails; virtual; abstract;
// procedure InitBtnSize(Scaled: Boolean);
procedure LockGroup;
procedure KeyDown(var Key: Word; Shift: TShiftState); override;
procedure KeyUp(var Key: Word; Shift: TShiftState); override;
procedure MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: Integer); override;
procedure MouseEnter; override;
procedure MouseLeave; override;
procedure MouseUp(Button: TMouseButton; Shift: TShiftState; X, Y: Integer); override;
procedure Paint; override;
procedure TextChanged; override;
procedure UnlockGroup;
procedure WMSize(var Message: TLMSize); message LM_SIZE;
property Alignment: TLeftRight read FAlignment write SetAlignment default taRightJustify;
property AllowGrayed: Boolean read FAllowGrayed write FAllowGrayed default False;
property ButtonLayout: TTextLayout read FBtnLayout write SetBtnLayout default tlCenter;
property Caption: TCaption read GetCaption write SetCaption;
property Checked: Boolean read GetChecked write SetChecked default false;
property DrawFocusRect: Boolean read FDrawFocusRect write SetDrawFocusRect default true;
property Images: TCustomImageList read FImages write SetImages;
property ImagesWidth: Integer read FImagesWidth write SetImagesWidth default 0;
property ReadOnly: Boolean read FReadOnly write FReadOnly default false;
property State: TCheckBoxState read FState write SetState default cbUnchecked;
property TextLayout: TTextLayout read FTextLayout write SetTextLayout default tlCenter;
property ThemedCaption: Boolean read FThemedCaption write SetThemedCaption default true;
//property Transparent: Boolean read FTransparent write SetTransparent default true;
property WordWrap: Boolean read FWordWrap write SetWordWrap default false;
property OnChange: TNotifyEvent read FOnChange write FOnChange;
property OnGetImageIndex: TGetImageIndexEvent read FOnGetImageIndex write FOnGetImageIndex;
public
constructor Create(AOwner: TComponent); override;
end;
{ TCustomCheckboxEx }
TCustomCheckboxEx = class(TCustomCheckControlEx)
private
protected
function GetThemedButtonDetails(AHovered, APressed, AEnabled: Boolean;
AState: TCheckboxState): TThemedElementDetails; override;
public
constructor Create(AOwner: TComponent); override;
end;
{ TCheckBoxEx }
TCheckBoxEx = class(TCustomCheckBoxEx)
published
//property Action;
property Align;
property Alignment;
property AllowGrayed;
property Anchors;
property AutoSize default true;
property BiDiMode;
property BorderSpacing;
property ButtonLayout;
property Caption;
property Checked;
property Color;
property Constraints;
property Cursor;
property DoubleBuffered;
property DragCursor;
property DragKind;
property DragMode;
property DrawFocusRect;
property Enabled;
property Font;
property Height;
property HelpContext;
property HelpKeyword;
property HelpType;
property Hint;
property Images;
property ImagesWidth;
property Left;
property Name;
property ParentBiDiMode;
property ParentColor;
property ParentDoubleBuffered;
property ParentFont;
property ParentShowHint;
property ReadOnly;
property ShowHint;
property State;
property TabOrder;
property TabStop;
property Tag;
property TextLayout;
property ThemedCaption;
property Top;
//property Transparent;
property Visible;
property Width;
property WordWrap;
property OnChange;
property OnChangeBounds;
property OnClick;
property OnContextPopup;
property OnDragDrop;
property OnDragOver;
property OnEditingDone;
property OnEndDrag;
property OnEnter;
property OnExit;
property OnGetImageIndex;
property OnKeyDown;
property OnKeyPress;
property OnKeyUp;
property OnMouseDown;
property OnMouseEnter;
property OnMouseLeave;
property OnMouseUp;
property OnMouseWheel;
property OnMouseWheelDown;
property OnMouseWheelUp;
property OnResize;
property OnStartDrag;
property OnUTF8KeyPress;
end;
{ TCustomRadioButtonEx }
TCustomRadioButtonEx = class(TCustomCheckControlEx)
protected
procedure AfterSetState; override;
function GetThemedButtonDetails(AHovered, APressed, AEnabled: Boolean;
AState: TCheckboxState): TThemedElementDetails; override;
public
constructor Create(AOwner: TComponent); override;
published
end;
{ TRadioButtonEx }
TRadioButtonEx = class(TCustomRadioButtonEx)
published
property Align;
property Alignment;
property Anchors;
property AutoSize default true;
property BiDiMode;
property BorderSpacing;
property ButtonLayout;
property Caption;
property Checked;
property Color;
property Constraints;
property Cursor;
property DoubleBuffered;
property DragCursor;
property DragKind;
property DragMode;
property DrawFocusRect;
property Enabled;
property Font;
property Height;
property HelpContext;
property HelpKeyword;
property HelpType;
property Hint;
property Images;
property ImagesWidth;
property Left;
property Name;
property ParentBiDiMode;
property ParentColor;
property ParentDoubleBuffered;
property ParentFont;
property ParentShowHint;
property PopupMenu;
property ReadOnly;
property ShowHint;
property State;
property TabOrder;
property TabStop;
property Tag;
property TextLayout;
property ThemedCaption;
//property Transparent;
property Visible;
property WordWrap;
property Width;
property OnChange;
property OnChangeBounds;
property OnClick;
property OnContextPopup;
property OnDragDrop;
property OnDragOver;
property OnEnter;
property OnExit;
property OnKeyDown;
property OnKeyPress;
property OnKeyUp;
property OnMouseDown;
property OnMouseEnter;
property OnMouseLeave;
property OnMouseUp;
property OnMouseWheel;
property OnMouseWheelDown;
property OnMouseWheelUp;
property OnResize;
property OnStartDrag;
property OnGetImageIndex;
end;
{ TCustomCheckControlGroupEx }
TCustomCheckControlGroupEx = class(TCustomGroupBox)
private
FAutoFill: Boolean;
FButtonList: TFPList;
FColumnLayout: TColumnLayout;
FColumns: integer;
FImages: TCustomImageList;
FImagesWidth: Integer;
FItems: TStrings;
FIgnoreClicks: boolean;
FReadOnly: Boolean;
FUpdatingItems: Boolean;
FOnClick: TNotifyEvent;
FOnGetImageIndex: TGetImageIndexEvent;
FOnItemEnter: TNotifyEvent;
FOnItemExit: TNotifyEvent;
FOnSelectionChanged: TNotifyEvent;
procedure ItemEnter(Sender: TObject); virtual;
procedure ItemExit(Sender: TObject); virtual;
procedure ItemKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState); virtual;
procedure ItemKeyUp(Sender: TObject; var Key: Word; Shift: TShiftState); virtual;
procedure ItemKeyPress(Sender: TObject; var Key: Char); virtual;
procedure ItemUTF8KeyPress(Sender: TObject; var UTF8Key: TUTF8Char); virtual;
procedure SetAutoFill(const AValue: Boolean);
procedure SetColumnLayout(const AValue: TColumnLayout);
procedure SetColumns(const AValue: integer);
procedure SetImages(const AValue: TCustomImageList);
procedure SetImagesWidth(const AValue: Integer);
procedure SetItems(const AValue: TStrings);
procedure SetOnGetImageIndex(const AValue: TGetImageIndexEvent);
procedure SetReadOnly(const AValue: Boolean);
protected
procedure UpdateAll;
procedure UpdateControlsPerLine;
procedure UpdateInternalObjectList;
procedure UpdateItems; virtual; abstract;
procedure UpdateTabStops;
property AutoFill: Boolean read FAutoFill write SetAutoFill default true;
property ColumnLayout: TColumnLayout read FColumnLayout write SetColumnLayout default clHorizontalThenVertical;
property Columns: Integer read FColumns write SetColumns default 1;
property Images: TCustomImageList read FImages write SetImages;
property ImagesWidth: Integer read FImagesWidth write SetImagesWidth default 0;
property Items: TStrings read FItems write SetItems;
property ReadOnly: Boolean read FReadOnly write SetReadOnly default false;
property OnClick: TNotifyEvent read FOnClick write FOnClick;
property OnGetImageIndex: TGetImageIndexEvent read FOnGetImageIndex write SetOnGetImageIndex;
property OnItemEnter: TNotifyEvent read FOnItemEnter write FOnItemEnter;
property OnItemExit: TNotifyEvent read FOnItemExit write FOnItemExit;
property OnSelectionChanged: TNotifyEvent read FOnSelectionChanged write FOnSelectionChanged;
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
function CanModify: boolean; virtual;
procedure FlipChildren(AllLevels: Boolean); override;
function Rows: integer;
end;
{ TCustomRadioGroupEx }
TCustomRadioGroupEx = class(TCustomCheckControlGroupEx)
private
FCreatingWnd: Boolean;
FHiddenButton: TRadioButtonEx;
FItemIndex: integer;
FLastClickedItemIndex: Integer;
FReading: Boolean;
procedure Changed(Sender: TObject);
procedure Clicked(Sender: TObject);
function GetButtonCount: Integer;
function GetButtons(AIndex: Integer): TRadioButtonEx;
procedure SetItemIndex(const AValue: Integer);
protected
procedure CheckItemIndexChanged; virtual;
procedure InitializeWnd; override;
procedure ItemKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState); override;
procedure ReadState(AReader: TReader); override;
procedure UpdateItems; override;
procedure UpdateRadioButtonStates; virtual;
property ItemIndex: Integer read FItemIndex write SetItemIndex default -1;
public
property ButtonCount: Integer read GetButtonCount;
property Buttons[AIndex: Integer]: TRadioButtonEx read GetButtons;
published
constructor Create(AOwner: TComponent); override;
end;
{ TRadioGroupEx }
TRadioGroupEx = class(TCustomRadioGroupEx)
published
property Align;
property Anchors;
property AutoFill;
property AutoSize;
property BiDiMode;
property BorderSpacing;
property Caption;
property ChildSizing;
property Color;
property ColumnLayout;
property Columns;
property Constraints;
property Cursor;
property DoubleBuffered;
property DragCursor;
property DragMode;
property Enabled;
property Font;
property Height;
property HelpContext;
property HelpKeyword;
property HelpType;
property Hint;
property Images;
property ImagesWidth;
property ItemIndex;
property Items;
property Left;
property Name;
property ParentBiDiMode;
property ParentColor;
property ParentFont;
property ParentShowHint;
property PopupMenu;
property ReadOnly;
property ShowHint;
property TabOrder;
property TabStop;
property Tag;
property Top;
property Visible;
property Width;
property OnChangeBounds;
property OnClick;
property OnDblClick;
property OnDragDrop;
property OnDragOver;
property OnEndDrag;
property OnEnter;
property OnExit;
property OnGetImageIndex;
property OnItemEnter;
property OnItemExit;
property OnKeyDown;
property OnKeyPress;
property OnKeyUp;
property OnMouseDown;
property OnMouseEnter;
property OnMouseLeave;
property OnMouseUp;
property OnMouseWheel;
property OnMouseWheelDown;
property OnMouseWheelUp;
property OnResize;
property OnSelectionChanged;
property OnStartDrag;
property OnUTF8KeyPress;
end;
TCustomCheckGroupEx = class(TCustomCheckControlGroupEx)
private
FOnItemClick: TCheckGroupClicked;
procedure Clicked(Sender: TObject);
procedure DoClick(AIndex: integer);
function GetButtonCount: Integer;
function GetButtons(AIndex: Integer): TCheckBoxEx;
function GetChecked(AIndex: integer): boolean;
function GetCheckEnabled(AIndex: integer): boolean;
procedure RaiseIndexOutOfBounds(AIndex: integer);
procedure SetChecked(AIndex: integer; const AValue: boolean);
procedure SetCheckEnabled(AIndex: integer; const AValue: boolean);
protected
procedure DefineProperties(Filer: TFiler); override;
procedure Loaded; override;
procedure ReadData(Stream: TStream);
procedure UpdateItems; override;
procedure WriteData(Stream: TStream);
// procedure DoOnResize; override;
public
constructor Create(AOwner: TComponent); override;
property ButtonCount: Integer read GetButtonCount;
property Buttons[AIndex: Integer]: TCheckBoxEx read GetButtons;
public
property Checked[Index: integer]: boolean read GetChecked write SetChecked;
property CheckEnabled[Index: integer]: boolean read GetCheckEnabled write SetCheckEnabled;
property OnItemClick: TCheckGroupClicked read FOnItemClick write FOnItemClick;
end;
{ TCheckGroupEx }
TCheckGroupEx = class(TCustomCheckGroupEx)
published
property Align;
property Anchors;
property AutoFill;
property AutoSize;
property BiDiMode;
property BorderSpacing;
property Caption;
property ChildSizing;
property ClientHeight;
property ClientWidth;
property Color;
property ColumnLayout;
property Columns;
property Constraints;
property DoubleBuffered;
property DragCursor;
property DragMode;
property Enabled;
property Font;
property Images;
property ImagesWidth;
property Items;
property ParentBiDiMode;
property ParentFont;
property ParentColor;
property ParentDoubleBuffered;
property ParentShowHint;
property PopupMenu;
property ReadOnly;
property ShowHint;
property TabOrder;
property TabStop;
property Visible;
property OnChangeBounds;
property OnClick;
property OnDblClick;
property OnDragDrop;
property OnDragOver;
property OnEndDrag;
property OnEnter;
property OnExit;
property OnGetImageIndex;
property OnItemClick;
property OnItemEnter;
property OnItemExit;
property OnKeyDown;
property OnKeyPress;
property OnKeyUp;
property OnMouseDown;
property OnMouseEnter;
property OnMouseLeave;
property OnMouseMove;
property OnMouseUp;
property OnMouseWheel;
property OnMouseWheelDown;
property OnMouseWheelUp;
property OnResize;
property OnStartDrag;
property OnUTF8KeyPress;
end;
implementation
uses
Math, LCLStrConsts, LResources;
const
cIndent = 5;
FIRST_RADIOBUTTON_DETAIL = tbRadioButtonUncheckedNormal;
FIRST_CHECKBOX_DETAIL = tbCheckBoxUncheckedNormal;
HOT_OFFSET = 1;
PRESSED_OFFSET = 2;
DISABLED_OFFSET = 3;
CHECKED_OFFSET = 4;
MIXED_OFFSET = 8;
procedure DrawParentImage(Control: TControl; Dest: TCanvas);
var
SaveIndex: integer;
DC: HDC;
Position: TPoint;
begin
with Control do
begin
if Parent = nil then Exit;
DC := Dest.Handle;
SaveIndex := SaveDC(DC);
GetViewportOrgEx(DC, @Position);
SetViewportOrgEx(DC, Position.X - Left, Position.Y - Top, nil);
IntersectClipRect(DC, 0, 0, Parent.ClientWidth, Parent.ClientHeight);
Parent.Perform(LM_ERASEBKGND, DC, 0);
Parent.Perform(LM_PAINT, DC, 0);
RestoreDC(DC, SaveIndex);
end;
end;
function ProcessLineBreaks(const AString: string; ToC: Boolean): String;
var
idx: Integer;
procedure AddChar(ch: Char);
begin
Result[idx] := ch;
inc(idx);
if idx > Length(Result) then
SetLength(Result, Length(Result) + 100);
end;
var
P, PEnd: PChar;
begin
if AString = '' then
begin
Result := '';
exit;
end;
SetLength(Result, Length(AString));
idx := 1;
P := @AString[1];
PEnd := P + Length(AString);
if ToC then
// Replace line breaks by '\n'
while P < PEnd do begin
if (P^ = #13) then begin
AddChar('\');
AddChar('n');
inc(P);
if P^ <> #10 then dec(P);
end else
if P^ = #10 then
begin
AddChar('\');
AddChar('n');
end else
if P^ = '\' then
begin
AddChar('\');
AddChar('\');
end else
AddChar(P^);
inc(P);
end
else
// Replace '\n' by LineEnding
while (P < PEnd) do
begin
if (P^ = '\') and (P < PEnd-1) then
begin
inc(P);
if (P^ = 'n') or (P^ = 'N') then
AddChar(#10)
else
AddChar(P^);
end else
AddChar(P^);
inc(P);
end;
SetLength(Result, idx-1);
end;
{ TCheckboxControlEx }
constructor TCustomCheckControlEx.Create(AOwner: TComponent);
begin
inherited;
ControlStyle := ControlStyle + [csParentBackground, csReplicatable] - [csOpaque]
- csMultiClicks - [csClickEvents, csNoStdEvents]; { inherited Click not used }
FAlignment := taRightJustify;
FBtnLayout := tlCenter;
FDrawFocusRect := true;
FKind := cckCheckbox;
FDistance := cIndent;
FFocusBorder := FDistance div 2;
FTextLayout := tlCenter;
FThemedCaption := true;
// FTransparent := true;
AutoSize := true;
TabStop := true;
end;
// Is called after the State has changed in SetState. Will be overridden by
// TCustomRadioButtonEx to uncheck all other iteme.s
procedure TCustomCheckControlEx.AfterSetState;
begin
end;
procedure TCustomCheckControlEx.CalculatePreferredSize(var PreferredWidth,
PreferredHeight: Integer; WithThemeSpace: Boolean);
var
flags: Cardinal;
textSize: TSize;
R: TRect;
captn: String;
details: TThemedElementDetails;
btnSize: TSize;
begin
captn := inherited Caption;
if (captn = '') then
begin
btnSize := GetBtnSize;
PreferredWidth := btnSize.CX;
PreferredHeight := btnSize.CY;
exit;
end;
Canvas.Font.Assign(Font);
R := ClientRect;
btnSize := GetBtnSize;
dec(R.Right, btnSize.CX + FDistance);
R.Bottom := MaxInt; // Max height possible
flags := GetDrawTextFlags + DT_CALCRECT;
// rectangle available for text
if FThemedCaption then
begin
details := GetThemedButtonDetails(false, false, true, cbChecked);
if FWordWrap then
begin
with ThemeServices.GetTextExtent(Canvas.Handle, details, captn, flags, @R) do begin
textSize.CX := Right;
textSize.CY := Bottom;
end;
end else
with ThemeServices.GetTextExtent(Canvas.Handle, details, captn, flags, nil) do begin
textSize.CX := Right;
textSize.CY := Bottom;
end;
end else
begin
DrawText(Canvas.Handle, PChar(captn), Length(captn), R, flags);
textSize.CX := R.Right - R.Left;
textSize.CY := R.Bottom - R.Top;
end;
PreferredWidth := btnSize.CX + FDistance + textSize.CX + FFocusBorder;
PreferredHeight := Max(btnSize.CY, textSize.CY + 2*FFocusBorder);
end;
procedure TCustomCheckControlEx.CMBiDiModeChanged(var Message: TLMessage);
begin
Invalidate;
end;
procedure TCustomCheckControlEx.CreateHandle;
var
w, h: Integer;
begin
inherited;
if (Width = 0) or (Height = 0) then begin
CalculatePreferredSize(w{%H-}, h{%H-}, false);
if Width <> 0 then w := Width;
if Height <> 0 then h := Height;
SetBounds(Left, Top, w, h);
end;
end;
procedure TCustomCheckControlEx.DoAutoAdjustLayout(
const AMode: TLayoutAdjustmentPolicy;
const AXProportion, AYProportion: Double);
begin
inherited;
if AMode in [lapAutoAdjustWithoutHorizontalScrolling, lapAutoAdjustForDPI] then
begin
FDistance := Round(cIndent * AXProportion);
FFocusBorder := FDistance div 2;
end;
end;
procedure TCustomCheckControlEx.DoClick;
begin
if FReadOnly then
exit;
if AllowGrayed then begin
case FState of
cbUnchecked: SetState(cbGrayed);
cbGrayed: SetState(cbChecked);
cbChecked: SetState(cbUnchecked);
end;
end else
Checked := not Checked;
end;
procedure TCustomCheckControlEx.DoEnter;
begin
inherited DoEnter;
Invalidate;
end;
procedure TCustomCheckControlEx.DoExit;
begin
inherited DoExit;
Invalidate;
end;
procedure TCustomCheckControlEx.DrawBackground;
var
R: TRect;
begin
R := Rect(0, 0, Width, Height);
Canvas.Brush.Style := bsSolid;
Canvas.Brush.Color := Color;
Canvas.FillRect(R);
end;
procedure TCustomCheckControlEx.DrawButton(AHovered, APressed, AEnabled: Boolean; AState: TCheckboxState);
var
btnRect: TRect;
btnPoint: TPoint = (X:0; Y:0);
details: TThemedElementDetails;
imgIndex: Integer;
imgRes: TScaledImageListResolution;
btnSize: TSize;
begin
// Checkbox/Radiobutton size and position
btnSize := GetBtnSize;
case FAlignment of
taLeftJustify:
if not IsRightToLeft then btnPoint.X := ClientWidth - btnSize.CX;
taRightJustify:
if IsRightToLeft then btnPoint.X := ClientWidth - btnSize.CX;
end;
case FBtnLayout of
tlTop: btnPoint.Y := FFocusBorder;
tlCenter: btnPoint.Y := (ClientHeight - btnSize.CY) div 2;
tlBottom: btnPoint.Y := ClientHeight - btnSize.CY - FFocusBorder;
end;
btnRect := Rect(0, 0, btnSize.CX, btnSize.CY);
OffsetRect(btnRect, btnPoint.X, btnPoint.Y);
imgIndex := -1;
if (FImages <> nil) and Assigned(FOnGetImageIndex) then
FOnGetImageIndex(Self, AHovered, APressed, AEnabled, AState, imgIndex);
if imgIndex > -1 then
begin
ImgRes := FImages.ResolutionForPPI[FImagesWidth, Font.PixelsPerInch, GetCanvasScaleFactor];
ImgRes.Draw(Canvas, btnRect.Left, btnRect.Top, imgIndex, AEnabled);
end else
begin
// Drawing style of button
details := GetThemedButtonDetails(AHovered, APressed, AEnabled, AState);
// Draw button
ThemeServices.DrawElement(Canvas.Handle, details, btnRect);
end;
end;
procedure TCustomCheckControlEx.DrawButtonText(AHovered, APressed, AEnabled: Boolean;
AState: TCheckboxState);
var
R: TRect;
// textStyle: TTextStyle;
delta: Integer;
details: TThemedElementDetails;
flags: Cardinal;
textSize: TSize;
captn: TCaption;
btnSize: TSize;
begin
captn := inherited Caption; // internal string with line breaks
if captn = '' then
exit;
// Determine text drawing parameters
flags := GetDrawTextFlags;
btnSize := GetBtnSize;
delta := btnSize.CX + FDistance;
R := ClientRect;
dec(R.Right, delta);
Canvas.Font.Assign(Font);
if FThemedCaption then
begin
R.Bottom := MaxInt; // max height for word-wrap
details := GetThemedButtonDetails(AHovered, APressed, AEnabled, AState);
with ThemeServices.GetTextExtent(Canvas.Handle, details, captn, flags, @R) do begin
textSize.CX := Right;
textSize.CY := Bottom;
end;
end else
begin
if not AEnabled then Canvas.Font.Color := clGrayText;
DrawText(Canvas.Handle, PChar(captn), Length(captn), R, flags + DT_CALCRECT);
textSize.CX := R.Right - R.Left;
textSize.CY := R.Bottom - R.Top;
end;
R := ClientRect;
case FTextLayout of
tlTop:
R.Top := 0;
tlCenter:
R.Top := (R.Top + R.Bottom - textSize.CY) div 2;
tlBottom:
R.Top := R.Bottom - textSize.CY;
end;
R.Bottom := R.Top + textSize.CY;
if (FAlignment = taRightJustify) and IsRightToLeft then
begin
dec(R.Right, delta);
R.Left := R.Right - textSize.CX;
end else
begin
inc(R.Left, delta);
R.Right := R.Left + textSize.CX;
end;
// Draw text
if FThemedCaption then
begin
ThemeServices.DrawText(Canvas, details, captn, R, flags, 0);
end else
begin
Canvas.Brush.Style := bsClear;
DrawText(Canvas.Handle, PChar(captn), Length(captn), R, flags);
end;
// Draw focus rect
if Focused and FDrawFocusRect then begin
InflateRect(R, FFocusBorder, 0);
if R.Left + R.Width > ClientWidth then R.Width := ClientWidth - R.Left;
if R.Left < 0 then R.Left := 0;
//LCLIntf.SetBkColor(Canvas.Handle, ColorToRGB(clBtnFace));
Canvas.Font.Color := clBlack;
LCLIntf.DrawFocusRect(Canvas.Handle, R);
end;
end;
function TCustomCheckControlEx.GetBtnSize: TSize;
var
ImgRes: TScaledImageListResolution;
begin
if (FImages <> nil) then begin
ImgRes := FImages.ResolutionForPPI[FImagesWidth, Font.PixelsPerInch, GetCanvasScaleFactor];
Result.CX := ImgRes.Width;
Result.CY := ImgRes.Height;
end else
begin
with ThemeServices do
if FKind = cckCheckbox then
Result := GetDetailSize(GetElementDetails(tbCheckBoxCheckedNormal))
else
if FKind = cckRadioButton then
Result := GetDetailSize(GetElementDetails(tbRadioButtonCheckedNormal));
//Result.CX := Scale96ToFont(Result.CX);
//Result.CY := Scale96ToFont(Result.CY);
end;
end;
// Replaces linebreaks in the inherited Caption by '\n' (and '\' by '\\') so
// that line breaks can be entered at designtime.
function TCustomCheckControlEx.GetCaption: TCaption;
const
TO_C = true;
begin
Result := ProcessLineBreaks(inherited Caption, TO_C);
end;
function TCustomCheckControlEx.GetChecked: Boolean;
begin
Result := (FState = cbChecked);
end;
// Determine text drawing parameters for the DrawText command
function TCustomCheckControlEx.GetDrawTextFlags: Cardinal;
begin
Result := 0;
case FTextLayout of
tlTop: inc(Result, DT_TOP);
tlCenter: inc(Result, DT_VCENTER);
tlBottom: inc(Result, DT_BOTTOM);
end;
if (FAlignment = taRightJustify) and IsRightToLeft then
inc(Result, DT_RIGHT)
else
inc(Result, DT_LEFT);
if IsRightToLeft then inc(Result, DT_RTLREADING);
if FWordWrap then inc(Result, DT_WORDBREAK);
end;
function TCustomCheckControlEx.GetTextExtent(const ACaption: String): TSize;
var
L: TStrings;
s: String;
begin
Result := Size(0, 0);
L := TStringList.Create;
try
L.Text := ACaption;
for s in L do
begin
Result.CY := Result.CY + Canvas.TextHeight(s);
Result.CX := Max(Result.CX, Canvas.TextWidth(s));
end;
finally
L.Free;
end;
end;
(*
procedure TCustomCheckControlEx.InitBtnSize(Scaled: Boolean);
var
ImgRes: TScaledImageListResolution;
begin
if (FImages <> nil) then begin
if Scaled then begin
ImgRes := FImages.ResolutionForPPI[FImagesWidth, Font.PixelsPerInch, GetCanvasScaleFactor];
FBtnSize.CX := ImgRes.Width;
FBtnSize.CY := ImgRes.Height;
end else
begin
FBtnSize.CX := FImages.Width;
FBtnSize.CY := FImages.Height;
end;
end else
begin
with ThemeServices do
if FKind = cckCheckbox then
FBtnSize := GetDetailSize(GetElementDetails(tbCheckBoxCheckedNormal))
else if FKind = cckRadioButton then
FBtnSize := GetDetailSize(GetElementDetails(tbRadioButtonCheckedNormal));
if Scaled then
begin
FBtnSize.CX := Scale96ToFont(FBtnSize.CX);
FBtnSize.CY := Scale96ToFont(FBtnSize.CY);
end;
end;
end;
*)
procedure TCustomCheckControlEx.KeyDown(var Key: Word; Shift: TShiftState);
begin
inherited KeyDown(Key, Shift);
if (Key in [VK_RETURN, VK_SPACE]) and not (ssCtrl in Shift) and (not FReadOnly) then
begin
FPressed := True;
Invalidate;
end;
end;
procedure TCustomCheckControlEx.KeyUp(var Key: Word; Shift: TShiftState);
begin
inherited KeyUp(Key, Shift);
if (Key in [VK_RETURN, VK_SPACE]) and not (ssCtrl in Shift) then
begin
FPressed := False;
DoClick;
end;
end;
procedure TCustomCheckControlEx.LockGroup;
begin
inc(FGroupLock);
end;
procedure TCustomCheckControlEx.MouseDown(Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
begin
inherited MouseDown(Button, Shift, X, Y);
if (Button = mbLeft) and FHover and not FReadOnly then
begin
FPressed := True;
Invalidate;
end;
SetFocus;
end;
procedure TCustomCheckControlEx.MouseEnter;
begin
FHover := true;
Invalidate;
inherited;
end;
procedure TCustomCheckControlEx.MouseLeave;
begin
FHover := false;
Invalidate;
inherited;
end;
procedure TCustomCheckControlEx.MouseUp(Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
begin
inherited MouseUp(Button, Shift, X, Y);
if Button = mbLeft then begin
if PtInRect(ClientRect, Point(X, Y)) then DoClick;
FPressed := False;
end;
end;
procedure TCustomCheckControlEx.Paint;
begin
{
if FTransparent then
DrawParentImage(Self, Self.Canvas)
else
DrawBackground;
}
DrawButton(FHover, FPressed, IsEnabled, FState);
DrawButtonText(FHover, FPressed, IsEnabled, FState);
end;
procedure TCustomCheckControlEx.SetAlignment(const AValue: TLeftRight);
begin
if AValue = FAlignment then exit;
FAlignment := AValue;
Invalidate;
end;
procedure TCustomCheckControlEx.SetBtnLayout(const AValue: TTextLayout);
begin
if AValue = FBtnLayout then exit;
FBtnLayout := AValue;
Invalidate;
end;
procedure TCustomCheckControlEx.SetCaption(const AValue: TCaption);
const
FROM_C = false;
begin
if AValue = GetCaption then exit;
inherited Caption := ProcessLineBreaks(AValue, FROM_C);
end;
procedure TCustomCheckControlEx.SetChecked(const AValue: Boolean);
begin
if AValue then
State := cbChecked
else
State := cbUnChecked;
end;
procedure TCustomCheckControlEx.SetDrawFocusRect(const AValue: Boolean);
begin
if AValue = FDrawFocusRect then exit;
FDrawFocusRect := AValue;
Invalidate;
end;
procedure TCustomCheckControlEx.SetImages(const AValue: TCustomImageList);
begin
if AValue = FImages then exit;
FImages := AValue;
// InitBtnSize(true);
InvalidatePreferredSize;
AdjustSize;
end;
procedure TCustomCheckControlEx.SetImagesWidth(const AValue: Integer);
begin
if AValue = FImagesWidth then exit;
FImagesWidth := AValue;
// InitBtnSize(true);
InvalidatePreferredSize;
AdjustSize;
end;
procedure TCustomCheckControlEx.SetTextLayout(const AValue: TTextLayout);
begin
if AValue = FTextLayout then exit;
FTextLayout := AValue;
Invalidate;
end;
procedure TCustomCheckControlEx.SetThemedCaption(const AValue: Boolean);
begin
if AValue = FThemedCaption then exit;
FThemedCaption := AValue;
Invalidate;
end;
procedure TCustomCheckControlEx.SetState(const AValue: TCheckboxState);
begin
if (FState = AValue) then exit;
FState := AValue;
if [csLoading, csDestroying, csDesigning] * ComponentState = [] then begin
if Assigned(OnEditingDone) then OnEditingDone(self);
if Assigned(OnChange) then OnChange(self);
{
// Execute only when Action.Checked is changed
if not CheckFromAction then begin
if Assigned(OnClick) then
if not (Assigned(Action) and
CompareMethods(TMethod(Action.OnExecute), TMethod(OnClick)))
then OnClick(self);
if (Action is TCustomAction) and
(TCustomAction(Action).Checked <> (AValue = cbChecked))
then ActionLink.Execute(self);
end;
}
AfterSetState;
end;
Invalidate;
end;
{
procedure TCustomCheckControlEx.SetTransparent(const AValue: Boolean);
begin
if AValue = FTransparent then exit;
FTransparent := AValue;
Invalidate;
end;
}
procedure TCustomCheckControlEx.SetWordWrap(const AValue: Boolean);
begin
if AValue = FWordWrap then exit;
FWordWrap := AValue;
Invalidate;
end;
procedure TCustomCheckControlEx.TextChanged;
begin
inherited TextChanged;
Invalidate;
end;
procedure TCustomCheckControlEx.UnlockGroup;
begin
dec(FGroupLock);
end;
procedure TCustomCheckControlEx.WMSize(var Message: TLMSize);
begin
inherited WMSize(Message);
Invalidate;
end;
{ TCustomRadioButtonEx }
constructor TCustomRadioButtonEx.Create(AOwner: TComponent);
begin
inherited;
FKind := cckRadioButton;
// InitBtnSize(false);
end;
{ Is called by SetState and is supposed to uncheck all other radiobuttons in the
same group, i.e. having the same parent. Provides a locking mechanism because
uncheding another radiobutton would trigger AfterSetState again. }
procedure TCustomRadioButtonEx.AfterSetState;
var
i: Integer;
C: TControl;
begin
if (FGroupLock > 0) or (Parent = nil) then
exit;
for i := 0 to Parent.ControlCount-1 do
begin
C := Parent.Controls[i];
if (C is TCustomRadioButtonEx) and (C <> self) then
with TCustomRadioButtonEx(C) do
begin
LockGroup;
try
State := cbUnChecked;
finally
UnlockGroup;
end;
end;
end;
// Parent.Invalidate;
end;
function TCustomRadioButtonEx.GetThemedButtonDetails(
AHovered, APressed, AEnabled: Boolean; AState: TCheckboxState): TThemedElementDetails;
var
offset: Integer = 0;
tb: TThemedButton;
begin
offset := ord(FIRST_RADIOBUTTON_DETAIL);
if APressed then
inc(offset, PRESSED_OFFSET)
else if AHovered then
inc(offset, HOT_OFFSET);
if not AEnabled then inc(offset, DISABLED_OFFSET);
if AState = cbChecked then inc(offset, CHECKED_OFFSET);
tb := TThemedButton(offset);
Result := ThemeServices.GetElementDetails(tb);
end;
(*
offset := 0
const // hovered pressed state
caEnabledDetails: array [False..True, False..True, cbUnChecked..cbChecked] of TThemedElementDetails =
(
(
(tbRadioButtonUncheckedNormal, tbRadioButtonCheckedNormal),
(tbRadioButtonUncheckedPressed, tbRadioButtonCheckedPressed)
),
(
(tbRadioButtonUncheckedHot, tbRadioButtonCheckedHot),
(tbRadioButtonUncheckedPressed, tbRadioButtonCheckedPressed)
)
);
caDisabledDetails: array [cbUnchecked..cbChecked] of TThemedButton =
(tbRadioButtonUncheckedDisabled, tbRadioButtonCheckedDisabled);
begin
if Enabled then
Result := caEnabledDetails[AHovered, APressed, AState]
else
Result := caDisabledDetails[AState];
end;
*)
{==============================================================================}
{ TCustomCheckboxEx }
{==============================================================================}
constructor TCustomCheckboxEx.Create(AOwner: TComponent);
begin
inherited;
FKind := cckCheckbox;
// InitBtnSize(false);
end;
function TCustomCheckBoxEx.GetThemedButtonDetails(
AHovered, APressed, AEnabled: Boolean; AState: TCheckboxState): TThemedElementDetails;
var
offset: Integer = 0;
tb: TThemedButton;
begin
offset := ord(FIRST_CHECKBOX_DETAIL);
if APressed then
inc(offset, PRESSED_OFFSET)
else if AHovered then
inc(offset, HOT_OFFSET);
if not AEnabled then inc(offset, DISABLED_OFFSET);
case AState of
cbChecked: inc(offset, CHECKED_OFFSET);
cbGrayed: inc(offset, MIXED_OFFSET);
end;
tb := TThemedButton(offset);
Result := ThemeServices.GetElementDetails(tb);
end;
(*
const // hovered pressed state
caEnabledDetails: array [False..True, False..True, cbUnchecked..cbGrayed] of TThemedButton =
(
(
(tbCheckBoxUncheckedNormal, tbCheckBoxCheckedNormal, tbCheckBoxMixedNormal),
(tbCheckBoxUncheckedPressed, tbCheckBoxCheckedPressed, tbCheckBoxMixedPressed)
),
(
(tbCheckBoxUncheckedHot, tbCheckBoxCheckedHot, tbCheckBoxMixedHot),
(tbCheckBoxUncheckedPressed, tbCheckBoxCheckedPressed, tbCheckBoxMixedPressed)
)
);
caDisabledDetails: array [cbUnchecked..cbGrayed] of TThemedButton =
(tbCheckBoxUncheckedDisabled, tbCheckBoxCheckedDisabled, tbCheckBoxMixedDisabled);
var
tb: TThemedButton;
begin
if Enabled then
tb := caEnabledDetails[AHovered, APressed, AState]
else
tb := caDisabledDetails[AState];
Result := ThemeServices.GetElementDetails(tb);
end; *)
{==============================================================================}
{ TCustomCheckControlGroupEx }
{==============================================================================}
constructor TCustomCheckControlGroupEx.Create(AOwner: TComponent);
begin
inherited;
FAutoFill := true;
FButtonList := TFPList.Create;
FColumns := 1;
FColumnLayout := clHorizontalThenVertical;
ChildSizing.Layout := cclLeftToRightThenTopToBottom;
ChildSizing.ControlsPerLine := FColumns;
ChildSizing.ShrinkHorizontal := crsScaleChilds;
ChildSizing.ShrinkVertical := crsScaleChilds;
ChildSizing.EnlargeHorizontal := crsHomogenousChildResize;
ChildSizing.EnlargeVertical := crsHomogenousChildResize;
ChildSizing.LeftRightSpacing := 6;
ChildSizing.TopBottomSpacing := 0;
end;
destructor TCustomCheckControlGroupEx.Destroy;
var
i: Integer;
begin
for i:=0 to FButtonList.Count-1 do
TCustomCheckControlEx(FButtonList[i]).Free;
FButtonList.Free;
FItems.Free;
inherited;
end;
function TCustomCheckControlGroupEx.CanModify: Boolean;
begin
Result := not FReadOnly;
end;
procedure TCustomCheckControlgroupEx.FlipChildren(AllLevels: Boolean);
begin
// no flipping
end;
procedure TCustomCheckControlGroupEx.ItemEnter(Sender: TObject);
begin
if Assigned(FOnItemEnter) then FOnItemEnter(Sender);
end;
procedure TCustomCheckControlGroupEx.ItemExit(Sender: TObject);
begin
if Assigned(FOnItemExit) then FOnItemExit(Sender);
end;
procedure TCustomCheckControlGroupEx.ItemKeyDown(Sender: TObject;
var Key: Word; Shift: TShiftState);
begin
if Key <> 0 then
KeyDown(Key, Shift);
end;
procedure TCustomCheckControlGroupEx.ItemKeyUp(Sender: TObject; var Key: Word;
Shift: TShiftState);
begin
if Key <> 0 then
KeyUp(Key, Shift);
end;
procedure TCustomCheckControlGroupEx.ItemKeyPress(Sender: TObject; var Key: Char);
begin
if Key <> #0 then
KeyPress(Key);
end;
procedure TCustomCheckControlGroupEx.ItemUTF8KeyPress(Sender: TObject;
var UTF8Key: TUTF8Char);
begin
UTF8KeyPress(UTF8Key);
end;
function TCustomCheckControlGroupEx.Rows: integer;
begin
if FItems.Count > 0 then
Result := ((FItems.Count-1) div Columns) + 1
else
Result := 0;
end;
procedure TCustomCheckControlGroupEx.SetAutoFill(const AValue: Boolean);
begin
if FAutoFill = AValue then exit;
FAutoFill := AValue;
DisableAlign;
try
if FAutoFill then begin
ChildSizing.EnlargeHorizontal := crsHomogenousChildResize;
ChildSizing.EnlargeVertical := crsHomogenousChildResize;
end else begin
ChildSizing.EnlargeHorizontal := crsAnchorAligning;
ChildSizing.EnlargeVertical := crsAnchorAligning;
end;
finally
EnableAlign;
end;
end;
procedure TCustomCheckControlGroupEx.SetColumnLayout(const AValue: TColumnLayout);
begin
if FColumnLayout = AValue then exit;
FColumnLayout := AValue;
if FColumnLayout = clHorizontalThenVertical then
ChildSizing.Layout := cclLeftToRightThenTopToBottom
else
ChildSizing.Layout := cclTopToBottomThenLeftToRight;
UpdateControlsPerLine;
end;
procedure TCustomCheckControlGroupEx.SetColumns(const AValue: integer);
begin
if AValue <> FColumns then begin
if (AValue < 1) then
raise Exception.Create('TCustomRadioGroup: Columns must be >= 1');
FColumns := AValue;
UpdateControlsPerLine;
end;
end;
procedure TCustomCheckControlGroupEx.SetOnGetImageIndex(const AValue: TGetImageIndexEvent);
var
i: Integer;
begin
FOnGetImageIndex := AValue;
for i := 0 to FButtonList.Count - 1 do
TCustomCheckControlEx(FButtonList[i]).OnGetImageIndex := AValue;
end;
procedure TCustomCheckControlGroupEx.SetImages(const AValue: TCustomImagelist);
var
i: Integer;
begin
if AValue = FImages then exit;
FImages := AValue;
for i:=0 to FButtonList.Count-1 do
TCustomCheckControlEx(FButtonList[i]).Images := FImages;
end;
procedure TCustomCheckControlGroupEx.SetImagesWidth(const AValue: Integer);
var
i: Integer;
begin
if AValue = FImagesWidth then exit;
FImagesWidth := AValue;
for i := 0 to FButtonList.Count - 1 do
TCustomCheckControlEx(FButtonList[i]).ImagesWidth := FImagesWidth;
end;
procedure TCustomCheckControlGroupEx.SetItems(const AValue: TStrings);
begin
if (AValue <> FItems) then
begin
FItems.Assign(AValue);
UpdateItems;
UpdateControlsPerLine;
end;
end;
procedure TCustomCheckControlGroupEx.SetReadOnly(const AValue: Boolean);
var
i: Integer;
begin
if AValue = FReadOnly then exit;
FReadOnly := AValue;
for i := 0 to FButtonList.Count -1 do
TCustomCheckControlEx(FButtonList[i]).ReadOnly := FReadOnly;
end;
procedure TCustomCheckControlGroupEx.UpdateAll;
begin
UpdateItems;
UpdateControlsPerLine;
OwnerFormDesignerModified(Self);
end;
procedure TCustomCheckControlGroupEx.UpdateControlsPerLine;
var
newControlsPerLine: LongInt;
begin
if ChildSizing.Layout = cclLeftToRightThenTopToBottom then
newControlsPerLine := Max(1, FColumns)
else
newControlsPerLine := Max(1, Rows);
ChildSizing.ControlsPerLine := NewControlsPerLine;
end;
procedure TCustomCheckControlGroupEx.UpdateInternalObjectList;
begin
UpdateItems;
end;
procedure TCustomCheckControlGroupEx.UpdateTabStops;
var
i: Integer;
btn: TCustomCheckControlEx;
begin
for i := 0 to FButtonList.Count - 1 do
begin
btn := TCustomCheckControlEx(FButtonList[i]);
btn.TabStop := btn.Checked;
end;
end;
{==============================================================================}
{ TRadioGroupExStringList }
{==============================================================================}
type
TRadioGroupExStringList = class(TStringList)
private
FRadioGroup: TCustomRadioGroupEx;
protected
procedure Changed; override;
public
constructor Create(ARadioGroup: TCustomRadioGroupEx);
procedure Assign(Source: TPersistent); override;
end;
constructor TRadioGroupExStringList.Create(ARadioGroup: TCustomRadioGroupEx);
begin
inherited Create;
FRadioGroup := ARadioGroup;
end;
procedure TRadioGroupExStringList.Assign(Source: TPersistent);
var
savedIndex: Integer;
begin
savedIndex := FRadioGroup.ItemIndex;
inherited Assign(Source);
if savedIndex < Count then FRadioGroup.ItemIndex := savedIndex;
end;
procedure TRadioGroupExStringList.Changed;
begin
inherited Changed;
if (UpdateCount = 0) then
FRadioGroup.UpdateAll
else
FRadioGroup.UpdateInternalObjectList;
FRadioGroup.FLastClickedItemIndex := FRadioGroup.FItemIndex;
end;
{==============================================================================}
{ TCustomRadioGroupEx }
{==============================================================================}
constructor TCustomRadioGroupEx.Create(AOwner: TComponent);
begin
inherited;
FItems := TRadioGroupExStringList.Create(Self);
FItemIndex := -1;
FLastClickedItemIndex := -1;
end;
procedure TCustomRadioGroupEx.Changed(Sender: TObject);
begin
CheckItemIndexChanged;
end;
procedure TCustomRadioGroupEx.CheckItemIndexChanged;
begin
if FCreatingWnd or FUpdatingItems then
exit;
if [csLoading,csDestroying]*ComponentState<>[] then exit;
UpdateRadioButtonStates;
if [csDesigning]*ComponentState<>[] then exit;
if FLastClickedItemIndex=FItemIndex then exit;
FLastClickedItemIndex:=FItemIndex;
EditingDone;
// for Delphi compatibility: OnClick should be invoked, whenever ItemIndex
// has changed
if Assigned (FOnClick) then FOnClick(Self);
// And a better named LCL equivalent
if Assigned (FOnSelectionChanged) then FOnSelectionChanged(Self);
end;
procedure TCustomRadioGroupEx.Clicked(Sender: TObject);
begin
if FIgnoreClicks then exit;
CheckItemIndexChanged;
end;
function TCustomRadioGroupEx.GetButtonCount: Integer;
var
i: Integer;
begin
Result := 0;
for i := 0 to ControlCount-1 do
if (Controls[i] is TCustomRadioButtonEx) and (Controls[i] <> FHiddenButton) then
inc(Result);
end;
function TCustomRadioGroupEx.GetButtons(AIndex: Integer): TRadioButtonEx;
begin
Result := Controls[AIndex] as TRadioButtonEx;
end;
procedure TCustomRadioGroupEx.InitializeWnd;
procedure RealizeItemIndex;
var
i: Integer;
begin
if (FItemIndex <> -1) and (FItemIndex<FButtonList.Count) then
TRadioButtonEx(FButtonList[FItemIndex]).Checked := true
else if FHiddenButton<>nil then
FHiddenButton.Checked := true;
for i:=0 to FItems.Count-1 do begin
TRadioButtonEx(FButtonList[i]).Checked := (FItemIndex = i);
end;
end;
begin
if FCreatingWnd then RaiseGDBException('TCustomRadioGroup.InitializeWnd');
FCreatingWnd := true;
UpdateItems;
inherited InitializeWnd;
RealizeItemIndex;
//debugln(['TCustomRadioGroup.InitializeWnd END']);
FCreatingWnd := false;
end;
procedure TCustomRadioGroupEx.ItemKeyDown(Sender: TObject; var Key: Word;
Shift: TShiftState);
procedure MoveSelection(HorzDiff, VertDiff: integer);
var
Count: integer;
StepSize: integer;
BlockSize : integer;
NewIndex : integer;
WrapOffset: integer;
begin
if FReadOnly then
exit;
Count := FButtonList.Count;
if FColumnLayout = clHorizontalThenVertical then begin
//add a row for ease wrapping
BlockSize := Columns * (Rows+1);
StepSize := HorzDiff + VertDiff * Columns;
WrapOffSet := VertDiff;
end
else begin
//add a column for ease wrapping
BlockSize := (Columns+1) * Rows;
StepSize := HorzDiff * Rows + VertDiff;
WrapOffSet := HorzDiff;
end;
NewIndex := ItemIndex;
repeat
Inc(NewIndex, StepSize);
if (NewIndex >= Count) or (NewIndex < 0) then begin
NewIndex := (NewIndex + WrapOffSet + BlockSize) mod BlockSize;
// Keep moving in the same direction until in valid range
while NewIndex >= Count do
NewIndex := (NewIndex + StepSize) mod BlockSize;
end;
until (NewIndex = ItemIndex) or TCustomCheckControlEx(FButtonList[NewIndex]).Enabled;
ItemIndex := NewIndex;
TCustomCheckControlEx(FButtonList[ItemIndex]).SetFocus;
Key := 0;
end;
begin
if Shift=[] then begin
case Key of
VK_LEFT: MoveSelection(-1,0);
VK_RIGHT: MoveSelection(1,0);
VK_UP: MoveSelection(0,-1);
VK_DOWN: MoveSelection(0,1);
end;
end;
if Key <> 0 then
KeyDown(Key, Shift);
end;
procedure TCustomRadioGroupEx.ReadState(AReader: TReader);
begin
FReading := True;
inherited ReadState(AReader);
FReading := False;
if (FItemIndex < -1) or (FItemIndex >= FItems.Count) then
FItemIndex := -1;
FLastClickedItemIndex := FItemIndex;
end;
procedure TCustomRadioGroupEx.SetItemIndex(const AValue: integer);
var
oldItemIndex: LongInt;
oldIgnoreClicks: Boolean;
begin
if (AValue = FItemIndex) or FReadOnly then exit;
// needed later if handle isn't allocated
oldItemIndex := FItemIndex;
if FReading then
FItemIndex := AValue
else begin
if (AValue < -1) or (AValue >= FItems.Count) then
raise Exception.CreateFmt(rsIndexOutOfBounds, [ClassName, AValue, FItems.Count-1]);
if HandleAllocated then
begin
// the radiobuttons are grouped by the widget interface
// and some does not allow to uncheck all buttons in a group
// Therefore there is a hidden button
FItemIndex := AValue;
oldIgnoreClicks := FIgnoreClicks;
FIgnoreClicks := true;
try
if (FItemIndex <> -1) then
TCustomCheckControlEx(FButtonList[FItemIndex]).Checked := true
else
FHiddenButton.Checked := true;
// uncheck old radiobutton
if (OldItemIndex <> -1) then begin
if (OldItemIndex >= 0) and (OldItemIndex < FButtonList.Count) then
TCustomCheckControlEx(FButtonList[OldItemIndex]).Checked := false
end else
FHiddenButton.Checked := false;
finally
FIgnoreClicks := OldIgnoreClicks;
end;
// this has automatically unset the old button. But they do not recognize
// it. Update the states.
CheckItemIndexChanged;
UpdateTabStops;
OwnerFormDesignerModified(Self);
end else
begin
FItemIndex := AValue;
// maybe handle was recreated. issue #26714
FLastClickedItemIndex := -1;
// trigger event to be delphi compat, even if handle isn't allocated.
// issue #15989
if (AValue <> oldItemIndex) and not FCreatingWnd then
begin
if Assigned(FOnClick) then FOnClick(Self);
if Assigned(FOnSelectionChanged) then FOnSelectionChanged(Self);
FLastClickedItemIndex := FItemIndex;
end;
end;
end;
end;
procedure TCustomRadioGroupEx.UpdateItems;
var
i: integer;
button: TCustomCheckControlEx;
begin
if FUpdatingItems then exit;
FUpdatingItems := true;
try
// destroy radiobuttons, if there are too many
while FButtonList.Count > FItems.Count do
begin
TObject(FButtonList[FButtonList.Count-1]).Free;
FButtonList.Delete(FButtonList.Count-1);
end;
// create as many TRadioButtons as needed
while (FButtonList.Count < FItems.Count) do
begin
button := TRadioButtonEx.Create(Self);
with TCustomCheckControlEx(button) do
begin
Name := 'RadioButtonEx' + IntToStr(FButtonList.Count);
OnClick := @Self.Clicked;
OnChange := @Self.Changed;
OnEnter := @Self.ItemEnter;
OnExit := @Self.ItemExit;
OnKeyDown := @Self.ItemKeyDown;
OnKeyUp := @Self.ItemKeyUp;
OnKeyPress := @Self.ItemKeyPress;
OnUTF8KeyPress := @Self.ItemUTF8KeyPress;
ParentFont := True;
ReadOnly := Self.ReadOnly;
BorderSpacing.CellAlignHorizontal := ccaLeftTop;
BorderSpacing.CellAlignVertical := ccaCenter;
ControlStyle := ControlStyle + [csNoDesignSelectable];
end;
FButtonList.Add(button);
end;
if FHiddenButton = nil then begin
FHiddenButton := TRadioButtonEx.Create(nil);
with FHiddenButton do
begin
Name := 'HiddenRadioButton';
Visible := False;
ControlStyle := ControlStyle + [csNoDesignSelectable, csNoDesignVisible];
end;
end;
if (FItemIndex >= FItems.Count) and not (csLoading in ComponentState) then
FItemIndex := FItems.Count-1;
if FItems.Count > 0 then
begin
// to reduce overhead do it in several steps
// assign Caption and then Parent
for i:=0 to FItems.Count-1 do
begin
button := TCustomCheckControlEx(FButtonList[i]);
button.Caption := FItems[i];
button.Parent := Self;
end;
FHiddenButton.Parent := Self;
// the checked and unchecked states can be applied only after all other
for i := 0 to FItems.Count-1 do
begin
button := TCustomCheckControlEx(FButtonList[i]);
button.Checked := (i = FItemIndex);
button.Visible := true;
end;
//FHiddenButton must remain the last item in Controls[], so that Controls[] is in sync with Items[]
Self.RemoveControl(FHiddenButton);
Self.InsertControl(FHiddenButton);
if HandleAllocated then
FHiddenButton.HandleNeeded;
FHiddenButton.Checked := (FItemIndex = -1);
UpdateTabStops;
end;
finally
FUpdatingItems := false;
end;
end;
procedure TCustomRadioGroupEx.UpdateRadioButtonStates;
var
i: Integer;
begin
if FReadOnly then
exit;
FItemIndex := -1;
FHiddenButton.Checked;
for i:=0 to FButtonList.Count-1 do
if TCustomRadioButtonEx(FButtonList[i]).Checked then FItemIndex := i;
UpdateTabStops;
end;
{==============================================================================}
{ TCheckGroupExStringList }
{==============================================================================}
type
TCheckGroupExStringList = class(TStringList)
private
FCheckGroup: TCustomCheckGroupEx;
procedure RestoreCheckStates(const AStates: TByteDynArray);
procedure SaveCheckStates(out AStates: TByteDynArray);
protected
procedure Changed; override;
public
constructor Create(ACheckGroup: TCustomCheckGroupEx);
procedure Delete(AIndex: Integer); override;
end;
constructor TCheckGroupExStringList.Create(ACheckGroup: TCustomCheckGroupEx);
begin
inherited Create;
FCheckGroup := ACheckGroup;
end;
procedure TCheckGroupExStringList.Changed;
begin
inherited Changed;
if UpdateCount = 0 then
FCheckGroup.UpdateAll
else
FCheckGroup.UpdateInternalObjectList;
end;
procedure TCheckGroupExStringList.Delete(AIndex: Integer);
// Deleting destroys the checked state of the items -> we must save and restore it
// Issue https://bugs.freepascal.org/view.php?id=34327.
var
b: TByteDynArray;
i: Integer;
begin
SaveCheckStates(b);
inherited Delete(AIndex);
for i:= AIndex to High(b)-1 do b[i] := b[i+1];
SetLength(b, Length(b)-1);
RestoreCheckStates(b);
end;
procedure TCheckGroupExStringList.RestoreCheckStates(const AStates: TByteDynArray);
var
i: Integer;
begin
Assert(Length(AStates) = FCheckGroup.Items.Count);
for i:=0 to FCheckgroup.Items.Count-1 do begin
FCheckGroup.Checked[i] := AStates[i] and 1 <> 0;
FCheckGroup.CheckEnabled[i] := AStates[i] and 2 <> 0;
end;
end;
procedure TCheckGroupExStringList.SaveCheckStates(out AStates: TByteDynArray);
var
i: Integer;
begin
SetLength(AStates, FCheckgroup.Items.Count);
for i:=0 to FCheckgroup.Items.Count-1 do begin
AStates[i] := 0;
if FCheckGroup.Checked[i] then inc(AStates[i]);
if FCheckGroup.CheckEnabled[i] then inc(AStates[i], 2);
end;
end;
{==============================================================================}
{ TCustomCheckGroupEx }
{==============================================================================}
constructor TCustomCheckGroupEx.Create(AOwner: TComponent);
begin
inherited;
FItems := TCheckGroupExStringList.Create(Self);
end;
procedure TCustomCheckGroupEx.Clicked(Sender: TObject);
var
index: Integer;
begin
index := FButtonList.IndexOf(Sender);
if index < 0 then exit;
DoClick(index);
end;
procedure TCustomCheckGroupEx.DefineProperties(Filer: TFiler);
begin
inherited DefineProperties(Filer);
Filer.DefineBinaryProperty('Data', @ReadData, @WriteData, FItems.Count > 0);
end;
procedure TCustomCheckGroupEx.DoClick(AIndex: integer);
begin
if [csLoading,csDestroying, csDesigning] * ComponentState <> [] then exit;
EditingDone;
if Assigned(FOnItemClick) then FOnItemClick(Self, AIndex);
end;
function TCustomCheckGroupEx.GetButtonCount: Integer;
var
i: Integer;
begin
Result := 0;
for i := 0 to ControlCount-1 do
if (Controls[i] is TCustomCheckBoxEx) then
inc(Result);
end;
function TCustomCheckGroupEx.GetButtons(AIndex: Integer): TCheckBoxEx;
begin
Result := Controls[AIndex] as TCheckBoxEx;
end;
function TCustomCheckGroupEx.GetChecked(AIndex: Integer): Boolean;
begin
if (AIndex < -1) or (AIndex >= FItems.Count) then
RaiseIndexOutOfBounds(AIndex);
Result := TCustomCheckControlEx(FButtonList[AIndex]).Checked;
end;
function TCustomCheckGroupEx.GetCheckEnabled(AIndex: integer): boolean;
begin
if (AIndex < -1) or (AIndex >= FItems.Count) then
RaiseIndexOutOfBounds(AIndex);
Result := TCustomCheckControlEx(FButtonList[AIndex]).Enabled;
end;
procedure TCustomCheckGroupEx.Loaded;
begin
inherited Loaded;
UpdateItems;
end;
procedure TCustomCheckGroupEx.RaiseIndexOutOfBounds(AIndex: integer);
begin
raise Exception.CreateFmt(rsIndexOutOfBounds, [ClassName, AIndex, FItems.Count - 1]);
end;
procedure TCustomCheckGroupEx.ReadData(Stream: TStream);
var
ChecksCount: integer;
Checks: string;
i: Integer;
v: Integer;
begin
ChecksCount := ReadLRSInteger(Stream);
if ChecksCount > 0 then begin
SetLength(Checks, ChecksCount);
Stream.ReadBuffer(Checks[1], ChecksCount);
for i:=0 to ChecksCount-1 do begin
v := ord(Checks[i+1]);
Checked[i] := ((v and 1) > 0);
CheckEnabled[i] := ((v and 2) > 0);
end;
end;
end;
procedure TCustomCheckGroupEx.SetChecked(AIndex: integer; const AValue: boolean);
begin
if (AIndex < -1) or (AIndex >= FItems.Count) then
RaiseIndexOutOfBounds(AIndex);
// disable OnClick
TCheckBox(FButtonList[AIndex]).OnClick := nil;
// set value
TCheckBox(FButtonList[AIndex]).Checked := AValue;
// enable OnClick
TCheckBox(FButtonList[AIndex]).OnClick := @Clicked;
end;
procedure TCustomCheckGroupEx.SetCheckEnabled(AIndex: integer;
const AValue: boolean);
begin
if (AIndex < -1) or (AIndex >= FItems.Count) then
RaiseIndexOutOfBounds(AIndex);
TCustomCheckControlEx(FButtonList[AIndex]).Enabled := AValue;
end;
procedure TCustomCheckGroupEx.UpdateItems;
var
i: integer;
btn: TCustomCheckControlEx;
begin
if FUpdatingItems then exit;
FUpdatingItems := true;
try
// destroy checkboxes, if there are too many
while FButtonList.Count > FItems.Count do begin
TObject(FButtonList[FButtonList.Count-1]).Free;
FButtonList.Delete(FButtonList.Count-1);
end;
// create as many TCheckBoxes as needed
while (FButtonList.Count < FItems.Count) do begin
btn := TCheckBoxEx.Create(Self);
with TCheckBoxEx(btn) do begin
Name := 'CheckBoxEx' + IntToStr(FButtonList.Count);
OnClick := @Self.Clicked;
OnKeyDown := @Self.ItemKeyDown;
OnKeyUp := @Self.ItemKeyUp;
OnKeyPress := @Self.ItemKeyPress;
OnUTF8KeyPress := @Self.ItemUTF8KeyPress;
AutoSize := False;
Parent := Self;
ParentFont := true;
ReadOnly := Self.ReadOnly;
BorderSpacing.CellAlignHorizontal := ccaLeftTop;
BorderSpacing.CellAlignVertical := ccaCenter;
ControlStyle := ControlStyle + [csNoDesignSelectable];
end;
FButtonList.Add(btn);
end;
for i:=0 to FItems.Count-1 do begin
btn := TCustomCheckControlEx(FButtonList[i]);
btn.Caption := FItems[i];
end;
finally
FUpdatingItems := false;
end;
end;
procedure TCustomCheckGroupEx.WriteData(Stream: TStream);
var
ChecksCount: integer;
Checks: string;
i: Integer;
v: Integer;
begin
ChecksCount := FItems.Count;
WriteLRSInteger(Stream, ChecksCount);
if ChecksCount > 0 then begin
SetLength(Checks, ChecksCount);
for i := 0 to ChecksCount-1 do begin
v := 0;
if Checked[i] then inc(v, 1);
if CheckEnabled[i] then inc(v, 2);
Checks[i+1] := chr(v);
end;
Stream.WriteBuffer(Checks[1], ChecksCount);
end;
end;
end.