System - Window-Messages abfangen im BCB



  • So, ich habe jetzt schon sehr oft Fragen beantworten müssen wie "Wie fange ich denn sowas ab?". Das hat mich jetzt dazu bewogen, eine Anleitung für den BCB dazu zu schreiben. Hoffe, das kommt dann in die FAQ, damit man nicht immer wieder das Gleiche posten muss.
    Es gibt im BCB 2 Möglichkeiten, Messages abzufangen, die an eine Komponente (z.B. ein Formular) gesendet wurden. Weiterhin gibt es noch eine Möglichkeit, Messages abzufangen, die an die Anwendung (Application) gesendet wurden. Will man einige wenige Messages abfangen und behandeln, sollte man einen MessageHandler benutzen. Bei mehreren Messages sollte man der Form eine weitere WindowProc hinzufügen. Will man Messages abfangen, die an irgendein Formular oder irgendeine Komponente versendet wurden, dann fängt man die Messages per Application ab. Bei den ersten beiden Methoden ist eine Struktur von besonderer Bedeutung:

    struct TMessage
    {
        Cardinal Msg;
        union
        {
            struct 
            {
                Word WParamLo;
                Word WParamHi;
                Word LParamLo;
                Word LParamHi;
                Word ResultLo;
                Word ResultHi;
    
            };
            struct 
            {
                int WParam;
                int LParam;
                int Result;
    
            };
    
        };
    };
    

    Man benutzt größtenteils die zweite Struktur. Eine Windows-Message ist ein konstanter Bezeichner (z.B. WM_PAINT) gepaart mit zwei LONG-Werten (WParam und LParam). Der konstante Message-Bezeichner ist in TMessage::Msg zu finden. In den Variablen WParam und LParam sind weitere Informationen über die Message gespeichert. Wenn zum Beispiel WM_MOUSEMOVE an ein Fenster gesendet wird, dann beinhaltet LParam die Koordinaten des Maus-Cursors im Fenster. Wenn man Informationen über eine Windows-Message haben möchte (z.B., was in WParam und LParam gespeichert wird), dann kann man entweder "Start->Programme->Borland C++Builder x->MS-Hilfe->Win32 SDK" anklicken oder die Message im Quelltext-Editor schreiben und auf F1 drücken.
    Ich beschreibe nun die beiden Methoden zum Abfangen von Messages im BCB.

    1.) Abfangen von Messages per MessageHandler
    ---------------------------------------------

    protected:
      void __fastcall wmMouseMove(TMessage&);
    
      BEGIN_MESSAGE_MAP
        MESSAGE_HANDLER(WM_MOUSEMOVE, TMessage, wmMouseMove);
      END_MESSAGE_MAP(inherited)
    

    Dieser Code wird in die Header-Datei der Form gestellt. Er sorgt dafür, dass die Message WM_MOUSEMOVE abgefangen und an die Funktion wmMouseMove() zum weiteren Verarbeiten weitergeleitet wird. Nach END_MESSAGE_MAP muss in Klammern stets der Klassenbezeichner angegeben werden, von dem die Klasse abgeleitet ist, in der wir die MessageMap deklariert haben. Ist die MessageMap z.B. in TForm1 (abgeleitet von TForm) deklariert, so müssen wir TForm in den Klammern angeben. Der Pascal-Bezeichner "inherited" sorgt dafür, dass hier immer das richtige angegeben wird. inherited ist immer der Vorfahr der Klasse, in der man sich gerade befindet. Wir kommen zum Bearbeiten der Message. Dazu wird in der cpp-Datei die behandelnde Funktion definiert:

    void __fastcall TForm1::wmMouseMove(TMessage& Msg)
    {
       Label1->Caption = LOWORD(Msg.LParam);
       Label2->Caption = HIWORD(Msg.LParam);
       inherited::Dispatch(&Msg);
    }
    

    Wie oben schon abgedeutet, sind in Msg.LParam die Koordinaten des Maus-Cursors gespeichert. Diese werden nun in die Labels geschrieben. Die letzte Zeile stellt sicher, dass die Message von der VCL und schließlich von Windows weiterverarbeitet werden kann. Man sollte sie nie vergessen. Außer man möchte, dass die Message eben nicht von Windows weiterverarbeitet wird. Bevor man sowas dann aber startet, sollte man das Projekt zur Sicherheit nochmal abspeichern.

    2.) Abfangen von Messages per WindowProc
    -----------------------------------------

    Jedes TControl hat eine Eigenschaft "WindowProc". Diese ist ein Funktionszeiger und zeigt auf die Fensterprozedur, die auf an das Steuerelement gesendete Botschaften antwortet (Zitat BCB-Hilfe). Um Messages zu verarbeiten, kann man diese auf eine neue Funktion zeigen lassen. Bevor ich noch weiter rumschwafele, hier mal ein Stückchen Code:

    //  .h - Datei
    private:
        TWndMethod  OldWndProc;
    protected:
        void __fastcall NewWndProc(TMessage&);
    
    //  .cpp - Datei
    __fastcall TForm1::TForm1(TComponent* Owner)
        : TForm(Owner)
    {
       OldWndProc = WindowProc;
       WindowProc = NewWndProc;
    }
    //---------------------------------------------------------------------------
    
    void __fastcall TForm1::NewWndProc(TMessage& Msg)
    {
       switch(Msg.Msg)
       {
          case WM_LBUTTONUP:
             ShowMessage("Left button up");
             break;
    
          case WM_RBUTTONUP:
             ShowMessage("Right button up");
             break;
       }
    
       OldWndProc(Msg);
    }
    

    Im Konstruktor (hier von einer Form) wird der ursprüngliche Wert von WindowProc in OldWndProc gespeichert und WindowProc auf NewWndProc gesetzt. Wenn jetzt eine der Messages WM_LBUTTONUP oder WM_RBUTTONUP an die Form gesendet werden, dann werden sie in der neuen WindowProc abgefangen und behandelt. Am Ende muss die Message an die alte WindowProc weitergeleitet werden. Dies entspricht dem "inherited::Dispatch(&Msg);" bei den MessageHandlern. Das war's. Mehr ist nicht zu tun.

    3.) Abfangen von Messages per Application
    ------------------------------------------

    In jedem BCB-GUI-Projekt gibt es eine globale Variable "Application". Diese ist eine Instanz von TApplication und steht für die Anwendung an sich. Application besitzt ein Fenster, das viele nicht kennen. Es ist "versteckt" und ist das Owner-Fenster aller Formulare. Der Button in der Taskleiste bei gestarteter Anwendung gehört zu ihm - nicht zu einer Form. Man lasse einmal den Code

    ShowWindow(Application->Handle, SW_MAXIMIZE);
    

    ausführen. Dann sieht man das Fenster kurz. Es gibt einige Messages, die, wenn sie an ein Formular gesendet werden, danach weitergeleitet werden an das Application-Fenster. Wenn man z.B. die Message WM_KEYDOWN abfangen will und es einem egal ist, welches Steuerelement die Message empfängt, dann sollte man die Message per Application abfangen. Dies geht über das Ereignis TApplication::OnMessage. Wenn man sich die Hilfe dazu anschaut, bemerkt man, dass hier nicht mit TMessage, sondern mit einer anderen Message-Struktur gearbeitet wird: tagMSG bzw. TMsg oder MSG (ist alles das gleiche). Die Struktur sieht so aus:

    struct tagMSG
    {
        HWND    hwnd;
        UINT    message;
        WPARAM  wParam;
        LPARAM  lParam;
        DWORD   time;
        POINT   pt;
    }
    

    Wir könnten jetzt die Behandlung in die Klasse einer Form schreiben, aber das ist nicht Sinn der Sache, denn es hat ja nichts mit EINER Form zu tun, sondern mit ALLEN. Also schreiben wir die Behandlung in die Project.cpp ("Ansicht | Projekt-Quelltext" im Menu anklicken) oder in eine neue Unit, die von der Project.cpp benutzt wird. Mal ein kleines Beispiel: wir wollen jedes 'A', das eingegeben wird, in ein '*' verwandeln. Da dieses Beispiel recht klein ist, habe ich hier alles in der Project.cpp:

    //---------------------------------------------------------------------------
    #include <vcl.h>
    
    #pragma hdrstop
    USERES("Project1.res");
    USEFORM("Unit1.cpp", Form1);
    //---------------------------------------------------------------------------
    
    class TAppEvent
    {
    public:
        void __fastcall OnMessage(MSG&, bool&);
    };
    //---------------------------------------------------------------------------
    
    void __fastcall TAppEvent::OnMessage(MSG& Msg, bool& Handled)
    {
       switch(Msg.message)
       {
          case WM_CHAR:
             if((TCHAR)Msg.wParam == 'A')
                Msg.wParam = (WPARAM)'*';
             break;
       }
    }
    //---------------------------------------------------------------------------
    //---------------------------------------------------------------------------
    
    WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int)
    {
        TAppEvent* AppEvent = new TAppEvent;
    
        try
        {
            Application->Initialize();
            Application->CreateForm(__classid(TForm1), &Form1);
            Application->OnMessage = AppEvent->OnMessage;
            Application->Run();
        }
        catch (Exception &exception)
        {
            Application->ShowException(&exception);
        }
    
        delete AppEvent;
        return 0;
    }
    

    Wir müssen dazu eine neue Klasse erstellen, denn das Schlüsselwort __closure in der Definition des Typs von TApplication::OnMessage lässt nichts anderes zu. Natürlich könnte man wie gesagt auch die Klasse TForm1 dafür nehmen, aber das wäre nicht gerade OOP-mäßig. Macht, was ihr wollt, aber sagt am Ende nicht, ich sei Schuld. 😉
    Hier entscheidet der Parameter Handled, ob die Nachricht _am Ende_ weiterverarbeitet werden soll oder nicht. Setzen wir Handled auf true, dann bedeutet das, das wir sagen, die Message ist bereits verarbeitet worden und muss nicht mehr von Windows verarbeitet werden. Die "Voreinstellung" ist false. Der Unterschied zu den oben behandelten Methoden des Message-Handling ist, dass wir hier nicht die Message vor unserem Code von Windows verarbeiten lassen können. Jo, das war's.

    [ Dieser Beitrag wurde am 04.09.2002 um 16:23 Uhr von WebFritzi editiert. ]

    [ Dieser Beitrag wurde am 29.09.2002 um 14:20 Uhr von Jansen editiert. ]

    [ Dieser Beitrag wurde am 22.01.2003 um 02:51 Uhr von WebFritzi editiert. ]



  • Ich bekomme folgende Fehlermeldungen:

    Unit1.cpp(19): E2451 Undefiniertes Symbol 'Msg'
    Unit1.cpp(20): E2090 Qualifizierter 'inherited' ist kein Name einer Klasse oder einer Struktur
    Unit1.cpp(20): E2379 In Anweisung fehlt ;
    

    Muss ich die Struktur, die WebFritzi geschrieben hat etwa auch irgendwo eintippen?



  • Ich weiß nicht was du gemacht hast, aber ich habe beide Versionen einfach per Copy und Paste in ein TestProject eingefügt und beide funktionieren (mit BCB5Pro) ohne eine Änderung 😕

    PS Nein, die Struktur brauchst du nicht eintippen !

    [ Dieser Beitrag wurde am 04.09.2002 um 08:57 Uhr von WoWe editiert. ]



  • Ich habe eine Frage zu:

    void __fastcall TForm1::wmMouseMove(TMessage& Msg)
    {
       Label1->Caption = LOWORD(Msg.LParam);
       Label2->Caption = HIWORD(Msg.LParam);
       inherited::Dispatch(&Msg);
    }
    

    Soll man dieses "inherited::Dispatch(&Msg);" an den Anfang oder am Ende der Funktion schreiben oder macht das garkeinen Unterschied?

    [ Dieser Beitrag wurde am 07.09.2002 um 18:52 Uhr von Jansen editiert. ]



  • Ach was ist "inherited"? Ist das ein spezielle Schlüsselwort des BCB?



  • Ist doch oben im Text schon erklärt 😉



  • @<frasche>
    Zur zweiten Frage siehe WoWe's Beitrag. Zur erstaen Frage: Es macht sehr wohl einen Unterschied, ob du diese Zeile an den Anfang oder ans Ende setzt. Sie bewirkt (wie gesagt), dass die Message von Windows verarbeitet wird. Wenn du sie an den Anfang setzt, wird die Message eben zuerst von Windows und dann von dir verarbeitet. Wenn du sie ans Ende setzt, ist es andersherum. Beispiel: Du reagierst auf WM_CHAR. Diese Nachricht wird versendet, wenn eine Taste gedrückt wird. Du möchtest, dass, wenn ein 'A' gedrückt wird, ein 'B' dabei herauskommt (dass also kein 'A' mehr gedrückt werden kann). Dann veränderst du die Message zuerst (in den Params) und lässt die Message dann weiterverarbeiten. Die Zeile kommt in diesem Falle am Ende.



  • Beitrag von oben editiert: dritte Möglichkeit "Abfangen von Messages per Application" hinzugefügt.


Anmelden zum Antworten