Reputation: 2321
I'm trying to toggle the TCheckListBox
item state, so if it's checked, then uncheck it and vice versa. This normally only toggles when you click in the physical box itself, but I'd like it to toggle when the user clicks anywhere on the item row as well.
The code below works, but now prevents the item from being toggle-able when they click in the physical box (e.g. if it's currently unchecked then they click in the box, it stays unchecked).
Is it possible to have both behaviors or what is wrong in the code?
procedure TMainFrm.CheckListBoxModulesMouseDown(Sender: TObject;
Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
var
APoint: TPoint;
Index: integer;
begin
if (Button = mbLeft) then
begin
APoint.X := X;
APoint.Y := Y;
Index := CheckListBoxModules.ItemAtPos(APoint, True);
if(Index > -1) then
begin
CheckListBoxModules.Checked[Index] := not CheckListBoxModules.Checked[Index];
end;
end;
end;
Upvotes: 0
Views: 9862
Reputation: 125
I've add a little code to Sertac's answer to check on witch key pressed so we can also move up and down with the keyboard.
procedre TfrmRelBalanceteGerencial.cklGrupoContasClick(Sender: TObject);
begin
inherited;
if HiWord(GetKeyState(VK_UP)) <> 0 or HiWord(GetKeyState(VK_DOWN)) then
begin
// do nothing
end
else
begin
cklGrupoContas.Checked[cklGrupoContas.ItemIndex] := not cklGrupoContas.Checked[cklGrupoContas.ItemIndex];
end;
end;
Upvotes: 0
Reputation: 595369
This is the type of situation where a little bit of hit testing helps. Simply don't toggle the check state manually if the user is clicking on the checkbox. You can use logic similar to what TCheckListBox
uses internally to determine if the user is clicking on the checkbox or not. Let the TCheckListBox
handle the click if the mouse is over the checkbox. Do your manual toggle otherwise. For example:
type
TCheckListBoxAccess = class(TCheckListBox)
end;
procedure TMainFrm.CheckListBoxModulesMouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
var
Index: Integer;
procedure DoToggle;
var
State: TCheckBoxState;
begin
if (Index >= 0) and (Index < CheckListBoxModules.Items.Count) and CheckListBoxModules.ItemEnabled[Index] then
begin
State := CheckListBoxModules.State[Index];
case State of
cbUnchecked:
if CheckListBoxModules.AllowGrayed then State := cbGrayed else State := cbChecked;
cbChecked: State := cbUnchecked;
cbGrayed: State := cbChecked;
end;
CheckListBoxModules.State[Index] := State;
TCheckListBoxAccess(CheckListBoxModules).ClickCheck;
end;
end;
begin
if Button = mbLeft then
begin
Index := CheckListBoxModules.ItemAtPos(Point(X,Y),True);
if (Index <> -1) and CheckListBoxModules.ItemEnabled[Index] then
if not TCheckListBoxAccess(CheckListBoxModules).UseRightToLeftAlignment then
begin
if X - CheckListBoxModules.ItemRect(Index).Left >= TCheckListBoxAccess(CheckListBoxModules).GetCheckWidth then
DoToggle;
end
else
begin
Dec(X, CheckListBoxModules.ItemRect(Index).Right - TCheckListBoxAccess(CheckListBoxModules).GetCheckWidth);
if (X <= 0) or (X >= TCheckListBoxAccess(CheckListBoxModules).GetCheckWidth) then
DoToggle;
end;
end;
end;
Upvotes: 3
Reputation: 54772
Your problem is, after you've toggled the state, the box itself is toggling it again since the check is hit.
A workaround would be to undo this built-in checking behavior:
procedure TMainFrm.CheckListBoxModulesClickCheck(Sender: TObject);
begin
CheckListBoxModules.Checked[CheckListBoxModules.ItemIndex] :=
not CheckListBoxModules.Checked[CheckListBoxModules.ItemIndex];
end;
This works because OnClickCheck
is not fired when you change the check state by code. Only, state change caused by a mouse click in the check box is reversed.
A better solution would be to not to toggle the state at all when the click is on the check box. If you decide to implement this solution, see code in TCheckListBox.GetCheckSize
for how VCL determines the checkbox size and TCheckListBox.MouseDown
for how it decides the position.
Upvotes: 3