Benutzerdefinierte(s) Steuerelement(klasse)



  • Moin,

    ich möchte mir eine eigene Klasse für ein Steuerelement schreiben und bin nun etwas ratlos ...

    Ich habe die letzten Wochen recherchiert und gegoogled bis die Augen bluteten ... 🙄
    Diverse Quellen habe ich gefunden und durchforstet, u.a. hier im Forum.

    Das Ergebnis lässt sich kompilieren, gibt aber 278 Warnungen aus.

    Nun das Problem: Die Fensterprozedur wird nicht aufgerufen, bzw. das Fenster wird nicht erstellt !?

    Ich hoffe, dass mir jemand helfen kann das Problem zu lösen ...

    Bevor ich nun den Beispielcode poste, noch ein paar Erläuterungen wie der Ablauf sein soll:

    Das Steuerelement soll ganz normal wie jedes Fenster erstellt werden, d.h. das Klassen-Objekt für jedes Steuerelement soll dynamisch zur Laufzeit erstellt werden.
    Man muss lediglich zuvor die Fenster-Klasse initialisieren/registrieren, dies soll über eine globale Funktion möglich sein.

    Der benötigte Zeiger auf das Klassen-Objekt soll über cbWndExtra erhalten/gesetzt werden, da ich GWL_USERDATA vllt noch anderweitig nutzen möchte.

    Der Rest sollte aus dem Code ersichtlich sein. 🙂

    Bevor ich es vergesse zu Erwähnen, ich bin noch in der Lernphase und dies ist mein erster Versuch eine "eigene" Klasse zu kreiren. 🙄

    Klasse.h

    #ifndef CUSTOM_BUTTON_H
    #define CUSTOM_BUTTON_H
    
    #include <windows.h>
    
    void InitCustomButtonControls();
    
    struct BUTTONRECT {
        int left;
        int top;
        int width;
        int height;
    };
    
    class BmpButton {
        public:
            BmpButton(){}      // Standardkonstruktor
            BmpButton(HWND hwnd, CREATESTRUCT *cs);
            virtual ~BmpButton();
    
            static bool InitButtonClass();
            void PaintBitmap(HWND hwnd, HBITMAP bmp);
    
            static LRESULT CALLBACK StartWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);
    
            LRESULT BmpButtonProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);
    
        private:
            HWND       hWndCtrl;
            HMENU      BtnID;
            HBITMAP    BtnDefault;
            HBITMAP    BtnOnHover;
            HBITMAP    BtnOnClick;
            BUTTONRECT BtnRect;
            bool       IsOnHover;
    
    };
    
    #endif
    

    Klasse.cpp

    #include "CustomButton.h"
    
    const TCHAR buttonClassName [] = TEXT("BmpButtonClass");
    
            /////////////////////////////////////////////////////
            //  Fensterklasse initialisieren und registrieren  //
            /////////////////////////////////////////////////////
    
    void InitCustomButtonControls() {
    
        bool result = BmpButton::InitButtonClass();
    
        if (!result)
            ::MessageBox(NULL, TEXT("Die Fensterklasse konnte nicht registriert werden!"),
                                buttonClassName, MB_ICONERROR);
    }
    
    bool BmpButton::InitButtonClass() {
    
        WNDCLASSEX BtnWcl;
    
        BtnWcl.hInstance      = (HINSTANCE) ::GetModuleHandle(0);
        BtnWcl.lpszClassName  = buttonClassName;
        BtnWcl.lpfnWndProc    = ::BmpButton::StartWndProc;
        BtnWcl.style          = CS_DBLCLKS;
        BtnWcl.cbSize         = sizeof(WNDCLASSEX);
        BtnWcl.hIcon          = NULL;
        BtnWcl.hIconSm        = NULL;
        BtnWcl.hCursor        = NULL;
        BtnWcl.lpszMenuName   = NULL;
        BtnWcl.cbClsExtra     = 0;
        BtnWcl.cbWndExtra     = sizeof(BmpButton *);
        BtnWcl.hbrBackground  = (HBRUSH) ::GetStockObject(WHITE_BRUSH);
    
        if (!::RegisterClassEx (&BtnWcl)) {
            return false;
        } else {
            return true;
        }
    }
    
            ///////////////////////
            //  Konstruktor      //
            ///////////////////////
    
    BmpButton::BmpButton(HWND hwnd, CREATESTRUCT *cs) {
    
        hWndCtrl       = hwnd;
        BtnID          = cs->hMenu;
        BtnRect.left   = cs->x;
        BtnRect.top    = cs->y;
        BtnRect.width  = cs->cx;
        BtnRect.height = cs->cy;
    
    }
    
            ///////////////////////
            //  Destruktor       //
            ///////////////////////
    
    BmpButton::~BmpButton() {
        // Aufräumen
        ::DeleteObject(BtnDefault);
        ::DeleteObject(BtnOnHover);
        ::DeleteObject(BtnOnClick);
    }
    
            ///////////////////////
            //  Startprozedur    //
            ///////////////////////
    
    LRESULT CALLBACK BmpButton::StartWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
    
        BmpButton *bbp = reinterpret_cast<BmpButton *> (::GetWindowLong(hwnd, 0));
    
        if (bbp == 0) {
            if (msg == WM_NCCREATE) {
                bbp = new BmpButton(hwnd, reinterpret_cast<LPCREATESTRUCT> (lParam));
                ::SetWindowLong(hwnd, 0, reinterpret_cast<LONG> (bbp));
                ::MessageBox(NULL, TEXT("WM_NCCREATE OK"), buttonClassName, MB_ICONINFORMATION);
                return bbp->BmpButtonProc(hwnd, msg, wParam, lParam);
            } else {
                return ::DefWindowProc(hwnd, msg, wParam, lParam);
            }
        } else {
            if (msg == WM_NCDESTROY) {
                delete bbp;
                ::SetWindowLong(hwnd, 0, 0);
                return ::DefWindowProc(hwnd, msg, wParam, lParam);
            } else {
                return bbp->BmpButtonProc(hwnd, msg, wParam, lParam);
            }
        }
    }
    
            ///////////////////////
            //  Fensterprozedur  //
            ///////////////////////
    
    LRESULT BmpButton::BmpButtonProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
    
        BmpButton *bbp = reinterpret_cast<BmpButton *> (::GetWindowLong(hwnd, 0));
    
        switch (msg) {
            case WM_MOUSEHOVER:
                if (bbp->IsOnHover = false) {
                    bbp->PaintBitmap(hwnd, bbp->BtnOnHover);
                    bbp->IsOnHover = true;
                }
                break;
    
            case WM_MOUSELEAVE:
                bbp->PaintBitmap(hwnd, bbp->BtnDefault);
                bbp->IsOnHover = false;
                break;
    
            case WM_LBUTTONDOWN:
                ::SetWindowPos(hwnd, HWND_TOPMOST, bbp->BtnRect.left + 1,
                                                 bbp->BtnRect.top  + 1,
                                                 bbp->BtnRect.width  - 1,
                                                 bbp->BtnRect.height - 1,
                                                 SWP_SHOWWINDOW);
                bbp->PaintBitmap(hwnd, bbp->BtnOnClick);
                break;
    
            case WM_LBUTTONUP:
                ::SetWindowPos(hwnd, HWND_TOPMOST, bbp->BtnRect.left,
                                                 bbp->BtnRect.top,
                                                 bbp->BtnRect.width,
                                                 bbp->BtnRect.height,
                                                 SWP_SHOWWINDOW);
                bbp->PaintBitmap(hwnd, bbp->BtnOnHover);
                ::SendMessage(GetParent(hwnd), WM_COMMAND, 0, 0);
                break;
    
            case WM_KEYDOWN:
                break;
    
            case WM_PAINT:
                //bbp->PaintBitmap(hwnd, bbp->BtnOnHover);
                break;
            default:
                return ::DefWindowProc (hwnd, msg, wParam, lParam);
        }
    
        return 0;
    
    }
    
            ////////////////////////
            //  Fenster Zeichnen  //
            ////////////////////////
    
    void BmpButton::PaintBitmap(HWND hwnd, HBITMAP hbmp) {
    
        HDC hdc, hdcMem;
        RECT rect;
        HBITMAP hbmOld;
    
        GetWindowRect(hwnd, &rect);
    
        hdc    = GetDC(hwnd);
        hdcMem = CreateCompatibleDC(hdc);
        hbmOld = (HBITMAP) SelectObject(hdcMem, hbmp);
        BitBlt(hdc, 0, 0, BtnRect.width, BtnRect.height, hdcMem, 0, 0, SRCCOPY);
        SelectObject(hdcMem, hbmOld);
        DeleteDC(hdc);
    
    }
    

    Main.cpp

    #include "stdafx.h"
    #include "Resource.h"
    #include "BmpButton.h"
    
    #define IDC_BUTTON_1    1
    
    LRESULT CALLBACK WindowProcedure (HWND, UINT, WPARAM, LPARAM);
    
    char szClassName[ ] = "MainWndClass";
    
    int WINAPI WinMain (HINSTANCE hThisInstance, HINSTANCE hPrevInstance, LPSTR lpszArgument, int nCmdShow)
    {
        HWND hwnd;
        MSG messages;
        WNDCLASSEX wincl;
    
        wincl.hInstance = hThisInstance;
        wincl.lpszClassName = szClassName;
        wincl.lpfnWndProc = WindowProcedure;
        wincl.style = CS_DBLCLKS;
        wincl.cbSize = sizeof (WNDCLASSEX);
        wincl.hIcon = LoadIcon (NULL, IDI_APPLICATION);
        wincl.hIconSm = LoadIcon (NULL, IDI_APPLICATION);
        wincl.hCursor = LoadCursor (NULL, IDC_ARROW);
        wincl.lpszMenuName = NULL;
        wincl.cbClsExtra = 0;
        wincl.cbWndExtra = 0;
        wincl.hbrBackground = (HBRUSH) COLOR_BACKGROUND;
    
        if (!RegisterClassEx (&wincl))
            return 0;
    
        hwnd = CreateWindowEx (
               0,
               szClassName,
               TEXT("Test the Custom Control ..."),
               WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN,
               CW_USEDEFAULT,
               CW_USEDEFAULT,
               544,
               375,
               HWND_DESKTOP,
               NULL,
               hThisInstance,
               NULL
               );
    
        ShowWindow (hwnd, nCmdShow);
        UpdateWindow(hwnd);
    
        // Custom Control-Klasse initialisieren/registrieren
        InitBmpButtonControls();
    
        while (GetMessage (&messages, NULL, 0, 0))
        {
            TranslateMessage(&messages);
            DispatchMessage(&messages);
        }
    
        return messages.wParam;
    }
    
    LRESULT CALLBACK WindowProcedure (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
    {
        HWND hwndButton;
        HWND hwndBut;
        HINSTANCE hInst = (HINSTANCE) GetWindowLong(hwnd, GWL_HINSTANCE);
    
        switch (message)
        {
            case WM_CREATE:
                hwndButton = CreateWindowEx(0, TEXT("BmpButtonClass"), TEXT(""),
                                    WS_CHILD | WS_VISIBLE | WS_POPUP | WS_TABSTOP | WS_CLIPSIBLINGS,
                                    20, 20, 100, 25,
                                    hwnd, (HMENU) IDC_BUTTON_1, hInst, NULL);
                hwndBut = CreateWindowEx(0, TEXT("Button"), TEXT(""),
                                    WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON | WS_CLIPSIBLINGS,
                                    60, 60, 100, 25,
                                    hwnd, (HMENU) 2, hInst, NULL);
                break;
    
            case WM_DESTROY:
                PostQuitMessage (0);
                break;
    
            default:
                return DefWindowProc (hwnd, message, wParam, lParam);
        }
    
        return 0;
    }
    

    So, das isses im groben ...

    Ich habe mich auch durch den Sourcecode von Scintilla gewühlt, da ist es eigentlich genauso wie ich es habe, aber meines funzt eben nicht ...

    Vielen Dank

    Gruß



  • C++Greenhorn schrieb:

    Moin,
    Nun das Problem: Die Fensterprozedur wird nicht aufgerufen, bzw. das Fenster wird nicht erstellt !?

    Dann scheint es an der Parametrierung der Fensterklasse zu liegen. Die hast Du aber vorsichtshalber nicht gepostet. Oder welches "Fenster" meinst Du?

    Außerdem wäre es gut, wenn Du Dich zwischen OOP und prozeduraler Programmierung entscheiden könntest. 😉



  • Elektronix schrieb:

    Dann scheint es an der Parametrierung der Fensterklasse zu liegen. Die hast Du aber vorsichtshalber nicht gepostet. Oder welches "Fenster" meinst Du?

    Die Fensterprozedur ::BmpButton::StartWndProc wird nicht aufgerufen.

    Elektronix schrieb:

    Außerdem wäre es gut, wenn Du Dich zwischen OOP und prozeduraler Programmierung entscheiden könntest. 😉

    Ähm, was genau meinst Du denn damit ? 😕

    Gruß



  • Daß Du die Klasse des Hauptfensters in guter alter C-Manier erstellst

    WNDCLASSEX wincl;
        wincl.hInstance = hThisInstance;
        wincl.lpszClassName = szClassName;
        wincl.lpfnWndProc = WindowProcedure;
        wincl.style = CS_DBLCLKS;
        wincl.cbSize = sizeof (WNDCLASSEX);
        wincl.hIcon = LoadIcon (NULL, IDI_APPLICATION);
        wincl.hIconSm = LoadIcon (NULL, IDI_APPLICATION);
        wincl.hCursor = LoadCursor (NULL, IDC_ARROW);
        wincl.lpszMenuName = NULL;
        wincl.cbClsExtra = 0;
        wincl.cbWndExtra = 0;
        wincl.hbrBackground = (HBRUSH) COLOR_BACKGROUND;
    

    , für die BmpButton dann aber ein C++-Objekt erstellst:

    class BmpButton {
        public:
            BmpButton(){}      // Standardkonstruktor
            BmpButton(HWND hwnd, CREATESTRUCT *cs);
            virtual ~BmpButton();
    
            static bool InitButtonClass();
            void PaintBitmap(HWND hwnd, HBITMAP bmp);
    
            static LRESULT CALLBACK StartWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);
    
            LRESULT BmpButtonProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);
    
        private:
            HWND       hWndCtrl;
            HMENU      BtnID;
            HBITMAP    BtnDefault;
            HBITMAP    BtnOnHover;
            HBITMAP    BtnOnClick;
            BUTTONRECT BtnRect;
            bool       IsOnHover;
    
    };
    

    Es spricht nichts dagegen, für die WinAPI eigene Wrapper-Klassen zu erstellen. Dann sollte man es aber einheitlich machen und nicht C und C++ bunt durcheinander würfeln.

    Übrigens: Für die Structur ButtonRect stellt die WinAPI schon eine Struct RECT bereit.



  • Elektronix schrieb:

    Daß Du die Klasse des Hauptfensters in guter alter C-Manier erstellst, für die BmpButton dann aber ein C++-Objekt erstellst:
    ...

    Es spricht nichts dagegen, für die WinAPI eigene Wrapper-Klassen zu erstellen. Dann sollte man es aber einheitlich machen und nicht C und C++ bunt durcheinander würfeln.

    Ich wollte eigentlich nur eine eigene Steuerelement-Klasse schreiben und das soll in C++ ja recht einfach sein, wie ich bei meinen Recherchen immer wieder zu lesen bekam.
    Wie das Hauptfenster erstellt wird - ob nun C, C++ oder meinetwegen auch sonstirgendwas -, ist mir persönlich egal, mir kommt es im Moment nur auf die zu erstellende Klasse an.

    Elektronix schrieb:

    Übrigens: Für die Structur ButtonRect stellt die WinAPI schon eine Struct RECT bereit.

    Die Struktur RECT habe ich nicht benutzt, weil sie mir eben nicht die Infos liefert, die ich erhalten/speichern möchte.

    So, nachdem mein Programmierstil und Code nun ordentlich madig geredet wurde, wäre ich für ein paar konstruktive Antworten dankbar. 😉

    So long



  • C++Greenhorn schrieb:

    Elektronix schrieb:

    Daß Du die Klasse des Hauptfensters in guter alter C-Manier erstellst, für die BmpButton dann aber ein C++-Objekt erstellst:
    ...

    Es spricht nichts dagegen, für die WinAPI eigene Wrapper-Klassen zu erstellen. Dann sollte man es aber einheitlich machen und nicht C und C++ bunt durcheinander würfeln.

    Ich wollte eigentlich nur eine eigene Steuerelement-Klasse schreiben und das soll in C++ ja recht einfach sein, wie ich bei meinen Recherchen immer wieder zu lesen bekam.
    Wie das Hauptfenster erstellt wird - ob nun C, C++ oder meinetwegen auch sonstirgendwas -, ist mir persönlich egal, mir kommt es im Moment nur auf die zu erstellende Klasse an.

    Nun, wie Du siehst, ist das nicht einfacher. Wenn Du sowas "einfacher" haben willst, mußt MFC einsetzen. Das ist aber ein Kapitel für sich.

    Die Windows-Fensterklassen sind nicht gleich mit C++-Objektklassen. Fensterklassen werden erstellt, indem man die WNDCLASS(EX)-Struktur ausfüllt und RegisterClassEx() aufruft. Das ist reines C.

    Man kann sowas auch in einen Wrapper packen, das entbindet Dich aber nicht davon, erstmal die Klassenstruktur auszufüllen und zu registrieren.



  • Nun, vielen Dank erstmal, dass Du meinem Problem überhaupt Aufmerksamkeit schenkst.

    Nun, wie Du siehst, ist das nicht einfacher.

    Das sehe ich wirklich 😃 😉
    Ich habe mich bis jetzt nach diesem Beispiel gerichtet http://www.catch22.net/tuts/custctrl.asp, aber irgenetwas fehlt da ...

    Man kann sowas auch in einen Wrapper packen, das entbindet Dich aber nicht davon, erstmal die Klassenstruktur auszufüllen und zu registrieren.

    Aber genau das habe ich doch getan ...

    /////////////////////////////////////////////////////
            //  Fensterklasse initialisieren und registrieren  //
            /////////////////////////////////////////////////////
    
    void InitCustomButtonControls() {
    
        bool result = BmpButton::InitButtonClass();
    
        if (!result)
            ::MessageBox(NULL, TEXT("Die Fensterklasse konnte nicht registriert werden!"),
                                buttonClassName, MB_ICONERROR);
    }
    
    bool BmpButton::InitButtonClass() {
    
        WNDCLASSEX BtnWcl;
    
        BtnWcl.hInstance      = (HINSTANCE) ::GetModuleHandle(0);
        BtnWcl.lpszClassName  = buttonClassName;
        BtnWcl.lpfnWndProc    = ::BmpButton::StartWndProc;
        BtnWcl.style          = CS_DBLCLKS;
        BtnWcl.cbSize         = sizeof(WNDCLASSEX);
        BtnWcl.hIcon          = NULL;
        BtnWcl.hIconSm        = NULL;
        BtnWcl.hCursor        = NULL;
        BtnWcl.lpszMenuName   = NULL;
        BtnWcl.cbClsExtra     = 0;
        BtnWcl.cbWndExtra     = sizeof(BmpButton *);
        BtnWcl.hbrBackground  = (HBRUSH) ::GetStockObject(WHITE_BRUSH);
    
        if (!::RegisterClassEx (&BtnWcl)) {
            return false;
        } else {
            return true;
        }
    }
    

    Ich bin bei meinen weiteren Nachforschungen auf einige interessante Beispiele aufmerksam geworden, die es ähnlich wie Scintilla machen, mit einer Basisklasse und einer davon abgeleiteten ...
    Werde mich mal daran versuchen und Erfolg/Misserfolg posten.

    Gruß
    Greenhorn


Anmelden zum Antworten