Передать событие KeyCress InputComboBox в качестве параметра функции - PullRequest
0 голосов
/ 24 августа 2018

Моя ОС - Windows 10 64bit, и я использую 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, что привело к нарушению прав доступа.

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

1 Ответ

0 голосов
/ 25 августа 2018

Проблема в том, что тип, используемый для события 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);
...