将 InputComboBox 的 KeyPress 事件作为函数参数传递
Pass InputComboBox's KeyPress event as function parameter
我的 OS 是 Windows 10 64 位,我使用的是 Delphi 10.0 Seattle Update 1.
我有一个调用 InputBox 的函数,它包含一个 ComboBox,而不是一个 Edit。见下文:
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/function。
我的掩码验证程序有这个 header:
procedure proValidaMascaraPlaca(Sender: TObject; var Key: Char);
它不属于任何class,它被放置在你的日常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
上收到了一些奇怪的值,这导致了访问冲突。
我希望有人了解我正在尝试做的事情,或者已经 done/seen 这件事,并且知道我应该怎么做才能让它发挥作用。这是我一段时间以来一直在尝试做的事情,我觉得它会让我以一种全新的方式查看参数。
问题是用于 OnKeyPress
事件(与所有其他 VCL/FMX 事件一样)的类型被声明为 of object
,因此它 需要 分配的处理程序有一个 Self
参数。当使用 class 方法时,该参数是隐含的,由编译器为您管理。但是,当使用带有 MakeMethod()
的独立 proValidaMascaraPlaca()
过程时,您分配给 TMethod.Data
字段的任何值都会在 Self
参数中传递,但 proValidaMascaraPlaca()
没有Self
参数!这就是为什么您的 Sender
和 Key
参数中出现垃圾的原因,因为调用堆栈已损坏。
要使这项工作正常,您需要添加一个 explicit Self
参数作为 proValidaMascaraPlaca()
的第一个参数,例如:
procedure proValidaMascaraPlaca(Self: Pointer; Sender: TObject; var Key: Char);
Self
然后将接收您在 MakeMethod()
的 Data
参数中指定的任何值(在您的示例中为 @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
派生一个新的 class,并覆盖虚拟 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);
我的 OS 是 Windows 10 64 位,我使用的是 Delphi 10.0 Seattle Update 1.
我有一个调用 InputBox 的函数,它包含一个 ComboBox,而不是一个 Edit。见下文:
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/function。
我的掩码验证程序有这个 header:
procedure proValidaMascaraPlaca(Sender: TObject; var Key: Char);
它不属于任何class,它被放置在你的日常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
上收到了一些奇怪的值,这导致了访问冲突。
我希望有人了解我正在尝试做的事情,或者已经 done/seen 这件事,并且知道我应该怎么做才能让它发挥作用。这是我一段时间以来一直在尝试做的事情,我觉得它会让我以一种全新的方式查看参数。
问题是用于 OnKeyPress
事件(与所有其他 VCL/FMX 事件一样)的类型被声明为 of object
,因此它 需要 分配的处理程序有一个 Self
参数。当使用 class 方法时,该参数是隐含的,由编译器为您管理。但是,当使用带有 MakeMethod()
的独立 proValidaMascaraPlaca()
过程时,您分配给 TMethod.Data
字段的任何值都会在 Self
参数中传递,但 proValidaMascaraPlaca()
没有Self
参数!这就是为什么您的 Sender
和 Key
参数中出现垃圾的原因,因为调用堆栈已损坏。
要使这项工作正常,您需要添加一个 explicit Self
参数作为 proValidaMascaraPlaca()
的第一个参数,例如:
procedure proValidaMascaraPlaca(Self: Pointer; Sender: TObject; var Key: Char);
Self
然后将接收您在 MakeMethod()
的 Data
参数中指定的任何值(在您的示例中为 @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
派生一个新的 class,并覆盖虚拟 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);