MFC: strange behavior of CDC and DrawText with DT_CALCRECT? - visual-studio

In BeginPrinting, I calculate the printing rect like below. I rely on this to count the number of pages.
CRect rect(30, -50, 200, -60);
int height = pDC->DrawText(cstr, &rect, DT_EDITCONTROL | DT_WORDBREAK | DT_LEFT | DT_CALCRECT);
Then in OnPrint, I do the same calculation again. However, the height result is different. I don't know what could be wrong with it.

Related

Win32 - Change background color under ENTIRE 'Static' control area?

VS2019 c++
This is how I'm declaring a 'Static' label in my humble Win32 application:
hLabel = CreateWindowW(L"Static", L"My Application Text", WS_VISIBLE | WS_CHILD | ES_MULTILINE | ES_AUTOVSCROLL | ES_AUTOHSCROLL, 20, 50, 700, 50, hWnd, NULL, NULL, NULL);
Each line of text uses about 25 pixel worth vertically. So I effectively have room for two lines worth of text in this control since it is declared as 50 pixels high.
I would like to override the default colors used to paint the control, so I experiment with this code:
case WM_CTLCOLORSTATIC:
{
HDC hEdit = (HDC)wParam;
SetTextColor(hEdit, RGB(0, 255, 0));
SetBkColor(hEdit, RGB(0, 0, 255));
return 0;
}
This works "great" except it does not paint the entire area of the control?
As you can see above, the Text color is applied to the characters and the 'Background' color is applied not to the entire area of the control, enclosed by the WS_BORDER, but only to the extent of the characters. I start by filling the control with 100 characters. That's why you only see "one" line of text. My question is "how should I go about filling the entire are of the control"? Which is declared as having an area of 700 pixel wide by 50 pixel high.
I confess I searched around quite a bit but I feel I'm going in circle.
The answer is "42" -- kidding!
This is how I changed my code :
case WM_CTLCOLOREDIT:
{
HDC hEdit = (HDC)wParam;
SetTextColor(hEdit, RGB(0, 0, 255));
SetBkColor(hEdit, RGB(255, 255, 0));
SetBkMode(hEdit, TRANSPARENT);
BackEDIT = CreateSolidBrush(RGB(255, 255, 0));
return (INT_PTR)BackEDIT;
}
And then I added :
case WM_DESTROY:
DeleteObject(BackEDIT);
PostQuitMessage(0);
return 0;
So, adding a BRUSH and deleting the BRUSH. Works perfect!

difference when rendering horizontal line using TextOut char by char vs all at once

I am writing a win32 low level gui app that emulates a console app. I use a fixed width font, my test uses Cascadia Mono, but I have the same issue with any fixed width font.
The console app is trying to draw a horizontal line using U2500 character.
I output the characters that app is passing me one by one. When I do that I get spaces between the horizontal lines, when I output in one call to textout those gaps are filled in.
I made this using the VS c++ windows app template and added this code to the WM_PAINT handling
auto nHeight = -MulDiv(48, GetDeviceCaps(hdc, LOGPIXELSY), 72);
auto hfont = CreateFont(
nHeight,
0,
0,
0,
100,//200,
0,
0,
0,
DEFAULT_CHARSET,
OUT_OUTLINE_PRECIS,
CLIP_DEFAULT_PRECIS,
CLEARTYPE_QUALITY,
FIXED_PITCH,
L"Cascadia Mono"
);
TEXTMETRIC tm;
SelectObject(hdc, hfont);
GetTextMetrics(hdc, &tm);
auto str = L"kkkkkk─────k";
TextOut(hdc, 0, 0, L"kkkkkk─────k", 12);
for (int i = 0; i < 12; i++)
{
TextOut(hdc, i * tm.tmAveCharWidth, tm.tmHeight, &str[i], 1);
}
This displays
you can see that this is not due to me miscalculating the char cell width, the strings are exactly aligned , just there are some added pixels in the upper one, also notice some extra 'knobiness' where the joins are. V odd. Also note that the right edge of the last K before the line starts is slightly chopped off in the char by char one, but not in the all at once one.
So why am I doing it char by char, because I need to specify font weight, bg, fg for each cell.
Instead of using TextOut, you can use DrawText which is a bit more hi-level, like this:
for (int i = 0; i < 12; i++)
{
RECT rc;
rc.left = i * tm.tmAveCharWidth;
rc.top = tm.tmHeight;
rc.right = rc.left + 50; // todo: make sure this is ok
rc.bottom = rc.top + 100;
DrawText(hdc, (LPWSTR)&str[i], 1, &rc, 0);
}
And it seems to fix the "lineness" of it, although it's not 100% exactly the same (there are some pixels that show a difference):

MFC: Scrollbar gripper has a flashing cursor?

I am using the following code to draw a scrollbar gripper for resizing window.
// create a gripper in the bottom-right corner
if (m_bShowGripper && ((GetStyle() & WS_THICKFRAME) != 0))
{
SMovingChild s;
s.m_rcInitial.SetRect(-GetSystemMetrics(SM_CXVSCROLL), -GetSystemMetrics(SM_CYHSCROLL), 0, 0);
s.m_rcInitial.OffsetRect(rcClient.BottomRight());
m_hGripper = CreateWindow(_T("Scrollbar"), _T("size"), WS_CHILD | WS_VISIBLE | SBS_SIZEGRIP,
s.m_rcInitial.left, s.m_rcInitial.top, s.m_rcInitial.Width(), s.m_rcInitial.Height(),
this->GetSafeHwnd(), NULL, AfxGetInstanceHandle(), NULL);
ASSERT(m_hGripper != NULL);
if (m_hGripper != NULL)
{
s.m_hWnd = m_hGripper;
s.m_dXMoveFrac = 1.0;
s.m_dYMoveFrac = 1.0;
s.m_dXSizeFrac = 0.0;
s.m_dYSizeFrac = 0.0;
m_MovingChildren.push_back(s);
// put the gripper first in the z-order so it paints first and doesn't obscure other controls
::SetWindowPos(m_hGripper, HWND_TOP, 0, 0, 0, 0, SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOOWNERZORDER | SWP_NOSIZE | SWP_SHOWWINDOW);
}
}
Originally from here: How to create a resizable CDialog in MFC?. The code runs OK, but the gripper has a flashing block like cursor (figure below).
This issue can be addressed by commenting the last statement of ::SetWindowPos(...). But I am wondering what the real cause is and whether it is OK to remove this statement, or any better fix.

Reduce flicker when using CPaintDC

I need to make an interactive graph using MFC
It will be sort of like an equalizer control, where the user should be able to click on a point on the equalizer drag it to change it's y axis value
I am also just starting to learn MFC
Upto this point I have used CPaintDC in the OnPaint() function to draw the graph in a dialog box. For now the graph is very simple, with rectangle boarder, filled with white colour, and 4 points on the graph. I use OnMouseMove() function to know if the cursor is inside the graph area and OnLButtonDown() function to know where the user has clicked. If the user has clicked at a position which implies I want to change the y axis value of the graph point at that location, I repaint the figure using Invalidate() and calling OnPaint() inside OnLButtonDown(). However, every time the graph has to update, I can see a flicker. it is not a problem now, but I will need to extend this graph so that it has at least 64 changeable points, with the ability to change the y axis value for a point by dragging instead of just clicking where I want it to go. Will the flickering problem increase as I increase the number of points and the complexity of the appearance of the graph? The graph will need to have axes, gridlines, labels, etc later on. Is the flickering something I should be concerned about? Is there any way I can prevent it?
----UPDATE ----
This is how I updated my OnPaint() function according to how I understood CodeDreamer's suggestion
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);
bmpDC.CreateCompatibleBitmap(&dc, theGraph.width,theGraph.height );
dc.SelectObject(&bmpDC);
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.SelectObject(&pen);
// 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();
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);
}
// dc_blt.BitBlt(0,0,rcClient.Width(), rcClient.Height(), &dc, 0, 0, SRCCOPY);
dc_blt.BitBlt(theGraph.x1,theGraph.y1,theGraph.width, theGraph.height, &dc, 0, 0, SRCCOPY);
}
I will need to change the origins of the viewport a number of times and my guess is that this could be one of the reasons for the error. Any suggestions will be welcome.
This is what my output look like without double buffering
This is what it looks like with my attempt at double buffering
In this case, a general solution is 'double buffering'.
The principle is that it creates a compatible memory dc for drawing in advance, and when drawing is ended, it outputs on screen dc.
A code sample is below.
//in OnPaint() function
CPaintDC dc(this);
CDC dcMem;
CBitmap bmpDC;
CRect rcClient;
GetClientRect(&rcClient);
dcMem.CreateCompatibleDC(pDC);
bmpDC.CreateCompatibleBitmap(pDC, rcClient.Width(), rcClient.Height());
dcMem.SelectObject(&bmpDC);
CRect rect(0, 0, 100, 200);
dcMem.Rectangle(rect);
dc.BitBlt(0, 0, rcClient.Width(), rcClient.Height(), &dcMem, 0, 0, SRCCOPY);
A couple of references are below.
introduction
another reference
I hope this will help you a little.
Try the CMemDC MFC class.
In your OnPaintFunction you would have this:
CPaintDC DC(this);
CMemDC mDC(&DC, this);
// now use mDC instead of DC
Also look here fore more samples and explanations.

win32 - How to draw a rectangle around a text string?

I am new to Win32 and trying to get a GDI based code in C++ ( for technical reasons don't want to use GDI+)
Edit: Simplied the question:
I need to draw a rectangle around the text that is drawn in the middle of the window.
- How can I populate the rectangle co-ordinates?
- Can any one help with the line - Rectangle(x1,y1,x2,y2)? - How to calculate these (x1,y1) & (x2,y2) values?
Thank you..
hdc = BeginPaint(hWnd, &ps);
GetClientRect(hWnd, &rcClient);
SelectObject(hdc, GetStockObject(DEFAULT_GUI_FONT));
SetTextColor(hdc, RGB(255, 0, 0));
DrawText(hdc, wstring(s.begin(),s.end()).c_str(), -1, &rectResult, DT_SINGLELINE | DT_CALCRECT);
DrawText(hdc, wstring(s.begin(),s.end()).c_str(), -1, &rcClient, DT_SINGLELINE | DT_CENTER | DT_VCENTER);
// Here I need help - How to I place the rectangle around the Text - which is drawn in the middle of the window?
// It looks like need to use - rectResult.bottom/top/left/right - but don't know how..
Rectangle(hdc, 0,0,100,100);
You don't actually have to center the text yourself. The GDI text output functions can do that for you if you pass the appropriate flags.
For example, if you call DrawText and pass the DT_CENTER flag, it will automatically draw the text in the middle of the specified rectangle (horizontally centered).
Assuming you have only a single line of text (which it sounds like you do), you can get it to automatically vertically center the text by passing the DT_SINGLELINE and DT_VCENTER flags.
So all you really have to do is write code to divide your window's client area up into 4 equal parts, and then pass those rectangles to the DrawText function. That's not terribly difficult. Put a pencil and paper to it if you can't visualize it in your head.
void PaintWindow(HWND hWnd)
{
// Set up the device context for drawing.
PAINTSTRUCT ps;
HDC hDC = BeginPaint(hWnd, &ps);
HPEN hpenOld = static_cast<HPEN>(SelectObject(hDC, GetStockObject(DC_PEN)));
HBRUSH hbrushOld = static_cast<HBRUSH>(SelectObject(hDC, GetStockObject(NULL_BRUSH)));
// Calculate the dimensions of the 4 equal rectangles.
RECT rcWindow;
GetClientRect(hWnd, &rcWindow);
RECT rc1, rc2, rc3, rc4;
rc1 = rc2 = rc3 = rc4 = rcWindow;
rc1.right -= (rcWindow.right - rcWindow.left) / 2;
rc1.bottom -= (rcWindow.bottom - rcWindow.top) / 2;
rc2.left = rc1.right;
rc2.bottom = rc1.bottom;
rc3.top = rc1.bottom;
rc3.right = rc1.right;
rc4.top = rc1.bottom;
rc4.left = rc1.right;
// Optionally, deflate each of the rectangles by an arbitrary amount so that
// they don't butt up right next to each other and we can distinguish them.
InflateRect(&rc1, -5, -5);
InflateRect(&rc2, -5, -5);
InflateRect(&rc3, -5, -5);
InflateRect(&rc4, -5, -5);
// Draw (differently-colored) borders around these rectangles.
SetDCPenColor(hDC, RGB(255, 0, 0)); // red
Rectangle(hDC, rc1.left, rc1.top, rc1.right, rc1.bottom);
SetDCPenColor(hDC, RGB(0, 255, 0)); // green
Rectangle(hDC, rc2.left, rc2.top, rc2.right, rc2.bottom);
SetDCPenColor(hDC, RGB(0, 0, 255)); // blue
Rectangle(hDC, rc3.left, rc3.top, rc3.right, rc3.bottom);
SetDCPenColor(hDC, RGB(255, 128, 0)); // orange
Rectangle(hDC, rc4.left, rc4.top, rc4.right, rc4.bottom);
// Draw the text into the center of each of the rectangles.
SetBkMode(hDC, TRANSPARENT);
SetBkColor(hDC, RGB(0, 0, 0)); // black
// TODO: Optionally, set a nicer font than the default.
DrawText(hDC, TEXT("Hello World!"), -1, &rc1, DT_CENTER | DT_SINGLELINE | DT_VCENTER);
DrawText(hDC, TEXT("Hello World!"), -1, &rc2, DT_CENTER | DT_SINGLELINE | DT_VCENTER);
DrawText(hDC, TEXT("Hello World!"), -1, &rc3, DT_CENTER | DT_SINGLELINE | DT_VCENTER);
DrawText(hDC, TEXT("Hello World!"), -1, &rc4, DT_CENTER | DT_SINGLELINE | DT_VCENTER);
// Clean up after ourselves.
SelectObject(hDC, hpenOld);
SelectObject(hDC, hbrushOld);
EndPaint(hWnd, &ps);
}
RECT rect={0,0,0,0};
const char *str="Test Text";
DrawText(hDC, str, strlen(str), &rect, DT_CALCRECT | DT_NOCLIP);
Rectangle(hDC,rect.left,rect.top,rect.right,rect.bottom);
DrawText(hDC, str, strlen(str), &rect, DT_CENTER | DT_VCENTER | DT_SINGLELINE|DT_NOCLIP);
Got it finally :) Thanks a lot Cody Gray for pointing me in right direction about this :)
GetTextExtentPoint32(hDC, str, strlen(str), &sz2);
rect2.top=rect2.bottom+sz2.cy;
rect2.right=rect2.top+sz2.cx;
Rectangle(hDC,rect2.left,rect2.top,rect2.right,rect2.bottom);
DrawText(hDC, str, -1, &rect2, DT_CENTER | DT_VCENTER | DT_SINGLELINE|DT_NOCLIP);

Resources