Bloquer le menu contextuel du control Browser sur Windows Mobile

Pour faire un affichage de contenu « texte » sur une appli windows mobile (en réalité smartphone 5.0) j’utilise le controle navigateur. Celui-ci me permet une mise en forme plus simple en utilisant du html, ne serait-ce que pour mettre une partie du texte en gras. Cependant lorsque j’utilise mon application sur un pocket PC, avec le stylet j’ai accès au menu contextuel qui va entre autre me permettre de naviguer sur mes favoris. Je retrouve donc mon application avec google affiché en plein milieu (merci Olivier Courtois pour le debuggage 😉 ). Il faut donc que j’empeche ce menu d’apparaitre. On pourrait croire qu’il suffit de se brancher sur un évènement de type « ContextMenu_Popup » pour annuler l’apparition de ce menu, mais que neni cet évènement n’existe pas sur WindowsMobile. Il va falloir jongler avec des fonctions de l’API via du PInvoke pour « hooker » les message de fenêtre qui passe et intercepter le bon message.

La technique consiste donc déjà à récupérer le handle de fenêtre correspondant au navigateur, récupérer « un pointeur » sur la methode qui reçoit le message windows pour cette fenêtre,définir une nouvelle methode pour recevoir les message windows et enfin « filtrer » le message de type « context_menu » et renvoyer les autres messages dans la methode originelle. Je m’explique plus en détail :

Première étape : déterminer le handle qui correspond au navigateur:

On va énumérer les « windows » de notre application, sachant qu’elle sont imbriquée, et il va falloir trouver celle qui correspond au browser. Pour ttrouver la bonne window on va se baser sur sa « ClassName ». Le navigateur à une classe name nommé : PIEHTML.

Pour s’y retrouver il faut utilisant « remote spy », outil fourni avec visual studio, pour comprendre l’imbrication des fenêtres et constater que notre browser à une fenetre de ce nom.

Nous avons donc besoin de 2 methodes natives pour parcourir les fenêtre et determiner la « ClassName » :

public const int GW_HWNDNEXT = 2;
        public const int GW_CHILD = 5;

        [DllImport("coredll.dll", SetLastError = true)]
        private static extern IntPtr GetWindow(IntPtr wnd, uint flag);

        [DllImport("coredll.dll", SetLastError = true)]
        private static extern int GetClassName(IntPtr wnd, char[] name, int size);

et on va déclarer une variable de type IntPtr pour garder le handle trouvé si on le trouve, ainsi qu’un bool pour indiquer si on a trouvé la fenêtre :

private static IntPtr hWndTarget = IntPtr.Zero;
        static string strChildClassName = "PIEHTML";
        static bool bFound = false;

Enfin on met en place une methode de parcour récursif des fenêtre à partir du hanlde parent de l’application. (On verra un peu plus loin comment récupérer le handle parent de l’application). Dans cette methode on va utiliser « GetWindow » en ayant 2 flags possible : CW_CHILD (récupération d’une windows enfant) ou GW_HWNDNEXT (récupération d’une window soeur). Celà va donc nous permettre de parcourir toutes les windows de l’application :

private static IntPtr FindWindow(string strChildClassName, IntPtr hWndTopLevel)
        {
        IntPtr hwndCur = IntPtr.Zero;
        hwndCur = GetWindow(hWndTopLevel, (uint)GW_CHILD);

        return RecurseFindWindow(strChildClassName, hwndCur);

        }

        private static IntPtr RecurseFindWindow(string strChildClassName, IntPtr hWndParent)
        {
        IntPtr hwndCur = IntPtr.Zero;
        char[] chArWindowClass = new char[32];
        if (hWndParent == IntPtr.Zero)
        return IntPtr.Zero;
        else
        {
        //check if we get the searched class name
        GetClassName(hWndParent, chArWindowClass, 256);
        string strWndClass = new string(chArWindowClass);
        strWndClass = strWndClass.Substring(0, strWndClass.IndexOf('\0'));
        bFound = (strWndClass.ToLower() == strChildClassName.ToLower());
        if (bFound)
        return hWndParent;
        else
        {
        //recurse into first child
        IntPtr hwndChild = GetWindow(hWndParent, (uint)GW_CHILD);
        if (hwndChild != IntPtr.Zero)
        hwndCur = RecurseFindWindow(strChildClassName, hwndChild);
        if (!bFound)
        {
        IntPtr hwndBrother = IntPtr.Zero;
        //enumerate each brother windows and recurse into
        do
        {
        hwndBrother = GetWindow(hWndParent, (uint)GW_HWNDNEXT);
        hWndParent = hwndBrother;
        if (hwndBrother != IntPtr.Zero)
        {
        hwndCur = RecurseFindWindow(strChildClassName, hwndBrother);
        if (bFound)
        break;
        }
        }
        while (hwndBrother != IntPtr.Zero);
        }

        }
        return hwndCur;
        }
        }

Deuxième étape : intercepter les message windows sur cette window

Pour celà on va utiliser 3 autres methodes natives pour d’une part récupérer le « pointeur » sur la methode traitant les messages, et d’autres par définir une nouvelle methode pour traiter les message et enfin pouvoir « router » les message à la methode intiale. C’est la mise en place du hook. Il faut declarer le delegate qui correspond à la signature d’une telle methode, il faut 2 variables membres pour pointer sur l’ancienne methode de traitement des message, et pointer sur la nouvelle methode:

//signature d'une methode de type "message pump"
        public delegate IntPtr WndProcDelegate(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam);

        //methodes permettant de recuperer ou définit le pointeur de fonction pour traiter les messages
        [DllImport("coredll", SetLastError = true)]
        public static extern IntPtr GetWindowLong(IntPtr hWnd, int nIndex);

        [DllImport("coredll", SetLastError = true)]
        public static extern int SetWindowLong(IntPtr hWnd, int nIndex, IntPtr newWndProc);

        //permet d'invoquer l'ancienne methode de traitement
        [DllImport("coredll", SetLastError = true)]
        public static extern IntPtr CallWindowProc(IntPtr lpPrevWndFunc, IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);

        //les membres pour stoker l'ancienne et la nouvelle methode de traitement des messages
        private static IntPtr oldWndProc = IntPtr.Zero;
        private static WndProcDelegate newWndProc;

Dernière étape : appliquer le hook

Il reste à utiliser tout ça, j’ai donc 2 methodes : SetupWndProc et UnSetupWndProc qui vont etre chargée d’appliquer ou defaire le hook et la méthode de traitement des messages maison pour intercepter le message de type « context menu ». Je declare donc aussi la valeur du message « WM_CONTEXTMENU ». Pour utiliser ma fonction de recherche de window, j’ai besoin du handle de la fenêtre principal de l’application. Pour celà une methode simple est d’utiliser la classe Process pour récupérer le process en cours. Ce process a une proprité nommé « MainWindowHandle ».

public static void SetupWndProc()
        {
        Process pp = Process.GetCurrentProcess();
        hWndTarget = FindWindow(strChildClassName, pp.MainWindowHandle);

        if (bFound && hWndTarget != IntPtr.Zero && oldWndProc == IntPtr.Zero)
        {
        newWndProc = new WndProcDelegate(NewWndProc);
        oldWndProc = GetWindowLong(hWndTarget, GWL_WNDPROC);
        int success = SetWindowLong(hWndTarget, GWL_WNDPROC, Marshal.GetFunctionPointerForDelegate(newWndProc));
        }
        }

        public static void UnSetupWndProc()
        {
        //remove hook
        if (oldWndProc != IntPtr.Zero && hWndTarget != IntPtr.Zero && bFound)
        {
        SetWindowLong(hWndTarget, GWL_WNDPROC, oldWndProc);
        oldWndProc = IntPtr.Zero;
        bFound = false;
        }
        }

        private static IntPtr NewWndProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam)
        {
        switch (msg)
        {
        //case WM_NOTIFY: if we want to disable animation when pressing
        //    //block WM_NOTIFY
        //    return IntPtr.Zero;

        case WM_CONTEXTMENU:
        return IntPtr.Zero;
        default:
        break;
        }
        //les autres messages sont transférés vers l'ancienne methode de traitement
        return CallWindowProc(oldWndProc, hWnd, msg, wParam, lParam);
        }

Et voilà je n’ai plus qu’à utiliser mes 2 methodes SetupWndProc et UnSetupWndProc aux moments adéquat où mon browser est visible par exemple…

Ci dessous le code complet de ma classe outil :

public static class BrowserUtil
        {
        #region DllImport
        private const uint WM_NOTIFY = 0x004E;
        private const uint WM_CONTEXTMENU = 0x007B;

        public const int GWL_WNDPROC = (-4);
        public const int GW_HWNDNEXT = 2;
        public const int GW_CHILD = 5;

        public delegate IntPtr WndProcDelegate(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam);

        [DllImport("coredll", SetLastError = true)]
        public static extern IntPtr GetWindowLong(IntPtr hWnd, int nIndex);

        [DllImport("coredll", SetLastError = true)]
        public static extern int SetWindowLong(IntPtr hWnd, int nIndex, IntPtr newWndProc);

        [DllImport("coredll", SetLastError = true)]
        public static extern IntPtr CallWindowProc(IntPtr lpPrevWndFunc, IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);

        [DllImport("coredll.dll", SetLastError = true)]
        private static extern IntPtr GetWindow(IntPtr wnd, uint flag);

        [DllImport("coredll.dll", SetLastError = true)]
        private static extern int GetClassName(IntPtr wnd, char[] name, int size);

        #endregion DllImport

        private static IntPtr oldWndProc = IntPtr.Zero;
        private static WndProcDelegate newWndProc;

        private static IntPtr hWndTarget = IntPtr.Zero;
        static string strChildClassName = "PIEHTML";
        static bool bFound = false;


        public static void SetupWndProc()
        {
        Cursor cur = Cursors.WaitCursor;
        Process pp = Process.GetCurrentProcess();
        hWndTarget = FindWindow(strChildClassName, pp.MainWindowHandle);
        cur = Cursors.Default;

        //as per native apps, associate new WndProc
        if (bFound && hWndTarget != IntPtr.Zero && oldWndProc == IntPtr.Zero)
        {
        newWndProc = new WndProcDelegate(NewWndProc);
        oldWndProc = GetWindowLong(hWndTarget, GWL_WNDPROC);
        int success = SetWindowLong(hWndTarget, GWL_WNDPROC, Marshal.GetFunctionPointerForDelegate(newWndProc));
        }
        }



        public static void UnSetupWndProc()
        {
        //remove hook
        if (oldWndProc != IntPtr.Zero && hWndTarget != IntPtr.Zero && bFound)
        {
        SetWindowLong(hWndTarget, GWL_WNDPROC, oldWndProc);
        oldWndProc = IntPtr.Zero;
        bFound = false;
        }
        }

        private static IntPtr NewWndProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam)
        {
        switch (msg)
        {
        //case WM_NOTIFY: if we want to disable animation when pressing
        //    //block WM_NOTIFY
        //    return IntPtr.Zero;

        case WM_CONTEXTMENU:
        return IntPtr.Zero;
        default:
        break;
        }
        return CallWindowProc(oldWndProc, hWnd, msg, wParam, lParam);
        }

        private static IntPtr FindWindow(string strChildClassName, IntPtr hWndTopLevel)
        {
        IntPtr hwndCur = IntPtr.Zero;
        hwndCur = GetWindow(hWndTopLevel, (uint)GW_CHILD);

        return RecurseFindWindow(strChildClassName, hwndCur);

        }

        private static IntPtr RecurseFindWindow(string strChildClassName, IntPtr hWndParent)
        {
        IntPtr hwndCur = IntPtr.Zero;
        char[] chArWindowClass = new char[32];
        if (hWndParent == IntPtr.Zero)
        return IntPtr.Zero;
        else
        {
        //check if we get the searched class name
        GetClassName(hWndParent, chArWindowClass, 256);
        string strWndClass = new string(chArWindowClass);
        strWndClass = strWndClass.Substring(0, strWndClass.IndexOf('\0'));
        bFound = (strWndClass.ToLower() == strChildClassName.ToLower());
        if (bFound)
        return hWndParent;
        else
        {
        //recurse into first child
        IntPtr hwndChild = GetWindow(hWndParent, (uint)GW_CHILD);
        if (hwndChild != IntPtr.Zero)
        hwndCur = RecurseFindWindow(strChildClassName, hwndChild);
        if (!bFound)
        {
        IntPtr hwndBrother = IntPtr.Zero;
        //enumerate each brother windows and recurse into
        do
        {
        hwndBrother = GetWindow(hWndParent, (uint)GW_HWNDNEXT);
        hWndParent = hwndBrother;
        if (hwndBrother != IntPtr.Zero)
        {
        hwndCur = RecurseFindWindow(strChildClassName, hwndBrother);
        if (bFound)
        break;
        }
        }
        while (hwndBrother != IntPtr.Zero);
        }

        }
        return hwndCur;
        }
        }
        }

Merci aussi à Raffael pour son post sur le sujet qui m’a bien aidé.

Publié dans Développement divers Tagués avec : , ,

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *

*

Verifions que vous êtes un humain * Time limit is exhausted. Please reload CAPTCHA.

Archives

Social

  • Twitter
  • LinkedIn
  • Flux RSS
  • mvp
  • technet
  • Google+