[C#] Abfangen des Close-Events (Close-Message) eines fremden Fensters

ralfz78

Grünschnabel
Hallo,

ich habe eine App, die ein Fenster einer anderen Applikation aufruft bzw. nutzt und beim starten nach vorne holt (funktioniert soweit).

Wenn meine App nun aktiv ist, soll sich das Verhalten des anderen Fensters ändern, wenn auf das "X" geklickt wird. Anstelle das Fenster zu schliessen (Prozess bleibt, aber Window wird zerstört), soll es nur versteckt werden, damit es beim erneuten aufruf nur wiedergeholt werden und nicht neu erstellt werden muss.

FRAGE: WIE mache ich das?

Meine Ideen (bisher erfolglos, bzw. mir fehlt das Verständnis):
Prinzipiell sehe ich momentan zwei Möglichkeiten.

1) Ich versuche irgendwie den Close Event abzufangen und sende stattdessen einen Hide-Befehl bzw. Hide-Message oder setzte die Window-Styles. Ich habe es bis jetzt mit der "NativeWindow"-Klasse und dem AssignHandle() am Bsp. von Notepad.exe versucht und dem überschreiben der WndProc Methode, aber da läuft er nie rein.
Unten mal der Code dazu.
(Das Thema bzw. die Richtung heisst dann wohl "Windows Hooks", da habe ich aber keine/kaum Ahnung von)

2) möchte ich eigentlich nicht benutzen: "Re-Parenting"
Ich erstelle eine eigene Form als Rahmen, hole mir die Caption des Ziel-Fensters(hier 'child'), nehme dem child den Rahmen, die Menüleiste etc., passe es in meine Form ein (mit Winapi.SetParent etc.) und behandle dann den Close-Event meiner eigenen Form. Leider hatte ich bisher mit Re-parenting doch einige Probleme in Bezug auf Eingaben....

Code:
// Die aufrufende Form:

public partial class Form1 : Form {
        int pid;
        IntPtr handle;
        NativeWindowManager wm; //von NativeWindow abgeleitet
        
        public Form1() {
            InitializeComponent();
            Application.DoEvents();
            try {
                handle = getWindowHandle("notepad", out pid);
                if (System.IntPtr.Zero != handle) {
                    WinApi.AllowSetForegroundWindow(pid);
                    WinApi.SetForegroundWindow((int)handle);
                    WinApi.ShowWindow(handle.ToInt32(), 9);
                    
                   wm = new NativeWindowManager(handle); //erstellen und aufruf von AssignHandle
                   
                } else {
                    MessageBox.Show("Prozess nicht gefunden");
                }
            } catch (Exception ex) {
                MessageBox.Show("Fehler:" + ex.ToString());
            }

        }

        private System.IntPtr getWindowHandle(string processName, out int processId) {
            processId = -1;
            System.IntPtr clientHandle = System.IntPtr.Zero;
            try {
                System.Diagnostics.Process[] processes
                    = System.Diagnostics.Process.GetProcessesByName(processName);

                if (processes.Length > 0) {
                    System.Diagnostics.Process process = processes[0]; //there should not be more than one
                    if (process != null) {
                        
                        clientHandle = process.MainWindowHandle;
                        processId = process.Id;
                    }
                }
            } catch (System.Exception ex) {
                
                MessageBox.Show("Fehler:" + ex.ToString());
            }
            return clientHandle;
        }

        private void Form1_FormClosing(object sender, FormClosingEventArgs e) {
            //MessageBox.Show("Closing....");
            if(null!=wm)wm.ReleaseHandle();
        }
    }

und die NativeWindow Klasse

Code:
public partial class NativeWindowManager : System.Windows.Forms.NativeWindow {
        
            [System.Runtime.InteropServices.DllImport("user32", SetLastError = true)]
            private static extern bool PostMessage(IntPtr hWnd, uint Msg, int wParam, int lParam);

            private const int WM_CLOSE = 16; // ? 0x0010
            private const int WM_DESTROY = 0x0002;
            private const int WM_MINIMIZE = 0xF020;
            private const int WM_MAXIMIZE = 0xF030;
            //private IntPtr handleToWindow;


            public NativeWindowManager(IntPtr windowHandle) {
                try {
                    this.AssignHandle(windowHandle);
                } catch (Exception ex) {
                    System.Windows.Forms.MessageBox.Show("Fehler:" + ex.ToString());
                }

            }

            protected override void WndProc(ref System.Windows.Forms.Message m) {
                // Listen for operating system messages
                if (m.Msg == WM_CLOSE) {
                    // send a Hide Message or just hide it 
                    //TEST: erst einmal nur minimieren des Fensters
                    System.Windows.Forms.MessageBox.Show("Receiving WM_CLOSE");

                    PostMessage(this.Handle, WM_MINIMIZE, 0, 0);
                } else if (m.Msg == WM_DESTROY) {
                    System.Windows.Forms.MessageBox.Show("Receiving WM_DESTROY");
                    PostMessage(this.Handle, WM_MINIMIZE, 0, 0);
                } else {
                    System.Windows.Forms.MessageBox.Show("Sonstige Message:"+m.ToString());
                    this.DefWndProc(ref m);
                }
            } //WndProc
               
    }

Ich hoffe jemand kann mir weiterhelfen. Oder kennt jemand ein "Tutorial" für Window Hooks, dass auf C# basiert? Habe nur eines für C++ gefunden (kann ich leider nicht).

Gruß
Ralf
 
Zuletzt bearbeitet:
Das trifft aber nur zu, wenn es mein eigenes Fenster bzw. meine eigene Form ist. Hier handelt es sich um ein externes Fenster, z.B. von Notepad.exe

Oder übersehe ich hier etwas?

Gruß
Ralf
 
Ah ok,.. das hab ich überlesen! Wunderte mich schon, warum du das so kompliziert angehst. ^^

Muss dazu selber mal schauen, melde mich später wohl nochmal dazu :)
 
Also, mit Native Window bin ich nicht weiter gekommen. Da stand auch etwas von, dass es nur im eigenen Prozess funktioniert.

Somit musste es entweder mit Window Hook oder mit re-parenting gemacht werden. Hooks war mir doch arg zu umständlich.

Habe jetzt also eine funktionierende Reparenting (Win32.SetParent) Lösung:

(Grober Lösungsweg)
- Hole das Fenster-Handle,
+ besorge die Eigenschaften: Titel, Position, Größe und Icon (Icon war etwas aufwendiger) vom Window
- Erzeuge ein neues Form als Parent des Zielfensters
- Nehme dem Zielfenster die Styles: "Caption", "Border", "SizeBox" und setze es als "Child"
- Bei einem Resize meiner Form passe das Zielfenster mit SetWindowPos an.
- Beim schliessen meiner Form, fange dies im FormClosing-Event ab und verstecke das Fenster mit Form.Hide() und hole es bei Anfrage mit Form.Show() wieder.

Benötigte Win32 Api-Funktionen: (Imports (für user32.dll) und Konstanten findet man gut unter pinvoke.net)
-für Caption:
-- GetWindowText ( + evtl. GetWindowTextLength)

-für Icon: (siehe http://www.codeproject.com/KB/graphics/screen_capturing.aspx)
-- SendMessageTimeout mit WM_GETICON + SendMessageTimeoutFlags (funktionierte in diesem Fall nicht)
-- zusätzlich: GetClassLong mit GCL_HICON (funktioniert in meinem Fall super)
-- zusätzlich: SendMessageTimeout mit WM_QUERYDRAGICON

-für re-parenting (inkl. Location, Styles):
-- GetWindowRect
-- GetClientRect
-- SetWindowLong und GetWindowLong (bzw. xxxPtr)
-- SetParent
-- SetWindowPos

Bei Fragen einfach posten oder für Sample-Code mail an mich (ralfz äättt freenet punkt de)

Gruß
Ralf
 

Neue Beiträge

Zurück