Thread: Drawing to client area of tabbed window

  1. #1
    Registered User
    Join Date
    Feb 2013
    Posts
    10

    Drawing to client area of tabbed window

    Hi,
    I'm trying to create an application which consists of tabbed windows (panes), like those of a typical spreadsheet application. Pressing on a particular tab would bring up that window's graphics while hiding the graphics of other windows.

    So one way to approach this would be to simply use the existing Tab Controls feature in Windows.

    I'm not sure how to manage the repaint, however. Here's what I've got so far:

    Code:
    #include <windows.h>
    #if defined __MINGW_H
    #define _WIN32_IE 0x400
    
    #endif // defined
    #include <commctrl.h>
    #include <stdio.h>
    
    
    LRESULT CALLBACK WindowProcedure (HWND, UINT, WPARAM, LPARAM);
    
    
    
    char szWinName[]= "MyWin";
    
    HINSTANCE hInst;
    HWND hwnd;
    HWND hTabWnd;
    int maxX,maxY;
    HPEN hOldpen;
    HBITMAP hbit;
    HBRUSH hbrush, hOldBrush;
    
    HPEN hRedpen;
    RECT windowRect;
    
    int WINAPI WinMain (HINSTANCE hThisInst,
                         HINSTANCE hPrevInstance,
                         LPSTR lpszArgst,
                         int nWinMode)
    {
        HWND hwnd;               
        MSG messages;            
        WNDCLASSEX wcl;
       INITCOMMONCONTROLSEX cc;
    
    
        wcl.cbSize = sizeof (WNDCLASSEX);
        /* The Window structure */
        wcl.hInstance = hThisInst;
        wcl.lpszClassName = szWinName;
       // wcl.lpfnWndProc = WindowFunc;
        wcl.lpfnWndProc = WindowProcedure;      
        wcl.style = CS_DBLCLKS;                 
    
    
        /* Use default icon and mouse-pointer */
        wcl.hIcon = LoadIcon (NULL, IDI_APPLICATION);
        wcl.hIconSm = LoadIcon (NULL, IDI_APPLICATION);
        wcl.hCursor = LoadCursor (NULL, IDC_ARROW);
        wcl.lpszMenuName = NULL;                 
        wcl.cbClsExtra = 0;                      
        wcl.cbWndExtra = 0;                      
       
        wcl.hbrBackground = (HBRUSH) GetStockObject(WHITE_BRUSH);
    
      
        if (!RegisterClassEx (&wcl))
            return 0;
    
     
        hwnd = CreateWindowEx (
               0,                   
               szWinName,         
               "Tab Control Demo",       
               WS_OVERLAPPEDWINDOW, 
               CW_USEDEFAULT,       
               CW_USEDEFAULT,       
               CW_USEDEFAULT,              
               CW_USEDEFAULT,                 
               HWND_DESKTOP,        
               NULL,                
               hThisInst,       
               NULL                 
               );
        cc.dwSize = sizeof(INITCOMMONCONTROLSEX);
        cc.dwICC = ICC_TAB_CLASSES;
        InitCommonControlsEx(&cc);
        hInst=hThisInst;
    
    
    
        ShowWindow (hwnd, nWinMode);
        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)
    {
        NMHDR *nmptr;
        int tabnumber;
        HDC hdc;
        char str[80];
        TCITEM tci;
        RECT WinDim;
        HDC memdc;
    
    
        switch (message)                 
        {
           case WM_CREATE:
              maxX=GetSystemMetrics(SM_CXSCREEN);
              maxY=GetSystemMetrics(SM_CYSCREEN);
    
             GetClientRect(hwnd,&WinDim);
                hRedpen=CreatePen(PS_SOLID,1,RGB(255,0,0));
             hTabWnd= CreateWindow(
                 WC_TABCONTROL,
                 "",
                 WS_VISIBLE|WS_TABSTOP|WS_CHILD,0,0,WinDim.right,WinDim.bottom,
                 hwnd,
                 NULL,
                 hInst,
                 NULL
                );
              tci.mask=TCIF_TEXT;
              tci.iImage=-1;
              tci.pszText="One";
              TabCtrl_InsertItem(hTabWnd,0,&tci);
              tci.pszText="Two";
              TabCtrl_InsertItem(hTabWnd,1,&tci);
              tci.pszText="Three";
              TabCtrl_InsertItem(hTabWnd,2,&tci);
            break;
    
            case WM_NOTIFY:
              nmptr=(LPNMHDR) lParam;
              if(nmptr->code==TCN_SELCHANGE)
              {
                 tabnumber=TabCtrl_GetCurSel((HWND)nmptr->hwndFrom);
                 hdc=GetDC(hTabWnd);
                 LineTo(hdc,200,200);
                 /*
                  memdc=CreateCompatibleDC(hdc);
                  hbit=CreateCompatibleBitmap(hdc,maxX,maxY);
                  SelectObject(memdc, hbit);
                  hbrush=(HBRUSH)GetStockObject(WHITE_BRUSH);
                  hOldpen=(HPEN)SelectObject(memdc,hRedpen);
    
                  LineTo(memdc,30,200);
                  PatBlt(memdc,0,0,maxX,maxY,PATCOPY);
                  InvalidateRect(hwnd,NULL,1);
                  */
    
    
    
                 //sprintf(str,"Changed to Tab %d", tabnumber+1);
    
                 //TextOut(hdc,40,100,str,strlen(str));
    
    
    
                 ReleaseDC(hTabWnd,hdc);
              }
              break;
            case WM_DESTROY:
                DeleteObject(hRedpen);
                 DeleteDC(memdc);
                PostQuitMessage (0);       
                break;
            case WM_PAINT:
                {
                    PAINTSTRUCT ps;
                    hdc=BeginPaint(hwnd, &ps);
                    BitBlt(hdc,0,0,maxX,maxY,memdc,0,0,SRCCOPY);
    
                  
                    EndPaint(hwnd,&ps);
                    break;
            }
            default:                     
                return DefWindowProc (hwnd, message, wParam, lParam);
        }
    
        return 0;
    }
    Basically at this point I've only been able to get a line drawn to the tabbed window by directly drawing to it, which is what this code does. If you click on one of the tabs it will draw a line from (0,0) to (200,200).

    As the commented code shows I'm trying to draw to a device context and then repaint it.

    Invalidating the parent window hwnd doesn't do the trick, at least the way I've configured WM_Paint.

    On the other hand invalidating/repainting hTabWnd winds up erasing the tabs, so some other solution will be necessary.

    Thanks in advance for any input

  2. #2
    Registered User
    Join Date
    Mar 2013
    Posts
    7
    I'm reviewing your code I also am developing a project with the same resource, by the time I leave these examples:

    FoosYerDoos - C++ Programming
    FoosYerDoos - C++ Programming

    Regards

  3. #3
    train spotter
    Join Date
    Aug 2001
    Location
    near a computer
    Posts
    3,868
    1. Any variables local to the CALLBACK must be STATIC if you want them to retain a value for more than one message.

    2. To generate a WM_PAINT after changing your memDC use;
    InvalidateRect() //generate paint msg
    UpdateWindow() // send paint directly to app callback bypassing OS msg queue

    3. Only redraw the minimum area by using the RECT in the PAINTSTRUCT.
    Do NOT always redraw the entire client area.
    Not all paint msgs are generated inside your app (ie moving a msgbox over a corner of your app will redraw the entire screen).

    4. Catch the default GDI objects when using SelectObject(), as you have done with the pen, but not with the bitmap. Select these default GDI objects back into the DC before deleting the GDIs (ie return the created MemDC back to the default state)


    Generally complex TABs are done by creating an individual dialog for each TAB and positioned over the TAB client area.
    Otherwise an array of DCs is used, or just a single DC that is redrawn based on the TAB selected.
    In this case it is hard to determine the 'correct' approach (as I lack the details of the other TAbs).

    As a pattern for single DCs;

    WM_CREATE / WM_INITDIALOG
    Create MemDC
    Create the MemDC bitmap to the max size (ie screen res or app client area if app not resizeable).
    This negates the need to resize the MemDC if the app changes size.

    WM_SIZE
    Redraw MemDC to new size / scale (StretchBit() is the easiest but will not work well for line drawings)

    WM_PAINT
    BitBlt() from MemDC to PAINTSTRUCT DC using PAINTSTRUCT rect

    WM_CLOSE / DESTROY
    Clean up GDI (put default GDI objects back into the MemDC, then delete the created bitmap, DC, etc).

    TCN_SELCHANGE
    Redraw MemDC based on TAB selected
    Generate a paint msg

    If you want to do mouse drawing I am sure I have posted code previously (in the last 10 years...)
    "Man alone suffers so excruciatingly in the world that he was compelled to invent laughter."
    Friedrich Nietzsche

    "I spent a lot of my money on booze, birds and fast cars......the rest I squandered."
    George Best

    "If you are going through hell....keep going."
    Winston Churchill

  4. #4
    Registered User
    Join Date
    Feb 2013
    Posts
    10
    Hi,
    Thanks for the replies.
    I can't seem to figure out how to repaint just certain areas of the Tabbed window. Invalidating anything but a window handle results in an error (I was trying to invalidate just the RECT which was being drawn, relating to Novacaine's point #3). But any time I use hTabWnd in the BeginPaint/EndPaint functions it erases the tabs, though it does successfully draw things. This erasing of the tabs is independent of anything I do with the paintstruct, in fact the tabs are erased even if no paintstruct is used.

    On a positive note, painting to the parent hwnd doesn't erase the tabs but predictably doesn't paint anything to hTabWnd.

    It seems logical that I would be painting to hTabWnd rather than hwnd, but this invariably winds up erasing the tabs. I was wondering if there was a way to tell windows NOT to paint the top portion of the tabbed window?

  5. #5
    train spotter
    Join Date
    Aug 2001
    Location
    near a computer
    Posts
    3,868
    >>I can't seem to figure out how to repaint just certain areas of the Tabbed window.

    To clairfy; I ment in the WM_PAINT be sure to use the RECT from the PAINTSTRUCT in your BItBlt(). Do not always use the entire client area / size of the TAB in this BitBlt().

    Code:
    CASE WM_PAINT:
    PAINTSTRUCT ps;
    BeginPaint(hwnd, &ps);
    BitBlt(	ps.hdc, 	
    	ps.rcPaint.left,
    	ps.rcPaint.top,
    	ps.rcPaint.right-ps.rcPaint.left,
    	ps.rcPaint.bottom-ps.rcPaint.top,
    	memDC,
    	ps.rcPaint.left,
    	ps.rcPaint.top,
    	SCRCOPY);
    EndPaint(hwnd,&ps);
    break;
    You might also need some offset to draw on just the TAB, if it does not cover the entire main windows client area.

    >>Invalidating anything but a window handle results in an error

    Something like (not tested, written from memory...)
    Code:
    //some drawing changes the contents of memDC
    
    //find the offset between the TAB and its parent's client
    
    //if the TAB has width 200 and height 200
    //get the TAB client area, top left point will be 0,0 and bottom right will be 200,200
    GetClientRect(hTabWnd,&TABClient);
    
    //convert from coods relative to the TAB control -> coords relative to the main windows 
    //ie if the top left of the TAB controls client area was at 100,100 on the main window
    //we would get top left point will be 100,100 and bottom right will be 300,300 (as 200 wide and high)
    
    MapWindowPoints(hTABWnd, hwnd, &TABClient, (sizeof(RECT)/sizeof(POINT)); //or just use 2 instead of (sizeof(RECT)/sizeof(POINT)
    
    //call for a paint the just the TAB control's client area on the main window
    InvalidateRect(hwnd,&TABClient,FALSE);//use FALSE (not TRUE or 1) so you do not redraw the background
    UpdateWindow(hwnd); //bypass OS msg queue and post to windows callback
    EDIT: Make sure you check that I have the HWNDs the right way round and you are converting from the TAB control to the main window (not visa versa).
    Last edited by novacain; 03-14-2013 at 01:36 AM.
    "Man alone suffers so excruciatingly in the world that he was compelled to invent laughter."
    Friedrich Nietzsche

    "I spent a lot of my money on booze, birds and fast cars......the rest I squandered."
    George Best

    "If you are going through hell....keep going."
    Winston Churchill

  6. #6
    train spotter
    Join Date
    Aug 2001
    Location
    near a computer
    Posts
    3,868
    Also...

    When debugging these types of things I used to colour the TABs client area and get the colour to change for each TAB index.

    Something like (not compiled, written from memory)....

    Code:
    LRESULT CALLBACK WindowProcedure (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
    {
        NMHDR *nmptr;
        
        HDC hdc;
        char str[80];
        TCITEM tci;
        
        //change to static
        static HDC memdc = null;
        static int tabnumber=0;//set to zero to start with
        static RECT WinDim ={0};
        //brush array
        static HBRUSH brushArray[3] = {0};
        //default bitmap from the memDC
        static HBITMAP hOrigBMP = null;
    
        switch (message)                 
        {
           case WM_CREATE:
              maxX=GetSystemMetrics(SM_CXSCREEN);
              maxY=GetSystemMetrics(SM_CYSCREEN);
    
             GetClientRect(hwnd,&WinDim);
             
             hTabWnd= CreateWindow(
                 WC_TABCONTROL,
                 "",
                 WS_VISIBLE|WS_TABSTOP|WS_CHILD,0,0,WinDim.right,WinDim.bottom,
                 hwnd,
                 NULL,
                 hInst,
                 NULL
                );
              tci.mask=TCIF_TEXT;
              tci.iImage=-1;
              tci.pszText="One";
              TabCtrl_InsertItem(hTabWnd,0,&tci);
              tci.pszText="Two";
              TabCtrl_InsertItem(hTabWnd,1,&tci);
              tci.pszText="Three";
              TabCtrl_InsertItem(hTabWnd,2,&tci);
    
              //create memDC
              //get the main sceen DC
              hdc = GetDC(NULL);
              memdc=CreateCompatibleDC(hdc);
              hbit=CreateCompatibleBitmap(hdc,maxX,maxY);
              hOrigBMP = SelectObject(memdc, hbit);
              //release main screen DC
              ReleaseDC(hdc);
    
              //create brush array
              brushArray[0] = CreateSolidBrush(RGB(255,0,0));//red
              brushArray[1] = CreateSolidBrush(RGB(0,255,0));//green
              brushArray[2] = CreateSolidBrush(RGB(0,0,255));//blue  
       
              //fill the memDC with TAB index 1 colour (RED)
              //set our memDC rect
              SetRect(&WinDim, 0,0,maxX,maxY);
              FillRect(memDC,&WinDim, brushArray[tabnumber]);
              //call a paint
              GetClientRect(hTABWnd,&WinDim);
              MapWindowPoints(hTABWnd, hwnd, &WinDim, (sizeof(RECT)/sizeof(POINT)); 
    
              //generate paint msg
              InvalidateRect(hwnd,&WinDim,FALSE);//use FALSE (not TRUE or 1) so you do not redraw the background
              UpdateWindow(hwnd); //bypass OS msg queue and post to windows callback    
    
            break;
    
            case WM_NOTIFY:
              nmptr=(LPNMHDR) lParam;
              if(nmptr->code==TCN_SELCHANGE)
              {
                 tabnumber=TabCtrl_GetCurSel((HWND)nmptr->hwndFrom);
                 //fill rect with new colour
                 //note as the WinDim RECT is static we do not need to change it unless we get a WM_SIZE
                 FillRect(memDC,&WinDim, brushArray[tabnumber]);
                 //generate a paint msg
    	     InvalidateRect(hwnd,&WinDim,FALSE);//use FALSE (not TRUE or 1) so you do not redraw the background
                 UpdateWindow(hwnd); //bypass OS msg queue and post to windows callback  
              }
              break;
            case WM_DESTROY:
    
                //put the DC back to default and delete
                SelectObject(memdc, hOrigBMP);
                DeleteObject(hbit);
                DeleteDC(memdc);
      
                //clean up the brush array
                DeleteObject(brushArray[0]);
                DeleteObject(brushArray[1]);
                DeleteObject(brushArray[2]);
    
                PostQuitMessage (0);
                break;
            case WM_PAINT:
                {
                PAINTSTRUCT ps;
                BeginPaint(hwnd, &ps);
                BitBlt(	ps.hdc, 	
                	ps.rcPaint.left,
                	ps.rcPaint.top,
                	ps.rcPaint.right-ps.rcPaint.left,
                	ps.rcPaint.bottom-ps.rcPaint.top,
                	memDC,
                	ps.rcPaint.left,
                	ps.rcPaint.top,
                	SCRCOPY);
                EndPaint(hwnd,&ps);
                break;
                }
            }
            default:                     
                return DefWindowProc (hwnd, message, wParam, lParam);
        }
    
        return 0;
    }
    "Man alone suffers so excruciatingly in the world that he was compelled to invent laughter."
    Friedrich Nietzsche

    "I spent a lot of my money on booze, birds and fast cars......the rest I squandered."
    George Best

    "If you are going through hell....keep going."
    Winston Churchill

  7. #7
    Registered User
    Join Date
    Feb 2013
    Posts
    10
    Thanks Novacain for your detailed response! I'm currently working on some of your solutions.

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. How to fit to client area (MFC)
    By sorrowful in forum Windows Programming
    Replies: 1
    Last Post: 04-23-2012, 02:31 AM
  2. Drawing two or more rectangles on client area.. HELP!
    By csonx_p in forum Windows Programming
    Replies: 2
    Last Post: 05-12-2008, 01:43 AM
  3. drawing outside client area
    By h_howee in forum Windows Programming
    Replies: 8
    Last Post: 03-28-2007, 07:40 AM
  4. Problems Drawing OpenGL to the Client Area
    By Niytaan in forum Windows Programming
    Replies: 3
    Last Post: 10-27-2002, 07:15 PM
  5. Clearing the client area
    By Isometric in forum Windows Programming
    Replies: 15
    Last Post: 01-29-2002, 10:07 PM

Tags for this Thread