it-swarm.com.de

Hosten einer externen App im WPF-Fenster

Wir entwickeln einen Layout-Manager in WPF, der Ansichtsfenster enthält, die von einem Benutzer verschoben/in der Größe verändert werden können. Ansichtsfenster werden normalerweise mit Daten (Bildern/Filmen usw.) über Anbieter gefüllt, die im Layout-Manager unter unserer Kontrolle stehen. Meine Aufgabe ist es zu prüfen, ob es auch möglich ist, externe Windows-Apps (z. B. Notepad, Calc, Adobe Reader usw.) in einem Ansichtsfenster zu hosten. Ich habe eine Reihe von Problemen.

Die meisten Ressourcen verweisen auf die Verwendung der HwndHost-Klasse. Ich experimentiere mit dieser exemplarischen Vorgehensweise von Microsoft selbst: http://msdn.Microsoft.com/en-us/library/ms752055.aspx

Ich habe dies so angepasst, dass das Listenfeld durch das Fensterhandle aus der externen Anwendung ersetzt wird. Kann mir jemand mit diesen Fragen helfen?

  1. Die exemplarische Vorgehensweise fügt ein zusätzliches statisches Unterfenster hinzu, in dem ListBox platziert wird. Ich glaube nicht, dass ich das für externe Apps brauche. Wenn ich es nicht zulasse, muss ich die externe App zu einem untergeordneten Fenster machen (mithilfe von Get/SetWindowLong aus user32.dll, um GWL_STYLE als WS_CHILD festzulegen). Wenn ich das mache, verschwindet die Menüleiste der App (aufgrund des WS_CHILD-Stils) und sie erhält keine Eingaben mehr. 
  2. Wenn ich das Unterfenster verwende und die externe App zu einem untergeordneten Element mache, funktionieren die Dinge zwar sinnvoll, aber manchmal funktioniert die externe App nicht in Ordnung. 
  3. Ich brauche auch das untergeordnete Fenster, um die Größe des Darstellungsbereichs zu ändern. Ist das möglich? 
  4. Wenn die externe App ein untergeordnetes Fenster erzeugt (d. H. Notepad-> Hilfe-> Info), wird dieses Fenster nicht von HwndHost gehostet (und kann daher außerhalb des Darstellungsbereichs verschoben werden). Gibt es eine Möglichkeit, das zu verhindern? 
  5. Da ich keine weitere Interaktion zwischen der externen Anwendung und dem Layout-Manager benötige, gehe ich davon aus, dass ich keine Nachrichten abfangen und weiterleiten muss? (Die exemplarische Vorgehensweise fügt dem Unterfenster ein HwndSourceHook hinzu, um Änderungen der Auswahl im Listenfeld abzufangen). 
  6. Wenn Sie das (unveränderte) Beispiel VS2010 ausführen und das Fenster schließen, erkennt VS2010 nicht, dass das Programm beendet wurde. Wenn Sie alle zusammenbrechen, landen Sie in Assembly ohne Quelle. Es riecht etwas, aber ich kann es nicht finden. 
  7. Die Komplettlösung selbst scheint sehr schlampig zu sein, aber ich habe keine bessere Dokumentation zu diesem Thema gefunden. Irgendwelche anderen Beispiele?
  8. Ein anderer Ansatz besteht darin, nicht HwndHost zu verwenden, sondern WindowsFormHost wie hier hier . Es funktioniert (und ist viel einfacher!), Aber ich habe keine Kontrolle über die Größe der Anwendung? WinFormHost ist auch nicht wirklich dafür gedacht? 

Danke für alle Hinweise in die richtige Richtung.

37
Stiggy

Nun, wenn die Frage vor 20 Jahren gestellt worden wäre, hätte man die Antwort: "Sicher, schauen Sie sich 'OLE' an!", Hier ist ein Link zu "Objektverknüpfung und Einbettung":

http://en.wikipedia.org/wiki/Object_Linking_and_Embedding

Wenn Sie diesen Artikel lesen, sehen Sie die Anzahl der Schnittstellen, die von dieser Spezifikation definiert wurden, nicht weil der Autor dies als Spaß empfand, sondern weil es technisch schwierig ist, in den allgemeinen Fällenzu erreichen. _ 

Es wird tatsächlich immer noch von einigen Apps unterstützt (meistens von Microsoft, da Microsoft fast der einzige Sponsor von OLE war ...)

Sie können diese Apps mit DSOFramer einbetten (siehe Links hier auf SO: MS KB311765 und DsoFramer fehlen auf MS-Site ), einer Komponente, die das Hosten von OLE -Servern ermöglicht (z. B. externe Apps, die ausgeführt werden) als anderer Prozess) visuell in einer Anwendung. Es ist eine Art großer Hack, den Microsoft vor ein paar Jahren rausgelassen hat und der nicht mehr so ​​weit unterstützt wird, dass die Binärdateien schwer zu finden sind!

Es funktioniert (kann) immer noch für einfache OLE -Server, aber ich denke, ich habe irgendwo gelesen, dass es nicht einmal für neue Microsoft-Anwendungen wie Word 2010 funktioniert. So können Sie DSOFramer für Anwendungen verwenden, die dies unterstützen. Du kannst es versuchen.

Für andere Anwendungen ist es heutzutage in der modernen Welt, in der wir leben, nicht Anwendungen ausgeführt, sie wurden in einem externen Prozess ausgeführt, Sie hosten Komponenten, und sie werden im Allgemeinen angenommen inprocess..__ ausführen. Deshalb werden Sie große Schwierigkeiten haben, das zu tun, was Sie wollen im Allgemeinen. Ein Problem, mit dem Sie (und nicht zuletzt bei aktuellen Windows-Versionen) konfrontiert sind, ist die Sicherheit: Wie können Ihr -Prozess, dem ich nicht vertraue, mein Fenster und Menüs, die von my erstellt wurden, rechtmäßig verarbeiten können? Prozess :-)?

Sie können jedoch eine ganze Reihe von Anwendungen mit verschiedenen Windows-Hacks ausführen. __ SetParent ist im Grunde die Mutter aller Hacks :-)

Hier ist ein Stück Code, der das von Ihnen angezeigte Beispiel erweitert, die automatische Größenänderung hinzufügt und das Beschriftungsfeld entfernt .. __ Es zeigt, wie das Kontrollkästchen (Systemmenü) als Beispiel implizit entfernt wird:

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;
    }
}

Dies sind im Grunde alle "traditionellen" Hacks von Windows. Sie können auch Elementmenüs entfernen, die Sie nicht mögen, wie hier erläutert: http://support.Microsoft.com/kb/110393/en-us (Entfernen von Menüelementen aus dem Steuerfeld eines Formulars ).

Sie können "notepad.exe" auch durch "winword.exe" ersetzen, und es scheint scheint zu funktionieren. Es gibt jedoch Einschränkungen (Tastatur, Maus, Fokus usw.).

Viel Glück!

23
Simon Mourier

Nachdem ich die Antworten in diesem Thread gelesen und selbst versucht hatte, etwas zu probieren, bekam ich etwas, das ziemlich gut funktioniert, aber einige Dinge brauchen natürlich Ihre Aufmerksamkeit für spezielle Fälle.

Ich habe HwndHostEx als Basisklasse für meine Host-Klasse verwendet. Sie finden sie hier: http://microsoftdwayneneed.codeplex.com/SourceControl/changeset/view/69631#1034035

Beispielcode:

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;
    }
}

Die HWND, NativeMethods und Enums stammen ebenfalls aus der DwayneNeed-Bibliothek (Microsoft.DwayneNeed.User32).

Fügen Sie einfach NotepadHwndHost als Kind in einem WPF-Fenster hinzu, und Sie sollten das dort bereitgestellte Notepad-Fenster sehen.

4
TGasdf

Die Antwort von Simon Mourier ist sehr gut geschrieben. Als ich es jedoch mit einer von mir erstellten Winform-App ausprobierte, schlug es fehl.

_process.WaitForInputIdle();

kann durch ersetzt werden 

while (_process.MainWindowHandle==IntPtr.Zero)
            {
                Thread.Sleep(1);
            }

und alles läuft reibungslos.

Vielen Dank für die großartige Frage und alle von Ihnen für Ihre Antworten.

4
PauLEffect

Ich habe dies in der Produktion und bisher so gut in einer WPF-Anwendung. Stellen Sie sicher, dass Sie SetNativeWindowInWPFWindowAsChild() von dem UI-Thread aufrufen, der window besitzt.

    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;
    }

Hier ist die native Win32-API, die ich verwendet habe. (Es gibt Extras hier, weil ich das Fenster nach dem Einstellen der Größe fokussiere.)

        [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);
1
Andy

Die Lösung ist unglaublich eng. Viel Code. Hier sind ein paar Hinweise. 

Zunächst sind Sie auf dem richtigen Weg. 

Sie müssen die Sache HwndHost und HwndSource verwenden. Wenn Sie dies nicht tun, erhalten Sie visuelle Artefakte. Wie Flimmern. Eine Warnung, wenn Sie den Host und die Quelle nicht verwenden, scheint es, als würde es funktionieren, aber am Ende wird es nicht funktionieren - es wird zufällige kleine dumme Fehler haben.

Werfen Sie einen Blick darauf, um einige Hinweise zu erhalten. Es ist nicht vollständig, aber es wird Ihnen helfen, in die richtige Richtung zu gehen . http://microsoftdwayneneed.codeplex.com/SourceControl/changeset/view/50925#1029346

Sie müssen sich mit Win32 befassen, um zu kontrollieren, worüber Sie fragen. Sie müssen Nachrichten abfangen und weiterleiten. Sie müssen kontrollieren, welche Fenster die untergeordneten Fenster "besitzen".

Verwenden Sie Spy ++ viel.

1
010110110101

Sehen Sie sich meine Antwort an: Wie führe ich eine Anwendung in der Wpf-Anwendung aus?

Ich habe es geschafft, das Notepad-Beispiel ohne DwayneNeed-Jiggery zum Laufen zu bringen. Ich habe gerade SetParent () und Boom hinzugefügt ... sie funktioniert genauso wie das DwayneNeed-Beispiel.

0
Youngy