Передайте событие KeyPress InputComboBox как параметр функции

Моя ОС - 64-битная Windows 10, и я использую Delphi 10.0 Seattle Update 1.

У меня есть функция, которая вызывает InputBox, который вместо Edit содержит ComboBox. Увидеть ниже:

function funInputComboBox(const STRC_Label: String; out STRV_Result: String; const STRC_Items: String = ''; const STRC_LocateItem: String = ''): Boolean;

  function GetCharSize(Canvas: TCanvas): TPoint;
  var
    I: Integer;
    Buffer: array[0..51] of Char;
  begin
    for I := 0 to 25 do Buffer[I] := Chr(I + Ord('A'));
    for I := 0 to 25 do Buffer[I + 26] := Chr(I + Ord('a'));
    GetTextExtentPoint(Canvas.Handle, Buffer, 52, TSize(Result));
    Result.X := Result.X div 52;
  end;

var
  Form: TForm;
  Prompt: TLabel;
  Combo: TComboBox;
  DialogUnits: TPoint;
  ButtonTop, ButtonWidth, ButtonHeight: Integer;
begin
  Result := False;
  STRV_Result := '';

  Form := TForm.Create(Application);

  with Form do
  try
    Canvas.Font := Font;
    DialogUnits := GetCharSize(Canvas);
    BorderStyle := bsDialog;
    Caption := Application.Title;
    ClientWidth := MulDiv(180, DialogUnits.X, 4);
    Position := poScreenCenter;
    Prompt := TLabel.Create(Form);

    with Prompt do
    begin
      Parent := Form;
      Caption := STRC_Label;
      Left := MulDiv(8, DialogUnits.X, 4);
      Top := MulDiv(8, DialogUnits.Y, 8);
      Constraints.MaxWidth := MulDiv(164, DialogUnits.X, 4);
      WordWrap := True;
    end;

    Combo := TComboBox.Create(Form);

    with Combo do
    begin
      Parent := Form;
      Style := csDropDown;
      Items.Text := STRC_Items;
      ItemIndex := Items.IndexOf(STRC_LocateItem);
      Left := Prompt.Left;
      Top := Prompt.Top + Prompt.Height + 5;
      Width := MulDiv(164, DialogUnits.X, 4);
      CharCase := ecUpperCase;
      -- OnKeyPress := ?????
    end;

    ButtonTop := Combo.Top + Combo.Height + 15;
    ButtonWidth := MulDiv(50, DialogUnits.X, 4);
    ButtonHeight := MulDiv(14, DialogUnits.Y, 8);

    with TButton.Create(Form) do
    begin
      Parent := Form;
      Caption := 'OK';
      ModalResult := mrOk;
      Default := True;
      SetBounds(MulDiv(38, DialogUnits.X, 4), ButtonTop, ButtonWidth, ButtonHeight);
    end;

    with TButton.Create(Form) do
    begin
      Parent := Form;
      Caption := 'Cancelar';
      ModalResult := mrCancel;
      Cancel := True;
      SetBounds(MulDiv(92, DialogUnits.X, 4), Combo.Top + Combo.Height + 15, ButtonWidth, ButtonHeight);
      Form.ClientHeight := Top + Height + 13;
    end;

    Result := (ShowModal = mrOk);

    if Result then
      STRV_Result := Combo.Text;
  finally
    Form.Free;
  end;
end;

Он отлично работает и выполняет свою работу, но я хочу добавить туда кое-что еще. Иногда эта функция будет использоваться в местах, где текст должен быть замаскирован, например, номера автомобилей здесь выглядят так: AAA-0000 (3 цифры и 4 буквы), поэтому, когда я вызываю это, я хочу передать процедура / функция, которая будет вызываться при срабатывании OnKeyPress.

Моя процедура проверки маски имеет этот заголовок:

procedure proValidaMascaraPlaca(Sender: TObject; var Key: Char);

Он не принадлежит ни к какому классу, он находится в вашем повседневном устройстве utils.

Когда нам нужно его использовать, мы делаем это вот так:

procedure TForm1.Edit1KeyPress(Sender: TObject; var Key: Char);
begin
  proValidaMascaraPlaca(Sender, Key);
end;

Итак, я хочу, чтобы мой InputComboBox имел эту функцию, где мог бы иметь любую процедуру проверки, которую мы передаем в качестве параметра:

with Combo do
begin
  OnKeyPress := proValidaMascaraPlaca
end;

Очевидно, это не сработало, поэтому я попробовал другой метод, который видел здесь. Он состоял из функции, которая имитировала бы это, как если бы это был procedure(Sender: TObject; var Key: Char) of object:

function MakeMethod(Data, Code: Pointer): TMethod;
begin
  Result.Data := Data;
  Result.Code := Code;
end;

Моя функция по-прежнему не имеет параметра процедуры, поэтому я исправляю его из этого примера:

TKeyPressEvent = procedure(Sender: TObject; var Key: Char) of object;

with Combo do
begin
  OnKeyPress := TKeyPressEvent(MakeMethod(@Combo, @proValidaMascaraPlaca));
end;

Я попробовал nil вместо @Combo, но оба потерпели неудачу. Они компилировались, но когда был вызван proValidaMascaraPlaca(), он получил какое-то странное значение для Char и Sender, что привело к нарушению доступа.

Я надеюсь, что кто-то понимает, что я пытаюсь сделать, или уже сделал / видел это, и знает, что я должен сделать, чтобы это сработало. Это то, что я пытался сделать в течение некоторого времени, и я подумал, что это заставит меня просматривать параметры совершенно по-новому.

Почему бы не сделать эту форму отдельным классом (в отдельном модуле), обрабатывающим это событие, и не заполнить только эту функцию (это можно сделать довольно легко)?

Victoria 24.08.2018 22:21

OnKeyPress объявлен как процедура of object, что означает, что он должен быть методом объекта. Итак, объявите класс, реализующий событие, назначьте это событие событию combobox и вызовите свою функцию, как в примере TEdit выше.

Ken White 24.08.2018 22:22

Измените параметры MakeMethod на: (Sender: TObject; procAddr: pointer), затем вызовите: MakeMethod (Combo, @proValidaMascaraPlaca)

FredS 25.08.2018 00:19
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
3
269
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Проблема в том, что тип, используемый для события OnKeyPress (как и для всех других событий VCL / FMX), объявлен как of object, поэтому назначенный обработчик требует должен иметь параметр Self. При использовании метода класса подразумевается, что этот параметр управляется компилятором за вас. Но при использовании вашей автономной процедуры proValidaMascaraPlaca() с MakeMethod(), любое значение, которое вы присваиваете полю TMethod.Data, передается в параметре Self, но proValidaMascaraPlaca() не имеет параметра Self! Вот почему вы получаете мусор в параметрах Sender и Key, потому что стек вызовов поврежден.

Чтобы это работало, вам нужно добавить параметр явныйSelf в качестве 1-го параметра в proValidaMascaraPlaca(), например:

procedure proValidaMascaraPlaca(Self: Pointer; Sender: TObject; var Key: Char);

Затем Self получит любое значение, указанное вами в параметре Data для MakeMethod() (@Combo в вашем примере), а Sender и Key получат любые значения, которые Combo отправляет им, как и ожидалось.

Если вы не хотите редактировать сам proValidaMascaraPlaca() (потому что он находится в служебной библиотеке), вам придется создать отдельную функцию-оболочку для передачи в MakeMethod(), а затем эта оболочка может вызывать proValidaMascaraPlaca(), игнорируя Self, например:

procedure MyComboKeyPress(Self: Pointer; Sender: TObject; var Key: Char);
begin
  proValidaMascaraPlaca(Sender, Key);
end;

...

with Combo do
begin
  ...
  Combo.OnKeyPress := TKeyPressEvent(MakeMethod(nil, @MyComboKeyPress));
end;

Гораздо более простым (и в конечном итоге более безопасным) решением было бы вместо этого унаследовать новый класс от TComboBox и переопределить виртуальный метод KeyPress(), например:

type
  TMyValidatingComboBox = class(TComboBox)
  protected
    procedure KeyPress(var Key: Char); override;
  end;

procedure TMyValidatingComboBox.KeyPress(var Key: Char);
begin
  inherited;
  proValidaMascaraPlaca(Self, Key);
end;

...

Combo := TMyValidatingComboBox.Create(Form);

Это оно! Именно то, что я хотел. @Victoria предложила способ, которого я не хотел делать, а именно преобразовать эту функцию в нормальную форму. Если бы это был ЕДИНСТВЕННЫЙ способ, то я бы сделал это, но этот делал то, что я пытался делать все это время, без необходимости в фактическом файле формы. Спасибо.

Geovane Silveira 27.08.2018 13:21

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