如何解决如何在运行时嵌入到 EXE 中另一个 TForm 的 TPanel 中的 DLL 中的 TForm 上的控件中切换到 TForm 上的控件,并获得正确的 Tab 顺序?
给定一个包含表单和 2 个用于显示/隐藏表单的导出程序的动态链接库,以及一个包含 2 个按钮和一个面板的表单的 VCL 可执行文件,我如何让 Tab 键在两个控件之间循环在exe中的窗体上,在dll中的窗体上的控件,显示在面板中?
这是 DLL 的 DPR:
library Project18;
{ Important note about DLL memory management: ShareMem must be the
first unit in your library's USES clause AND your project's (select
Project-View Source) USES clause if your DLL exports any procedures or
functions that pass strings as parameters or function results. This
applies to all strings passed to and from your DLL--even those that
are nested in records and classes. ShareMem is the interface unit to
the BORLNDMM.DLL shared memory manager,which must be deployed along
with your DLL. To avoid using BORLNDMM.DLL,pass string information
using PChar or ShortString parameters. }
uses
System.SysUtils,System.Classes,Winapi.Windows,Unit25 in 'Unit25.pas' {Form25};
{$R *.res}
procedure ShowForm(const AParentWindow: HWND); StdCall;
begin
Form25 := TForm25.Create(nil);
Form25.ParentWindow := AParentWindow;
Form25.Show;
end;
procedure CloseForm; StdCall;
begin
if Assigned(Form25) then
begin
Form25.Close;
FreeAndNil(Form25);
end;
end;
exports
ShowForm,CloseForm;
begin
end.
以及 EXE 的 DPR:
program Project19;
uses
Vcl.Forms,Unit26 in 'Unit26.pas' {Form26};
{$R *.res}
begin
Application.Initialize;
Application.MainFormOnTaskbar := True;
Application.CreateForm(TForm26,Form26);
Application.Run;
end.
这是 DLL 中的表单布局:
这是 EXE 中的表单布局:
这是EXE中表单的代码:
unit Unit26;
interface
uses
Winapi.Windows,Winapi.Messages,System.SysUtils,System.Variants,Vcl.Graphics,Vcl.Controls,Vcl.Forms,Vcl.Dialogs,Vcl.ExtCtrls,Vcl.StdCtrls,Vcl.ComCtrls;
type
TShowForm = procedure(AParentWindow: HWND); stdcall;
TCloseForm = procedure; stdcall;
TForm26 = class(TForm)
Button1: TButton;
Button2: TButton;
Panel1: TPanel;
Panel2: TPanel;
ComboBox1: TComboBox;
DateTimePicker1: TDateTimePicker;
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
private
{ Private declarations }
LibHandle: THandle;
ShowForm: TShowForm;
CloseForm: TCloseForm;
public
{ Public declarations }
end;
var
Form26: TForm26;
implementation
{$R *.dfm}
procedure TForm26.Button1Click(Sender: TObject);
begin
ShowForm(Panel1.Handle);
end;
procedure TForm26.Button2Click(Sender: TObject);
begin
CloseForm;
end;
procedure TForm26.FormCreate(Sender: TObject);
begin
LibHandle := LoadLibrary('Project18.dll');
@ShowForm := GetProcAddress(LibHandle,'ShowForm');
@CloseForm := GetProcAddress(LibHandle,'CloseForm');
end;
procedure TForm26.FormDestroy(Sender: TObject);
begin
FreeLibrary(LibHandle);
end;
end.
DLL 中表单的代码是标准的自动生成的东西。
当我第一次运行 EXE 时,表单如下所示:
点击Button1后,在Panel1内的DLL中显示窗体,如下所示:
但是当我通过 Tab 键浏览控件时,它会跳过 DLL 中表单中显示的那些:
例如,如果我单击 Edit1 并按 Tab,它会跳转到 exe 主窗体上的下一个控件,而不是嵌入窗体中的下一个控件。
我在 Stack Overflow 和更广泛的网络上查看了 How to avoid issues when embedding a TForm in another TForm? 和其他答案,但我无法使用 TFrame,因为我嵌入的 GUI 是不同 DLL 中的 TForm。
我是否需要捕获选项卡的按键,然后将 Windows 消息发送到嵌入式表单?为此,我想我需要嵌入表单的 HWND,而我没有。
编辑:我将 ShowForm 更改为返回所创建表单的 HWND 的函数,但这没有区别,因为即使 KeyPreview 设置为 true,按下 Tab 时 FormKeyPress 和 FormKeyUp 事件也不会触发.
编辑:必须添加一个 CM_Dialog 消息处理程序来捕获 Tab 键,根据 https://community.embarcadero.com/article/technical-articles/149-tools/13071-detecting-tab-key-press
编辑:但是这样做会阻止 Tab 键在 EXE 中的窗体上的控件中实际使用 Tab 键进行切换,因此这根本不是解决方案,而且使用 PostMessage 将选项卡发送到嵌入的表单也没有任何作用。
如何在使用 Tab 键时获得控件焦点以包含嵌入表单中的控件?
编辑:如果我向 EXE 添加第二个表单,再添加两个按钮,并对该表单执行相同的步骤(关于显示和隐藏),它具有相同的行为,即使第二个表单在EXE 而不是 DLL。
显然,仅设置表单 ParentWindow
不足以使其正确处理。如果我将表单 Parent
属性设置为 TPanel
,而不是使用 ParentWindow
,它确实适用于 EXE 中的第二个表单。这似乎表明必须将 TWinControl
传递给 DLL。
编辑:不确定这是否可以完成。正如这个问题中所述,它不能,
How to get instance of TForm from a Handle?
我查看了这个问题中提到的 CreateParented,
How to instantiate a class which normally needs parent(TWinControl) in a dll?
它仍然不允许选项卡流入创建的控件。
编辑:我现在发现的一个部分解决方案是在 EXE 中为应用程序提供一个 OnMessage
事件处理程序,它使用 TMsg
将 IsDialogMessage
传递给 DLL 中的表单,通过对示例程序进行这些更改:
public
{ Public declarations }
protected
procedure MessageHandler(var Msg: TMsg; var Handled: Boolean);
end;
procedure TForm26.FormCreate(Sender: TObject);
begin
LibHandle := LoadLibrary('Project18.dll');
@ShowForm := GetProcAddress(LibHandle,'CloseForm');
Application.OnMessage := MessageHandler;
end;
procedure TForm26.MessageHandler(var Msg: TMsg; var Handled: Boolean);
begin
if (Msg.message = WM_KEYDOWN) then
begin
if IsDialogMessage(FormHandle,Msg) then
Handled := True;
end;
end;
只有两个问题。
- 该选项卡在具有焦点的任何窗体的控件之间循环。它不会从 DLL 中切换到窗体上的控件,也不会从这些控件中跳出到 EXE 中窗体上的控件。
- 虽然 DLL 中的表单具有焦点,但该表单上控件的选项卡顺序是相反的。
我大概可以编写代码,在退出EXE窗体的最后一个控件时,利用Winapi.Windows.SetFocus()
将焦点移到DLL窗体中,同样地,在最后一个控件退出时,将焦点设置回EXE窗体DLL 中的选项卡。
对于处理反向 Tab 键顺序还没有答案。我知道这不是控件的 TabOrder
属性。他们是正确的。也许我需要在 DLL 中使用另一个 WM_KEYDOWN
消息处理程序?
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。