Can't seem to make double-buffered painting with multiple monitors
CodeGuru Home VC++ / MFC / C++ .NET / C# Visual Basic VB Forums Developer.com
Results 1 to 13 of 13

Thread: Can't seem to make double-buffered painting with multiple monitors

  1. #1
    Join Date
    Feb 2009
    Location
    Portland, OR
    Posts
    1,488

    Can't seem to make double-buffered painting with multiple monitors

    I'm trying to remake my Windows screensaver written with C++ and WinAPIs to work on multiple monitors. I found this article that gives the basics. But when I implement it in my own code, I get a weird result. Take a look at this code:

    Code:
    case WM_PAINT:
    {
        PAINTSTRUCT ps = {0};
        HDC hdcE = BeginPaint(hWnd, &ps );
    
        EnumDisplayMonitors(hdcE, NULL, MyPaintEnumProc, 0);
    
        EndPaint(hWnd, &ps);
    }
    break;
    
    BOOL CALLBACK MyPaintEnumProc(
          HMONITOR hMonitor,  // handle to display monitor
          HDC hdc1,     // handle to monitor DC
          LPRECT lprcMonitor, // monitor intersection rectangle
          LPARAM data       // data
          )
    {
    	MONITORINFO mi = {0};
    	mi.cbSize = sizeof(mi);
    	if(GetMonitorInfo(hMonitor, &mi))
    	{
    		//Is it a primary monitor?
    		BOOL bPrimary = mi.dwFlags & MONITORINFOF_PRIMARY;
    
    		DoDrawing(bPrimary, hdc1, &mi.rcMonitor);
    	}
    
    	return 1;
    }
    
    void DoDrawing(BOOL bPrimaryMonitor, HDC hDC, RECT* pRcMonitor)
    {
    //#define DIRECT_PAINT			//Comment out for double-buffering
    
    	int nMonitorW = abs(pRcMonitor->right - pRcMonitor->left);
    	int nMonitorH = abs(pRcMonitor->bottom - pRcMonitor->top);
    
    	HDC hMemDC = ::CreateCompatibleDC(hDC);
    	if(hMemDC)
    	{
    		HBITMAP hMemBmp = ::CreateCompatibleBitmap(hDC, nMonitorW, nMonitorH);
    		if(hMemBmp)
    		{
    			HBITMAP hOldBmp = (HBITMAP)SelectObject(hMemDC, hMemBmp);
    
    			COLORREF clr, clrBorder;
    			if(bPrimaryMonitor)
    			{
    				clr = RGB(0, 128, 0);			//Green
    				clrBorder = RGB(255, 0, 0);
    			}
    			else
    			{
    				clr = RGB(128, 0, 0);			//Red
    				clrBorder = RGB(0, 255, 0);
    
    			}
    
    			RECT rcRect;
    #ifndef DIRECT_PAINT
    			//With double-buffering
    			rcRect.left = 0;
    			rcRect.top = 0;
    			rcRect.right = nMonitorW;
    			rcRect.bottom = nMonitorH;
    #else
    			rcRect = *pRcMonitor;
    #endif
    
    			HBRUSH hBrush = ::CreateSolidBrush(clr);
    
    #ifndef DIRECT_PAINT
    			//With double-buffering
    			::FillRect(hMemDC, &rcRect, hBrush);
    #else
    			::FillRect(hDC, &rcRect, hBrush);
    #endif
    
    
    #ifndef DIRECT_PAINT
    			//With double-buffering
    			::BitBlt(hDC, pRcMonitor->left, pRcMonitor->top, nMonitorW, nMonitorH, hMemDC, 0, 0, SRCCOPY);
    #endif
    
    //Debugging output
    CString _s;
    _s.Format(_T("%s\n")
    		  _T("%s\n")
    		  _T("hDC=0x%X\n")
    		  _T("hMemDC=0x%X\n")
    		_T("RcMonitor: L=%d, T=%d, R=%d, B=%d")
    		  , 
    		  bPrimaryMonitor ? _T("Primary") : _T("Secondary"),
    #ifndef DIRECT_PAINT
    		  _T("Double-buffering"),
    #else
    		  _T("Direct paint"),
    #endif
    		  hDC,
    		  hMemDC,
    		  pRcMonitor->left,
    		  pRcMonitor->top,
    		  pRcMonitor->right,
    		  pRcMonitor->bottom);
    ::DrawText(hDC, _s, _s.GetLength(), pRcMonitor, DT_NOCLIP | DT_NOPREFIX);
    
    
    			SelectObject(hMemDC, hOldBmp);
    			::DeleteObject(hMemBmp);
    		}
    
    		::DeleteDC(hMemDC);
    	}
    
    }
    Painting always works on a primary monitor. But when I paint to the secondary monitor, I can only paint directly to its DC. When I use double-buffering technique (with DIRECT_PAINT pre-processor directive commented out) I only get a black screen on a secondary monitor when it should've been red.

    I'm attaching two screenshots here.

    First one with direct painting that works:
    Name:  screenshot_direct_paint.png
Views: 147
Size:  8.9 KB

    and then the one that doesn't, with double-buffering technique:
    Name:  screenshot_double_buffering.png
Views: 132
Size:  9.2 KB

    Any ideas what am I doing wrong here?

  2. #2
    Join Date
    Oct 2006
    Location
    Sweden
    Posts
    3,632

    Re: Can't seem to make double-buffered painting with multiple monitors

    I haven't done any Windows programming in a long time so my memory isn't the best these days...
    Anyway, BitBlt uses logical coordinates so doesn't that mean that you have to set up mapmode and so on before calling it?
    Debugging is twice as hard as writing the code in the first place.
    Therefore, if you write the code as cleverly as possible, you are, by
    definition, not smart enough to debug it.
    - Brian W. Kernighan

    To enhance your chance's of getting an answer be sure to read
    http://www.codeguru.com/forum/announ...nouncementid=6
    and http://www.codeguru.com/forum/showthread.php?t=366302 before posting

    Refresh your memory on formatting tags here
    http://www.codeguru.com/forum/misc.php?do=bbcode

    Get your free MS compiler here
    http://www.microsoft.com/visualstudio/eng/downloads

  3. #3
    Join Date
    Feb 2009
    Location
    Portland, OR
    Posts
    1,488

    Re: Can't seem to make double-buffered painting with multiple monitors

    Why would it work then without a double buffer?

  4. #4
    Join Date
    Nov 2000
    Location
    Voronezh, Russia
    Posts
    5,923

    Re: Can't seem to make double-buffered painting with multiple monitors

    Code:
    #ifndef DIRECT_PAINT
    			//With double-buffering
    			::FillRect(hMemDC, &rcRect, hBrush);
    #else
    			::FillRect(hDC, &rcRect, hBrush);
    #endif
    
    
    #ifndef DIRECT_PAINT
    			//With double-buffering
    			::BitBlt(hDC, pRcMonitor->left, pRcMonitor->top, nMonitorW, nMonitorH, hMemDC, 0, 0, SRCCOPY);
    #endif
    These highlighted must just match in their top left, I believe. You seem just blitting from non-painted area.

    Code:
    #ifndef DIRECT_PAINT
    			//With double-buffering
    			::BitBlt(hDC, pRcMonitor->left, pRcMonitor->top, nMonitorW, nMonitorH, hMemDC, rcRect.left, rcRect.top, SRCCOPY);
    #endif
    Last edited by Igor Vartanov; September 20th, 2012 at 08:33 AM.
    Best regards,
    Igor

  5. #5
    Join Date
    Oct 2006
    Location
    Sweden
    Posts
    3,632

    Re: Can't seem to make double-buffered painting with multiple monitors

    Quote Originally Posted by ahmd View Post
    Why would it work then without a double buffer?
    Without it you don't call BitBlt to finalize the painting
    Debugging is twice as hard as writing the code in the first place.
    Therefore, if you write the code as cleverly as possible, you are, by
    definition, not smart enough to debug it.
    - Brian W. Kernighan

    To enhance your chance's of getting an answer be sure to read
    http://www.codeguru.com/forum/announ...nouncementid=6
    and http://www.codeguru.com/forum/showthread.php?t=366302 before posting

    Refresh your memory on formatting tags here
    http://www.codeguru.com/forum/misc.php?do=bbcode

    Get your free MS compiler here
    http://www.microsoft.com/visualstudio/eng/downloads

  6. #6
    Join Date
    Feb 2009
    Location
    Portland, OR
    Posts
    1,488

    Re: Can't seem to make double-buffered painting with multiple monitors

    Quote Originally Posted by Igor Vartanov View Post
    [code]
    These highlighted must just match in their top left
    They do match. If you check above the left-top of RECT is set to 0,0.

    As for setting mapmode, I'm not really sure what you mean. Can you give a code example of what should I add? It might be it, because I don't think there's anything else I can do with this sample. It seems right.

  7. #7
    Join Date
    Oct 2006
    Location
    Sweden
    Posts
    3,632

    Re: Can't seem to make double-buffered painting with multiple monitors

    SetMapMode sets how to translate between logical and device coordinates (SetWindowOrg and SetViewPortOrg might also be good to checkup).
    As I said it's been a long time since I did any Windows programming (except console quickies) but I did browse MSDN a bit and it seems like what you're doing should work. As far as I remember though this type of code easily ended up being a pain in the ... as suddenly SetWindowOrg, SetViewportOrg and SetMapMode had to be involved.

    Try calling SetMapMode http://msdn.microsoft.com/en-us/libr...(v=vs.85).aspx with MM_TEXT before calling BitBlt. Also try removing the text from your output. It might a little red dot somewhere indicating that pixel scaling occur.
    Debugging is twice as hard as writing the code in the first place.
    Therefore, if you write the code as cleverly as possible, you are, by
    definition, not smart enough to debug it.
    - Brian W. Kernighan

    To enhance your chance's of getting an answer be sure to read
    http://www.codeguru.com/forum/announ...nouncementid=6
    and http://www.codeguru.com/forum/showthread.php?t=366302 before posting

    Refresh your memory on formatting tags here
    http://www.codeguru.com/forum/misc.php?do=bbcode

    Get your free MS compiler here
    http://www.microsoft.com/visualstudio/eng/downloads

  8. #8
    Join Date
    Nov 2000
    Location
    Voronezh, Russia
    Posts
    5,923

    Re: Can't seem to make double-buffered painting with multiple monitors

    Quote Originally Posted by ahmd View Post
    They do match. If you check above the left-top of RECT is set to 0,0.
    Oh sorry, you're right, my bad.

    As for setting mapmode, I'm not really sure what you mean. Can you give a code example of what should I add? It might be it, because I don't think there's anything else I can do with this sample. It seems right.
    The map mode by default is MM_TEXT. Considering that BitBlt works fine for your primary monitor, the problem is somewhere else. Need to play with your code a bit. It's a pity you did not provide a compilable project.
    Last edited by Igor Vartanov; September 21st, 2012 at 01:25 AM.
    Best regards,
    Igor

  9. #9
    Join Date
    Oct 2002
    Location
    Timisoara, Romania
    Posts
    14,360

    Re: Can't seem to make double-buffered painting with multiple monitors

    Replace the code for WM_PAINT

    Code:
    case WM_PAINT:
       hdc = BeginPaint(hWnd, &ps);
       EnumDisplayMonitors(hdc, NULL, MyPaintEnumProc, 0);
       EndPaint(hWnd, &ps);
    with

    Code:
    case WM_PAINT:
       hdc = GetDC(NULL);
       EnumDisplayMonitors(hdc, NULL, MyPaintEnumProc, 0);
       ReleaseDC(NULL, hdc);
    And it will work.
    Marius Bancila
    Home Page
    My CodeGuru articles

    I do not offer technical support via PM or e-mail. Please use vbBulletin codes.

  10. #10
    Join Date
    Oct 2002
    Location
    Timisoara, Romania
    Posts
    14,360

    Re: Can't seem to make double-buffered painting with multiple monitors

    And for full double buffering (with texts too) you need this (though I'm pretty sure you know that):
    Code:
    void DoDrawing(BOOL bPrimaryMonitor, HDC hDC, RECT* pRcMonitor)
    {
    //#define DIRECT_PAINT			//Comment out for double-buffering
    
       int nMonitorW = abs(pRcMonitor->right - pRcMonitor->left);
       int nMonitorH = abs(pRcMonitor->bottom - pRcMonitor->top);
    
       HDC hMemDC = ::CreateCompatibleDC(hDC);
       if(hMemDC)
       {
          HBITMAP hMemBmp = ::CreateCompatibleBitmap(hDC, nMonitorW, nMonitorH);
          if(hMemBmp)
          {
             HBITMAP hOldBmp = (HBITMAP)SelectObject(hMemDC, hMemBmp);
    
             COLORREF clr, clrBorder;
             if(bPrimaryMonitor)
             {
                clr = RGB(0, 128, 0);			//Green
                clrBorder = RGB(255, 0, 0);
             }
             else
             {
                clr = RGB(128, 0, 0);			//Red
                clrBorder = RGB(0, 255, 0);
             }
    
             RECT rcRect;
    #ifndef DIRECT_PAINT
             //With double-buffering
             rcRect.left = 0;
             rcRect.top = 0;
             rcRect.right = nMonitorW;
             rcRect.bottom = nMonitorH;
    #else
             rcRect = *pRcMonitor;
    #endif
    
             HBRUSH hBrush = ::CreateSolidBrush(clr);
    
    #ifndef DIRECT_PAINT
             //With double-buffering
             ::FillRect(hMemDC, &rcRect, hBrush);
    #else
             ::FillRect(hDC, &rcRect, hBrush);
    #endif
    
             //Debugging output
             TCHAR _s[256] = {0};
             _stprintf_s(
                _s, 
                255, 
                _T("%s\n")
                _T("%s\n")
                _T("hDC=0x%X\n")
                _T("hMemDC=0x%X\n")
                _T("RcMonitor: L=%d, T=%d, R=%d, B=%d")
                , 
                bPrimaryMonitor ? _T("Primary") : _T("Secondary"),
    #ifndef DIRECT_PAINT
                _T("Double-buffering"),
    #else
                _T("Direct paint"),
    #endif
                hDC,
                hMemDC,
                pRcMonitor->left,
                pRcMonitor->top,
                pRcMonitor->right,
                pRcMonitor->bottom);
    
    #ifndef DIRECT_PAINT
             ::DrawText(hMemDC, _s, _tcslen(_s), &rcRect, DT_NOCLIP | DT_NOPREFIX);
    #else
             ::DrawText(hDC, _s, _tcslen(_s), &rcRect, DT_NOCLIP | DT_NOPREFIX);
    #endif
    
    #ifndef DIRECT_PAINT
             //With double-buffering
             ::BitBlt(hDC, pRcMonitor->left, pRcMonitor->top, nMonitorW, nMonitorH, hMemDC, 0, 0, SRCCOPY);
    #endif
    
             SelectObject(hMemDC, hOldBmp);
             ::DeleteObject(hMemBmp);
          }
    
          ::DeleteDC(hMemDC);
       }
    }
    Marius Bancila
    Home Page
    My CodeGuru articles

    I do not offer technical support via PM or e-mail. Please use vbBulletin codes.

  11. #11
    Join Date
    Feb 2009
    Location
    Portland, OR
    Posts
    1,488

    Re: Can't seem to make double-buffered painting with multiple monitors

    Igor, sorry, I wanted to make a sample project but it seems to be quite a pain in the butt with screensavers.

    cilu, much appreciated! There's no way in heck I would think of doing that. Any idea how exactly it makes it work (by doing it completely opposite to what I've been taught to do in WM_PAINT)?

  12. #12
    Join Date
    Oct 2002
    Location
    Timisoara, Romania
    Posts
    14,360

    Re: Can't seem to make double-buffered painting with multiple monitors

    I guess it's because BeginPaint() clips what's outside the update region, while GetDC(NULL) returns the DC of the entire screen. In your case the update region is not the entire screen.
    Marius Bancila
    Home Page
    My CodeGuru articles

    I do not offer technical support via PM or e-mail. Please use vbBulletin codes.

  13. #13
    Join Date
    Feb 2009
    Location
    Portland, OR
    Posts
    1,488

    Re: Can't seem to make double-buffered painting with multiple monitors

    One follow-up question. While trying to find a solution I did the following: I started using the initial virtual screen DC (received from WM_PAINT) to render each monitor. In this case I needed to know the size of the monitor (for which I used mi.rcMonitor) and also the offset from the virtual screen's anchor point, which I was able to obtain by calling GetDCOrgEx(hMonitorDC, &pnt), were hMonitorDC is the DC passed into the MyPaintEnumProc() enum proc. I illustrated it with a diagram attached.

    The question I have, even though this approach (with using only one DC for the whole virtual screen) seems to work as well, am I doing something that shouldn't be done?
    Attached Images Attached Images  

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •  


Azure Activities Information Page

Windows Mobile Development Center


Click Here to Expand Forum to Full Width

This is a CodeGuru survey question.


Featured


HTML5 Development Center