将窗口嵌入到另一个进程中

4

我在StackOverflow上看了一些帖子,但是没有一个对我有用。这里是我正在使用的代码,在我的表单上显示标准计算器的窗口:

procedure TForm1.Button1Click(Sender: TObject);
var
  Tmp: Cardinal;
  R: TRect;
begin
  CalcWindow := FindWindow(nil, 'Calculator');
  if (CalcWindow <> 0) then
  begin
    GetWindowThreadProcessID(CalcWindow, CalcProcessID);

    Tmp := GetWindowLong(CalcWindow, GWL_STYLE);
    Tmp := (Tmp and not WS_POPUP) or WS_CHILD;
    SetWindowLong(CalcWindow, GWL_STYLE, Tmp);
    GetWindowRect(CalcWindow, R);

    SetForegroundWindow(CalcWindow);
    Windows.SetParent(CalcWindow, Panel1.Handle);
    SetWindowPos(CalcWindow, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOSIZE or SWP_FRAMECHANGED);

    AttachThreadInput(GetCurrentThreadID(), CalcWindow, True);
  end;
end;

它确实在我的窗体上显示了窗口,但玻璃边框丢失了,有时(特别是当我移动我的窗体时)很难将焦点恢复到嵌入的窗口(需要点击几次)。可能是什么原因导致这种情况?此方法可能会遇到哪些潜在问题?感谢您的时间。

也许Peter Below的这篇帖子与你有关。我没有看到你改变计算器的边框样式。 - Lieven Keersmaekers
我想要能够嵌入任何应用程序,计算器只是一个例子。 - Pateman
@DavidHeffernan 是的,我注意到了。你有什么建议吗?我想过使用BitBlt来进行窗口截图并在我的表单上显示它,但我认为这不是一个好的解决方案。 - Pateman
6
我建议不要尝试在你的应用程序中嵌入其他应用程序。 - David Heffernan
无论如何,我同意David的看法;@Pateman,关于潜在问题,我已经尝试了问题和答案中的两个代码,在我的计算机上没有任何作用(除了将计算器窗口置顶)。我正在使用Windows 7上的Delphi 2009。SetParent函数在我的系统上失败(结果=0)。我也尝试以管理员身份运行应用程序,但结果相同。 - TLama
显示剩余3条评论
2个回答

10

试试这段代码。我从我的一个旧源代码中取得了它。你将会失去玻璃框架,但主菜单是可见的,并且在设置焦点回嵌入应用程序时我没有注意到任何问题。你应该能够使用SetForegroundWindow() API函数来这样做。每当你移动容器表单时,你的嵌入应用程序就会失去焦点,所以你需要再次调用SetForegroundWindow来恢复焦点:

procedure ShowAppEmbedded(WindowHandle: THandle; Container: TWinControl);
var
  WindowStyle : Integer;
  FAppThreadID: Cardinal;
begin
  /// Set running app window styles.
  WindowStyle := GetWindowLong(WindowHandle, GWL_STYLE);
  WindowStyle := WindowStyle
                 - WS_CAPTION
                 - WS_BORDER
                 - WS_OVERLAPPED
                 - WS_THICKFRAME;
  SetWindowLong(WindowHandle,GWL_STYLE,WindowStyle);

  /// Attach container app input thread to the running app input thread, so that
  ///  the running app receives user input.
  FAppThreadID := GetWindowThreadProcessId(WindowHandle, nil);
  AttachThreadInput(GetCurrentThreadId, FAppThreadID, True);

  /// Changing parent of the running app to our provided container control
  Windows.SetParent(WindowHandle,Container.Handle);
  SendMessage(Container.Handle, WM_UPDATEUISTATE, UIS_INITIALIZE, 0);
  UpdateWindow(WindowHandle);

  /// This prevents the parent control to redraw on the area of its child windows (the running app)
  SetWindowLong(Container.Handle, GWL_STYLE, GetWindowLong(Container.Handle,GWL_STYLE) or WS_CLIPCHILDREN);
  /// Make the running app to fill all the client area of the container
  SetWindowPos(WindowHandle,0,0,0,Container.ClientWidth,Container.ClientHeight,SWP_NOZORDER);

  SetForegroundWindow(WindowHandle);
end;
你可以这样调用它:

You can call it this way:

  ShowAppEmbedded(FindWindow(nil, 'Calculator'), Panel1);

谢谢!我会尝试这个方法和另一种方法。那么子窗口呢?比如说,应用程序打开标准的“打开文件”对话框。有没有办法也将其保留在我的应用程序中?你觉得呢? - Pateman
1
AttachThreadInput函数的第三个参数fAttach设置为False是正常的吗?这不意味着您要分离线程吗? - FileVoyager
1
还有一件事,就是现在的AttachInputThread必定会失败,因为你传递了processId。我认为你需要传递ThreadId,这是GetWindowThreadProcessId函数的返回值。 - FileVoyager
这段代码在最新的 Windows 10calc.exe 上已经无法工作了。这行代码出现了错误:Windows.SetParent(WindowHandle,Container.Handle);GetLastError 返回 参数不正确 - user1439838
看起来Windows 10中有一个bug:https://social.msdn.microsoft.com/Forums/windows/en-US/df783714-2074-4127-a469-65964bf89e0a/setparent-to-the-window-of-another-process-fails-in-windows-10-but-works-in-window-7?forum=windowsgeneraldevelopmentissues - user1439838
显示剩余2条评论

2
为了您的理智和程序用户的理智,我认为您最好放弃这个想法:
我曾经尝试过使用自己的软件(32位应用程序窗口嵌入64位包装器)来实现这个想法,即使使用了其他答案中提供的技巧,它也从未完美地工作过。
以下是需要注意的问题: 1. 要可靠地使其正常工作非常困难,有无数微小而微妙的问题,您永远无法解决。如果您正在操纵其他应用程序的窗口,而这些应用程序不知道您的操作,那么通常会出现问题。 2. 您正在改变用户期望Windows表现的方式。这将令他们感到困惑和意外。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接