Question

J'ai une nouvelle application écrite en WPF qui doit prendre en charge une ancienne API qui lui permet de recevoir un message posté dans une fenêtre masquée. En règle générale, une autre application utilise FindWindow pour identifier la fenêtre masquée à l'aide du nom de sa classe de fenêtre personnalisée.

1) Je suppose que pour implémenter une classe de fenêtre personnalisée, j'ai besoin d'utiliser des appels Win32 à l'ancienne école?

Mon ancienne application c ++ a utilisé RegisterClass et CreateWindow pour créer la fenêtre invisible la plus simple possible.

Je crois que je devrais pouvoir faire la même chose dans c #. Je ne veux pas que mon projet doive compiler du code non managé.

J'ai essayé d'hériter de System.Windows.Interop.HwndHost et d'utiliser System.Runtime.InteropServices.DllImport pour extraire les méthodes de l'API ci-dessus.

Ce faisant, je peux héberger avec succès une fenêtre Win32 standard, par exemple. " listbox " à l'intérieur de WPF. Cependant, lorsque j'appelle CreateWindowEx pour ma fenêtre personnalisée, elle renvoie toujours la valeur null.

Mon appel à RegisterClass a abouti, mais je ne suis pas sûr de ce que je devrais définir. WNDCLASS.lpfnWndProc membre à.

2) Quelqu'un sait-il comment faire cela avec succès?

Était-ce utile?

La solution

Pour mémoire, je me suis enfin mis au travail. En fin de compte, les difficultés que je rencontrais étaient dues à des problèmes de formation de cordes. Je devais être plus précis dans mon importation de fonctions win32.

Vous trouverez ci-dessous le code qui créera une classe de fenêtre personnalisée en c # - utile pour la prise en charge d'anciennes API pouvant s'appuyer sur des classes de fenêtre personnalisées.

Cela devrait fonctionner dans WPF ou Winforms tant qu'une pompe de messages est en cours d'exécution sur le thread.

EDIT: Mise à jour pour corriger le plantage signalé en raison de la collecte anticipée du délégué qui encapsule le rappel. Le délégué est maintenant tenu en tant que membre et le délégué explicitement marshalé en tant que pointeur de fonction. Cela corrige le problème et facilite la compréhension du comportement.

class CustomWindow : IDisposable
{
    delegate IntPtr WndProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam);

    [System.Runtime.InteropServices.StructLayout(
        System.Runtime.InteropServices.LayoutKind.Sequential,
       CharSet = System.Runtime.InteropServices.CharSet.Unicode
    )]
    struct WNDCLASS
    {
        public uint style;
        public IntPtr lpfnWndProc;
        public int cbClsExtra;
        public int cbWndExtra;
        public IntPtr hInstance;
        public IntPtr hIcon;
        public IntPtr hCursor;
        public IntPtr hbrBackground;
        [System.Runtime.InteropServices.MarshalAs(System.Runtime.InteropServices.UnmanagedType.LPWStr)]
        public string lpszMenuName;
        [System.Runtime.InteropServices.MarshalAs(System.Runtime.InteropServices.UnmanagedType.LPWStr)]
        public string lpszClassName;
    }

    [System.Runtime.InteropServices.DllImport("user32.dll", SetLastError = true)]
    static extern System.UInt16 RegisterClassW(
        [System.Runtime.InteropServices.In] ref WNDCLASS lpWndClass
    );

    [System.Runtime.InteropServices.DllImport("user32.dll", SetLastError = true)]
    static extern IntPtr CreateWindowExW(
       UInt32 dwExStyle,
       [System.Runtime.InteropServices.MarshalAs(System.Runtime.InteropServices.UnmanagedType.LPWStr)]
       string lpClassName,
       [System.Runtime.InteropServices.MarshalAs(System.Runtime.InteropServices.UnmanagedType.LPWStr)]
       string lpWindowName,
       UInt32 dwStyle,
       Int32 x,
       Int32 y,
       Int32 nWidth,
       Int32 nHeight,
       IntPtr hWndParent,
       IntPtr hMenu,
       IntPtr hInstance,
       IntPtr lpParam
    );

    [System.Runtime.InteropServices.DllImport("user32.dll", SetLastError = true)]
    static extern System.IntPtr DefWindowProcW(
        IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam
    );

    [System.Runtime.InteropServices.DllImport("user32.dll", SetLastError = true)]
    static extern bool DestroyWindow(
        IntPtr hWnd
    );

    private const int ERROR_CLASS_ALREADY_EXISTS = 1410;

    private bool m_disposed;
    private IntPtr m_hwnd;

    public void Dispose() 
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    private void Dispose(bool disposing) 
    {
        if (!m_disposed) {
            if (disposing) {
                // Dispose managed resources
            }

            // Dispose unmanaged resources
            if (m_hwnd != IntPtr.Zero) {
                DestroyWindow(m_hwnd);
                m_hwnd = IntPtr.Zero;
            }

        }
    }

    public CustomWindow(string class_name){

        if (class_name == null) throw new System.Exception("class_name is null");
        if (class_name == String.Empty) throw new System.Exception("class_name is empty");

        m_wnd_proc_delegate = CustomWndProc;

        // Create WNDCLASS
        WNDCLASS wind_class = new WNDCLASS();
        wind_class.lpszClassName = class_name;
        wind_class.lpfnWndProc = System.Runtime.InteropServices.Marshal.GetFunctionPointerForDelegate(m_wnd_proc_delegate);

        UInt16 class_atom = RegisterClassW(ref wind_class);

        int last_error = System.Runtime.InteropServices.Marshal.GetLastWin32Error();

        if (class_atom == 0 && last_error != ERROR_CLASS_ALREADY_EXISTS) {
            throw new System.Exception("Could not register window class");
        }

        // Create window
        m_hwnd = CreateWindowExW(
            0,
            class_name,
            String.Empty,
            0,
            0,
            0,
            0,
            0,
            IntPtr.Zero,
            IntPtr.Zero,
            IntPtr.Zero,
            IntPtr.Zero
        );
    }

    private static IntPtr CustomWndProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam) 
    {
        return DefWindowProcW(hWnd, msg, wParam, lParam);
    }

    private WndProc m_wnd_proc_delegate;
}

Autres conseils

Je voudrais commenter la réponse de morechilli:

public CustomWindow(string class_name){

    if (class_name == null) throw new System.Exception("class_name is null");
    if (class_name == String.Empty) throw new System.Exception("class_name is empty");

    // Create WNDCLASS
    WNDCLASS wind_class = new WNDCLASS();
    wind_class.lpszClassName = class_name;
    wind_class.lpfnWndProc = CustomWndProc;

    UInt16 class_atom = RegisterClassW(ref wind_class);

    int last_error = System.Runtime.InteropServices.Marshal.GetLastWin32Error();

    if (class_atom == 0 && last_error != ERROR_CLASS_ALREADY_EXISTS) {
        throw new System.Exception("Could not register window class");
    }

    // Create window
    m_hwnd = CreateWindowExW(
        0,
        class_name,
        String.Empty,
        0,
        0,
        0,
        0,
        0,
        IntPtr.Zero,
        IntPtr.Zero,
        IntPtr.Zero,
        IntPtr.Zero
    );
}

Dans le constructeur que j'ai copié ci-dessus, une légère erreur s'est produite: l'instance WNDCLASS est créée, mais n'est pas enregistrée. Il sera éventuellement ramassé les ordures. Mais le WNDCLASS détient le délégué WndProc. Cela entraîne une erreur dès que WNDCLASS est nettoyé. L'instance de WNDCLASS doit être conservée dans une variable membre jusqu'à ce que la fenêtre soit détruite.

1) Vous pouvez simplement sous-classer une classe Windows Forms normale ... vous n'avez pas besoin de tous ces appels win32, il vous suffit d'analyser le message WndProc manuellement ... c'est tout.

2) Vous pouvez importer l'espace de nom System.Windows.Forms et l'utiliser au côté de WPF. Je pense qu'il n'y aura pas de problème tant que vous n'entrelacez pas trop de formulaires Windows. votre application WPF. Vous souhaitez simplement instancier votre formulaire masqué personnalisé pour recevoir un message, n'est-ce pas?

exemple de sous-classement WndProc:

protected override void WndProc(ref System.Windows.Forms.Message m)
{
   // *always* let the base class process the message
   base.WndProc(ref m);

   const int WM_NCHITTEST = 0x84;
   const int HTCAPTION = 2;
   const int HTCLIENT = 1;

   // if Windows is querying where the mouse is and the base form class said
   // it's on the client area, let's cheat and say it's on the title bar instead
   if ( m.Msg == WM_NCHITTEST && m.Result.ToInt32() == HTCLIENT )
      m.Result = new IntPtr(HTCAPTION);
}

Puisque vous connaissez déjà RegisterClass et tous ces appels Win32, je suppose que le message WndProc ne vous poserait aucun problème ...

WNDCLASS wind_class; placez la définition dans la classe, pas la fonction, et le plantage sera corrigé.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top