Моя ОС - 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, что привело к нарушению доступа.
Я надеюсь, что кто-то понимает, что я пытаюсь сделать, или уже сделал / видел это, и знает, что я должен сделать, чтобы это сработало. Это то, что я пытался сделать в течение некоторого времени, и я подумал, что это заставит меня просматривать параметры совершенно по-новому.
OnKeyPress объявлен как процедура of object, что означает, что он должен быть методом объекта. Итак, объявите класс, реализующий событие, назначьте это событие событию combobox и вызовите свою функцию, как в примере TEdit выше.
Измените параметры MakeMethod на: (Sender: TObject; procAddr: pointer), затем вызовите: MakeMethod (Combo, @proValidaMascaraPlaca)





Проблема в том, что тип, используемый для события 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 предложила способ, которого я не хотел делать, а именно преобразовать эту функцию в нормальную форму. Если бы это был ЕДИНСТВЕННЫЙ способ, то я бы сделал это, но этот делал то, что я пытался делать все это время, без необходимости в фактическом файле формы. Спасибо.
Почему бы не сделать эту форму отдельным классом (в отдельном модуле), обрабатывающим это событие, и не заполнить только эту функцию (это можно сделать довольно легко)?