44 votes

Hébergement d'une application externe dans une fenêtre WPF

Nous développons un gestionnaire de mise en page dans WPF qui possède des fenêtres d'affichage qui peuvent être déplacées/réduites en taille/etc par un utilisateur. Les fenêtres sont normalement remplies de données (images/films/etc) via des fournisseurs qui sont sous notre contrôle dans le gestionnaire de mise en page. Mon travail consiste à examiner s'il est également possible d'héberger une application Windows externe (par exemple notepad, calc, adobe reader, etc.) dans une fenêtre. Je rencontre un certain nombre de problèmes.

La plupart des ressources indiquent qu'il faut utiliser la classe HwndHost. J'expérimente avec ce guide de Microsoft lui-même : http://msdn.microsoft.com/en-us/library/ms752055.aspx

J'ai adapté ce système de façon à ce que la boîte de liste soit remplacée par la poignée Windows de l'application externe. Quelqu'un peut-il m'aider à répondre à ces questions ?

  1. Le guide ajoute une sous-fenêtre statique supplémentaire dans laquelle l'option ListBox est placé. Je ne pense pas avoir besoin de cela pour les applications externes. Si je l'omet, je dois faire de l'application externe une fenêtre enfant (en utilisant Get/SetWindowLong de user32.dll pour définir les paramètres de la fenêtre enfant). GWL_STYLE comme WS_CHILD ). Mais si je fais cela, la barre de menu de l'application disparaît (à cause de l'option WS_CHILD ) et il ne reçoit plus d'entrée.
  2. Si j'utilise la sous-fenêtre, et que je fais de l'application externe un enfant de celle-ci, les choses fonctionnent raisonnablement, mais parfois l'application externe ne se peint pas correctement.
  3. J'ai également besoin que la fenêtre enfant soit redimensionnée en fonction de la fenêtre d'affichage. Cela est-il possible ?
  4. Lorsque l'application externe génère une fenêtre enfant (par exemple Notepad->Help->About), cette fenêtre n'est pas hébergée par l'application externe. HwndHost (et peut donc être déplacé en dehors de la fenêtre d'affichage). Existe-t-il un moyen d'empêcher cela ?
  5. Puisque je n'ai pas besoin d'interaction supplémentaire entre l'application externe et le gestionnaire de mise en page, ai-je raison de supposer que je n'ai pas besoin d'attraper et de transmettre des messages ? (le guide ajoute un HwndSourceHook à la sous-fenêtre afin d'intercepter les changements de sélection dans la boîte de liste).
  6. Lorsque vous exécutez l'exemple (non modifié) VS2010 et que vous fermez la fenêtre, VS2010 ne voit pas que le programme s'est terminé. Si vous faites un break-all, vous vous retrouvez en assembly sans source. Il y a quelque chose qui sent mauvais, mais je ne le trouve pas.
  7. Le guide lui-même semble être très mal codé, mais je n'ai pas trouvé de meilleure documentation à ce sujet. D'autres exemples ?
  8. Une autre approche consiste à ne pas utiliser HwndHost mais WindowsFormHost comme discuté ici . Cela fonctionne (et c'est beaucoup plus simple !) mais je n'ai pas le contrôle de la taille de l'application ? De plus, WinFormHost n'est pas vraiment conçu pour cela ?

Merci pour toute indication dans la bonne direction.

0 votes

Bonjour, je choisirais sans aucun doute le point 8.

32voto

Simon Mourier Points 49585

Eh bien... si la question avait été posée il y a 20 ans, on aurait répondu : "Bien sûr, regardez 'OLE' !", voici un lien vers ce qu'est "Object Linking and Embedding" :

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

Si vous lisez cet article, vous verrez le nombre d'interfaces que cette spécification a définies, non pas parce que son auteur pensait que c'était amusant, mais parce que c'est techniquement difficile à réaliser dans les cas généraux

En fait, il est toujours supporté par certaines applications (principalement celles de Microsoft, car Microsoft était presque le seul sponsor d'OLE...).

Vous pouvez intégrer ces applications en utilisant un outil appelé DSOFramer (voir les liens ici sur SO : MS KB311765 et DsoFramer sont absents du site MS ), un composant qui vous permet d'héberger un serveur OLE (c'est-à-dire des applications externes s'exécutant sous un autre processus) visuellement à l'intérieur d'une application. C'est une sorte de gros hack que Microsoft a laissé sortir il y a quelques années, qui n'est plus supporté au point que les binaires sont assez difficiles à trouver !

Cela fonctionne (peut-être) encore pour les simples serveurs OLE, mais je crois avoir lu quelque part que cela ne fonctionne même pas pour les nouvelles applications Microsoft telles que Word 2010. Donc, vous pouvez utiliser DSOFramer pour les applications qui le supportent. Vous pouvez l'essayer.

Pour d'autres applications, eh bien, aujourd'hui, dans le monde moderne dans lequel nous vivons, vous n'hébergez pas applications exécuté dans le processus externe, vous accueillez composants et ils sont en général censés fonctionner en cours de traitement . C'est pourquoi vous aurez de grandes difficultés à faire ce que vous voulez faire. en général . Un problème auquel vous serez confronté (et non des moindres avec les versions récentes de Windows) est celui de la sécurité : comment faire pour votre un processus dont je n'ai pas confiance peut légitimement gérer mon Fenêtres et menus créés par mon procédé :-) ?

Néanmoins, vous pouvez faire beaucoup de choses, application par application, en utilisant divers piratages de Windows. SetParent est en fait la mère de toutes les astuces :-)

Voici un morceau de code qui étend l'exemple que vous indiquez, en ajoutant le redimensionnement automatique et la suppression de la boîte à légende. Il montre comment supprimer implicitement la boîte de contrôle, le menu système, à titre d'exemple :

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

Il s'agit essentiellement de tous les hacks "traditionnels" de Windows. Vous pouvez également supprimer les menus d'éléments que vous n'aimez pas, comme expliqué ici : http://support.microsoft.com/kb/110393/en-us (Comment supprimer les éléments de menu de la boîte de menu de contrôle d'un formulaire).

Vous pouvez aussi remplacer "notepad.exe" par "winword.exe" et c'est semble pour fonctionner. Mais il y a des limites à cela (clavier, souris, focus, etc.).

Bonne chance !

4 votes

Attendez, les appels WINApi sont considérés comme des "hacks" pour les programmeurs .NET ? o_O

1 votes

@TamásSzelei - L'API est prise en charge, mais vous n'êtes pas censé jouer avec les applications des autres, par exemple en modifiant leur légende, leur cadre et leur relation parentale. Cela pourrait très bien faire planter l'application cible.

1 votes

Cela suffit. BTW, MainWindowHandle ne fonctionne souvent pas (avec les derniers IE, Chrome, Firefox par exemple). Une approche un peu plus fiable consiste à utiliser FindWindow avec la classe de fenêtre pour obtenir le hwnd. En dehors de cela, connaissez-vous un moyen de garder le focus de la fenêtre même lorsque l'application externe est cliquée ?

7voto

TGasdf Points 209

Après avoir lu les réponses dans ce fil de discussion et fait quelques essais et erreurs moi-même, j'ai trouvé quelque chose qui fonctionne assez bien, mais bien sûr, certaines choses nécessiteront votre attention pour des cas particuliers.

J'ai utilisé la HwndHostEx comme classe de base pour ma classe hôte, vous pouvez la trouver ici : http://microsoftdwayneneed.codeplex.com/SourceControl/changeset/view/69631#1034035

Exemple de code :

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

        const int GWL_STYLE = -16;
        const int BORDER = 0x00800000;
        const int DLGFRAME = 0x00400000;
        const int WS_CAPTION = BORDER | DLGFRAME;
        const int WS_THICKFRAME = 0x00040000;
        const int WS_CHILD = 0x40000000;

        int style = GetWindowLong(notepadHandle, GWL_STYLE);
        style = style & ~WS_CAPTION & ~WS_THICKFRAME; // Removes Caption bar and the sizing border
        style |= 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;
    }
}

Les HWND, NativeMethods et enums proviennent également de la bibliothèque DwayneNeed (Microsoft.DwayneNeed.User32).

Ajoutez simplement le NotepadHwndHost comme enfant dans une fenêtre WPF et vous devriez voir la fenêtre du bloc-notes hébergée à cet endroit.

7voto

PauLEffect Points 65

La réponse de Simon Mourier est extrêmement bien écrite. Cependant, lorsque je l'ai essayée avec une application winform réalisée par moi-même, elle a échoué.

_process.WaitForInputIdle();

peut être remplacé par

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

et tout se passe bien.

Merci pour cette grande question et à vous tous pour vos réponses.

2 votes

Je ne pense pas que bloquer le thread de l'interface utilisateur avec Thread.Sleep(1) est une bonne idée - cela réduira les performances de l'application async dans WPF ou WinForms en utilisant le contexte de synchronisation par défaut, par exemple. Vous pourriez remplacer Thread.Sleep(1) avec Application.DoEvents(); ou await Task.Yield() par exemple.

1voto

010110110101 Points 2240

La solution est incroyablement complexe. Beaucoup de code. Voici quelques conseils.

Tout d'abord, vous êtes sur la bonne voie.

Vous devez utiliser le truc HwndHost et HwndSource. Si vous ne le faites pas, vous obtiendrez des artefacts visuels. Comme le scintillement. Un avertissement : si vous n'utilisez pas l'hôte et la source, vous aurez l'impression que cela fonctionnera, mais ce ne sera pas le cas à la fin - il y aura des petits bogues stupides et aléatoires.

Jetez un coup d'œil à ce document pour obtenir quelques conseils. Il n'est pas complet, mais il vous aidera à aller dans la bonne direction. http://microsoftdwayneneed.codeplex.com/SourceControl/changeset/view/50925#1029346

Vous devez entrer dans Win32 pour contrôler une grande partie de ce que vous demandez. Vous devez attraper et transmettre les messages. Vous devez contrôler quelles fenêtres "possèdent" les fenêtres enfants.

J'utilise souvent Spy++.

0voto

Youngy Points 71

Regardez ma réponse à : Comment exécuter une application à l'intérieur d'une application wpf ?

J'ai réussi à faire fonctionner l'exemple du bloc-notes sans la bidouille de DwayneNeed. J'ai juste ajouté SetParent() et boom... elle fonctionne exactement comme l'exemple de DwayneNeed.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X