问题
[Compiler: Delphi XE2]
I have spent all day yesterday trying all sorts of ways to accomplish this specific task but they have all ended in the same result.
Using TRZCheckGroup and this example to see what has been checked ect..
procedure TFrmMain.cbOptionsChange(Sender: TObject; Index: Integer; NewState: TCheckBoxState);
var
ItmIndex0, ItmIndex1: Integer;
begin
{ Initialize ItemIndex's }
ItmIndex0 := -1;
ItmIndex1 := -1;
{ Return the position Index of the string's(0 and 1) }
ItmIndex0 := cbOptions.Items.IndexOf('One');
ItmIndex1 := cbOptions.Items.IndexOf('Two');
{ Which CheckBox has been Checked }
cbOptions.ItemChecked[ItmIndex0] := True;
cbOptions.ItemChecked[ItmIndex1] := False;
end;
NOTE: ^This is not my Final code just an example of how i am dealing with the CheckBoxes.
things like -
if cbOptions.ItemChecked[ItmIndex0] then
cbOptions.ItemChecked[ItmIndex1] := False
else cbOptions.ItemChecked[ItmIndex1] := True;
They work the first time then it always evaluates to true which i understand why. The else bit will only be in action when i un-check the first CheckBox, which is obviously not my desired result.
Seems the Event stops working and on some of my attempts it has fired twice for some reason.
The NewState Param on cbListOptionsChange, what is this and can it help me in anyway?
Any help on this will be much appreciated.
Thanks.
if cbOptions.ItemChecked[ItmIndex0] then
cbOptions.ItemChecked[ItmIndex1] := False
else if cbOptions.ItemChecked[ItmIndex1] then
cbOptions.ItemChecked[ItmIndex0] := False;
See something like this if the second CheckBox is checked then i check the first one it works as required, but then obviously after that you cannot check the second CheckBox no more.
Ken White - Snippet(Working). Replaced the name of the component with Default as people could get confused else, sometimes helps with default naming to save future questions.
procedure TForm1.RzCheckGroup1Change(Sender: TObject; Index: Integer; NewState: TCheckBoxState);
var
i: Integer;
begin
// Keep this event from being fired again while we're here.
// Your code isn't clear about what the actual name of the
// component or this event, (the event is named `cbListOptionsChange`,
// but your code references `cbOptions` - I don't know which is
// correct, so change it if needed in the next line and
// the one in the `finally` block below. I'm using `cbListOptions`
// here.
RzCheckGroup1.OnChange := nil;
try
// If we're getting notified of a new item being checked...
if NewState = cbChecked then
begin
// Iterate through the items, unchecking all that aren't
// at the index that just became checked.
// I wouldn't use `for..in`, because the ordering works better here
for i := 0 to RzCheckGroup1.Items.Count - 1 do
if i <> Index then
RzCheckGroup1.ItemChecked[i] := False; // Ryan - Just changed to this from this cbListOptions.Items[i].Checked := False;
end;
// Ryan - Uncomment these two lines if you want one of them to be Checked at all times, this will set the CheckBox you are trying to Uncheck to Checked.
//if not RzCheckGroup1.ItemChecked[Index] then
// RzCheckGroup1.ItemChecked[Index] := True;
finally
// Reconnect the event
RzCheckGroup1.OnChange := RzCheckGroup1Change;
end;
end;
回答1:
I'm not familiar with TRZCheckGroup
, but your current code will always check the item at ItmIndex0
and uncheck the other.
TCheckBoxState
is defined in the Delphi documentation as
TCheckBoxState = (
cbUnchecked,
cbChecked,
cbGrayed
);
So NewState
appears to tell you the newly set state of the CheckBox
, and Index
tells you which checkbox is changing. Most of the time, cbGrayed
isn't used, because it indicates the value has never been set; it's usually only useful when you're reading a BOOLEAN (or bit) column in a database and it's NULL.
This event isn't meant really to alternate the state of two checkboxes, it would appear; it's meant to just let you react when a single item (among the group of items) changes it's state:
procedure TFrmMain.cbListOptionsChange(Sender: TObject; Index: Integer;
NewState: TCheckBoxState);
begin
case NewState of
cbUnchecked: // Do whatever when cbOptions.Items[Index] is unchecked
cbChecked: // Do whatever when cbOptions.Items[Index] is checked
cbGrayed: // Usually ignored unless NULL in db column is indicated
end;
end;
To just reverse the state of two checkboxes (changing one to the alternate state and the other to it's opposite), you can use something like this (uses two standard TCheckBox
controls with the same event defined for both of their OnClick
events):
procedure TFrmMain.CheckBoxClick(Sender: Object);
var
ChkBox: TCheckBox;
BoxToToggle: TCheckBox;
begin
// If you're sure the event is only for TCheckBox
ChangingBox := TCheckBox(Sender);
// If there's a chance it's used for something else
// if (Sender is TCheckBox) then
// begin
// ChangingBox := TCheckBox(Sender);
//
// or
// ChangingBox := Sender as TCheckBox
if ChangingBox = CheckBox1 then
BoxToToggle := CheckBox2
else
BoxToToggle := CheckBox1;
// Disable this event for both checkboxes, so it doesn't
// fire recursively
ChangingBox.OnClick := nil;
BoxToToggle.OnClick := nil;
try
BoxToToggle.Checked := not ChangingBox.Checked;
finally
// Reconnect event handlers
ChangingBox.OnClick := CheckBoxClick;
BoxToToggle.OnClick := CheckBoxClick;
end;
However, if you're just dealing with a list of items where one should be checked and all the others unchecked, you should be using a TRadioGroup
instead. It gives you this behavior automatically. Using checkboxes is against the normal Windows GUI behavior, and will confuse your users.
With that being said (and with my strong objections to doing this!), and untested because I don't have the component you're using, you can try this (this is SO against my better judgement to even write!):
procedure TFrmMain.cbListOptionsChange(Sender: TObject; Index: Integer;
NewState: TCheckBoxState);
var
i: Integer;
begin
// Keep this event from being fired again while we're here.
// Your code isn't clear about what the actual name of the
// component or this event, (the event is named `cbListOptionsChange`,
// but your code references `cbOptions` - I don't know which is
// correct, so change it if needed in the next line and
// the one in the `finally` block below. I'm using `cbListOptions`
// here.
cbListOptions.OnChange := nil;
try
// If we're getting notified of a new item being checked...
if NewState = cbChecked then
begin
// Iterate through the items, unchecking all that aren't
// at the index that just became checked.
// I wouldn't use `for..in`, because the ordering works better here
for i := 0 to cbListOptions.Items.Count - 1 do
if i <> Index then
cbListOptions.Items[i].Checked := False;
end;
finally
// Reconnect the event
cbListOptions.OnChange := cbListOptionsChange;
end;
end;
To make sure it's clear, I think this is a very bad idea, and if you were working for me I'd not allow it. It's simply wrong to do something so opposed to the expected Windows behavior when there's a proper option available, IMO.
回答2:
This example uses three TCheckbox controls.
Drop three TCheckbox controls onto a form. For this example, I've named them
cbOpenorders, cbClosedorders and cbAllorders
Add an event to the cbOpenorders.Onclick property in the object inspector.
Then, set the OnClick event properties of cbClosedorders and cbAllorders to this cbOpenorders event. All three boxes will call the same event handler which reduces the amount of code required.
procedure TFrmPreorderViewDialog.cbOpenOrdersClick(Sender: TObject);
begin
if TCheckbox(Sender).Checked then
begin
cbOpenorders.Checked := (TCheckbox(Sender) = cbOpenorders);
cbClosedorders.checked := (TCheckbox(Sender) = cbClosedorders);
cbAllorders.checked := (TCheckbox(Sender) = cbAllorders);
End;
end;
In this example, the user would have the limited ability to check one box, or no boxes.
来源:https://stackoverflow.com/questions/10903132/only-one-checkbox-checked-at-a-time