Link to home
Start Free TrialLog in
Avatar of AbhiJeet
AbhiJeet

asked on

delphi - 2

in a panel i have two check boxes. User can only check one checkbox at a time. It should show a message and doesn't allow to check the check box if one is already checked.

? to implement?( don't tell me to take radio button)
Avatar of bokist
bokist
Flag of Hungary image

Better use TCheckListBox instead of TCheckBox.
In attached example, I'm using it application wide.
Regards,
  Steve
Unit1.pas
Unit1.dfm
Avatar of Geert G
take radiobutton :)
Avatar of AbhiJeet
AbhiJeet

ASKER

i am searching a way to implement this taking tcheckbox only, any idea?
It's your choice at the end to use the component you prefer.
To have the TCheckBoxes do exactly what you mentioned then you have many options:
procedure TForm1.CheckBox1Click(Sender: TObject);
begin
  if CheckBox1.Checked then
    CheckBox2.Enabled := False
  else
    CheckBox2.Enabled := True;
end;

procedure TForm1.CheckBox2Click(Sender: TObject);
begin
  if CheckBox1.Checked then
    CheckBox1.Enabled := False
  else
    CheckBox1.Enabled := True;
end;

Open in new window


Or:

procedure TForm1.CheckBox1Click(Sender: TObject);
begin
  if CheckBox2.Checked then
    begin
      CheckBox1.Checked := False;
      CheckBox1.Hint := 'uncheck the other one first';  // or whatever hint you see better
      CheckBox1.ShowHint := True;
    end;

  if not CheckBox1.Checked then
    begin
      CheckBox2.Hint := '';
      CheckBox2.ShowHint := False;
    end;
end;

procedure TForm1.CheckBox2Click(Sender: TObject);
begin
  if CheckBox1.Checked then
    begin
      (Sender as TCheckBox).Checked := False;
      (Sender as TCheckBox).Hint := 'uncheck the other one first';
      (Sender as TCheckBox).ShowHint := True;
    end;

  if not CheckBox2.Checked then
    begin
      CheckBox1.Hint := '';
      CheckBox1.ShowHint := False;
    end;
end;

Open in new window

checkboxes are getting created dynamically, so i may not able to use these events easily.

Any other nice workaround?
and obviously there are lot other components in the panel.
create procedure as jimyX suggest and attach it to runtime created components in code:
chk1.OnClick := CheckBox1Click;
chk2.OnClick := CheckBox2Click;

Open in new window

ASKER CERTIFIED SOLUTION
Avatar of jimyX
jimyX

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
What is wrong with my example?
Did you try it?
It can work with runtime created components also.
jimmy, doesn't the onclick fire when setting the checked ?

i came up with this... somewhat similar
pasfile:
unit uMain;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, ExtCtrls;

type
  TfrmCheckboxMaster = class(TForm)
    pnlCheckOptions: TPanel;
    chkItem1: TCheckBox;
    chkItem3: TCheckBox;
    chkItem2: TCheckBox;
    chkItem4: TCheckBox;
    procedure chkSingleMasterCheckboxClick(Sender: TObject);
  private
  public
  end;

var
  frmCheckboxMaster: TfrmCheckboxMaster;

implementation

{$R *.dfm}

procedure TfrmCheckboxMaster.chkSingleMasterCheckboxClick(Sender: TObject);
var
  MasterItem, SlaveItem: TCheckbox;
  MasterContainer: TWinControl;
  I: Integer;
begin
  // The master is the checkbox clicked
  // if checked, all other checkboxes in the same panel are disabled and unchecked
  // if unchecked all other checkboxes in the same panel are enabled and unchecked
  MasterItem := Sender as TCheckbox;
  // Find the parent of the Master checkbox
  MasterContainer := MasterItem.Parent;
  // Traverse all items in same panel
  for I := 0 to MasterContainer.ControlCount - 1 do
  begin
    // it the item is of the same class in this container, but not the same object
    if (MasterContainer.Controls[I].ClassType = MasterItem.ClassType) and
      (MasterContainer.Controls[I] <> MasterItem) then
    begin
      // Placeholder for the slave item
      SlaveItem := MasterContainer.Controls[I] as TCheckBox;
      // detach the onclick event
      SlaveItem.OnClick := Nil;
      try
        // If master is checked
        if MasterItem.Checked then
        begin
          // Uncheck the slave item
          if SlaveItem.Checked then
            SlaveItem.Checked := False;
          // Disable the slave item
          SlaveItem.Enabled := False;
        end
          else // master is not checked
        begin
          if not SlaveItem.Enabled then
            SlaveItem.Enabled := True;
        end;
      finally
        // Attach the onclick event again
        SlaveItem.OnClick := MasterItem.OnClick;
      end;
    end;
  end;
end;

end.

Open in new window


dfm:
object frmCheckboxMaster: TfrmCheckboxMaster
  Left = 689
  Top = 164
  Caption = 'frmCheckboxMaster'
  ClientHeight = 346
  ClientWidth = 637
  Color = clBtnFace
  Font.Charset = DEFAULT_CHARSET
  Font.Color = clWindowText
  Font.Height = -11
  Font.Name = 'Tahoma'
  Font.Style = []
  OldCreateOrder = False
  PixelsPerInch = 96
  TextHeight = 13
  object pnlCheckOptions: TPanel
    Left = 24
    Top = 40
    Width = 489
    Height = 249
    TabOrder = 0
    object chkItem1: TCheckBox
      Left = 48
      Top = 16
      Width = 97
      Height = 17
      Caption = 'chkItem1'
      TabOrder = 0
      OnClick = chkSingleMasterCheckboxClick
    end
    object chkItem3: TCheckBox
      Left = 120
      Top = 48
      Width = 97
      Height = 17
      Caption = 'chkItem3'
      TabOrder = 1
      OnClick = chkSingleMasterCheckboxClick
    end
    object chkItem2: TCheckBox
      Left = 72
      Top = 72
      Width = 97
      Height = 17
      Caption = 'chkItem2'
      TabOrder = 2
      OnClick = chkSingleMasterCheckboxClick
    end
    object chkItem4: TCheckBox
      Left = 112
      Top = 112
      Width = 97
      Height = 17
      Caption = 'chkItem4'
      TabOrder = 3
      OnClick = chkSingleMasterCheckboxClick
    end
  end
end

Open in new window


basically, assign the onclick of all the checkboxes to this event

i see jimmy also hinted towards your next question
>   onclick fire when setting the checked
Correct Geert, that's why I set it back to false.

It was obvious that there are two ways to accomplish what the asker is after, either disable the other checkboxes or notify the user that there is a checkbox already checked and undo the checking.

The difference is not that much since you have to iterate through the TCheckBoxes on the panel.

For disabling the other checkboxes same code will be used but the procedure 'ChkBxsTicked' would be:
procedure ChkBxsTicked(Pnl:TPanel; Sndr: TObject);
var
  I: Integer;
  OthrChkBx: TControl;
begin
  if Pnl <> nil then
    begin
      for I := 0 to Pnl.ControlCount - 1 do
        begin
          if Pnl.Controls[I] is TCheckBox then
            begin
              OthrChkBx := Pnl.Controls[I];
              if (OthrChkBx is TCheckBox) and (OthrChkBx <> Sndr) then
                begin
                  if (Sndr AS TCheckbox).Checked then
                    (OthrChkBx as TCheckBox).Enabled := False
                  else
                    (OthrChkBx as TCheckBox).Enabled := True;
                end;
            end;
        end;
    end;
end;

Open in new window

fwiw ... pnl is the same as Sender.Parent
Missed that. You are absolutely right.
Here is an example that registers any check box created dynamically and handles the click independent of the form

unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls;

type
  TNotifyList = class(TComponent)
  private
    FList: TList;
    FOnClick: TNotifyEvent;
    procedure DoClickCheck(Sender: TObject);
    function GetCheckedItem(ASkip: TCheckBox; out AItem: TCheckBox): Boolean;
    procedure SetOnClick(const Value: TNotifyEvent);
  protected
    procedure Notification(AComponent: TComponent; Operation: TOperation); override;
  public
    constructor Create(Aowner: TComponent); override;
    destructor Destroy; override;
    procedure RegisterItem(AItem: TCheckBox);
    property OnClick: TNotifyEvent read FOnClick write SetOnClick;
  end;

  TForm1 = class(TForm)
    procedure FormCreate(Sender: TObject);
  private
    FNotifyList: TNotifyList;
    procedure OtherItemCheckedMessage(Sender: TObject);
    procedure TestCreateCheckBox(Top: Integer);
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

{ TNotifyList }

constructor TNotifyList.Create(AOwner: TComponent);
begin
  inherited;
  FList := TList.Create;
end;

destructor TNotifyList.Destroy;
begin
  FreeAndNil(FList);
  inherited;
end;

procedure TNotifyList.DoClickCheck(Sender: TObject);
var
  Chk, Chk2: TCheckBox;
  Index: Integer;
begin
  Index := FList.IndexOf(Sender);
  if Index >= 0 then
  begin
    Chk := TCheckBox(FList[Index]);
    if Chk.Checked and GetCheckedItem(Chk, Chk2) then
    begin
      Chk.OnClick := nil;
      Chk.Checked := False;
      Chk.OnClick := DoClickCheck;
      if Assigned(FOnClick) then
        FOnClick(Chk2);
    end;
  end;
end;

function TNotifyList.GetCheckedItem(ASkip: TCheckBox; out AItem: TCheckBox): Boolean;
var
  I: Integer;
begin
  for I := 0 to FList.Count -1 do
    if TCheckBox(FList[I]).Checked then
    begin
      AItem := TCheckBox(FList[I]);
      if AItem.Checked and (ASkip <> AItem) then
      begin
        Result := True;
        Exit;
      end;
    end;
  Result := False;
end;

procedure TNotifyList.Notification(AComponent: TComponent; Operation: TOperation);
var
  Index: Integer;
begin
  inherited Notification(AComponent, Operation);
  if (Operation = opRemove) and (AComponent is TCheckBox) then
  begin
    Index := FList.IndexOf(AComponent);
    if Index >= 0 then
      FList.Delete(Index);
  end;
end;

procedure TNotifyList.RegisterItem(AItem: TCheckBox);
begin
  if FList.IndexOf(AItem) < 0 then
  begin
    FList.Add(AItem);
    AItem.OnClick := DoClickCheck;
  end;
end;

procedure TNotifyList.SetOnClick(const Value: TNotifyEvent);
begin
  FOnClick := Value;
end;

//Test
procedure TForm1.FormCreate(Sender: TObject);
var
  I: Integer;
begin
  FNotifyList := TNotifyList.Create(Self);
  FNotifyList.OnClick := OtherItemCheckedMessage;
  //Create five check boxes
  for I := 1 to 5 do
    TestCreateCheckBox(I * 20);
end;

procedure TForm1.OtherItemCheckedMessage(Sender: TObject);
begin
  if Sender is TCheckBox then
    ShowMessage(TCheckBox(Sender).Caption + ' is already checked');
end;

procedure TForm1.TestCreateCheckBox(Top: Integer);
var
  Chk: TCheckBox;
begin
  Chk := TCheckBox.Create(Self);
  Chk.Parent := Self;
  Chk.Top := Top;
  Chk.Left := 8;
  Chk.Caption := 'CheckBox' + IntToStr(Top);
  FNotifyList.RegisterItem(Chk);
end;

end.

Open in new window


and the form
object Form1: TForm1
  Left = 192
  Top = 124
  Width = 870
  Height = 640
  Caption = 'Form1'
  Color = clBtnFace
  Font.Charset = DEFAULT_CHARSET
  Font.Color = clWindowText
  Font.Height = -11
  Font.Name = 'MS Sans Serif'
  Font.Style = []
  OldCreateOrder = False
  OnCreate = FormCreate
  PixelsPerInch = 96
  TextHeight = 13
end

Open in new window