如何用 TCheckBox 中的属性替换全局变量?

How to replace global variables with properties in TCheckBox?

我有一个复选框 1,我想对其进行自定义,当用户单击复选框的标题(文本)时,它不会更改其状态 (checked/unchecked),但只有在单击实际时才会更改复选框方块。

这是带有 2 个全局变量的当前代码,一个用于说明何时跳过更改状态,另一个用于记住当前状态并确保它在 OnClick 之后保持不变 - 因为当流处于 OnClick 时状态已经改变:

var
  gSkipClick:boolean = false;
  gPrevState:boolean;

procedure TForm1.CheckBox1Click(Sender: TObject);
begin
  // Make sure previous state is assigned when Skip
  if gSkipClick then
    Checkbox1.Checked := gPrevState;
end;

procedure TForm1.CheckBox1MouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
begin
  if (x > 12) then
  begin
    // Click outside checkbox square
    gSkipClick := True; // skip Click
    gPrevState := CheckBox1.Checked; // save current state
  end
  else
    gSkipClick := False;  // enable Click
end;

现在我想用 TCheckBox 中的 2 个新属性来替换全局变量:

TCheckBox = class(Vcl.StdCtrls.TCustomCheckBox)
    private
      FSkipStateChange:Boolean;
      FPrevState:Boolean;
    protected
    published
      property SkipStateChange:Boolean read FSkipStateChange write FSkipStateChange;
      property PrevState:Boolean read FPrevState write FPrevState;
  end;

procedure TForm1.CheckBox1Click(Sender: TObject);
begin
  // Click outside checkbox square
  If TCheckBox(Checkbox1).SkipStateChange Then
    Checkbox1.Checked := TCheckBox(Checkbox1).PrevState;
end;

procedure TForm1.CheckBox1MouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
begin
  if (x > 12) then
  begin
    // Click outside checkbox square
    TCheckBox(Checkbox1).SkipStateChange := True;
    TCheckBox(Checkbox1).PrevState := Checkbox1.Checked;
  end
  else
    TCheckBox(Checkbox1).SkipStateChange := False;
end;

它有效,但如果我单击标题然后关闭表单,则会出现此错误:

Project Project1.exe raised exception class $C0000005 with message 'access violation at 0x004080c5: read of address 0x0000000d'.

系统单位procedure TMonitor.Destroy;出现错误:

procedure TMonitor.Destroy;
begin
  if (MonitorSupport <> nil) and (FLockEvent <> nil) then { <-- ERROR ON THIS LINE}
    MonitorSupport.FreeSyncObject(FLockEvent);
  FreeMem(@Self);
end;

我哪里做错了,为什么会出错?

首先回答有关运行时错误的问题。您的插入器 class 需要在您的表单 class 之前声明。你还没有做到这一点,正如你需要投射所猜测的那样。你写

TCheckBox(Checkbox1)

但是如果插入器声明正确,你可以写

CheckBox1

没有演员表。

再次查看插入器模式的每个代码示例。插入器 class 在使用它的任何 class 之前声明。

因为插入器是在您的代码中的表单之后声明的,所以流机制正在实例化原始 class 而不是插入的 class。因此,您的强制转换无效并导致运行时内存损坏。

定义类型并转换为它是不够的。您必须确保实例化此类型。对象的类型在实例化时确定。

作为一项规则,您应该始终使用 isas 检查您的演员阵容。如果您在这里这样做,系统可能会告诉您出了什么问题。尽管您应该问自己为什么一开始就必须施法。那应该是警告信号。请记住,您始终可以通过强制转换来抑制编译器错误,但这样做并不能解决问题。将某物塑造成它不是的东西实际上并不能使它成为现实。它只是向编译器撒了一个弥天大谎,最终它得到了报复!


对于中介层,您应该从 Vcl.StdCtrls.TCheckBox 而不是 Vcl.StdCtrls.TCustomCheckBox 派生。


在派生的插入器 class 和表单之间混合代码是非常糟糕的设计。当你想在不同的表单上做类似的事情时,你真的要再次复制代码吗?像这样的代码需要在控件中自包含。

另一方面,逻辑可能特定于表单。在这种情况下,它应该存在于那里,您可以使用标准复选框。你提到使用全局变量让我很伤心。如果您需要与表单关联的变量,请将它们添加到表单 class。


批评您 UI 设计的评论者在我看来是正确的。用户喜欢可预测性。当一个控件以他们期望的方式运行时,没有人会感到不安,就像系统中所有其他类似控件的运行方式一样。停止修补标准控件,使用它们,让用户能够预测您的程序的行为。