如何在 TDBCheckBox 后代中将 NULL 值显示为未选中状态?

How to display NULL value as unchecked state in a TDBCheckBox descendant?

具有布尔类型的可空数据集字段,如何在链接到该字段的 TDBCheckBox 控件后代中将其 NULL 值显示为未选中状态。默认情况下,TDBCheckBox 将字段的 NULL 值显示为灰色复选框:

但我需要它在我的 TDBCheckBox 控件后代中显示为未选中状态:

修改原始 TDBCheckBox 源代码对我来说不是一个选项,我也不能覆盖 TDBCheckBox.GetFieldState 因为它是一个私有方法。

那么,如何在我的 TDBCheckBox 后代中将 NULL 值显示为未选中状态?

如果您的项目是闭源项目,我建议复制一份 DBCtrls,更改显示 Result := cbGrayed 的那一行并将其明确添加到您的项目中。更改将在您的整个应用程序中进行,但不会更改原始代码。

但是还有另一种方法 - 实际上是一种 hack 所以 小心 我建议放置一个编译器指令以防止它在不同的 Delphi 版本中编译以要求再次查看该代码并确保其有效。

这是在 Delphi XE 中运行的代码 - 它在 Delphi 6 中看起来可能有所不同,但您会明白的。

type
  TDBCheckBoxHack = class(TCustomCheckBox)
  private
    FDataLink: TFieldDataLink;
    FValueCheck: string;
    FValueUncheck: string;
    procedure DataChange(Sender: TObject);
    function GetFieldState: TCheckBoxState;
    function ValueMatch(const ValueList, Value: string): Boolean;
  end;

我省略了这些方法的实现,因为它们只是原始版权代码的副本。您必须更改 GetFieldState 方法中的一两行。

诀窍是创建与原始 TDBCheckBox 相同的内存布局,以便您可以访问私有字段 - 这就是为什么应谨慎使用此代码的原因!

然后将固定的 DataChange 方法分配给数据链路:

TDBCheckBoxHack(DBCheckBox1).FDataLink.OnDataChange := 
  TDBCheckBoxHack(DBCheckBox1).DataChange;

为了使它更易于使用,您可以使用另一个从原始 TDBCheckBox 继承的技巧,并完全相同地调用您的 class。然后,您可以将其放入某个单元,并在您的使用中将 添加到 DBCtrls 之后。这会导致为每个放置在表单上的 TDBCheckBox 调用构造函数,而无需使用自己的构造函数并将其注册到 IDE:

type
  TDBCheckBox = class(DBCtrls.TDBCheckBox)
  public
    constructor Create(AOwner: TComponent); override;
  end;

constructor TDBCheckBox.Create(AOwner: TComponent);
begin
  inherited;
  TDBCheckBoxHack(Self).FDataLink.OnDataChange := TDBCheckBoxHack(Self).DataChange;
end;

感谢大家的帮助!

我是这样实现的:

  type
  TDBCheckBoxHack = class(TDBCheckBox)
    FDataLink: TFieldDataLink;
    procedure DataChange(Sender: TObject); virtual;
    function GetFieldState: TCheckBoxState; virtual;
  public
    constructor Create(AOwner: TComponent); override;
  end;

在构造函数中,我使用消息 CM_GETDATALINK 将 DataLink 检索到我的组件

  constructor TDBCheckBoxHack.Create(AOwner: TComponent);
  var
    AMessage: TMessage;
  begin
    inherited;
    FillChar(AMessage, 0, sizeof(AMessage));
    AMessage.Msg := CM_GETDATALINK;
    Dispatch(AMessage);
    FDataLink := TFieldDataLink(Pointer(AMessage.Result));
    FDataLink.OnDataChange := DataChange;
  end;

和 GetFieldState 实现:

  function TFsDBCheckBox.GetFieldState: TCheckBoxState;
  var
    Text: string;
  begin
    if FDatalink.Field <> nil then
      if FDataLink.Field.IsNull then
        Result := cbUnchecked
      else if FDataLink.Field.DataType = ftBoolean then
        if FDataLink.Field.AsBoolean then
          Result := cbChecked
        else
          Result := cbUnchecked
      else
      begin
        Result := cbGrayed;
        Text := FDataLink.Field.Text;
        if ValueMatch(ValueChecked, Text) then Result := cbChecked else
          if ValueMatch(ValueUnchecked, Text) then Result := cbUnchecked;
      end
   else
     Result := cbUnchecked;
 end;