Hosting esterni app in finestra WPF

Stiamo sviluppando un layout manager in WPF che ha finestre che possono essere spostati o ridimensionati/ecc da un utente. Finestre sono di norma compilato con i dati (foto/film/ecc), attraverso i provider che sono sotto il nostro controllo nel layout manager. Il mio lavoro è quello di esaminare se è possibile, inoltre, host esterno di app di Windows (ossia il blocco note, calcolatrice, adobe reader, ecc) in un riquadro di visualizzazione. Ho incontrato un certo numero di problemi.

La maggior parte delle risorse del punto all’utilizzo di HwndHost classe. Sto sperimentando con questa procedura dettagliata da Microsoft stessa: http://msdn.microsoft.com/en-us/library/ms752055.aspx

Ho adattato questo modo la casella di riepilogo è sostituito con il windows maniglia dall’esterno. C’è qualcuno che può aiutarmi con queste domande:

  1. Il walkthrough aggiunge un ulteriore sub static finestra in cui il ListBox è posto. Non credo che ho bisogno che per applicazioni esterne. Se ho ommit, devo fare l’app esterne una finestra figlio (tramite Get/SetWindowLong da user32.dll per impostare GWL_STYLE come WS_CHILD). Ma se lo faccio, la barra dei menu dell’app scompare (a causa della WS_CHILD stile) e non riceve l’input.
  2. Se io uso il sub finestra, e rendere l’app esterne di un bambino di che le cose funzionano abbastanza, ma a volte l’app esterne non dipingere su ok.
  3. Inoltre, ho bisogno il bambino finestra per ridimensionare il riquadro di visualizzazione. È possibile questo?
  4. Quando il exernal app genera una finestra figlio (cioè il blocco note->Help->Circa), questa finestra non è ospitato dal HwndHost (e quindi possono essere spostati al di fuori della finestra). C’è un modo per evitarlo?
  5. Dal momento che non ho bisogno di un’ulteriore interazione tra l’applicazione esterna e la gestione del layout, ho ragione nel ritenere che non ho bisogno di catturare e inoltrare i messaggi? (il walkthrough aggiunge un HwndSourceHook per il sub finestra per prendere modifiche di selezione nella casella di riepilogo).
  6. Quando si esegue l’ (non modificato) esempio VS2010 e chiudere la finestra, VS2010 non vedere che il programma è terminato. Se si rompe e si finisce in assemblea senza fonte. Qualcosa di puzzolente sta succedendo, ma non riesco a trovarlo.
  7. La procedura sembra essere molto sciatta codificato, ma non ho trovato nulla meglio di documentazione su questo argomento. Altri esempi?
  8. Un altro approccio è di non utilizzare HwndHost ma WindowsFormHost come discusso qui. Funziona (ed è molto più semplice!) ma non ho il controllo sulla dimensione della domanda? Inoltre, WinFormHost non è stato pensato per questo?

Grazie per tutti i puntatori nella giusta direzione.

  • Ciao, vorrei sicuramente andare per il punto 8.
InformationsquelleAutor Stiggy | 2011-02-17

 

6 Replies
  1. 25

    Beh… se la domanda era stata posta come 20 anni fa, si sarebbe potuto rispondere: “Certo, guarda ‘OLE’!”, ecco un link a ciò che è “Object Linking and Embedding”:

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

    Se leggete questo articolo, si può vedere il numero di interfacce di questa spec definito, non perché il suo autore ha pensato fosse divertente, ma perché è tecnicamente difficile da realizzare in generale i casi

    In realtà è ancora supportato da alcune applicazioni (soprattutto quelli di Microsoft, come Microsoft era quasi l’unico sponsor di OLE…)

    È possibile incorporare queste app utilizza qualcosa chiamato DSOFramer (vedi link qui COSÌ: MS KB311765 e DsoFramer sono mancanti dal sito MS), un componente che consente di ospitare il server OLE (ie: applicazioni esterne in esecuzione di un altro processo)
    visivamente all’interno di un’applicazione. E ‘ una sorta di un grande hack Microsoft lasciare fuori un paio di anni fa, che non verrà più supportata, al punto che i binari sono abbastanza difficili da trovare!

    Si (può) funziona ancora per il semplice server OLE, ma credo di aver letto da qualche parte ma non funziona per le nuove applicazioni Microsoft come Word 2010.
    Così, è possibile utilizzare DSOFramer per applicazioni che lo supportano. Si può provare.

    Per altre applicazioni, bene, oggi, nel mondo moderno in cui viviamo, non è host applicazioni, corse nel processo esterno, host è componenti, e sono, in generale, deve essere eseguito in corso.
    Ecco perché non si avranno grandi difficoltà a fare ciò che si vuole fare in generale. Un problema che si dovrà affrontare (e non ultimo con le versioni più recenti di Windows) è la sicurezza: come è possibile tuo processo non mi fido può legittimamente gestire mio le finestre e i menu creati da mio processo 🙂 ?

    Ancora, si può fare un bel po ‘ applicazione per applicazione, utilizzando vari Windows hack.
    SetParent è fondamentalmente la madre di tutte le hack 🙂

    Qui è un pezzo di codice che si estende campione che punto, l’aggiunta di ridimensionamento automatico, e la rimozione della casella didascalia.
    Questo dimostra come il valore rimuovere la casella di controllo, il menu di sistema, come ad esempio:

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

    Questo è fondamentalmente tutte le Finestre “tradizionale” hack. Si potrebbe anche rimuovere una voce di menu che non ti piace, come spiegato qui: http://support.microsoft.com/kb/110393/en-us (Come per Rimuovere le Voci di Menu da un Modulo del Menu di Controllo).

    Si può anche sostituire “notepad.exe” da “winword.exe” e sembra di lavoro. Ma ci sono delle limitazioni a questo (tastiera, mouse, messa a fuoco, etc.).

    Buona fortuna!

    • Attendere, WINApi chiamate sono considerati “hack” per .NET programmatori? o_O
    • L’API è supportata, ma non si dovrebbe giocare con altre applicazioni, come cambiare la didascalia, il telaio e la relazione genitore. Potrebbe benissimo mandare in crash l’applicazione di destinazione.
    • Sono abbastanza fiera. BTW, MainWindowHandle spesso non funziona (con l’ultima versione di IE, Chrome, Firefox, ad esempio). Un po ‘ più affidabile è quella di utilizzare FindWindow con la classe di finestra per ottenere l’hwnd. A parte questo, conoscete un modo per mantenere la finestra di messa a fuoco anche quando l’app esterne viene cliccato?
    • Ho ottenuto questo parzialmente lavoro, però ho trovato impostare gli stili sono molto colpito e perdere su XP, in quanto in esso opera prima volta, poi ho ri correre e non, posso cambiare la risoluzione e lo fa, allora non esiste. Win7 funziona bene, qualche idea ?
    • È in generale abbastanza duro per garantire queste tecniche sono in stile/tema agnostico. Cambiare il tema di Windows Classico ” (senza Aero su Vista/Win7) è un buon modo per testare. Modifica la dimensione del carattere (120%) è anche un buon test. Non posso fare molto di più 🙂
    • Questo approccio di lavoro per l’hosting di un’applicazione QT? Io non posso farlo funzionare
    • Non ho idea, ma nulla è garantito, si dovrebbe porre un’altra domanda.

  2. 5

    Simon Mourier la risposta è molto ben scritto. Tuttavia, quando ho provato con una winform app fatta da solo, non è riuscito.

    _process.WaitForInputIdle();

    può essere sostituito da

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

    e tutto va liscio.

    Grazie per la domanda e a tutti voi per le vostre risposte.

  3. 4

    Dopo aver letto le risposte in questo thread e fare qualche prova ed errore, io ho finito con qualcosa che funziona abbastanza bene, ma di certo alcune cose avranno bisogno della vostra attenzione per casi particolari.

    Ho usato il HwndHostEx come classe base per il mio host di classe, che si può trovare qui: http://microsoftdwayneneed.codeplex.com/SourceControl/changeset/view/69631#1034035

    Codice di esempio:

    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 e enumerazioni deriva dal DwayneNeed biblioteca (di Microsoft.DwayneNeed.User32).

    Semplicemente aggiungere il NotepadHwndHost come un bambino in una finestra WPF e si dovrebbe vedere la finestra di blocco note ospitato.

  4. 1

    La soluzione è incredibilmente coinvolto. Un sacco di codice. Ecco un paio di suggerimenti.

    Prima, siete sulla strada giusta.

    È necessario utilizzare il HwndHost e HwndSource cosa. Se non, si otterrà la presenza di artefatti visivi. Come sfarfallio. Un avvertimento, se non si utilizza l’Host di Origine e, sembra come funziona, ma non nel fine-che avrà casuale po ‘ stupido bug.

    Dare un’occhiata a questo per un po ‘ di suggerimenti. Non è completa, ma vi aiuterà ad andare nella giusta direzione.
    http://microsoftdwayneneed.codeplex.com/SourceControl/changeset/view/50925#1029346

    Devi entrare in Win32 per il controllo di un sacco di quello che stai chiedendo. Hai bisogno di catturare e di inoltrare i messaggi. È necessario controllare che windows “proprio” il bambino di windows.

    Utilizzare Spy++ un sacco.

  5. 1

    Ho questo in esecuzione in produzione e finora tutto bene in un’applicazione WPF. Assicurarsi di chiamare SetNativeWindowInWPFWindowAsChild() dal thread dell’interfaccia utente che possiede window.

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

    Qui è il Nativo Win32 API che ho usato. (Ci sono gli extra qui perché ho formato, messa a fuoco e la finestra dopo aver impostato)

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

Lascia un commento