Why is DrawFocusRect affected by the Text color? - winapi

I was getting weird results with DrawFocusRect() in a TreeView custom draw routine. The outline was somewhat different, some were almost a solid line and others were dashed. I found out that it's the HDC SetTextColor() value that is affecting it. Even though the selection bars fill color is exactly the same, as I changed various text colors, indeed the drawn outline was different.
I ended up with setting the text color to match the fill color of the highlight bar which gives the same outline the default tree drawing routine gives.
Is this not documented anywhere? Is there even more to it?
Thanks?

DrawFocusRect draws the focus rectangle with a bitwise XOR operation. This is hinted at in the SDK documentation, under the "Remarks" section, where it says:
Because DrawFocusRect is an XOR function, calling it a second time with the same rectangle removes the rectangle from the screen.
This is the whole advantage of DrawFocusRect. It means that you (or the window manager) can draw the focus rectangle and erase it whenever needed without redrawing the entire underlying contents. (This was a big performance win in the era before double-buffering, gobs of memory, fast graphic cards, etc.) The focus rectangle is drawn wherever it needs to be drawn, then, when it needs to be erased, it is just drawn again on top of where it was drawn the first time. The only thing that you (or the window manager) need to keep track of is the coordinates of the rectangle.
You can achieve exactly the same effect as DrawFocusRect by using code like the following to draw an XOR rectangle manually:
void DrawXorRect(HDC hDC, const RECT* prc)
{
// (1)
static const WORD pattern[] = { 0x5555, 0xAAAA,
0x5555, 0xAAAA,
0x5555, 0xAAAA,
0x5555, 0xAAAA,
};
HBITMAP hbmpPattern = CreateBitmap(8, 8, 1, 1, pattern);
// (2)
HBRUSH hbrPattern = CreatePatternBrush(hbmpPattern);
// (3)
HBRUSH hbrOriginal = (HBRUSH)SelectObject(hDC, hbrPattern);
// (4)
UINT cx;
UINT cy;
SystemParametersInfo(SPI_GETFOCUSBORDERWIDTH , 0, &cx, 0);
SystemParametersInfo(SPI_GETFOCUSBORDERHEIGHT, 0, &cy, 0);
// (5)
PatBlt(hDC, prc->left , prc->top , prc->right - prc->left, cy , PATINVERT); // top
PatBlt(hDC, prc->left , prc->bottom - cy, prc->right - prc->left, cy , PATINVERT); // bottom
PatBlt(hDC, prc->left , prc->top + cy, cx , prc->bottom - prc->top - (cy * 2), PATINVERT); // left
PatBlt(hDC, prc->right - cx, prc->top + cy, cx , prc->bottom - prc->top - (cy * 2), PATINVERT); // right
// (6)
SelectObject(hDC, hbrOriginal);
DeleteObject(hbrPattern);
DeleteObject(hbmpPattern);
}
Error-checking has been elided for brevity, but this code does work, and it does mimic DrawFocusRect exactly. (Yes, I even tested it.)
Let's go through the code, step by step, to see what it does, how it works, and what that means:
First, it creates a monochrome 8×8 bitmap (CreateBitmap()) consisting of an alternating pattern of bits (pattern[]): on, off, on, off, etc. (If you don't know why, open up the Windows calculator, switch it to hex mode, and paste in the hex values that comprise the pattern. Look at the bits: see how they alternate between 0s and 1s? Now, imagine creating a bitmap from that.)
Then, it uses that bitmap to create a brush (CreatePatternBrush()).
Next, it selects the newly-created brush into the DC (SelectObject()). It saves the return value, which is the brush that was originally selected in the DC because it needs to restore that later.
With all the drawing objects created, it calls SystemParametersInfo() to retrieve the width (SPI_GETFOCUSBORDER_WIDTH) and height (SPI_GETFOCUSBORDERHEIGHT) of the focus rectangle.
Armed with all the objects and information needed to do the drawing, it blits each of the 4 sides of the rectangle using the PATINVERT raster operation. What is PATINVERT?
Combines the colors of the specified pattern with the colors of the destination rectangle by using the Boolean XOR operator.
Finally, it cleans up after itself by restoring the originally-selected brush and deleting the drawing objects that it created.
So, basically, the focus rectangle is drawn by XORing an alternating pattern of on-and-off pixels (a checkerboard pattern) with the contents of the device context (i.e., whatever is on the screen). That's what PATINVERT does. But wait—we know what the colors of the device context are, but what are the colors of the brush? Well, remember that the brush was created from a monochrome bitmap. The SDK documentation for CreateBitmap says:
If the bitmap is monochrome, zeros represent the foreground color and ones represent the background color for the destination device context.
Aha! So when the brush's bitmap pattern is 0, the brush's color is your device context's foreground color (SetTextColor); when the brush's bitmap pattern is 1, the brush's color is your device context's background color (SetBackColor). This is how the foreground and background color of the device context come into play. These colors are merged together, via an XOR operation, with the color of the original pixel in the device context.
When using DrawFocusRect, your DC's foreground and background colors should be black and white, respectively. This is the default foreground and background colors for a device context, so if you haven't called the SetTextColor or SetBackColor functions, these will be its colors. If you have called either of those functions, you either need to:
Save the value returned by each and restore it later, once you are finished drawing, but before you call DrawFocusRect (just as the code above did with the return value of the first call to SelectObject), or
Call the SaveDC function at the beginning of your drawing code to save its state (by pushing it onto a stack internally), and then call the RestoreDC function at the end of your drawing code to restore it to its original state.
When the DC's foreground and background colors are black and white, respectively, then the DrawFocusRect function works as it is expected to. This is what you need to do if you want to match how the window manager draws focus rectangles around controls. (And you do, obviously, want to do this!)
If, for some reason, you don't want to reset the DC's colors, then you won't be able to use DrawFocusRect. You'll need to take another tack where you control the colors yourself. For example, you could create a color (non-monochrome) bitmap, with its colors fixed as white and black, and then create a brush from that. Alternatively, you could consider drawing with a pen and the R2_NOT ROP code; e.g.:
void DrawXorRect2(HDC hDC, const RECT* prc)
{
LOGBRUSH lb;
lb.lbStyle = BS_SOLID;
lb.lbColor = RGB(0, 0, 0); // black
HPEN hpenNew = ExtCreatePen(PS_COSMETIC | PS_ALTERNATE, 1, &lb, 0, NULL);
HPEN hpenOld = (HPEN)SelectObject(hDC, hpenNew);
int ropOld = SetROP2(hDC, R2_NOT);
int modeOld = SetBkMode(hDC, TRANSPARENT);
HBRUSH hbrOriginal = (HBRUSH)SelectObject(hDC, GetStockObject(NULL_BRUSH));
Rectangle(hDC, prc->left, prc->top, prc->right, prc->bottom);
SelectObject(hDC, hbrOriginal);
SetBkMode(hDC, modeOld);
SetROP2(hDC, ropOld);
SelectObject(hDC, hpenOld);
DeleteObject(hpenNew);
}
I'm not sure this is actually much better, as it still requires that you set attributes of the DC—namely, the ROP and the background mode. If you're going to set and restore attributes of the DC, why not just set and restore the foreground and background colors? Plus, this pen-based implementation has the additional disadvantage of not respecting the user's preference for the thickness of the focus rectangle, which is an accessibility bug. Also, the pixel grid is slightly different here compared to the built-in DrawFocusRect, but that is, admittedly, an extremely minor drawback.
Another approach would be to create a PS_GEOMETRIC pen with a custom pattern (PS_USERSTYLE, to make it alternating), and setting the ROP style to R2_XORPEN/R2_NOTXORPEN:
void DrawXorRect3(HDC hDC, const RECT* prc)
{
UINT cx;
UINT cy;
SystemParametersInfo(SPI_GETFOCUSBORDERWIDTH , 0, &cx, 0);
SystemParametersInfo(SPI_GETFOCUSBORDERHEIGHT, 0, &cy, 0);
LOGBRUSH lb;
lb.lbStyle = BS_SOLID;
lb.lbColor = RGB(0, 0, 0);
static const DWORD pattern[] = { 0, 2 };
HPEN hpenWidth = ExtCreatePen(PS_GEOMETRIC | PS_USERSTYLE, cx, &lb, 2, pattern);
HPEN hpenHeight = ExtCreatePen(PS_GEOMETRIC | PS_USERSTYLE, cy, &lb, 2, pattern);
int ropOriginal = SetROP2(hDC, R2_NOTXORPEN);
HPEN hpenOriginal = (HPEN)SelectObject(hDC, hpenHeight);
MoveToEx(hDC, prc->left , prc->top , NULL); // \ top
LineTo (hDC, prc->right , prc->top ); // / edge
MoveToEx(hDC, prc->left + cx, prc->bottom - cy , NULL); // \ bottom
LineTo (hDC, prc->right - cx, prc->bottom - cy ); // / edge
SelectObject(hDC, hpenWidth);
MoveToEx(hDC, prc->left , prc->top + (cy * 2), NULL); // \ left
LineTo (hDC, prc->left , prc->bottom - cy ); // / edge
MoveToEx(hDC, prc->right - cx, prc->top + cy , NULL); // \ right
LineTo (hDC, prc->right - cx, prc->bottom ); // / edge
SelectObject(hDC, hpenOriginal);
SetROP2(hDC, ropOriginal);
DeleteObject(hpenHeight);
DeleteObject(hpenWidth);
}
This addresses the accessibility bug and makes the pixel pattern match that produced by DrawFocusRect, at the expense of creating an additional pen. It also, like the second attempt, works as expected regardless of what the DC's foreground or background colors are set to.
Note that all of these implementations of DrawXorRect become more efficient if you create the required drawing objects once and cache them, instead of creating and destroying them each time. This is surely a big part of the reason (along with convenience—who wants to write all this ugly code?) why the window manager provides the DrawFocusRect function for applications to use.

Related

How to determine horizontal and vertical extents in device units with SetViewportExtEx() and printer?

I am experimenting with using the Windows GDI API for printing and have been doing a few experiments to attempt to understand the translation and how window and viewport extents work.
Examples I have found are using GetDeviceCaps() to get the HORZRES and VERTRES dimensions (despite the known fact they can be unreliable and inaccurate) and then using these values with SetViewportExtEx() however they divide the values returned by GetDeviceCaps() by two.
Why is the cxpage and the cypage values halved and how can I predict the values to use and the effect on the printed output? Is this due to using MM_ISOTROPIC as the mapping mode?
Examples use something like the following code:
int cxpage = GetDeviceCaps (hDC, HORZRES);
int cypage = GetDeviceCaps (hDC, VERTRES);
SetMapMode (hDC, MM_ISOTROPIC);
SetWindowExtEx(hDC, 1500, 1500, NULL);
SetViewportExtEx(hDC, cxpage/2, cypage/2, NULL);
SetViewportOrgEx(hDC, 0, 0, NULL);
In my actual test program I have the following function to print a page when my main Windows message handler sees the message IDM_PRINT generated when the user selects Print from the File menu of the test application. The handler uses PrintDlg() to get a handle to a Device Context (hDC) then calls this function to exercise the printing.
int PrintMyPages (HDC hDC)
{
int cxpage = GetDeviceCaps (hDC, HORZRES);
int cypage = GetDeviceCaps (hDC, VERTRES);
// When MM_ISOTROPIC mode is set, an application must call the
// SetWindowExtEx function before it calls SetViewportExtEx. Note that
// for the MM_ISOTROPIC mode certain portions of a nonsquare screen may
// not be available for display because the logical units on both axes
// represent equal physical distances.
SetMapMode (hDC, MM_ISOTROPIC);
// Since mapping mode is MM_ISOTROPIC we need to specify the extents of the
// window and the viewport we are using to see the window in order to establish
// the proper translation between window and viewport coordinates.
SetWindowExtEx(hDC, 1500, 1500, NULL);
SetViewportExtEx(hDC, cxpage/2, cypage/2, NULL);
SetViewportOrgEx(hDC, 0, 0, NULL);
// figure out the page size in logical units for the loop that is printing
// out the pages of output. we must do this after setting up our window and
// viewport extents so Windows will calculate the DPtoLP() for the specified
// translation correctly.
RECT pageRect = {0};
pageRect.right = GetDeviceCaps (hDC, HORZRES);
pageRect.bottom = GetDeviceCaps (hDC, VERTRES);
DPtoLP(hDC, (LPPOINT)&pageRect, 2);
// create my font for drawing the text to be printed and select it into the DC for printing.
HFONT DisplayFont = CreateFont (166, 0, 0, 0, FW_DONTCARE, false, false, false, DEFAULT_CHARSET,
OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY,
DEFAULT_PITCH | FF_DONTCARE, _T("Arial Rounded MT Bold"));
HGDIOBJ hSave = SelectObject (hDC, DisplayFont);
POINT ptLine = {300, 200}; // our printer line cursor for where printing should start.
static DOCINFO di = { sizeof (DOCINFO), TEXT ("INVOICE TABLE : Printing...")};
StartDoc (hDC, &di);
StartPage (hDC);
for (int i = 1; i < 30; i++) {
TCHAR xBuff[256] = {0};
swprintf (xBuff, 255, _T("This is line %d of my text."), i);
TextOut (hDC, ptLine.x, ptLine.y, xBuff, _tcslen(xBuff));
// get the dimensions of the text string in logical units so we can bump cursor to next line.
SIZE lineSize = {0};
GetTextExtentPoint32(hDC, xBuff, _tcslen(xBuff), &lineSize);
ptLine.y += lineSize.cy; // bump the cursor down to the next line of the printer. X coordinate stays the same.
if (ptLine.y + lineSize.cy > pageRect.bottom) {
// reached the end of this page so lets start another.
EndPage (hDC);
StartPage (hDC);
ptLine.y = 200;
}
}
// end the final page and then end the document so that physical printing will start.
EndPage (hDC);
EndDoc (hDC);
// Release the font object that we no longer need.
SelectObject (hDC, hSave);
DeleteObject (DisplayFont);
return 1;
}
When I modify the call of SetViewportExtEx() from SetViewportExtEx(hDC, cxpage/2, cypage/2, NULL); (output on the right in the image below) to SetViewportExtEx(hDC, cxpage, cypage, NULL); (output on the left in the image below) the printed text seems almost double in height and width.
Additional Notes on Extents and Mapping Modes
Charles Petzold Programming Windows 5th Edition (Chapter 5 - Basic Drawing, page 180) writes:
The formulas also include two points that specify "extents": the point
(xWinExt, yWinExt) is the window extent in logical coordinates;
(xViewExt, yViewExt) is the viewpoort extent in device
coordinates. In most mapping modes, the extents are implied by the
mapping mode and cannot be changed. Each extent means nothing by
itself, but the ratio of the viewport extent to the window extent is a
scaling factor for converting logical units to device units.
For example, when you set the MM_LOENGLISH mapping mode, Windows sets
xViewExt to be a certain number of pixels and xWinExt to be the length in hundredths of an inch occupied by xViewExt pixels. The
ratio gives you pixels per hundredths of an inch. The scaling factors
are expressed as ratios of integers rather than floating point values
for performance reasons.
Petzold then goes on to discuss MM_ISOTROPIC and MM_ANISOTROPIC on page 187.
The two remaining mapping modes are named MM_ISOTROPIC and
MM_ANISOTROPIC. These are the only two mapping modes for which
Windows lets you change the viewport and window extents, which means
that you can change the scaling factor that Windows uses to translate
logical and device coordinates. The word isotropic means "equal in
all directions"; anisotropic is the opposite - "not equal." Like the
metric mapping modes shown earlier, MM_ISOTROPIC uses equally scaled
axes. Logical units on the x-axis have the same physical dimensions as
logical units on the y-axis. This helps when you need to create images
that retain the correct aspect ratio regardless of the aspect ratio of
the display device.
The difference between MM_ISOTROPIC and the metric mapping modes is
that with MM_ISOTROPIC you can control the physical size of the
logical unit. If you want, you can adjust the size of the logical unit
based on the client area. This lets you draw images that are always
contained within the client area, shrinking and expanding
appropriately. The two clock programs in Chapter 8 have isotropic
images. As you size the window, the clocks are resized appropriately.
A Windows program can handle the resizing of an image entirely through
adjusting the window and viewport extents. The program can then use
the same logical units in the drawing functions regardless of the size
of the window.
... why do so many examples use SetViewportExtEx(hDC, cxpage/2, cypage/2, NULL); where cxpage and cypage are GetDeviceCaps(hDC, HORZRES) and GetDeviceCaps(hDC, VERTRES) respectively[?]
I suspect MM_ISOTROPIC is often used for plotting graphs where the origin would be in the center of the page rather than the corner. If we took your code and tweaked it to move the origin to the center of the printable region, like this:
SetWindowExtEx(hDC, 1500, 1500, NULL);
SetViewportExtEx(hDC, cxpage/2, cypage/2, NULL);
SetViewportOrgEx(hDC, cxpage/2, cypage/2, NULL);
Then you could plot using logical coordinates that range from -1500 to +1500. (You might also want to flip the sign of one of the y-extents to get positive "up".)
For textual output, I don't see any advantage to halving the viewport extents and I would keep the origin in the upper left.

win32 drawing function plot with brush

I'm supposed to write an application using win32 API which draws a function f(x)= x^2 plot. To accomplish this I'm asked to use HBRUSH structure, but there seems to be no appropriate procedure in win32 API. There are tons of them mostly used to draw complete shapes.
Is there one I can use to draw my plot point by point?
Brush is intended to draw surfaces, rect areas, filling, etc; you really need pens
Try this:
HDC hdc = /* init this */;
HPEN pen = CreatePen(PS_SOLID, 0, RGB(0, 0, 0));
HGDIOBJ old_pen = SelectObject(hdc, pen);
// move to first poing in plot
MoveToEx(hdc, startingpoint_x, statingpoint_y, NULL);
// executes for each point in plot
LineTo(hdc, pointx, pointy);
// clean up
SelectObject(hdc, old_pen);
DeleteObject(pen);

Are the coordinates in WINDOWPOS on WM_WINDOWPOSCHANGED in parent coordinates or screen coordinates?

Quick and simple one this time. I have a subclassed tab control that handles WM_WINDOWPOSCHANGED to, when not SWP_NOSIZE, resize its content. It passes the WINDOWPOS lParam's cx and cy fields to TCM_ADJUSTRECT to get the content rect size.
I need to do this on command as well (after changing tabs, say). However, I can't just do a dummy resize to the same size; even with an explicit SetWindowPos(), real Windows seems to add SWP_NOSIZE itself if the size doesn't change. So I want to write the analogous code to my WM_WINDOWPOSCHANGED handler.
My question is: what coordinate system are the coordinates given to WM_WINDOWPOSCHANGED given in, parent coordinates or screen coordinates? GetWindowRect() returns screen coordinates, so I'd like to know if I need to convert the coordinates to get the same values that I would get in WM_WINDOWPOSCHANGED. The documentation for WM_WINDOWPOSCHANGED doesn't say; neither does the documentation for WINDOWPOS.
Thanks.
WINDOWPOS, GetWindowRect, GetCursorPos, etc. give screen coordinates. When you use SetWindowPos you have to supply coordinates in relation to parent. This is straight forward for main window and popup windows which use screen coordinates. For moving child windows, you can use ScreenToClient and ClientToScreen for conversion.
For example, this will find the coordinates of OK button in relation to top-left corner of dialog box:
RECT rcOK;
HWND hitem = ::GetDlgItem(m_hWnd, IDOK);
GetWndRect(rcOK, hitem, m_hWnd);
void GetWndRect(RECT &rect, HWND item, HWND parent)
{
::GetWindowRect(item, &rect);//screen coordinates of OK button
POINT offset{ 0 };
ClientToScreen(parent, &offset); //Top-left (0,0) of client area of dialog -> screen coordinates
rect.left -= offset.x;
rect.right -= offset.x;
rect.top -= offset.y;
rect.bottom -= offset.y;
//client coordinates of OK button in relation to Dialog's Top-Right
}
Now we can move up the OK button by 10px:
rc.top -= 10;
rc.bottom -= 10;
::SetWindowPos(hitem, 0, rc.left, rc.top, 0, 0, SWP_NOSIZE);

Flicker does not reduce after double buffering in MFC CPaintDC

I am trying to make an interactive graph using MFC where the y axis of a sample point in the graph can be changed using mouse click. I implemented double buffering using this tutorial
enter link description here. I should also point out that I need to change the origins of the viewport from time to time for my program. However, when I click on the graph for the sample point to be updated, I can still see it flicker. It's not an inconvenience, but I need to extend this graph to include lots of sample points and other features such as gridlines, axes, labels, boundary areas, etc and I am worried that the flickering might become a problem for me in the future as the size of this project grows. Implementing double buffering did not seem to make any changes to the output. Moreover, now that I have implemented double buffering, the program seems to stop in the middle of execution (when I am running it in Debug mode in Visual Studio) with this error:
Unhandled exception at 0xffffff3a in graph_on_dlgbox.exe: 0xC0000005: Access violation reading location 0xffffff3a.
I am still not sure what causes it to appear, but seems to happen if I start randomly clicking around the graph area rapidly. Since I have not seen this error (yet) in my code that does not use double buffering, I am assuming it has something to do with the double buffering code, but I am not sure.
Anyway I would like to tackle this one problem at a time, and the first problem is the flicker. Here is my code without double buffering:
void Cgraph_on_dlgboxDlg::OnPaint()
{
CPaintDC dc(this);
if (IsIconic())
{
// CPaintDC dc(this); // device context for painting
SendMessage(WM_ICONERASEBKGND, reinterpret_cast<WPARAM>(dc.GetSafeHdc()), 0);
// Center icon in client rectangle
int cxIcon = GetSystemMetrics(SM_CXICON);
int cyIcon = GetSystemMetrics(SM_CYICON);
CRect rect;
GetClientRect(&rect);
int x = (rect.Width() - cxIcon + 1) / 2;
int y = (rect.Height() - cyIcon + 1) / 2;
// Draw the icon
dc.DrawIcon(x, y, m_hIcon);
}
else
{
CDialogEx::OnPaint();
}
CPen pen;
COLORREF pencolour = RGB(0, 0, 0);
COLORREF brushcolour = RGB(0, 0, 255);
COLORREF graphColour = RGB(0, 0, 150);
// Draw boarder
pen.CreatePen(PS_SOLID, 3, pencolour);
// CBrush brush(HS_CROSS, brushcolour);
dc.SetBkMode(TRANSPARENT);
dc.SetMapMode(MM_TEXT);
dc.SetViewportOrg(theGraph.x1, theGraph.y1);
// Dc.SetViewportExt(theGraph.width, theGraph.height);
dc.SelectObject(&pen);
// dc.SelectObject(&brush);
// Draw graph boundary
CPoint point1(0,0);
point1.x = 0;
point1.y = 0;
CPoint point2(0,0);
point2.x = point1.x + theGraph.width;
point2.y = point1.y + theGraph.height;
dc.Rectangle(CRect(point1, point2));
pen.DeleteObject();
// Draw Horizontal at 0
pen.CreatePen(PS_SOLID, 1, pencolour);
dc.SelectObject(&pen);
dc.MoveTo(0, theGraph.height - ORG_DIST_FROM_BOTTOM);
dc.LineTo(theGraph.width, theGraph.height - ORG_DIST_FROM_BOTTOM);
pen.DeleteObject();
// Shift overall graph origin from top left corner to beginning of this horizontal line
dc.SetViewportOrg(theGraph.x1, theGraph.y1 + theGraph.height - ORG_DIST_FROM_BOTTOM); // dc.SetViewportOrg() always works relative to the clinet origin
// Draw graph line
pen.CreatePen(PS_SOLID, 2, graphColour);
dc.SelectObject(&pen);
for(int i = 0; i<NUM_OF_SECTIONS_IN_GRAPH; i++){
dc.MoveTo(graphSamplePoints[i].x, graphSamplePoints[i].y);
dc.LineTo(graphSamplePoints[i+1].x, graphSamplePoints[i+1].y);
}
// draw circles at graph sample points
for(int i = 0; i<NUM_OF_POINTS_IN_GRAPH; i++){
CIRCLE(dc, graphSamplePoints[i].x, graphSamplePoints[i].y, GRP_SMP_RAD);
}
}
and here is the modified version with double buffering:
void Cgraph_on_dlgboxDlg::OnPaint()
{
// /*****
CPaintDC dc_blt(this);
CDC dc;
CBitmap bmpDC;
// CRect rcClient;
// GetClientRect(rcClient);
if (IsIconic())
{
// CPaintDC dc(this); // device context for painting
SendMessage(WM_ICONERASEBKGND, reinterpret_cast<WPARAM>(dc.GetSafeHdc()), 0);
// Center icon in client rectangle
int cxIcon = GetSystemMetrics(SM_CXICON);
int cyIcon = GetSystemMetrics(SM_CYICON);
CRect rect;
GetClientRect(&rect);
int x = (rect.Width() - cxIcon + 1) / 2;
int y = (rect.Height() - cyIcon + 1) / 2;
// Draw the icon
dc.DrawIcon(x, y, m_hIcon);
}
else
{
CDialogEx::OnPaint();
}
dc.CreateCompatibleDC(&dc_blt);
// bmpDC.CreateCompatibleBitmap(&dc_blt, rcClient.Width(),rcClient.Height());
bmpDC.CreateCompatibleBitmap(&dc_blt, theGraph.width,theGraph.height );
dc.SelectObject(&bmpDC);
// ----------- After this point, do all operations considering (0,0) to be the origin of the bitmap
// consider bitmap coordinates a device coordinates for Viewport operations
CPen pen;
COLORREF pencolour = RGB(0, 0, 0);
COLORREF brushcolour = RGB(0, 0, 255);
COLORREF graphColour = RGB(0, 0, 150);
// Draw boarder
pen.CreatePen(PS_SOLID, 3, pencolour);
// CBrush brush(HS_CROSS, brushcolour);
dc.SetBkMode(TRANSPARENT);
dc.SetMapMode(MM_TEXT);
// dc.SetViewportOrg(theGraph.x1, theGraph.y1);
// dc.SetViewportExt(theGraph.width, theGraph.height);
dc.SelectObject(&pen);
// dc.SelectObject(&brush);
// Draw graph boundary
CPoint point1(0,0);
point1.x = 0;
point1.y = 0;
CPoint point2(0,0);
point2.x = point1.x + theGraph.width;
point2.y = point1.y + theGraph.height;
dc.Rectangle(CRect(point1, point2));
pen.DeleteObject();
// Draw Horizontal at 0
pen.CreatePen(PS_SOLID, 1, pencolour);
dc.SelectObject(&pen);
dc.MoveTo(0, theGraph.height - ORG_DIST_FROM_BOTTOM);
dc.LineTo(theGraph.width, theGraph.height - ORG_DIST_FROM_BOTTOM);
pen.DeleteObject();
// Shift overall graph origin from top left corner to beginning of this horizontal line
// dc.SetViewportOrg(theGraph.x1, theGraph.y1 + theGraph.height - ORG_DIST_FROM_BOTTOM); // dc.SetViewportOrg() always works relative to the client area origin
// New origin defined in terms of the Bitmap's origin
dc.SetViewportOrg(0, theGraph.height - ORG_DIST_FROM_BOTTOM);
// Draw graph line
pen.CreatePen(PS_SOLID, 2, graphColour);
dc.SelectObject(&pen);
for(int i = 0; i<NUM_OF_SECTIONS_IN_GRAPH; i++){
dc.MoveTo(graphSamplePoints[i].x, graphSamplePoints[i].y);
dc.LineTo(graphSamplePoints[i+1].x, graphSamplePoints[i+1].y);
}
// draw circles at graph sample points
for(int i = 0; i<NUM_OF_POINTS_IN_GRAPH; i++){
CIRCLE(dc, graphSamplePoints[i].x, graphSamplePoints[i].y, GRP_SMP_RAD);
}
dc.SetViewportOrg(0, 0);
// dc_blt.BitBlt(rcClient.left+100,rcClient.top+50,rcClient.Width(), rcClient.Height(), &dc, 0, 0, SRCCOPY);
// dc_blt.BitBlt(0,0,rcClient.Width(), rcClient.Height(), &dc, theGraph.x1, theGraph.y1, SRCCOPY);
dc_blt.BitBlt(theGraph.x1,theGraph.y1, theGraph.width, theGraph.height, &dc, 0, 0, SRCCOPY);
// --- Bring the bitmap to this particular location on screen specified by (theGraph.x1,theGraph.y1, theGraph.width, theGraph.height)
// dc_blt.BitBlt(0,0,theGraph.width, theGraph.height, &dc, 0, 0, SRCCOPY);
// dc_blt.BitBlt(theGraph.x1,theGraph.y1,theGraph.width, theGraph.height, &dc, 0, 0, SRCCOPY);
// *****/
m_bMyDraw = FALSE;
}
Here is a sample screenshot of the output:
The y axis values of the sample points on the graph can be changed by clicking, and the program redraws the graph after every click by calling InvalidateRect() with the area of the graph as the rectangle to be repainted.
The coordinates of the sample points are stored in a array of CPoint objects, and it's members are updated every time the graph is clicked at the appropriate area. The graph then repaints, due to the call to InvalidateRect(), but with a flicker; unless of course, the program crashes in debug mode with this error:
How do I remove the flickering?
---- UPDATE ----
BOOL Cgraph_on_dlgboxDlg::OnEraseBkgnd(CDC* pDC)
{
// TODO: Add your message handler code here and/or call default
if ( m_bMyDraw )
return TRUE;
else
return CDialogEx::OnEraseBkgnd(pDC);
}
This function has been made this way since it was done like this in the tutorial I mentioned earlier
------ UPDATE 2 ----------
If I just put return TRUE; in the body of the above function, the flicker seems to vanish, but now the output looks like this
The dialog box background seems to have taken the contents of my Visual Studio window. How do I prevent this?
You're close! The idea of double buffering is to paint every pixel in your window exactly once. If it is painted zero times, artifacts like Visual Studio remain. And if it is painted a first time, and then painted again with a different color, you will see flicker. So, to make sure every pixel is painted, create your compatible dc the full width and height of the window so that when it is copied to the CPaintDC, it covers the entire area and not just theGraph. Keep returning TRUE in OnEraseBkgnd, so that the pixels are not first painted in OnEraseBkgnd, and then again in OnPaint.
Two things :
Have you made sure OnEraseBkgnd() just returns TRUE and doesn't call the base class to erase the view?
You don't need to do all that drawing for the double buffering in OnPaint(). All you need to do in the OnPaint() is the BitBlt. You can do the drawing to the memory bitmap in a UpdateRect() function which gets called whenever you need to update the screen, which then calls InvalidateRect() to update the screen. I've posted some code about a flicker-free double buffering method I've used many times here which might help.
The way that flicker prevention work is that first you return TRUE from OnEraseBkgnd to suppress the default erase. But then your OnPaint code must include a full erase of the window. You don't do that so you get the background image of your source code or whatever was there before. So add a FillSolidRect call to your OnPaint to clear the window.
Your creation of a CPaintDC before calling CDialogEx::OnPaint destroys the dialog's ability to properly paint itself, since that function also creates a CPaintDC. But only one call to CPaintDC is permitted for each paint message. To avoid this problem you need a completely different approach. The dialog should have a picture control on it (a CStatic), and you should paint your graph in a class you derive from CStatic.

Bitblt blackness

I am running this following code,
HDC hdc;
HDC hdcMem;
HBITMAP bitmap;
RECT c;
GetClientRect(viewHandle, &c);
// instead of BeginPaint use GetDC or GetWindowDC
hdc = GetDC(viewHandle);
hdcMem = CreateCompatibleDC(hdc);
// always create the bitmap for the memdc from the window dc
bitmap = CreateCompatibleBitmap(hdc,c.right-c.left,200);
SelectObject(hdcMem, bitmap);
// only execute the code up to this point one time
// that is, you only need to create the back buffer once
// you can reuse it over and over again after that
// draw on hdcMem
// for example ...
Rectangle(hdcMem, 126, 0, 624, 400);
// when finished drawing blit the hdcMem to the hdc
BitBlt(hdc, 0, 0, c.right-c.left,200, hdcMem, 0, 0, SRCCOPY);
// note, height is not spelled i before e
// Clean up - only need to do this one time as well
DeleteDC(hdcMem);
DeleteObject(bitmap);
ReleaseDC(viewHandle, hdc);
The code is just fine. But I am seeing black color around this rectangle. Why is that? Here is an example image.
The bitmap is most likely initialized to be all black. You are then drawing a white rectangle that between x-coordinates 126 and 624. Hence, everything to the left of x=126 and to the right of x=624 stays black.
Edit: The documentation for CreateCompatibleBitmap doesn't state how the bitmap will be initialized, so you should explicitly initialize the bitmap with a specific colour, as Goz suggests, using FillRect:
RECT rc;
rc.left=0;
rc.top=0;
rc.right=c.right-c.left;
rc.bottom=200;
FillRect(hdcMem, &rc, (HBRUSH)GetStockObject(GRAY_BRUSH));
This example fills the bitmap in gray -- you may need to CreateSolidBrush your own brush if you need a different colour. (Don't forget to call DeleteObject when you're done.)
As a side note, I find it a bit strange that your bitmap is being set to a constant height of 200 -- the normal thing would be to make the height of the bitmap equal to the height of the window (as is done for the width).
Might it be because you haven't initialised the memory bitmap area to a given colour? Try FillRect'ing the background to a different colour then draw your white rectangle over it and see what happens.
Per MSDN http://msdn.microsoft.com/en-us/library/dd162898.aspx:
The rectangle is outlined by using the current pen and filled by using the current brush.
Consider calling FillRect instead, or select an appropriate pen prior to calling Rectangle'.
I used:
// Fill the background
hdcMem->FillSolidRect(c, hdcMem->GetBkColor());
Just as a note.

Resources