【问题标题】:How to replace global variables with properties in TCheckBox?如何用 TCheckBox 中的属性替换全局变量?
【发布时间】:2016-12-24 18:41:42
【问题描述】:

我有一个 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; in System unit:

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

我做错了什么,为什么会出现错误?

【问题讨论】:

  • 复选框并不总是 12 像素宽。您的代码在复选框宽度不同的系统上效果不佳。另外,我认为您应该将逻辑添加到您的复选框类中。
  • 我还没有考虑到这一点。我假设您在谈论不同的 Windows 版本 (XP...10) 对吗?
  • 其他想要显示有关控件的附加信息的程序通常会在控件旁边包含一个额外 UI 元素,例如“信息”图像。请不要按照您的建议降低复选框的可用性。网站默认会这样做,这很糟糕。
  • 你不会有两个控件。你会有一个将两者结合起来的。你这样做的逻辑是有缺陷的——你正在改变标准 UI 组件的行为,使其行为方式与用户期望的方式不同。我的用户会非常沮丧,因为他们知道 Windows 是如何工作的,并且不希望跳过箍来使用他们的应用程序。如果您需要执行与复选框不同的操作,请不要使用复选框 - 使用更合适的控件。
  • 不要削弱复选框本身!创建一个新的复合控件,由无标题复选框和关联的 TLabel 组成。通过 TFrame 或半官方自定义容器包将它们合二为一

标签: delphi delphi-10.1-berlin tcheckbox


【解决方案1】:

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

TCheckBox(Checkbox1)

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

CheckBox1

没有演员表。

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

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

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

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


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


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

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


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

【讨论】:

  • 现在它可以工作了,当我把课程放在表单的课程之前!这是我第一次尝试自己自定义控件,我想我还有更多的学习要做。
猜你喜欢
  • 2020-03-25
  • 1970-01-01
  • 1970-01-01
  • 2013-09-23
  • 2017-08-30
  • 2014-12-08
  • 2017-10-06
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多