在Delphi VCL窗体中嵌入C# WinForms Panel并实现自适应大小
解决方案
核心思路
直接调用Win32 API SetParent 仅修改了窗口的父子层级关系,但VCL和WinForms两个框架的布局管理逻辑都没有识别到这个跨框架的控件关联,因此不会自动触发大小适配,也不会将C# Panel纳入VCL的组件管理体系。下面给出可落地的实现方案:
1 Delphi侧改造
我们在Delphi侧增加一层容器控件承接C# Panel,同时将其纳入VCL组件管理体系:
1.1 窗体单元修改代码
unit FrmParent; interface uses Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.ExtCtrls; type TForm5 = class(TForm) pnlContainer: TPanel; // 设计期拖入,设置Align = alClient,BevelOuter = bvNone procedure FormCreate(Sender: TObject); procedure FormResize(Sender: TObject); procedure FormClose(Sender: TObject; var Action: TCloseAction); private FCSharpPanelHandle: HWND; // 存储C# Panel的窗口句柄 public // 暴露给C#侧调用的方法,用于注册C# Panel句柄 procedure RegisterCSharpPanel(Handle: HWND); end; var Form5: TForm5; implementation {$R *.dfm} procedure TForm5.FormClose(Sender: TObject; var Action: TCloseAction); begin // 销毁前通知C#侧释放资源,可根据实际交互逻辑调整 if IsWindow(FCSharpPanelHandle) then SendMessage(FCSharpPanelHandle, WM_CLOSE, 0, 0); end; procedure TForm5.FormCreate(Sender: TObject); begin Caption := IntToStr(Handle); FCSharpPanelHandle := 0; end; procedure TForm5.FormResize(Sender: TObject); begin // 容器大小变化时,同步调整C# Panel大小 if IsWindow(FCSharpPanelHandle) then SetWindowPos(FCSharpPanelHandle, 0, 0, 0, pnlContainer.ClientWidth, pnlContainer.ClientHeight, SWP_NOZORDER); end; procedure TForm5.RegisterCSharpPanel(Handle: HWND); begin FCSharpPanelHandle := Handle; // 首次注册时直接设置初始大小 SetWindowPos(FCSharpPanelHandle, 0, 0, 0, pnlContainer.ClientWidth, pnlContainer.ClientHeight, SWP_NOZORDER); end;
1.2 可选优化:封装自定义VCL控件
如果需要完全让C# Panel成为VCL的一等公民,可以继承TWinControl封装自定义控件,在CreateParams中直接绑定C# Panel的句柄,这样无需手动处理Resize事件,VCL的布局逻辑会自动适配,同时控件会自动加入Components数组。
2 C#侧改造
调整WinForms侧的逻辑,适配跨框架父窗口:
using System; using System.Runtime.InteropServices; using System.Windows.Forms; namespace Parenttest { public partial class Form1 : Form { [DllImport("user32.dll")] public static extern IntPtr SetParent(IntPtr hWndChild, IntPtr hWndNewParent); [DllImport("user32.dll")] public static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, uint uFlags); private const uint SWP_NOZORDER = 0x0004; public Form1() { InitializeComponent(); // 提前配置Panel属性 panel1.Dock = DockStyle.Fill; // 隐藏主窗体,仅保留Panel this.ShowInTaskbar = false; this.FormBorderStyle = FormBorderStyle.None; this.WindowState = FormWindowState.Minimized; } private void button1_Click(object sender, EventArgs e) { // 这里替换为动态获取的Delphi容器Panel的Handle,不要写死 IntPtr delphiContainerHandle = (IntPtr)919640; SetParent(panel1.Handle, delphiContainerHandle); // 初始设置位置 SetWindowPos(panel1.Handle, IntPtr.Zero, 0, 0, 0, 0, SWP_NOZORDER | 0x0001 /* SWP_NOSIZE */); } } }
注意事项
- 不要写死窗口句柄,建议通过进程间通信(如WM_COPYDATA消息、共享内存)将Delphi容器的句柄动态传给C#程序,避免每次运行句柄变化导致功能失效
- 如果两个程序属于不同进程,需要处理跨进程窗口消息的权限问题,Windows Vista及以上系统默认开启UIPI限制,低权限进程无法向高权限进程的窗口发送消息,需配置manifest或者调整进程启动权限
- 不需要复杂交互逻辑的前提下,上述代码已经可以实现自动适配大小的需求,不需要额外的变通方案
内容的提问来源于stack exchange,提问作者White
相关产品推荐
相关产品推荐

