I want to draw text to a GDI Surface and rotate this text by 90 degrees counter clockwise. I would prefer to use DrawText to draw the text because it supports carriage return. I tried to use a font with lfEscapement (see the code below) but the line is not rotated - one line gets rendered over the other. Is there any possibility to rotate the text? Or to render without rotation and rotate the whole device context?
Normal text layout:
Rotated (desired result):
case WM_PAINT:
{
hdc = BeginPaint(hWnd, &ps);
LOGFONT lf = {0};
HANDLE hFont;
ZeroMemory(&lf, sizeof(LOGFONT));
lf.lfWeight = FW_NORMAL;
lstrcpy(lf.lfFaceName, _T("Tahoma"));
lf.lfEscapement = 90;
lf.lfHeight = 30;
hFont = CreateFontIndirect (&lf);
hFont = (HFONT)SelectObject (ps.hdc, hFont);
RECT RectBody = {10,lf.lfHeight+10,::GetSystemMetrics(SM_CXSCREEN)-10,::GetSystemMetrics(SM_CYSCREEN)-lf.lfHeight-20};
{
ScopedLock lock(me->m_mutex);
DrawText (ps.hdc, me->GetMessageString().c_str(), (int)me->GetMessageString().length(), &RectBody, 0);
}
hFont = (HFONT)SelectObject (ps.hdc, hFont);
DeleteObject (hFont);
EndPaint(hWnd, &ps);
break;
}
lf.lfEscapement = 90;
That should be 900 to get the text vertical, units are 0.1 degrees.
Your plan to let DrawText take care of the line breaks is going to fall flat I'm afraid. I could not convince it to align the text properly. It aligns on the last line, not the first. Some code to play with:
wchar_t* msg = L"Hello\r\nworld";
RECT rcMeasure = {0, 0, 400, 0};
DrawTextEx(hdc, msg, -1, &rcMeasure, DT_CALCRECT, 0);
RECT rcDraw = {10, 30, 10 + rcMeasure.bottom - rcMeasure.top, 30 + rcMeasure.right - rcMeasure.left };
FillRect(hdc, &rcDraw, (HBRUSH) (COLOR_WINDOW+2));
SetTextAlign(hdc, TA_TOP | TA_CENTER);
DrawTextEx(hdc, msg, -1, &rcDraw, DT_BOTTOM, 0);
I think I tried all alignment options.
I have the impression that this link answers your question, but using ExtTextOut, and not DrawText
http://www.codeproject.com/KB/GDI/textrotation.aspx
it's not GDI+ it's MFC but they are close.
Related
This question already has an answer here:
How to automatically set the width and height of static control base on its content?
(1 answer)
Closed 1 year ago.
I'm trying to get the size of the text string using GetTextExtentPoint32. I read the documentation many times and do some research and as far as I know, the below code should give me the correct width and height of the text.
vFontFamily = "Segoe UI"; //font family
vFontSize = 26; //font size
HDC hdc = GetDC(hwnd);
HFONT hFont = CreateFont(
-MulDiv(vFontSize, GetDeviceCaps(hdc, LOGPIXELSY), 72), //calculate the actual cHeight.
0, 0, 0, // normal orientation
FW_NORMAL, // normal weight--e.g., bold would be FW_BOLD
false, false, false, // not italic, underlined or strike out
DEFAULT_CHARSET, OUT_OUTLINE_PRECIS, // select only outline (not bitmap) fonts
CLIP_DEFAULT_PRECIS, CLEARTYPE_QUALITY, VARIABLE_PITCH | FF_SWISS, vFontFamily);
SIZE size;
HFONT oldfont = (HFONT)SelectObject(hdc, hFont);
GetTextExtentPoint32(hdc, L"This is Text", wcslen(L"This is Text"), &size);
width = size.cx; //get width
height = size.cy; //get height
SelectObject(hdc, oldfont); //don't forget to select the old.
DeleteObject(hFont); //always delete the object after creating it.
ReleaseDC(hwnd, hdc); //alway reelase dc after using.
Unfortunately, it doesn't give me the correct size. The width has too much for at least 10% and height is too much for at least 5%. To be exact, the result of width in the text of This is Text is 228 and the height is 62 whereas the more accurate is somewhat near to 190 x 55 I think.
The text is painted using GDI+.
case WM_PAINT: {
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hwnd, &ps);
Graphics g(hdc);
FontFamily theFontFamily(vFontFamily);
Font font(&theFontFamily, vFontSize, FontStyleRegular, UnitPixel);
SolidBrush brush(Color(255, R, G, B));
PointF pointF(0.0f, 0.0f);
TextRenderingHint hint = g.GetTextRenderingHint(); // Get the text rendering hint.
g.SetTextRenderingHint(TextRenderingHintAntiAlias); // Set the text rendering hint to TextRenderingHintAntiAlias.
g.DrawString(L"This is Text", strlen("This is Text"), &font, pointF, &brush);
EndPaint(hwnd, &ps);
return TRUE;
}
I'm thinking maybe it's somehow related to how I drew the text because, in the paint message, I used Font instead of HFONT. But that doesn't make any sense, right? As long as I set the correct font family and font size before GetTextExtentPoint32 then it should give me the exact width and height of the text. What do you think?
You are using GDI to measure your text while you are using GDI+ to draw it.
GDI+ is an improvement on GDI and there are differences between the two when you use it to render text, etc. Another thing is the DPI Awareness, when measuring text, you'll need to make sure to handle the scaling too. Your method will have larger result compare to the actual width and height when you drew it using GDI+ and if you don't handle the DPI properly on your measurement.
Try using Graphics::MeasureString and see if you get the expected width and height. Do it the same way how you draw your string just like what AlanBirtles said in the comment.
For example,
HDC hdc = GetDC(hwnd); //get dc of your handle.
Graphics graphics(hdc); //setup graphics.
FontFamily theFontFamily(vFontFamily); //setup your font family.
Font font(&theFontFamily, vFontSize, FontStyleRegular, UnitPixel); //create the font the same way how you do it on your paint message.
PointF pointF(0.0f, 0.0f); //use PointF instead of RectF since thats how you paint it.
RectF boundRect; //setup boundRect to get the width and height.
graphics.MeasureString(text, -1, &font, pointF, &boundRect); //Measure the text of the string
//or
//graphics.MeasureString(L"This is Text", strlen("This is Text"), pointF, &boundRect);
width = boundRect.Width; //get the width of text from boundRect.
height = boundRect.Height; //get the height of text from boundRect.
DeleteObject(&font); //delete the font.
ReleaseDC(LabelHandle, hdc); //always reelase dc after using.
If you need more explanation, the following might be help you.
How to automatically set the width and height of static control base on its content?
GDI+ font size difference under different processes on same machine
DPI-awareness is really needed?
How to draw a GDI + text independent of DPI
https://stackoverflow.com/questions/4551224/what-is-the-difference-between-gdi-and-gdi#:~:text=GDI%2B%20is%20object%20oriented%2C%20and,in%20some%20ways%20GDI%20usage.&text=GDI%2B%20is%20an%20improvement%20on,and%20more%20image%20format%20support.
I have a MFC C++ (unmanaged) Windows application that uses a “standard” icon in the System Tray. This icon was created & edited using Visual Studio and is 32x32 pixels with only 4bit colour (according to VS's Resource Editor).
With Visual Studio, I also set a transparent background (shown as white in the “before” image).
I wish to dynamically change the icon by writing 2 digits (1-99) on top of it.
Using the code below (based on that in this question: How to draw text with transparency using GDI?) to superimpose “55” in yellow on the icon, it works except that the transparency disappears (it appears black in the “after” image and on the System Tray). My actual code differs very very slightly in that the font size (20), font name (Courier New), text colour (yellow - RGB(255, 255, 0)) and the numeric value (55) are run-time variables rather than fixed values.
Any suggestions on how to make the background remain transparent as far as the System Tray is concerned gratefully received.
These images have been captured using MS’s Snipping tool with the image open in MS Paint as a 32x32 icon wouldn't be very visible as-is.
Before Image:
After image:
Code:
void CreateNewIcon(HICON &hNewIcon, HICON hBackgroundIcon)
{
::DestroyIcon(hNewIcon);
// First create font
LOGFONT lf = { 0 };
lf.lfHeight = -20;
lf.lfWeight = FW_BOLD;
lf.lfOutPrecision = OUT_TT_PRECIS;
lf.lfQuality = CLEARTYPE_QUALITY;
wmemset(lf.lfFaceName, 0, LF_FACESIZE);
lstrcpy(lf.lfFaceName, L"Courier New");
HFONT hFont = ::CreateFontIndirect(&lf);
ICONINFO ii = { 0 };
::GetIconInfo(hBackgroundIcon, &ii);
BITMAP bm = { 0 };
::GetObject(ii.hbmColor, sizeof(bm), &bm);
SIZE szBmp = { bm.bmWidth, bm.bmHeight };
HDC hDc = ::GetDC(NULL);
HDC hMemDC = ::CreateCompatibleDC(hDc);
HGDIOBJ hOldBmp = ::SelectObject(hMemDC, ii.hbmColor);
HGDIOBJ hOldFont = ::SelectObject(hMemDC, hFont);
::SetBkMode(hMemDC, TRANSPARENT);
::SetTextColor(hMemDC, RGB(255, 255, 0));
::TextOut(hMemDC, 0, 8, L"55", 2);
::SelectObject(hMemDC, hOldFont);
::SelectObject(hMemDC, hOldBmp);
// We need a simple mask bitmap for the icon
HBITMAP hBmpMsk = ::CreateBitmap(szBmp.cx, szBmp.cy, 1, 1, NULL);
ICONINFO ii2 = { 0 };
ii2.fIcon = TRUE;
ii2.hbmColor = ii.hbmColor;
ii2.hbmMask = hBmpMsk;
// Create updated icon
hNewIcon = ::CreateIconIndirect(&ii2);
// Cleanup
::DeleteObject(hBmpMsk);
::DeleteDC(hMemDC);
::ReleaseDC(NULL, hDc);
::DeleteObject(ii.hbmColor);
::DeleteObject(ii.hbmMask);
::DeleteObject(hFont);
}
There are multiple issues with our code:
You are trying to draw cleartype-quality text over transparent icon part. But cleartype font rendering must be performed over opaque background because it needs to inspect background color. So you should switch to Anitialiased quality (or to not antialiased quality) or provide opaque background for your text.
When you create hBmpMsk you skip it's content initialization by supplying NULL for bits pointer, so resulting icon will actually have completely random transparency. You need to fill this mask bitmat appropriately.
Also you probably need to switch to higher bit depth because monochrome bitmap mask can't handle semitransparent parts of antialiased text.
update
I think you should draw cleartype text but with opaque background, then get text rectangle using something like GetTextExtentPoint32, then copy data from the original bitmap mask into hBmpMsk and then finally fill white (text) rectangle on it so the new icon will preserve transparency from original and has opaque text block.
Thanks for all the help from VTT without which I wouldn't have been able to get this far. This appears to work for me.
void CreateNewIcon(HICON &hNewIcon, HICON hBackgroundIcon)
{
::DestroyIcon(hNewIcon);
HDC hDc = ::GetDC(NULL);
HDC hMemDC = ::CreateCompatibleDC(hDc);
// Load up background icon
ICONINFO ii = { 0 };
::GetIconInfo(hBackgroundIcon, &ii);
HGDIOBJ hOldBmp = ::SelectObject(hMemDC, ii.hbmColor);
// Create font
LOGFONT lf = { 0 };
lf.lfHeight = -20;
lf.lfWeight = FW_BOLD;
lf.lfOutPrecision = OUT_TT_PRECIS;
lf.lfQuality = ANTIALIASED_QUALITY;
wmemset(lf.lfFaceName, 0, LF_FACESIZE);
lstrcpy(lf.lfFaceName, L"Courier New");
HFONT hFont = ::CreateFontIndirect(&lf);
HGDIOBJ hOldFont = ::SelectObject(hMemDC, hFont);
// Write text
::SetBkMode(hMemDC, TRANSPARENT);
::SetTextColor(hMemDC, RGB(255, 255, 0));
::TextOut(hMemDC, 0, 8, L"55", 2);
// Set up mask
HDC hMaskDC = ::CreateCompatibleDC(hDc);
HGDIOBJ hOldMaskBmp = ::SelectObject(hMaskDC, ii.hbmMask);
// Also write text on here
HGDIOBJ hOldMaskFont = ::SelectObject(hMaskDC, hFont);
::SetBkMode(hMaskDC, TRANSPARENT);
::SetTextColor(hMaskDC, RGB(255, 255, 0));
::TextOut(hMaskDC, 0, 8, L"55", 2);
// Get handle to create mask bitmap
HBITMAP hMaskBmp = (HBITMAP)::SelectObject(hMaskDC, hOldMaskBmp);
// Use new icon bitmap with text and new mask bitmap with text
ICONINFO ii2 = { 0 };
ii2.fIcon = TRUE;
ii2.hbmMask = hMaskBmp;
ii2.hbmColor = ii.hbmColor;
// Create updated icon
hNewIcon = ::CreateIconIndirect(&ii2);
// Cleanup bitmap mask
::DeleteObject(hMaskBmp);
::DeleteDC(hMaskDC);
// Cleanup font
::SelectObject(hMaskDC, hOldMaskFont);
::SelectObject(hMemDC, hOldFont);
::DeleteObject(hFont);
// Release background bitmap
::SelectObject(hMemDC, hOldBmp);
// Delete background icon bitmap info
::DeleteObject(ii.hbmColor);
::DeleteObject(ii.hbmMask);
::DeleteDC(hMemDC);
::ReleaseDC(NULL, hDc);
}
My goal is to dynamically put some arbitrary text into an HICON image (at runtime.) I'm using the following code:
//Error checks are omitted for brevity
//First create font
LOGFONT lf = {0};
lf.lfHeight = -58;
lf.lfWeight = FW_NORMAL;
lf.lfOutPrecision = OUT_TT_PRECIS; //Use TrueType fonts for anti-alliasing
lf.lfQuality = CLEARTYPE_QUALITY;
lstrcpy(lf.lfFaceName, L"Segoe UI");
HFONT hFont = ::CreateFontIndirect(&lf);
//HICON hIcon = original icon to use as a source
//I'm using a large 256x256 pixel icon
hIcon = (HICON)::LoadImage(theApp.m_hInstance, MAKEINTRESOURCE(IDI_ICON_GREEN_DIAMOND), IMAGE_ICON, 256, 256, LR_DEFAULTCOLOR);
ICONINFO ii = {0};
::GetIconInfo(hIcon, &ii);
BITMAP bm = {0};
::GetObject(ii.hbmColor, sizeof(bm), &bm);
SIZE szBmp = {bm.bmWidth, bm.bmHeight};
HDC hDc = ::GetDC(hWnd);
HDC hMemDC = ::CreateCompatibleDC(hDc);
HGDIOBJ hOldBmp = ::SelectObject(hMemDC, ii.hbmColor);
HGDIOBJ hOldFont = ::SelectObject(hMemDC, hFont);
::SetBkMode(hMemDC, TRANSPARENT);
::SetTextColor(hMemDC, RGB(255, 0, 0)); //Red text
//Draw text
//NOTE that DrawText API behaves in a similar way
::TextOut(hMemDC, 0, 0, L"Hello", 5);
::SelectObject(hMemDC, hOldFont);
::SelectObject(hMemDC, hOldBmp);
//We need a simple mask bitmap for the icon
HBITMAP hBmpMsk = ::CreateBitmap(szBmp.cx, szBmp.cy, 1, 1, NULL);
ICONINFO ii2 = {0};
ii2.fIcon = TRUE;
ii2.hbmColor = ii.hbmColor;
ii2.hbmMask = hBmpMsk;
//Create updated icon
HICON hIcon2 = ::CreateIconIndirect(&ii2);
//Cleanup
::DeleteObject(hBmpMsk);
::DeleteDC(hMemDC);
::ReleaseDC(hWnd, hDc);
::DeleteObject(ii.hbmColor);
::DeleteObject(ii.hbmMask);
::DeleteObject(hFont);
and then I can display the icon in my window from OnPaint() handler (so that I can see how it turns out) as such:
::DrawIconEx(dc.GetSafeHdc(), 0, 0,
hIcon2,
256, 256, NULL,
::GetSysColorBrush(COLOR_BTNFACE),
DI_NORMAL);
So here's what I get:
To see what's going on pixel-wise in my hIcon2 I called GetDIBits on its ii.hbmColor from the code above. The resulting pixel array where my word "Hello" was supposed to be shown looked like this:
The pixels are encoded as BGRA in that memory dump, so the 4th byte in each DWORD stands for transparency: 0=transparent, FF=opaque. But in this case TextOut doesn't fill out transparency, or leaves it as 0, which is interpreted as "fully transparent." Instead it seems to pre-multiply it into the RGB colors themselves.
Note that if I keep looking further down the same bitmap, where the green diamond begins, the image pixels seem to have transparency bytes set correctly:
Any idea how to draw text so that the API could set those transparency bytes?
EDIT: As was suggested below I tried the following GDI+ method:
HGDIOBJ hOldBmp = ::SelectObject(hMemDC, ii.hbmColor);
Graphics grpx(hMemDC);
RectF rcfTxt(0.0f, 0.0f, (REAL)szBmp.cx, (REAL)szBmp.cy);
Font gdiFont(L"Segoe UI", 58.0f, FontStyleRegular, UnitPixel);
SolidBrush gdiBrush(Color(255, 0, 0));
StringFormat gdiSF;
gdiSF.SetAlignment(StringAlignmentNear);
gdiSF.SetFormatFlags(StringFormatFlagsNoWrap);
gdiSF.SetHotkeyPrefix(HotkeyPrefixNone);
//The reason I was using GDI was because I was setting
//spacing between letters using SetTextCharacterExtra()
//Unfortunately with GDI+ this does not work!
HDC hTmpDC = grpx.GetHDC();
::SetTextCharacterExtra(hTmpDC, -4); //This doesn't do anything!
grpx.ReleaseHDC(hTmpDC);
grpx.DrawString(L"Hello", 5, &gdiFont, rcfTxt, &gdiSF, &gdiBrush);
::SelectObject(hMemDC, hOldBmp);
and besides not being able to set character spacing (which I could with GDI using SetTextCharacterExtra) here's what I got (slightly enlarged for visibility):
So clearly still an issue with transparency.
Taken from an old post by Microsoft MVP Mike D Sutton here.
When you create a DC it initially has default 'stock' objects selected
into it, including the stock 1*1*1 Bitmap. Since there is a Bitmap
already selected into the DC when you call DrawText() it will still
try and render to it even though pretty much everything (apart from
one pixel) will be clipped.
What you need to do is to create a Bitmap,
either DDB or DIBSection, and select that into your DC before drawing
to it.
First though you need to find the size of your Bitmap since you
want it large enough to display your text in, so for that you use the
DrawText() call again on the initial DC but include the DT_CALCRECT
flag. What this does is rather than drawing anything it simply
measures how large the text is and dumps that into the RECT you pass
the call. From here you can go ahead and create your DIBSection using
those dimensions and select it into your DC. Finally perform your
existing DrawText ()call (you may also want to use SetBkMode/Color())
which will render the text to the DIBSection from which you can get at
the data.
This seems to work pretty well here:
HBITMAP CreateAlphaTextBitmap(LPCSTR inText, HFONT inFont, COLORREF inColour) {
int TextLength = (int)strlen(inText);
if (TextLength <= 0) return NULL;
// Create DC and select font into it
HDC hTextDC = CreateCompatibleDC(NULL);
HFONT hOldFont = (HFONT)SelectObject(hTextDC, inFont);
HBITMAP hMyDIB = NULL;
// Get text area
RECT TextArea = {0, 0, 0, 0};
DrawText(hTextDC, inText, TextLength, &TextArea, DT_CALCRECT);
if ((TextArea.right > TextArea.left) && (TextArea.bottom > TextArea.top)) {
BITMAPINFOHEADER BMIH;
memset(&BMIH, 0x0, sizeof(BITMAPINFOHEADER));
void *pvBits = NULL;
// Specify DIB setup
BMIH.biSize = sizeof(BMIH);
BMIH.biWidth = TextArea.right - TextArea.left;
BMIH.biHeight = TextArea.bottom - TextArea.top;
BMIH.biPlanes = 1;
BMIH.biBitCount = 32;
BMIH.biCompression = BI_RGB;
// Create and select DIB into DC
hMyDIB = CreateDIBSection(hTextDC, (LPBITMAPINFO)&BMIH, 0, (LPVOID*)&pvBits, NULL, 0);
HBITMAP hOldBMP = (HBITMAP)SelectObject(hTextDC, hMyDIB);
if (hOldBMP != NULL) {
// Set up DC properties
SetTextColor(hTextDC, 0x00FFFFFF);
SetBkColor(hTextDC, 0x00000000);
SetBkMode(hTextDC, OPAQUE);
// Draw text to buffer
DrawText(hTextDC, inText, TextLength, &TextArea, DT_NOCLIP);
BYTE* DataPtr = (BYTE*)pvBits;
BYTE FillR = GetRValue(inColour);
BYTE FillG = GetGValue(inColour);
BYTE FillB = GetBValue(inColour);
BYTE ThisA;
for (int LoopY = 0; LoopY < BMIH.biHeight; LoopY++) {
for (int LoopX = 0; LoopX < BMIH.biWidth; LoopX++) {
ThisA = *DataPtr; // Move alpha and pre-multiply with RGB
*DataPtr++ = (FillB * ThisA) >> 8;
*DataPtr++ = (FillG * ThisA) >> 8;
*DataPtr++ = (FillR * ThisA) >> 8;
*DataPtr++ = ThisA; // Set Alpha
}
}
// De-select bitmap
SelectObject(hTextDC, hOldBMP);
}
}
// De-select font and destroy temp DC
SelectObject(hTextDC, hOldFont);
DeleteDC(hTextDC);
// Return DIBSection
return hMyDIB;
}
If you need an example of how to call it then try something like this
(inDC is the DC to render to):
void TestAlphaText(HDC inDC, int inX, int inY) {
const char *DemoText = "Hello World!\0";
RECT TextArea = {0, 0, 0, 0};
HFONT TempFont = CreateFont(50, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, "Arial\0");
HBITMAP MyBMP = CreateAlphaTextBitmap(DemoText, TempFont, 0xFF);
DeleteObject(TempFont);
if (MyBMP) { // Create temporary DC and select new Bitmap into it
HDC hTempDC = CreateCompatibleDC(inDC);
HBITMAP hOldBMP = (HBITMAP)SelectObject(hTempDC, MyBMP);
if (hOldBMP) {
BITMAP BMInf; // Get Bitmap image size
GetObject(MyBMP, sizeof(BITMAP), &BMInf);
// Fill blend function and blend new text to window
BLENDFUNCTION bf;
bf.BlendOp = AC_SRC_OVER;
bf.BlendFlags = 0;
bf.SourceConstantAlpha = 0x80;
bf.AlphaFormat = AC_SRC_ALPHA;
AlphaBlend(inDC, inX, inY, BMInf.bmWidth, BMInf.bmHeight,
hTempDC, 0, 0, BMInf.bmWidth, BMInf.bmHeight, bf);
// Clean up
SelectObject(hTempDC, hOldBMP);
DeleteObject(MyBMP);
DeleteDC(hTempDC);
}
}
}
All credit to answer and code go to original posters on that forum, I've simply reposted it so that this answer will be valid if the links die.
This reply is coming almost 3 years after the question was posted, but people still consult these things long into the future. So I'll explain what's happening.
DrawText (and other GDI text functions) will work on a transparent bitmap. The text is not coming out black even though it displays that way. The alpha channel is set to 0 on all pixels the text draws to, overriding whatever alpha you had set previously. If you set an alpha value in SetTextColor the text will render all black. If you're feeling ambitious you can run through pixel by pixel and target anything not your fill color (which requires a single fill color) but the problem then becomes one of the nature of ClearType being overridden and all alphas are set to whatever you set them to. The text ends up looking very funky. If you use a constant alpha for your background fill you can simply do a blanket run across the entire bitmap's bits after the text is drawn and reset all the alpha values. Since you have to read a byte to determine if it's background or not, you might as well just set every pixel's alpha to whatever the standard alpha is for that image and bypass the slow compares. This works reasonably well and I've found it to be very acceptable. In this day and age, MS should have taken care of this long ago but it's not to be.
https://learn.microsoft.com/en-us/windows/win32/gdiplus/-gdiplus-antialiasing-with-text-use
Gdiplus::Bitmap bmp( your_Width, your_Height, PixelFormat64bppARGB);
//PixelFormat64bppARGB ARGB needed
FontFamily fontFamily(L"Arial");
Font font(&fontFamily, 29, FontStyleRegular, UnitPoint);
Gdiplus::RectF rectF(00.0f, 10.0f, your_Width, your_Height);
StringFormat stringFormat;
SolidBrush solidBrush(Color(63, 0, 0, 255));
stringFormat.SetAlignment(StringAlignmentCenter);
//solidBrush Color(63, 0, 0, 255) ARGB neede
graphics.SetTextRenderingHint(TextRenderingHintAntiAlias);
graphics.DrawString("your_text", -1, &font, rectF, &stringFormat, &solidBrush);
//TextRenderingHintAntiAlias this needed
I am trying to make make my ui in win32 everywhere white. The problem is that my tab control's background is not white , so the not the tab itself, but the party next to the tabs is grey.
I am handling WM_CTLCOLORSTATIC for the static controls, but it does not seems to work for the tab control.
case WM_CTLCOLORSTATIC:
{
HDC hEdit = (HDC)w_param;
SetBkMode(hEdit, TRANSPARENT);
SetTextColor(hEdit, RGB(0, 0, 0));
SetBkColor(hEdit, RGB(255, 255, 255));
// Do not return a brush created by CreateSolidBrush(...) because you'll get a memory leak
return (INT_PTR)GetStockObject(WHITE_BRUSH);
}
I hope there is an 'easy' way to make my entire ui white :)
Grz
You can not capture a message to draw on the grey background. The system draws everything in the WM_PRINTCLIENT. There is however a nice hack! The idea is from this post.
I do this (in my WM_PAINT handler):
Create a memory DC to draw into
Send a WM_PRINTCLIENT message to the tab control to get it to draw the tabs into your memory DC
Create a region which mirrors the shape of the tabs
Fill the parts of the memory DC outside this region (RGN_DIFF) with the desired background brush
Blt the result into the DC returned by BeginPaint
Call EndPaint and return, without calling the tab control's own WndProc of course :)
Step 3 is a bit fiddly as you have to know the location and shape of the tabs, but other
than that it's a pretty clean solution (see image below the following sample code). You
could probably use TransparentBlt to replace the system background colour instead.
I am using TransparentBlt in this solution:
create hdcMemTab, step 1
HBITMAP hBitmap, hBitmapOld ; //keep them till the end of the program
HDC hdcMemTab; //keep it till the end of the program
HDC hdc;
Rect rt;
hdc = CreateIC(TEXT("DISPLAY"), NULL, NULL, NULL);
hdcMemTab = CreateCompatibleDC(hdc);
GetWindowRect(hwnd_Tab, &rt);
rt.right = rt.right - rt.left;
rt.bottom = rt.bottom - rt.top;
rt.left = 0;
rt.top = 0;
hBitmap = CreateCompatibleBitmap(hdc, rt.right, rt.bottom);
hBitmapOld = SelectObject(hdcMemTab, hBitmap);
DeleteDC(hdc);
In WM_PAINT of the subclassed tab control:
RECT rt, rtTab;
HDC hdc = BeginPaint(hwnd, &ps);
GetWindowRect(hwnd_Tab, &rt);
rt.right = rt.right - rt.left;
rt.bottom = rt.bottom - rt.top;
rt.left = 0;
rt.top = 0;
//step 2
SendMessage(hwnd_Tab, WM_PRINTCLIENT, (WPARAM)hdcMemTab, PRF_CLIENT);
FillRect(hdc, &rt, gBrushWhite); //gBrushWhite has the desired background color
HRGN hRgn = CreateRectRgn(0, 0, 0, 0);
int n_items = TabCtrl_GetItemCount(hwnd_Tab);
//get tabs region, step 3
for(i = 0; i < n_items; i++){
TabCtrl_GetItemRect(hwnd_Tab, i, &rtTab);
HRGN hTabRgn = CreateRectRgn(rtTab.left, rtTab.top, rtTab.right, rt.bottom);
CombineRgn(hRgn, hRgn, hTabRgn, RGN_OR);
DeleteObject(hTabRgn);
}
GetRgnBox(hRgn, &rtTab);
DeleteObject(hRgn);
//step 5
TransparentBlt(hdc, 0, 0, rt.right, rt.bottom, hdcMemTab, 0, 0, rt.right, rt.bottom, RGB(240, 240, 240)); //(240, 240, 240) is the grey color
BitBlt(hdc, rtTab.left, rtTab.top, rtTab.right - 5, rtTab.bottom, hdcMemTab, rtTab.left, rtTab.top, SRCCOPY);
EndPaint(hwnd, &ps);
//step 6
return 0;
Sorry about my english. What I do is return 0 in WM_PRINTCLIENT message without doing anything, I mean, preventing WM_PRINTCLIENT from calling DefWindowProc. This causes the tabcontrol header to have the same background color as its parent window. The same goes for the TrackBar.
I only have it tested on windows 10, I would like to know if it works for win 7 too.
I have the following code which sort of works provided you mask out the pink pixels however what I actually want is transparent bits like a PNG file so that I can avoid alpha blending issues and the need mask out a specific color everywhere the bitmap will be used.
CClientDC dc(pWnd);
CDC memDC;
if(!memDC.CreateCompatibleDC(&dc))
return NULL;
CRect bitmapRect;
bitmapRect.SetRectEmpty();
CFont* pOldFont = memDC.SelectObject(pWnd->GetFont());
CSize fontSize = memDC.GetTextExtent(imageText);
bitmapRect.right = fontSize.cx;
bitmapRect.bottom = fontSize.cy;
CBitmap bitmap;
if(!bitmap.CreateCompatibleBitmap(&dc, bitmapRect.Width(), bitmapRect.Height()))
return NULL;
CBitmap* pOldMemDCBitmap = memDC.SelectObject(&bitmap);
memDC.FillSolidRect(&bitmapRect, RGB(255,0,255));
//memDC.SetBkMode(TRANSPARENT); // doesn't work
//memDC.SetBkColor(TRANSPARENT); // doesn't work
memDC.SetTextColor(GetSysColor(COLOR_WINDOWTEXT));
//memDC.DrawText(imageText, bitmapRect, DT_TOP|DT_LEFT|DT_NOCLIP); // same difference
memDC.TextOut(0, 0, imageText);
memDC.SelectObject(pOldMemDCBitmap);
memDC.SelectObject(pOldFont);
memDC.DeleteDC();
CImageList bmImage;
bmImage.Create(bitmapRect.Width(), bitmapRect.Height(), ILC_COLOR32|ILC_MASK, 0, 1);
// this masks out the pink but for some windows blends edges of text causing pink text instead of black!
bmImage.Add(&bitmap, RGB(255,0,255));
Is just the bug filled beast that is MFC misbehaving or am I missing something?
Simple DrawText() with transparent background without MFC:
// in my case a user drawn button:
_windowHandle = CreateWindowEx(...);
SendMessage(_windowHandle, WM_SETFONT, (WPARAM)font, (LPARAM)NULL);
...
// WM_DRAWITEM
SetTextColor(hDC, RGB(216, 27, 27));
SetBkMode(hDC, TRANSPARENT);
RECT rect = { 0, 0, backgroundBitmap.bmWidth, backgroundBitmap.bmHeight };
DrawText(hDC, _text.c_str(), -1, &rect, DT_CENTER | DT_WORDBREAK);
--hfrmobile
About 10 minutes after asking this I read my own comment and realized that "some windows" means it was related to the windows being passed in. Specifically the font being used from said window. Fonts with default properties were exhibiting the strange blending.
At the end of the day I determined I needed to modify the font to turn off the things messing up my drawing code. I eventually narrowed it down to the one culprit causing the problem:
CClientDC dc(pWnd);
CDC memDC;
if(!memDC.CreateCompatibleDC(&dc))
return NULL;
LOGFONT tempFont;
CFont* winFont = pWnd->GetFont();
if (winFont)
winFont->GetLogFont(&tempFont);
else
{
// generate a likely font
SecureZeroMemory(&tempFont, sizeof(LOGFONT));
//TEXTMETRIC txt;
//GetTextMetrics(memDC, &txt);
//tempFont.lfHeight = txt.tmHeight * -1; // 16 is too big looking
tempFont.lfHeight = -12;
tempFont.lfWeight = FW_NORMAL;
tempFont.lfCharSet = DEFAULT_CHARSET;
wcscpy_s(tempFont.lfFaceName, L"Segoe UI"); // Win7 control default
}
tempFont.lfQuality = NONANTIALIASED_QUALITY; // this is the fix!!!
CFont newFont;
newFont.CreateFontIndirect(&tempFont);
CFont* pOldFont = memDC.SelectObject(&newFont);
// ... other stuff same as before ...
So I still FillSolidRect pink then draw my icons, text, whatever I want, etc. Then mask out the pink pixels. With the font quality adjustment it no longer blends pink into the font text and it looks good. The else case above creates a default font to use in case the CWnd* passed in doesn't have a valid one specified.