BeginPaint/EndPaint or GetDC/ReleaseDC?

هذه المقالة متوفرة أيضا باللغة العربية، اقرأها هنا.

Which is better, to use BeginPaint/EndPaint, or to use GetDC/ReleaseDC?

Actually, it depends! If you are handling WM_PAINT, you should use BeginPaint/EndPaint. Otherwise, you should use GetDC/ReleaseDC.

You already know that Windows sends WM_PAINT to your message queue as soon as a new area of the window’s client area becomes invalidated.

If Windows finds an invalidated area, it sets a flag in the message pump indicating that a new WM_PAINT is waiting for processing. If no messages are waiting in the queue, it sends the WM_PAINT to the window procedure.

An area of the client area of the window becomes invalidated in many ways. For example, when a portion of the window covered by another window, Windows combines the area covered by the other window with the currently invalidated area of the window. In addition, you can €œinvalidate€ an area of the window using functions like InvalidateRect (to invalidate a rectangular area.) Those functions add the area specified to the currently invalidated area (i.e. combine the new area with the currently invalidated area of the window.)

Remember that, Windows continues sending WM_PAINT messages to your message queue as long as there’s an invalidated area. Therefore, you should validate the client area before leaving the WM_PAINT handler block. That’s why it is recommended using BeginPaint/EndPaint in WM_PAINT message handler because EndPaint does validate the entire client area of the window.

The following is a pseudo-code for EndPaint:

BOOL EndPaint(...)
{
	. . .

	validate client area
	e.g. call ValidateRect()

	release the DC

	do the necessary finalization
	. . .
}

Therefore, using GetDC/ReleaseDC in WM_PAINT would clog the message pump with a sequence of WM_PAINT messages that would divert your application from continuing its work, unless you validate the client area before jumping out of WM_PAINT handler.

On the other hand, using BeginPaint/EndPaint outside the WM_PAINT handler would validates the client area each time you call EndPaint. And that would prevent WM_PAINT from arriving to your message queue.

Another interesting point to consider is the following block of code inside the window procedure:

	switch (uMsg)
	{
		. . .

		case WM_PAINT:

			return 0;

		. . .
	}

Why the previous code is considered wrong? Yes, you are right. It leaves the WM_PAINT with neither validating the client area nor passing the message to the default window procedure.

The default window procedure actually did nothing interesting inside the WM_PAINT. However, it is required to pass the WM_PAINT to the default window procedure if you are not going to handle WM_PAINT or you’re not validating the client area inside the WM_PAINT handler. That’s because Windows simply calls BeginPaint and EndPaint in pair. Thus, validates the client area.

		case WM_PAINT:
			BeginPaint(hWnd, &ps);

			EndPaint(hWnd, &ps);
			return 0;

Thus, you should use BeginPaint/EndPaint in WM_PAINT only and GetDC/ReleaseDC in all other places in your code.

Converting Colors to Gray Shades

هذه المقالة متوفرة أيضا باللغة العربية، اقرأها هنا.

Contents

Contents of this article:

  • Contents
  • Introduction
  • Colors in Grayscale
  • Detecting Print Preview Mode
  • Detecting Black-and-White Printers
  • Mixing all Together

Introduction

This article discusses how you can display the page in print preview as grayscale if the printer is black-and-white. It discusses first how you can convert colors to grayscale. After that is discusses how to detect whether you are in print preview or not and whether the current printer is color or black-and-white printer. Let’s go€¦

Colors in Grayscale

If your application offers printing capability to the user, it should be aware of whether the user has a black-and-white or color printer while previewing the page. Of course, the user won’t be happy at all if he previewed his page in full-colors and printed it in black-and-white.

To solve this dilemma, you should render your print preview in grayscale if the user has a black-and-white printer and in full-colors if the user has a color printer.

The formula that converts a color to gray shade is very easy:

R/G/B = (red * 0.30) + (green * 0.59) + (blue * 0.11)

Set all the red, green, and blue to the sum of 30% of the red value, 59% of the green, and 11% of the blue.

For example, we can convert the color Magenta (0xFF, 0x00, 0xFF) to grayscale using the following steps:

-> 255 * 0.30 = 76
-> 0 * 0.59 = 0
-> 255 * 0.11 = 28
-> 76 + 0 + 28 = 104
-> R/G/B = 76

Thus, Magenta in grayscale equals to the RGB values 76, 76, 76.

The following function converts the color to grayscale:

COLORREF GetGrayscale(COLORREF cr)
{
	BYTE byColor;

	byColor =
		( GetRValue(cr) * 0.30 ) +
		( GetGValue(cr) * 0.59 ) +
		( GetBValue(cr) * 0.11 );

	return
		RGB( byColor, byColor, byColor );
}

Detecting Print Preview Mode

If you have a CPrintInfo you can detect whether you are “print-previewing” or printing by checking the m_bPreview flag. If you don’t have a CPrintInfo (i.e. you are in the context of the OnDraw function) you can detect print preview mode by comparing CDC’s m_hDC and m_hAttribDC members.

MFC does some magic using m_hDC and m_hAttribDC. It uses m_hDC for output, while it uses m_hAttribDC for queries about DC attributes. How this helps?

If you are printing to the screen or to the printer, both m_hDC and m_hAttribDC will refer to the same HDC that’s used for drawing and retrieving attributes. On the other hand, while in print preview, MFC sets m_hDC to the window DC and sets m_hAttribDC to the HDC of the current printer. The results are unimaginable. If you are drawing, the calls are carried out to the screen. If you are querying about attributes (i.e. calling GetDeviceCaps,) the calls are carried out to the printer.

Therefore, you can detect print preview mode using a single line of code:

BOOL bPreview = (pDC->m_hDC != pDC->m_hAttribDC);

Or you can use the following code if you have a CPrintInfo:

BOOL bPreview = pInfo->m_bPreview;

Detecting Black-and-White Printers

Another point of interest is detecting whether the current printer is monochrome (black-and-white) or color printer.

This can be done through the GetDeviceCaps function with the NUMCOLORS item specified. It returns the number of colors if the device has a color depth of 8 bits per pixel or less. It’s not limited to printer DCs only. It can be used with display DCs too.

The following code detects if the device is monochrome:

BOOL bMono = (pDC->GetDeviceCaps(NUMCOLORS) == 2);

Mixing all Together

We have seen how to convert the color to grayscale, how to detect print preview mode, and how to detect a black-and white printer. Now, let’s mix them all together.

In OnDraw and OnPrint, we can solve the dilemma of black-and-white print-previewing by a simple change in the code. The following code segment sets the color based on the type of printer (it works fine too even if we are painting to the screen.)

BOOL bMono =
	(pDC->GetDeviceCaps (NUMCOLORS) == 2) &&
	(pDC->m_hDC != pDC->m_hAttribDC);

CBrush brush	(bMono ?
	GetGrayscale(RGB (255, 0, 255)) :
	RGB (255, 0, 255));

bMono is set to TRUE only if we are in print preview mode and the current printer is black-and-white printer.