Как заменить глобальные переменные свойствами в TCheckBox?

У меня есть 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;

Теперь я хотел сделать это с 2 новыми свойствами в TCheckBox, которые заменили бы глобальные переменные:

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;

Что я делаю не так, почему происходит ошибка?

1 ответ

Решение

Сначала ответьте на вопрос об ошибке во время выполнения. Ваш класс interposer должен быть объявлен перед вашим классом формы. Вы не сделали этого, как можно догадаться по вашей потребности в касте. Ты пишешь

TCheckBox(Checkbox1)

но если вставщик был объявлен правильно, вы могли бы написать

CheckBox1

без актерского состава.

Посмотрите еще раз на каждый пример кода для шаблона вставки. Класс interposer объявляется перед любым классом, который его использует.

Поскольку средство вставки объявлено после формы в вашем коде, механизм потоковой передачи создает экземпляр исходного класса, а не вставленного класса. Следовательно, ваши приведения являются недействительными и приводят к повреждению памяти во время выполнения.

Недостаточно определить тип и привести его к нему. Вы должны убедиться, что этот тип создан. Тип объекта определяется при его создании.

Как правило, вы всегда должны проверять свои приведения is или же as, Если бы вы сделали это здесь, система могла бы сказать вам, что случилось. Хотя вы должны были спросить себя, почему вы должны были сыграть в первую очередь. Это должен был быть предупредительный сигнал. Помните, что вы всегда можете подавить ошибки компилятора путем приведения, но это не решает проблему. Принятие чего-то, чтобы быть чем-то, чего нет, на самом деле не делает это таким Это просто говорит большую полную ложь компилятору, и в конце концов он получает свою месть!


Для вставки вы должны извлечь из Vcl.StdCtrls.TCheckBox скорее, чем Vcl.StdCtrls.TCustomCheckBox,


Смешивание кода между производным классом вставки и формой - очень плохой дизайн. Если вы хотите сделать что-то похожее в другой форме, вы действительно собираетесь снова скопировать код? Подобный код должен быть самодостаточным в элементе управления.

С другой стороны, возможно, логика специфична для формы. В этом случае он должен жить там, и вы будете использовать стандартный флажок. Это наполняет меня горе, которое вы имеете в виду, используя глобальные переменные. Если вам нужны переменные, связанные с формой, добавьте их в класс формы.


Комментаторы, которые критикуют ваш дизайн пользовательского интерфейса, на мой взгляд, верны. Пользователям нравится предсказуемость. Никто никогда не будет расстроен, когда элемент управления будет вести себя так, как они ожидают, так, как ведет себя любой другой аналогичный элемент управления в системе. Прекратите возиться со стандартными элементами управления, используйте их, чтобы пользователи могли предсказать поведение вашей программы.

Другие вопросы по тегам