ikathegreat
ikathegreat

Reputation: 2321

Clicking CheckListBox item to toggle the check state on that item

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

Answers (3)

Marisco
Marisco

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

Remy Lebeau
Remy Lebeau

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

Sertac Akyuz
Sertac Akyuz

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

Related Questions