Thread: classmember WndProc isn't called ...

  1. #1
    Registered User
    Join Date
    Jul 2008
    Posts
    67

    Post classmember WndProc isn't called ...

    Moin moin,

    I'm new to this forum and this is my first post, so I want to use this opportunity to say moin moin

    OK, but now back to topic.

    I would like to create a custom control class, at first a simple button.
    During my investigations about this affair I found a lot of articles and forum requests.
    There are several methods to realize it, but mostly there's a snag on it.

    Then I found some solutions working with derived classes to get rid of some disadvantages of other methods.
    "Scintilla" is a good examle for that what I want to do, but I don't understand all of the code and don't know what also depends to get it work like this.

    I thought by myself: "OK, no problem ...", but in fact after weeks of investigation and playing around with it, I'm fully frustrated ...

    I can't get it run in any way !!!

    So, please, could someone take notice of my problem and help me to solve it ?

    Here's a little example of my failing code:
    BaseCtrl.h
    Code:
    #ifndef _BASECTRL_H_
    #define _BASECTRL_H_
    
    #include <windows.h>
    
    
    class BaseCtrl {
      public:
    	BaseCtrl() {}
    	BaseCtrl(HWND hWindow)
    	: hWndCtrl(hWindow) {
    	}
    	virtual ~BaseCtrl() {}
    
    	static bool RegisterCtrlClass(LPCTSTR szClassName);
    
      protected:
    	// Statische Fensterprozedur
    	static LRESULT CALLBACK stWndProc(HWND, UINT, WPARAM, LPARAM);
    	// Virtuelle Fensterprozedur zur Verarbeitung der Nachrichten
    	virtual LRESULT MessageProc(HWND, UINT, WPARAM, LPARAM);
    
    	HWND  hWndCtrl;
    	HMENU ctrlID;
    
    };
    
    bool BaseCtrl::RegisterCtrlClass(LPCTSTR szClassName) {
    
    	WNDCLASSEX wcl;
    
        wcl.hInstance      = reinterpret_cast<HINSTANCE>(::GetModuleHandle(0));
        wcl.lpszClassName  = szClassName;
    	wcl.lpfnWndProc    = ::BaseCtrl::stWndProc;
        wcl.style          = CS_DBLCLKS;
        wcl.cbSize         = sizeof(WNDCLASSEX);
        wcl.hIcon          = NULL;
        wcl.hIconSm        = NULL;
        wcl.hCursor        = NULL;
        wcl.lpszMenuName   = NULL;
        wcl.cbClsExtra     = 0;
        wcl.cbWndExtra     = sizeof(BaseCtrl *);
        wcl.hbrBackground  = reinterpret_cast<HBRUSH>(::GetStockObject(BLACK_BRUSH));
    
        if (!::RegisterClassEx (&wcl)) {
            return false;
        } else {
            return true;
        }
    }
    
    LRESULT CALLBACK BaseCtrl::stWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
    
    	BaseCtrl *pCtrl = reinterpret_cast<BaseCtrl *>(::GetWindowLong(hwnd, 0));
    
    	if (!pCtrl) {
    		if (msg == WM_NCCREATE) {
    			pCtrl = new BaseCtrl(hwnd);
    			::SetWindowLong(hwnd, 0, reinterpret_cast<LONG>(pCtrl));
    			return pCtrl->MessageProc(hwnd, msg, wParam, lParam);
    		} else {
    			return ::DefWindowProc(hwnd, msg, wParam, lParam);
    		}
    	} else {
    		if (msg == WM_NCDESTROY) {
    			delete pCtrl;
    			return ::DefWindowProc(hwnd, msg, wParam, lParam);
    		} else {
    			return pCtrl->MessageProc(hwnd, msg, wParam, lParam);
    		}
    	}
    }
    
    LRESULT BaseCtrl::MessageProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
    
    	return 0;
    }
    #endif
    ButtonCtrl.h
    Code:
    #ifndef _BUTTONCTRL_H_
    #define _BUTTONCTRL_H_
    
    #include "BaseCtrl.h"
    
    const wchar_t szBtnClassName [] = TEXT("BUTTONCLASS");
    
    void RegisterButtonClass() {
    	if (!BaseCtrl::RegisterCtrlClass(szBtnClassName)) {
    		::MessageBox(NULL, TEXT("NOT REGISTERED"), TEXT("NO"), MB_ICONINFORMATION);
    }
    
    class ButtonCtrl : public BaseCtrl {
      public:
    	ButtonCtrl() {}
    	ButtonCtrl(HWND hWindow)
    		: BaseCtrl(hWindow) {
    	}
    	virtual ~ButtonCtrl() {}
    
      private:
    	virtual LRESULT MessageProc(HWND, UINT, WPARAM, LPARAM);
    
    };
    
    LRESULT ButtonCtrl::MessageProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
    
    	switch (msg) {
    		case WM_CREATE:
    			::MessageBox(NULL, TEXT("ButtonCtrl::MessageProc"), TEXT("WM_CREATE"), MB_ICONINFORMATION);
    			break;
    		default:
    			return ::DefWindowProc(hwnd, msg, wParam, lParam);
    	}
    	return 0;
    }
    #endif
    main.cpp for testing ...
    Code:
    #include "ButtonCtrl.h"
    
    #define IDC_BUTTON_1    1
    
    LRESULT CALLBACK WindowProcedure (HWND, UINT, WPARAM, LPARAM);
    
    const wchar_t szClassName[] = TEXT("MainWndClass");
    
    int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszArgument, int nCmdShow)
    {
        HWND       hwnd;
        MSG        messages;
        WNDCLASSEX wincl;
    
        wincl.hInstance      = hInstance;
        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,
               hInstance,
               NULL
               );
    
    
        ShowWindow  (hwnd, nCmdShow);
        UpdateWindow(hwnd);
    
    
        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;
        static HINSTANCE hInst = (HINSTANCE) GetWindowLong(hwnd, GWL_HINSTANCE);
    
        switch (message)
        {
            case WM_CREATE:
    			RegisterButtonClass();
    
                // Test the new class :) -> ;(
                hwndButton = CreateWindowEx(0, TEXT("BUTTONCLASS"), 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;
    }
    Sorry, the code isn't well commented ...

    The problem is that the static stWndProc isn't called !!!
    So what's wrong in my code ???

    Scintilla is mostly doin' the same like I do, isn't it ?

    Many thanx for taking notice.


    Greetz
    Greenhorn

    p.s.: Sorry for my bad english, I hope you understand what I want to express ...

  2. #2
    erstwhile
    Join Date
    Jan 2002
    Posts
    2,227
    Your main.cpp file contains only code for creating a standard window and some child windows with what is essentially an unregistered class(unregistered since that code is never called). The system will only create windows of registered 'window classes'(those registered with RegisterClassEx) and, since you never actually invoke any code to do so in the WM_CREATE handler in main.cpp, window creation will fail.

    Instantiate an object of your c++ class and invoke its methods to register and create windows of your own 'window classes' in your test source file(main.cpp).
    CProgramming FAQ
    Caution: this person may be a carrier of the misinformation virus.

  3. #3
    Registered User
    Join Date
    Jul 2008
    Posts
    67
    Sorry for my bad commented code ...

    I put the registration in a global function (ButtonCtrl.h) for easy use and the MessageBox isn't displayed ...
    Code:
    void RegisterButtonClass() {
    	if (!BaseCtrl::RegisterCtrlClass(szBtnClassName)) {
    		::MessageBox(NULL, TEXT("NOT REGISTERED"), TEXT("NO"), MB_ICONINFORMATION);
    }
    (main.cpp)
    Code:
            case WM_CREATE:
    			RegisterButtonClass();
    
                hwndButton = CreateWindowEx(0, TEXT("BUTTONCLASS"), TEXT(""),
                                    WS_CHILD | WS_VISIBLE | WS_POPUP | WS_TABSTOP | WS_CLIPSIBLINGS,
                                    20, 20, 100, 25,
                                    hwnd, (HMENU) IDC_BUTTON_1, hInst, NULL);
    ..., but there is no window created/shown, just the standard button I also created.


    Greetz
    EDIT:
    Scintilla does it in the same way, the object is created at runtime in the stWndProc ...
    Last edited by Greenhorn__; 07-12-2008 at 05:27 AM.

  4. #4
    erstwhile
    Join Date
    Jan 2002
    Posts
    2,227
    Actually, the fault's mine: I didn't see the RegisterButtonClass in your WM_CREATE handler.

    BaseCtrl::stWndProc creates a new instance of BaseCtrl if GetWindowLongPtr returns 0 which calls BaseCtrl::MessageProc and not ButtonCtrl::MessageProc as you probably intend.

    The more usual way to approach what you're doing is to pass a pointer to your window object(ButtonCtrl) as the last parameter of CreateWindowEx - that way your static window procedure can get and set relevant pointers with the result that the correct virtual function implementation will be called.

    If you haven't already done so search this board because there have been a lot of discussions about this sort of thing in the past with examples which you may find useful.
    CProgramming FAQ
    Caution: this person may be a carrier of the misinformation virus.

  5. #5
    Registered User
    Join Date
    Jul 2008
    Posts
    67
    Thank you very much Ken,

    I've already searched the hole Forum about this topic and yes, you're right, it is very often discussed here.

    But isn't it the same thing what I do, just using the window's extra bytes ?
    Code:
        wcl.cbWndExtra     = sizeof(BaseCtrl *);
    I'm not familiar with debugging, but it gives me an acces violation output, errorcode: 0xC0000005, and I think it's the pointer to
    Code:
    	wcl.lpfnWndProc    = ::BaseCtrl::stWndProc;
    So, I have a problem to understand the procedure:
    I can call the encapsulated member function "BaseCtrl::RegisterCtrlClass(szBtnClassName)" without having an existing object, didn't I ? (the class is registered in fact)

    Why can't I address the ::BaseCtrl::stWndProc ?
    I know, there is no object created yet, if I call the registering function, so it is a logical thing, but what is the trick Scintilla does, to get it work in this way !?

    Can I create an object dummy to get a valid address of ::BaseCtrl::stWndProc ???

    This little thing don't let me sleep ...


    Tired greetings

    EDIT:
    I also found this here Creating Custom Controls from scratch and it also uses the window's extra bytes, but there is no happy end that shows the C++ version in action ...
    Last edited by Greenhorn__; 07-12-2008 at 09:31 AM.

  6. #6
    erstwhile
    Join Date
    Jan 2002
    Posts
    2,227
    Sorry, I didn't explain what I meant very well and rather hoped previous examples on this subject would illuminate my intent further.

    The way your approach operates the window class is properly registered with the system(otherwise an error would be flagged via your MessageBox call) so you can go ahead and create windows of that window class. During system processing of CreateWindowEx a number of messages - including WM_NCREATE - are sent to the registered window procedure(BaseCtrl::stWndProc). It is here that you have chosen to create a c++ class object but the only kind of objects you can ever create with your existing code are BaseCtrl objects and, accordingly, this is what's stored as part of the window via SetWindowLongPtr(and later retrieved via GetWindowLongPtr). This means that the only c++ class object window procedure that will ever be called from BaseCtrl::stWndProc is BaseCtrl::MessageProc, regardless of whether you actually want a derived ButtonCtrl object MessageProc called.

    Contrast this approach with the more usual way that has been discussed on these boards in the past. In this approach, a pointer to the actual object is passed during window creation so when you invoke the call to the 'MessageProc' virtual method on the BaseCtrl pointer, it correctly resolves to the actual virtual function implementation of the derived class object required.

    In the example site you have linked to(catch22) a single class is used so the issue of inheritance/virtual functions does not arise. I haven't looked at the scintilla source but I imagine that the same applies ie a single, non-derived concrete class is used.

    You could still continue with the approach you are using - you'd just have to find a means to create an object of the required derived class and store that pointer as part of the window's extra data.

    The following (I hope) illustrates the problem:
    Code:
    #include <iostream>
    
    class Base
    {
      public:
        Base(){};
        
        virtual void CalledFrom()
        {
        std::cout<<"base"<<'\n';
        }
    };
    
    class Derived:public Base
    {
    public:
      Derived(){};
      
      virtual void CalledFrom()
      {
      std::cout<<"derived"<<'\n';
      }
    };
    
    int main()
    {
    Base *b = new Base();
    Derived *d = new Derived();
    
    b->CalledFrom();
    d->CalledFrom();
    
    /*this is okay and what you, ideally, want your static wnd proc to do */
    Base *bp = d;
    bp->CalledFrom();
    
    /*oops! This is what your static window procedure is essentially trying to do*/
    Derived *d2 = static_cast<Derived*>(b);
    d2->CalledFrom();
    
    delete b;
    delete d;
    }
    CProgramming FAQ
    Caution: this person may be a carrier of the misinformation virus.

  7. #7
    Registered User
    Join Date
    Jul 2008
    Posts
    67
    Ok, thanx, I understand what you mean, that's a bug ...

    The hint to do it with inherited classes I've got from the Scintilla source code and finally from here http://www.c-plusplus.de/forum/viewt...4.html#1249174
    The reason is explained in the first sentence a little bit ...
    He wrote that his first solution was too compiler specific http://www.c-plusplus.de/forum/viewt...8.html#1236048

    Scintilla does it contrariwise it puts the static window proc in the inherited class and overrides the virtual message handler proc. That is what I also should do, thank you for this hint

    But the main knot in my brain is: why is my static window proc not adressed ?
    The debug window shows me a value of 0xcccccccc for
    wcl.lpfnWndProc = ::BaseCtrl::stWndProc;
    but Scintilla does the same like I do to register the WNDCLASS

    The DLLMain is only registering and unregistering the WNDCLASS, that's mostly all, no object or pointer is created before ...

    Thank you very much Ken, for spending time at me and the useful hints
    I will try on and hope to find a good solution. (and lern a lot too)


    Greetz
    Greenhorn

    p.s.
    If you are interested I could send you the required files, just one .cpp and one header.

    Or take a look at here ...
    ScintillaBase.h
    ScintillaWin.cxx
    Line 146 - the inherit class
    Line 2361 and below to eof - the most interesting ...

  8. #8
    Registered User
    Join Date
    Jul 2008
    Posts
    67
    Now I tried it again with a single class, but it's the same problem ..., the static WndProc isn't called.

    In the debugger I saw that the ::ButtonCtrl::stWndProc is adressed and the window class is registerd.
    But, if I create a window with the classname the ::ButtonCtrl::stWndProc is never called and no window is created.

    Now I'm confused.

    In Scintilla it works in the same way like I wanna do it, so what is my fault ?

    ButtonCtrl.h
    Code:
    #ifndef _BUTTONCTRL_H_
    #define _BUTTONCTRL_H_
    
    const wchar_t szBtnClassName [] = TEXT("BUTTONCLASS");
    
    void RegisterButtonClass();
    
    class ButtonCtrl {
      public:
    	ButtonCtrl() {}
    	ButtonCtrl(HWND hWindow)
    		: hWndCtrl(hWindow) {
    	}
    	virtual ~ButtonCtrl() {}
    
    	static bool RegisterCtrlClass(LPCTSTR szClassName);
    
    	// Statische Fensterprozedur
    	static LRESULT CALLBACK stWndProc(HWND, UINT, WPARAM, LPARAM);
      private:
    	HWND hWndCtrl;
    
    	virtual LRESULT MessageProc(HWND, UINT, WPARAM, LPARAM);
    
    };
    
    // This function is externally visible so it can be called from container when building statically.
    // Must be called once only.
    void RegisterButtonClass() {
    	if (!ButtonCtrl::RegisterCtrlClass(szBtnClassName))
    		::MessageBox(NULL, TEXT("NOT REGISTERED"), TEXT("NO"), MB_ICONINFORMATION);
    }
    
    bool ButtonCtrl::RegisterCtrlClass(LPCTSTR szClassName) {
    
    	WNDCLASSEX wcl;
    
        wcl.hInstance      = reinterpret_cast<HINSTANCE>(::GetModuleHandle(0));
        wcl.lpszClassName  = szBtnClassName;
    	wcl.lpfnWndProc    = ::ButtonCtrl::stWndProc;
        wcl.style          = CS_DBLCLKS;
        wcl.cbSize         = sizeof(WNDCLASSEX);
        wcl.hIcon          = NULL;
        wcl.hIconSm        = NULL;
        wcl.hCursor        = NULL;
        wcl.lpszMenuName   = NULL;
        wcl.cbClsExtra     = 0;
        wcl.cbWndExtra     = sizeof(ButtonCtrl *);
        wcl.hbrBackground  = reinterpret_cast<HBRUSH>(::GetStockObject(BLACK_BRUSH));
    
        if (!::RegisterClassEx (&wcl)) {
            return false;
        } else {
            return true;
        }
    }
    
    LRESULT CALLBACK ButtonCtrl::stWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
    
    	ButtonCtrl *pCtrl = reinterpret_cast<ButtonCtrl *>(::GetWindowLong(hwnd, 0));
    
    	if (!pCtrl) {
    		if (msg == WM_NCCREATE) {
    			pCtrl = new ButtonCtrl(hwnd);
    			::SetWindowLong(hwnd, 0, reinterpret_cast<LONG>(pCtrl));
    			return pCtrl->MessageProc(hwnd, msg, wParam, lParam);
    		} else {
    			return ::DefWindowProc(hwnd, msg, wParam, lParam);
    		}
    	} else {
    		if (msg == WM_NCDESTROY) {
    			delete pCtrl;
    			return ::DefWindowProc(hwnd, msg, wParam, lParam);
    		} else {
    			return pCtrl->MessageProc(hwnd, msg, wParam, lParam);
    		}
    	}
    }
    
    LRESULT ButtonCtrl::MessageProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
    
    	switch (msg) {
    		case WM_CREATE:
    			::MessageBox(NULL, TEXT("ButtonCtrl::MessageProc"), TEXT("WM_CREATE"), MB_ICONINFORMATION);
    			break;
    		default:
    			return ::DefWindowProc(hwnd, msg, wParam, lParam);
    	}
    	return 0;
    }
    #endif
    main.cpp is the same like above, except
    Code:
    #include <windows.h>
    #include "ButtonCtrl.h"

    Greetz
    Greenhorn

  9. #9
    Registered User Codeplug's Avatar
    Join Date
    Mar 2003
    Posts
    4,981
    Be sure not to return 0 in response to WM_NCCREATE.

    gg

  10. #10
    Registered User
    Join Date
    Jul 2008
    Posts
    67
    Yes, I made that sure. I also tried to put the stWndProc as a global function..., same result: No Window, no call to the Window Procedure.

    Now I'm at my wit's end and I'm at the point to give up ...

    Sorry, I can't get it run.


    Greetz

  11. #11
    Registered User
    Join Date
    Jul 2008
    Posts
    67
    OK folks, I got it working !

    Now I'm very delighted and can go on ...

    Hmmm ..., what the error was ? You won't believe it, but I'll tell you what went wrong ...

    The static classmember static LRESULT CALLBACK stWndProc(HWND, UINT, WPARAM, LPARAM) was never called, because of the window style of the create window.
    WS_CHILD | WS_POPUP is an invalid combination, that's all of the secret.

    So, excluding the WS_POPUP style from the window will make it a lot easier ...
    (Sometimes I can't see the wood for the trees ... )

    Thanks to all for help ! ! !


    Greetz
    Greenhorn

    EDIT:
    @Boardteam
    I'm notable to edit my first post, so please, it would be nice if you can set the prefix [SOLVED] to the thread title.
    Last edited by Greenhorn__; 07-19-2008 at 11:55 AM.

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. Destructor being called on SGI hash_map key
    By cunnus88 in forum C++ Programming
    Replies: 4
    Last Post: 02-11-2009, 12:05 AM
  2. Replies: 4
    Last Post: 09-21-2008, 02:27 PM
  3. when is the constructor of a global object called??
    By mynickmynick in forum C++ Programming
    Replies: 6
    Last Post: 08-28-2008, 04:57 AM
  4. WNDPROC type
    By Garfield in forum Windows Programming
    Replies: 5
    Last Post: 01-17-2002, 10:32 PM
  5. Putting WndProc into a class?
    By Eber Kain in forum Windows Programming
    Replies: 3
    Last Post: 12-13-2001, 06:46 PM