tags:

views:

103

answers:

2

I have been working with C++ and Win32 (non MFC/ATL) I am playing around with writing my own class library to wrap certain Win32 objects (HWNDs in particular).

When it comes to creating windows, I find the "RegisterClassEx / CreateWindowEx" method very awkward. This design makes it hard to write simple class wrappers (one must resort to thunks, or TLS or some other complicated mechanism).

It seems to me it would have been simpler to just let the application specify the window procedure and a user-data pointer at window creation time.

Is there any obvious reason I'm missing for the design choice here? Is there a really simple and efficient way for this to work?

+4  A: 

ATL's CWindow and CWindowImpl are your friends.

CWindowImpl makes takes care of the RegisterClass/CreateWindow awkwardness that you speak of.

CWindow is a basic "wrapper" class for an HWND with all the win32 functions abstracted out.

The reason I prefer ATL over MFC. ATL is a very lightweight set of classes with all the source code provided. It's a simple #include with no extra libraries or runtimes to deal with. After rolling my own WndProcs and window encapsulation classes for many years, I've found CWindowImpl a joy to work with. You have to declare a global AtlModuleExe instance in your code to use it, but besides that, ATL stays out of the way.

Links to documenation for these classes below: CWindow: http://msdn.microsoft.com/en-us/library/d19y607d.aspx

CWindowImpl: http://msdn.microsoft.com/en-us/library/h4616bh2.aspx

Update: Here's some sample code I dug up for you:

class CMyApp : public CAtlExeModuleT<CMyApp>
{
public:
    static HRESULT InitializeCom()
    {
        CoInitialize(NULL);
        return S_OK;
    }
};

CMyApp g_app;

class CMyWindow : public CWindowImpl<CMyWindow>
{
public:
    CMyWindow();
    ~CMyWindow();
    BEGIN_MSG_MAP(CMyWindow)
        MESSAGE_HANDLER(WM_PAINT, OnPaint);
        MESSAGE_HANDLER(WM_CLOSE, OnClose);
    END_MSG_MAP();

    LRESULT OnPaint(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& fHandled);
    LRESULT OnClose(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& fHandled);
};

LRESULT CMyWindow::OnPaint(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& fHandled)
{
   // your WM_PAINT code goes here
}

LRESULT CMyWindow::OnClose(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& fHandled)
{
    PostQuitMessage();
}

int __stdcall WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR cmdline, int nCmdShow)
{
    // 640x480 window
    CMyWindow appwindow;
    RECT rect = {0, 0, 640, 480};
    RECT rectActual = {0};
    appwindow.Create(NULL, rect, L"App Window", WS_OVERLAPPEDWINDOW);
    appwindow.ShowWindow(SW_SHOW);

    {
        MSG msg;
        while (GetMessage(&msg, 0, 0, 0))
        {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
    }
    // app shutdown

    appwindow.DestroyWindow();

    return 0;
}
selbie
A: 

Resort to thunks or tls? I dont know what you mean by a thunk in this case, but its quite easy - if just a little convoluted - to bootstrap a window into a c++ class wrapper.

class UserWindow
{
  HWND _hwnd;
public:
  operator HWND(){
    return _hwnd;
  }
  UserWindow():_hwnd(0){}
  ~UserWindow(){
    if(_hwnd){
      SetWindowLongPtr(GWL_USERDATA,0);
      DestroyWindow(_hwnd);
  }
  static LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam){
    UserWindow* self = 0;
    if(uMsg == WM_CREATE)
    {
      LPCREATESTRUCT crst = (LPCREATESTRUCT)lParam;
      self = (Window*)crst->lpCreateParams;
      SetWindowLongPtr(hwnd,GWL_USERDATA,(LONG_PTR)self);
      self->_hwnd = hwnd;
    }
    else
      self = (Window*)GetWindowLongPtr(hwnd,GWL_USERDATA);

    if(self){
      LRESULT lr = self->WndProc(uMsg,wParam,lParam);
      if(uMsg == WM_DESTROY){ 
        if(self = (Window*)GetWindowLongPtr(hwnd,GWL_USERDATA))
          self->_hwnd = NULL;
      }
      return lr;
    }
    return DefWindowProc(hwnd,uMsg,wParam,lParam);
  }
  HWND Create(int x, int y, int w, int h, LPCTSTR pszTitle,DWORD dwStyle,DWORD dwStyleEx,LPCTSTR pszMenu,HINSTANCE hInstance, HWND hwndParent){
    WNDCLASSEX wcex = { sizeof (wcex),0};
    if(!GetClassInfo(hInstance,ClassName(),&wcex)){
      wcex.style = CS_HREDRAW | CS_VREDRAW;
      wcex.lpfnWndProc = WindowndProc;
      wcex.cbClsExtra = 0;
      wcex.cbWndExtra = 0;
      wcex.hInstance = hInstance;
      wcex.lpszClassName = ClassName();
      OnCreatingClass( wcex );
  RegisterClassEx(&wcex);
    }
    return CreateWindowEx( dwStyleEx, ClassName(), pszTitle, dwStyle, x, y, w, h, hwndParent, pszMenu, hInstance, this);
  }
  // Functions to override
  virtual LPCTSTR ClassName(){
    return TEXT("USERWINDOW");
  }
  virtual LRESULT WindowProc(UINT uMsg, WPARAM wParam,LPARAM lParam){
    return DefWindowProc(uMsg,wParam,lParam);
  }
  virtual void Window::OnCreatingClass(WNDCLASSEX& wcex){
    wcex.hCursor = LoadCursor(NULL,IDC_ARROW);
  }
};

It is all a bit convoluted, but it means that the window can be destroyed safely by deleting the class, OR by being destroyed. There are one or two sizing related messages sent during the call to CreateWindow before WM_CREATE sets GWL_USERDATA to "this" but practically they are of no consequence. The window class is automatically created the first time the window is instantiated.


One thing this style of automatic class registration on the first call to create does not support is the instantiation of this type of window as a control on a dialog - To support that case a whole slew of things would need to be changed... provide a static class registration function... a "new MyClass" in the static WM_CREATE handler... its not obvious to me how this could be done in a frameworkish type fashion.

Chris Becke
Two major failures: Firstly, Get/SetWindowLong should be Get/SetWindowLongPtr, else you can't compile 64bit. Secondly, SetWindowLong/Ptr has some obscure function you need to call before it's effect takes place. http://msdn.microsoft.com/en-us/library/ms644898(VS.85).aspx
DeadMG
1. I wrote that from memory, so typo's are expected. But correct, I should have used the Ptr forms of the functions. I remembered it everywhere else ;-P2. Yes and no. SetWindowLong can effect many aspects of a window, many of which are only really read on window creation - such as the non client frame style bits. Those styles need a call to SetWindowPos with a SWP_FRAMECHANGED in the flags to cause the window to regenerate its new frame.GWL_USERDATA "takes effect" immediately - because the effect is to ensure that GetWindowLong retrieves the value.
Chris Becke