【问题标题】:Only One CheckBox Checked at a time一次只检查一个复选框
【发布时间】:2012-06-05 18:51:29
【问题描述】:

[编译器:Delphi XE2]

昨天我花了一整天的时间尝试各种方法来完成这个特定的任务,但它们都以相同的结果结束。

使用 TRZCheckGroup 和此示例查看已检查的内容等..

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;

注意:^这不是我的最终代码,只是我如何处理复选框的示例。

类似的东西 -

if cbOptions.ItemChecked[ItmIndex0] then
  cbOptions.ItemChecked[ItmIndex1] := False
else cbOptions.ItemChecked[ItmIndex1] := True;

他们第一次工作,然后它总是评估为真,我明白为什么。只有当我取消选中第一个 CheckBox 时,else 位才会起作用,这显然不是我想要的结果。

似乎事件停止工作,并且在我的一些尝试中它由于某种原因被触发了两次。

cbListOptionsChange 上的 NewState 参数,这是什么,它可以帮助我吗?

对此的任何帮助将不胜感激。

谢谢。

if cbOptions.ItemChecked[ItmIndex0] then
  cbOptions.ItemChecked[ItmIndex1] := False
else if cbOptions.ItemChecked[ItmIndex1] then
  cbOptions.ItemChecked[ItmIndex0] := False;

如果第二个 CheckBox 被选中,那么我会检查第一个 CheckBox 是否按要求工作,但显然之后你不能再检查第二个 CheckBox。


Ken White - 片段(工作)。将组件的名称替换为 Default 否则人们可能会感到困惑,有时有助于默认命名以节省将来的问题。

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;

【问题讨论】:

  • 您选择了错误的控件。你所经历的挣扎是那个错误的结果。互斥选择的正确控制是单选组。
  • 您还应该更加注意编译器警告。初始化 ItmIndex0 和 ItmIndex1 的代码是没有意义的,编译器会告诉你的。
  • 是的,我知道 TRAdioGroup 最适合并给我我需要的结果,但我真的想使用 CheckBoxes。当我进行编译或构建时,我得到 0 个警告或提示,所以我不太确定你的意思:S
  • 使用复选框会使您的用户感到困惑,但我想您知道这一点。 Q 中的代码会发出警告。从不使用分配给 ItmIndex0 的值。
  • 在读取第一个值之前分配另一个值的事实就是出现警告的原因。也许您禁用了警告。事实上,这对你来说是一场斗争,这只是为你的 UI 罪行伸张正义!! ;-) 你可以让它工作。使用 Ken 的代码,但在修改 Checked 属性时禁用 OnChange 处理程序。我会在 OnChange 处理程序中使用重入保护。阻止导致堆栈溢出的重入执行的全局字段。

标签: delphi delphi-xe2 vcl


【解决方案1】:

我不熟悉TRZCheckGroup,但您当前的代码将始终选中ItmIndex0 处的项目并取消选中另一个。

TCheckBoxState 在 Delphi 文档中定义为

TCheckBoxState = (
  cbUnchecked,
  cbChecked,
  cbGrayed
);

所以NewState 似乎告诉你CheckBox 的新设置状态,Index 告诉你哪个复选框正在改变。大多数时候,cbGrayed 没有被使用,因为它表示该值从未被设置过;它通常仅在您读取数据库中的 BOOLEAN(或位)列并且它为 NULL 时才有用。

这个事件并不是真的要交替两个复选框的状态,它会出现;它旨在让您在单个项目(在项目组中)更改其状态时做出反应:

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;

要反转两个复选框的状态(将一个更改为备用状态,另一个更改为相反状态),您可以使用类似这样的东西(使用两个标准 TCheckBox 控件,并为它们的两个 @ 定义相同的事件987654331@事件):

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;

但是,如果您只是处理一个项目列表,其中一项应被选中,而其他所有项目均未选中,那么您应该改用TRadioGroup。它会自动为您提供此行为。使用复选框违反了正常的 Windows GUI 行为,并且会使您的用户感到困惑。

话虽如此(并且我的 strong 反对这样做!),并且未经测试,因为我没有您正在使用的组件,您可以试试这个(这是反对我更好的判断甚至写!):

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;

为了确保清楚,我认为这是一个非常糟糕的主意,如果你为我工作,我不会允许的。当有合适的选项可用时,做一些与预期的 Windows 行为相反的事情是完全错误,IMO。

【讨论】:

  • 是的,谢谢您的回复。我已经尝试过了,但它会不断闪烁开和关并最终触发堆栈溢出错误。还有更多想法或解决此问题的方法吗?谢谢。
  • :-) 你一定是在我删除它以在实际发布之前重新编写之前抓住了它。试试这个答案。
  • =) 是的,时机不好,哈哈。如果我单击 CheckBox1 然后再次单击它,则 CheckBox2 变为选中状态,而 CheckBox1 未选中。单击 CheckBox2 正常工作(单击 - 开 ~ 关)。我想要它,所以如果我在我的组框列表中单击 CheckBox1,那么它将变为选中其他人将被取消选中,如果我单击 CheckBox2,那么它将变为选中并且 CheckBox1 将变为未选中。感谢您的帮助,尽管很感激。听起来/应该很简单,不知道为什么这么难。感谢您提供有关我其他问题的信息。
  • 您的意思是,如果您选中其中一个,您希望 所有 其他人都处于未选中状态?然后你想要一个TRadioGroup,就像我在上一段中所说的那样。它适用于其中一项互斥的列表(如果选择了 1,则所有其他项都将被取消选择)。它旨在完全实现您正在寻找的行为,并且在预期这种行为时用户所期望的。
  • 是的,但我真的很想使用 CheckBox 而不是 RadioGroup 肯定有办法做到这一点,哈哈。我可以不从另一个组件下降并构建我自己的组件吗?如果你不能做到,它目前的工作方式。
【解决方案2】:

本示例使用三个 TCheckbox 控件。

将三个 TCheckbox 控件拖放到一个窗体上。对于这个例子,我给它们命名了

cbOpenorderscbClos​​edorderscbAllorders

在对象检查器的 cbOpenorders.Onclick 属性中添加一个事件。

然后,将 cbClos​​edorders 和 cbAllorders 的 OnClick 事件属性设置为此 cbOpenorders 事件。所有三个框都将调用相同的事件处理程序,从而减少所需的代码量。

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;

在此示例中,用户只能选择一个框或不选择框。

【讨论】:

  • 是的,有时使用复选框而不是无线电组的单选按钮是一个不错的选择。有趣的是,没有人想到将这个选项合并到无线电组中。我认为 woll2woll 软件中有一个具有此选项的组件,我不确定在哪里可以使用复选框而不是单选按钮。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-04-04
  • 2022-07-11
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多