0

我有一个 Checkbox1,我想自定义当用户单击复选框的标题(文本)时,它不会更改其状态(选中/未选中),但只有在单击实际复选框方格时才会更改。

这是具有 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 引发异常类 $C0000005,并带有消息“0x004080c5 处的访问冲突:读取地址 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;

我在做什么错,为什么会发生错误?

4

1 回答 1

5

首先是关于运行时错误的问题的答案。您的插入器类需要在表单类之前声明。您还没有这样做,因为您需要进行投射可以猜到。你写

TCheckBox(Checkbox1)

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

CheckBox1

没有演员表。

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

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

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

作为一项规则,您应该始终使用is或检查您的演员表as。如果您在这里这样做,系统可能会告诉您出了什么问题。尽管您应该一直问自己为什么必须首先进行投射。那应该是警告信号。请记住,您始终可以通过强制转换来抑制编译器错误,但这样做并不能解决问题。将某物铸造成它不是的东西实际上并不能使它如此。它只是对编译器撒了一个大大的谎言,最终它报仇了!


对于插入器,您应该派生自Vcl.StdCtrls.TCheckBox而不是Vcl.StdCtrls.TCustomCheckBox.


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

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


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

于 2016-08-18T06:27:15.330 回答