Mik*_*nni 0 delphi tcheckbox delphi-10.1-berlin
我有一个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;
Run Code Online (Sandbox Code Playgroud)
现在我想用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;
Run Code Online (Sandbox Code Playgroud)
并且它可以工作,但是如果我单击Caption然后关闭表单,则会发生以下错误:
项目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;
Run Code Online (Sandbox Code Playgroud)
我做错了什么,为什么会出现错误?
首先回答问题,询问运行时错误.您的插入器类需要在表单类之前声明.你还没有这样做,因为你需要投射.你写
TCheckBox(Checkbox1)
Run Code Online (Sandbox Code Playgroud)
但如果插入器被正确声明,你可以写
CheckBox1
Run Code Online (Sandbox Code Playgroud)
没有演员.
再看一下内插器模式的每个代码示例.内插器类在使用它的任何类之前声明.
因为插入器是在代码中的表单之后声明的,所以流机制是实例化原始类而不是插入的类.因此,您的强制转换无效并导致运行时内存损坏.
仅定义一个类型并强制转换它是不够的.您必须确保实例化此类型.对象的类型在实例化时确定.
作为一项规则,你应该总是用is或检查你的演员as.如果你这样做了,系统可以告诉你出了什么问题.虽然你本来应该问自己为什么要在第一时间施展.这应该是警告信号.请记住,您始终可以通过强制转换来抑制编译器错误,但这样做并不能解决问题.将某些东西变成某种东西实际上并不是这样.它只是告诉编译器一个很大的谎言,最终它得到报复!
对于插入者,你应该来自Vcl.StdCtrls.TCheckBox而不是Vcl.StdCtrls.TCustomCheckBox.
在派生的插入器类和表单之间混合代码是非常糟糕的设计.如果你想在不同的表格上做类似的事情,你真的要再次复制代码吗?像这样的代码需要在控件中自包含.
另一方面,逻辑可能是特定于表单的.在这种情况下,它应该住在那里,你会使用标准复选框.它让我感到沮丧,你提到使用全局变量.如果需要与表单关联的变量,请将它们添加到表单类.
批评你的UI设计的评论者在我看来是正确的.用户喜欢可预测性.当控件以他们期望的方式运行时,任何人都不会感到沮丧,就像系统中其他类似控件的行为一样.停止修改标准控件,使用它们,让用户能够预测程序的行为.