在WPF窗口中托pipe外部应用程序
我们正在WPF中开发一个布局pipe理器,该布局pipe理器具有可由用户移动/resize等的视口。 视图通常由布局pipe理器中的我们控制的提供者填充数据(图片/电影/等)。 我的工作是检查是否也有可能在视口中托pipe任何外部Windows应用程序(即记事本,计算器,Adobe阅读器等)。 我遇到了一些问题。
大多数资源指向使用HwndHost类。 我正在尝试从Microsoft本身的演练: http : //msdn.microsoft.com/en-us/library/ms752055.aspx
我已经适应了这个,所以列表框被外部应用程序的窗口句柄所取代。 有谁能帮我解决这些问题:
- 演练添加了一个额外的静态子窗口,其中放置了
ListBox
。 我不认为我需要外部应用程序。 如果我省略了,我必须将外部应用程序设置为子窗口(使用user32.dll中的Get / SetWindowLong将GWL_STYLE
设置为WS_CHILD
)。 但是,如果我这样做,该应用程序的菜单栏消失(因为WS_CHILD
风格),它不再接收input。 - 如果我使用子窗口,并使外部应用程序的一个孩子的东西合理的工作,但有时外部应用程序不画好。
- 另外,我需要子窗口调整到视口。 这可能吗?
- 当外部的应用程序产生一个子窗口(即记事本 – >帮助 – >关于),这个窗口不是由
HwndHost
托pipe(因此可以移动到视口外)。 有什么办法可以预防吗? - 由于我不需要外部应用程序和布局pipe理器之间的进一步交互,我是否认为我不需要捕获和转发消息? (演练将HwndSourceHook添加到子窗口中,以捕获列表框中的select更改)。
- 当你运行(未修改的)例子VS2010并closures窗口时,VS2010没有看到程序结束。 如果你打破了一切,你最终会在没有源代码的情况下进行汇编。 有些臭味正在发生,但我找不到。
- 演练本身似乎是非常草率的编码,但我还没有find关于这个问题的更好的文档。 任何其他的例子?
- 另一种方法是不要使用
HwndHost
而是像这里讨论的WindowsFormHost
。 它工作(而且更简单!),但我不能控制应用程序的大小? 另外,WinFormHost是不是真的是这个意思?
感谢任何正确的方向指针。
那么……如果这个问题是20年前提出来的,那么可以回答:“当然,看看'OLE'!”,这里是“对象链接和embedded”的链接:
http://en.wikipedia.org/wiki/Object_Linking_and_Embedding
如果你阅读这篇文章,你会看到这个规范定义的接口数量,并不是因为它的作者认为它很有趣,而是因为在一般情况下在技术上很难实现
实际上,它仍然受到一些应用程序(主要是微软的支持,因为微软几乎是OLE的唯一赞助商)
您可以使用名为DSOFramer的东西(请参阅SO: MS KB311765和DsoFramer从MS站点中缺less )来embedded这些应用程序,该组件允许您在应用程序内以可视方式托pipeOLE服务器(即:作为另一个进程运行的外部应用程序) 。 这是微软几年前发布的一个大黑客攻击,不再支持二进制文件很难find!
它(可能)仍然适用于简单的OLE服务器,但是我认为我读了它甚至不适用于新的Microsoft应用程序(如Word 2010)。因此,您可以使用DSOFramer作为支持它的应用程序。 你可以试试。
对于其他应用程序,当然,在现代世界中,我们生活在这样的环境中,您不需要托pipe应用程序 ,在外部进程中运行,托pipe组件 ,而且一般而言,这些应用程序可以在进程中运行。 这就是为什么你会有很大的困难做你想做的一般 。 你将面临的一个问题(而不是最近版本的Windows)是安全的:我不信任的进程怎么能合理地处理我的进程创build的窗口和菜单:-)?
不过,你可以通过应用程序,使用各种Windows黑客来做相当多的应用程序。 SetParent基本上是所有黑客的母亲:-)
下面是一段代码,用于扩展您指向的示例,添加自动resize以及删除标题框。 它演示了如何隐式删除控制框,系统菜单,例如:
public partial class Window1 : Window { private System.Windows.Forms.Panel _panel; private Process _process; public Window1() { InitializeComponent(); _panel = new System.Windows.Forms.Panel(); windowsFormsHost1.Child = _panel; } [DllImport("user32.dll")] private static extern int SetWindowLong(IntPtr hWnd, int nIndex, int dwNewLong); [DllImport("user32.dll", SetLastError = true)] private static extern int GetWindowLong(IntPtr hWnd, int nIndex); [DllImport("user32")] private static extern IntPtr SetParent(IntPtr hWnd, IntPtr hWndParent); [DllImport("user32")] private static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, int uFlags); private const int SWP_NOZORDER = 0x0004; private const int SWP_NOACTIVATE = 0x0010; private const int GWL_STYLE = -16; private const int WS_CAPTION = 0x00C00000; private const int WS_THICKFRAME = 0x00040000; private void button1_Click(object sender, RoutedEventArgs e) { button1.Visibility = Visibility.Hidden; ProcessStartInfo psi = new ProcessStartInfo("notepad.exe"); _process = Process.Start(psi); _process.WaitForInputIdle(); SetParent(_process.MainWindowHandle, _panel.Handle); // remove control box int style = GetWindowLong(_process.MainWindowHandle, GWL_STYLE); style = style & ~WS_CAPTION & ~WS_THICKFRAME; SetWindowLong(_process.MainWindowHandle, GWL_STYLE, style); // resize embedded application & refresh ResizeEmbeddedApp(); } protected override void OnClosing(System.ComponentModel.CancelEventArgs e) { base.OnClosing(e); if (_process != null) { _process.Refresh(); _process.Close(); } } private void ResizeEmbeddedApp() { if (_process == null) return; SetWindowPos(_process.MainWindowHandle, IntPtr.Zero, 0, 0, (int)_panel.ClientSize.Width, (int)_panel.ClientSize.Height, SWP_NOZORDER | SWP_NOACTIVATE); } protected override Size MeasureOverride(Size availableSize) { Size size = base.MeasureOverride(availableSize); ResizeEmbeddedApp(); return size; } }
这基本上是所有Windows“传统”的黑客。 您也可以删除不喜欢的项目菜单,如下所述: http : //support.microsoft.com/kb/110393/en-us (如何从窗体的控制菜单框中删除菜单项)。
你也可以用“winword.exe”replace“notepad.exe”,它似乎工作。 但是这有限制(键盘,鼠标,焦点等)。
祝你好运!
在阅读本主题中的答案之后,我自己做了一些反复试验,最后得到了一些效果很好的东西,但是当然有些东西在特殊情况下需要注意。
我使用HwndHostEx作为我的主机类的基类,你可以在这里find它: http ://microsoftdwayneneed.codeplex.com/SourceControl/changeset/view/69631#1034035
示例代码:
public class NotepadHwndHost : HwndHostEx { private Process _process; protected override HWND BuildWindowOverride(HWND hwndParent) { ProcessStartInfo psi = new ProcessStartInfo("notepad.exe"); _process = Process.Start(psi); _process.WaitForInputIdle(); // The main window handle may be unavailable for a while, just wait for it while (_process.MainWindowHandle == IntPtr.Zero) { Thread.Yield(); } HWND hwnd = new HWND(_process.MainWindowHandle); int style = NativeMethods.GetWindowLong(hwnd, GWL.STYLE); style = style & ~((int)WS.CAPTION) & ~((int)WS.THICKFRAME); // Removes Caption bar and the sizing border style |= ((int)WS.CHILD); // Must be a child window to be hosted NativeMethods.SetWindowLong(hwnd, GWL.STYLE, style); return hwnd; } protected override void DestroyWindowOverride(HWND hwnd) { _process.CloseMainWindow(); _process.WaitForExit(5000); if (_process.HasExited == false) { _process.Kill(); } _process.Close(); _process.Dispose(); _process = null; hwnd.Dispose(); hwnd = null; } }
HWND,NativeMethods和枚举也来自DwayneNeed库(Microsoft.DwayneNeed.User32)。
只需将NotepadHwndHost添加为WPF窗口中的一个子项,就可以看到托pipe在那里的记事本窗口。
Simon Mourier的回答非常好。 但是,当我用自己制作的Winform应用程序尝试时,失败了。
_process.WaitForInputIdle();
可以被replace
while (_process.MainWindowHandle==IntPtr.Zero) { Thread.Sleep(1); }
一切顺利
谢谢你的回答。
解决scheme是令人难以置信的参与。 很多代码。 这里有一些提示。
首先,你在正确的轨道上。
你必须使用HwndHost和HwndSource的东西。 如果你不这样做,你会得到视觉神器。 像闪烁一样。 一个警告,如果你不使用主机和源代码,它会看起来像它会工作,但它不会到最后 – 它会有随机的小愚蠢的错误。
看看这个提示。 这不完整,但会帮助你走向正确的方向。 http://microsoftdwayneneed.codeplex.com/SourceControl/changeset/view/50925#1029346
你必须进入Win32来控制你正在问的很多东西。 你需要捕捉和转发消息。 你需要控制哪些窗口“拥有”子窗口。
使用Spy ++很多。
我有这个在生产中运行,在WPF应用程序中这么好。 确保从拥有window
UI线程调用SetNativeWindowInWPFWindowAsChild()
。
public static bool SetNativeWindowInWPFWindowAsChild(IntPtr hWndNative, Window window) { UInt32 dwSyleToRemove = WS_POPUP | WS_CAPTION | WS_THICKFRAME; UInt32 dwExStyleToRemove = WS_EX_DLGMODALFRAME | WS_EX_WINDOWEDGE | WS_EX_CLIENTEDGE | WS_EX_STATICEDGE; UInt32 dwStyle = GetWindowLong(hWndNative, GWL_STYLE); UInt32 dwExStyle = GetWindowLong(hWndNative, GWL_EXSTYLE); dwStyle &= ~dwSyleToRemove; dwExStyle &= ~dwExStyleToRemove; SetWindowLong(hWndNative, GWL_STYLE, dwStyle | WS_CHILD); SetWindowLong(hWndNative, GWL_EXSTYLE, dwExStyle); IntPtr hWndOld = SetParent(hWndNative, new WindowInteropHelper(window).Handle); if (hWndOld == IntPtr.Zero) { System.Diagnostics.Debug.WriteLine("SetParent() Failed -> LAST ERROR: " + Marshal.GetLastWin32Error() + "\n"); } return hWndOld != IntPtr.Zero; }
这是我使用的Native Win32 API。 (这里有额外的东西,因为我设置了窗口的大小/焦点)
[StructLayout(LayoutKind.Sequential)] private struct RECT { public Int32 left; public Int32 top; public Int32 right; public Int32 bottom; } [DllImport("user32.dll", SetLastError = true)] private static extern IntPtr SetParent(IntPtr hWndChild, IntPtr hWndNewParent); [DllImport("user32.dll")] private static extern UInt32 SetWindowLong(IntPtr hWnd, int nIndex, UInt32 dwNewLong); [DllImport("user32.dll")] private static extern UInt32 GetWindowLong(IntPtr hWnd, int nIndex); [DllImport("user32.dll")] private static extern bool GetWindowRect(IntPtr hWnd, out RECT lpRect); [DllImport("user32.dll")] private static extern IntPtr SetFocus(IntPtr hWnd); [DllImport("user32.dll")] private static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, SetWindowPosFlags uFlags); private static int GWL_STYLE = -16; private static int GWL_EXSTYLE = -20; private static UInt32 WS_CHILD = 0x40000000; private static UInt32 WS_POPUP = 0x80000000; private static UInt32 WS_CAPTION = 0x00C00000; private static UInt32 WS_THICKFRAME = 0x00040000; private static UInt32 WS_EX_DLGMODALFRAME = 0x00000001; private static UInt32 WS_EX_WINDOWEDGE = 0x00000100; private static UInt32 WS_EX_CLIENTEDGE = 0x00000200; private static UInt32 WS_EX_STATICEDGE = 0x00020000; [Flags] private enum SetWindowPosFlags : uint { SWP_ASYNCWINDOWPOS = 0x4000, SWP_DEFERERASE = 0x2000, SWP_DRAWFRAME = 0x0020, SWP_FRAMECHANGED = 0x0020, SWP_HIDEWINDOW = 0x0080, SWP_NOACTIVATE = 0x0010, SWP_NOCOPYBITS = 0x0100, SWP_NOMOVE = 0x0002, SWP_NOOWNERZORDER = 0x0200, SWP_NOREDRAW = 0x0008, SWP_NOREPOSITION = 0x0200, SWP_NOSENDCHANGING = 0x0400, SWP_NOSIZE = 0x0001, SWP_NOZORDER = 0x0004, SWP_SHOWWINDOW = 0x0040 } private static readonly IntPtr HWND_TOPMOST = new IntPtr(-1); private static readonly IntPtr HWND_NOTOPMOST = new IntPtr(-2); private static readonly IntPtr HWND_TOP = new IntPtr(0); private static readonly IntPtr HWND_BOTTOM = new IntPtr(1);
看看我的答案: 如何在wpf应用程序内运行应用程序?
我设法得到记事本的例子,没有DwayneNeed jiggery。 我只是添加了SetParent()和繁荣…她的工作就像德维恩需要的例子。