如何用 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。因此,您的强制转换无效并导致运行时内存损坏。
定义类型并转换为它是不够的。您必须确保实例化此类型。对象的类型在实例化时确定。
作为一项规则,您应该始终使用 is
或 as
检查您的演员阵容。如果您在这里这样做,系统可能会告诉您出了什么问题。尽管您应该问自己为什么一开始就必须施法。那应该是警告信号。请记住,您始终可以通过强制转换来抑制编译器错误,但这样做并不能解决问题。将某物塑造成它不是的东西实际上并不能使它成为现实。它只是向编译器撒了一个弥天大谎,最终它得到了报复!
对于中介层,您应该从 Vcl.StdCtrls.TCheckBox
而不是 Vcl.StdCtrls.TCustomCheckBox
派生。
在派生的插入器 class 和表单之间混合代码是非常糟糕的设计。当你想在不同的表单上做类似的事情时,你真的要再次复制代码吗?像这样的代码需要在控件中自包含。
另一方面,逻辑可能特定于表单。在这种情况下,它应该存在于那里,您可以使用标准复选框。你提到使用全局变量让我很伤心。如果您需要与表单关联的变量,请将它们添加到表单 class。
批评您 UI 设计的评论者在我看来是正确的。用户喜欢可预测性。当一个控件以他们期望的方式运行时,没有人会感到不安,就像系统中所有其他类似控件的运行方式一样。停止修补标准控件,使用它们,让用户能够预测您的程序的行为。
我有一个复选框 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。因此,您的强制转换无效并导致运行时内存损坏。
定义类型并转换为它是不够的。您必须确保实例化此类型。对象的类型在实例化时确定。
作为一项规则,您应该始终使用 is
或 as
检查您的演员阵容。如果您在这里这样做,系统可能会告诉您出了什么问题。尽管您应该问自己为什么一开始就必须施法。那应该是警告信号。请记住,您始终可以通过强制转换来抑制编译器错误,但这样做并不能解决问题。将某物塑造成它不是的东西实际上并不能使它成为现实。它只是向编译器撒了一个弥天大谎,最终它得到了报复!
对于中介层,您应该从 Vcl.StdCtrls.TCheckBox
而不是 Vcl.StdCtrls.TCustomCheckBox
派生。
在派生的插入器 class 和表单之间混合代码是非常糟糕的设计。当你想在不同的表单上做类似的事情时,你真的要再次复制代码吗?像这样的代码需要在控件中自包含。
另一方面,逻辑可能特定于表单。在这种情况下,它应该存在于那里,您可以使用标准复选框。你提到使用全局变量让我很伤心。如果您需要与表单关联的变量,请将它们添加到表单 class。
批评您 UI 设计的评论者在我看来是正确的。用户喜欢可预测性。当一个控件以他们期望的方式运行时,没有人会感到不安,就像系统中所有其他类似控件的运行方式一样。停止修补标准控件,使用它们,让用户能够预测您的程序的行为。