Set console window size on Windows - c++

I know that there is a lot questions about how to set console size. But all found solutions are the same to my and my code doesn't works for me.
Ok, so for setting console window size, I need two functions. They are SetConsoleScreenBufferSize() and SetConsoleWindowInfo(). First version of my function:
bool SetWindowSize(size_t width, size_t height)
{
HANDLE output_handle = ::GetStdHandle(STD_OUTPUT_HANDLE);
if(output_handle == INVALID_HANDLE_VALUE)
return false;
COORD coord = {};
coord.X = static_cast<SHORT>(width);
coord.Y = static_cast<SHORT>(height);
if(::SetConsoleScreenBufferSize(output_handle, coord) == FALSE)
return false;
SMALL_RECT rect = {};
rect.Bottom = coord.X - 1;
rect.Right = coord.Y - 1;
return (::SetConsoleWindowInfo(output_handle, TRUE, &rect) != FALSE);
}
SetConsoleScreenBufferSize() will work not for all values. From documentation:
The specified width and height cannot be less than the width and
height of the console screen buffer's window
Lets try to get current window's size and call our function. To get window size, I need GetConsoleScreenBufferInfo() function. main() test code:
HANDLE output_handle = ::GetStdHandle(STD_OUTPUT_HANDLE);
if(output_handle == INVALID_HANDLE_VALUE)
return 0;
CONSOLE_SCREEN_BUFFER_INFO info = {};
if(::GetConsoleScreenBufferInfo(output_handle, &info) == FALSE)
return 0;
size_t width = info.srWindow.Right - info.srWindow.Left;
size_t height = info.srWindow.Bottom - info.srWindow.Top;
bool suc = SetWindowSize(width + 1, height + 1);
In this case SetConsoleScreenBufferSize() works fine. Next function is SetConsoleWindowInfo(). This function will work in case:
The function fails if the specified window rectangle extends beyond
the boundaries of the console screen buffer. This means that the Top
and Left members of the lpConsoleWindow rectangle (or the calculated
top and left coordinates, if bAbsolute is FALSE) cannot be less than
zero. Similarly, the Bottom and Right members (or the calculated
bottom and right coordinates) cannot be greater than (screen buffer
height – 1) and (screen buffer width – 1), respectively. The function
also fails if the Right member (or calculated right coordinate) is
less than or equal to the Left member (or calculated left coordinate)
or if the Bottom member (or calculated bottom coordinate) is less than
or equal to the Top member (or calculated top coordinate).
In our case, the values of rectangle are the same (because Left and Top are zeroes) as values of info.srWindow rectangle after call of GetConsoleScreenBufferInfo(). But! SetConsoleWindowInfo() fails with next ::GetLastError()
#err,hr ERROR_INVALID_PARAMETER : The parameter is incorrect. unsigned int
If I swap calls of this two functions:
bool SetWindowSize(size_t width, size_t height)
{
HANDLE output_handle = ::GetStdHandle(STD_OUTPUT_HANDLE);
if(output_handle == INVALID_HANDLE_VALUE)
return false;
SMALL_RECT rect = {};
rect.Bottom = static_cast<SHORT>(width);
rect.Right = static_cast<SHORT>(height);
if(::SetConsoleWindowInfo(output_handle, TRUE, &rect) == FALSE)
return false;
COORD coord = {};
coord.X = rect.Bottom + 1;
coord.Y = rect.Right + 1;
return (::SetConsoleScreenBufferSize(output_handle, coord) != FALSE);
}
then I will have the same error.
So, how can I use SetConsoleScreenBufferSize() and SetConsoleWindowInfo() correctly ?

SetConsoleWindowInfo() does not reposition the console window on the screen. The name of this function is misleading. It rather scrolls the current visible portion inside the console window. See this sample here.
If you want to set the position of a console window that runs your programm, use code such as:
HWND hwnd = GetConsoleWindow();
RECT rect = {100, 100, 300, 500};
MoveWindow(hwnd, rect.left, rect.top, rect.right-rect.left, rect.bottom-rect.top,TRUE);

From the TurboVision port:
void TDisplay::setCrtMode( ushort mode )
{
int oldr = getRows();
int oldc = getCols();
int cols = uchar(mode >> 8);
int rows = uchar(mode);
if ( cols == 0 ) cols = oldc;
if ( rows == 0 ) rows = oldr;
checksize(rows, cols);
COORD newSize = { cols, rows };
SMALL_RECT rect = { 0, 0, cols-1, rows-1 };
if ( oldr <= rows )
{
if ( oldc <= cols )
{ // increasing both dimensions
BUFWIN:
SetConsoleScreenBufferSize( TThreads::chandle[cnOutput], newSize );
SetConsoleWindowInfo( TThreads::chandle[cnOutput], True, &rect );
}
else
{ // cols--, rows+
SMALL_RECT tmp = { 0, 0, cols-1, oldr-1 };
SetConsoleWindowInfo( TThreads::chandle[cnOutput], True, &tmp );
goto BUFWIN;
}
}
else
{
if ( oldc <= cols )
{ // cols+, rows--
SMALL_RECT tmp = { 0, 0, oldc-1, rows-1 };
SetConsoleWindowInfo( TThreads::chandle[cnOutput], True, &tmp );
goto BUFWIN;
}
else
{ // cols--, rows--
SetConsoleWindowInfo( TThreads::chandle[cnOutput], True, &rect );
SetConsoleScreenBufferSize( TThreads::chandle[cnOutput], newSize );
}
}
GetConsoleScreenBufferInfo( TThreads::chandle[cnOutput], &TThreads::sbInfo );
}
ushort TDisplay::getRows()
{
GetConsoleScreenBufferInfo( TThreads::chandle[cnOutput], &TThreads::sbInfo );
return TThreads::sbInfo.dwSize.Y;
}
ushort TDisplay::getCols()
{
GetConsoleScreenBufferInfo( TThreads::chandle[cnOutput], &TThreads::sbInfo );
return TThreads::sbInfo.dwSize.X;
}

I solved this issue by making these functions which can get/set the console window/buffer sizes in characters taking into account increasing the buffer size if needed, console font size, window borders and all that jazz.
The variables at play here to understand:
Windows have a client area, which is the coordinates (in pixels) excluding the borders
Windows have a window area, which is the coordinates (in pixels) including the borders
Console has a view area, which is the window size in characters
Console has a screen buffer, which is the scrollable buffer size in characters
Console has a font size, which is the character size in coordinates (pixels)
Console's screen buffer cannot be smaller than the view area
You need to correctly mix and match these around to achieve the desired result.
These functions are plug-n-play so you don't need to worry about none of that though.
All functions return TRUE (1) on success and FALSE (0) on error.
Set Console Window Size
static BOOL SetConsoleSize(int cols, int rows) {
HWND hWnd;
HANDLE hConOut;
CONSOLE_FONT_INFO fi;
CONSOLE_SCREEN_BUFFER_INFO bi;
int w, h, bw, bh;
RECT rect = {0, 0, 0, 0};
COORD coord = {0, 0};
hWnd = GetConsoleWindow();
if (hWnd) {
hConOut = GetStdHandle(STD_OUTPUT_HANDLE);
if (hConOut && hConOut != (HANDLE)-1) {
if (GetCurrentConsoleFont(hConOut, FALSE, &fi)) {
if (GetClientRect(hWnd, &rect)) {
w = rect.right-rect.left;
h = rect.bottom-rect.top;
if (GetWindowRect(hWnd, &rect)) {
bw = rect.right-rect.left-w;
bh = rect.bottom-rect.top-h;
if (GetConsoleScreenBufferInfo(hConOut, &bi)) {
coord.X = bi.dwSize.X;
coord.Y = bi.dwSize.Y;
if (coord.X < cols || coord.Y < rows) {
if (coord.X < cols) {
coord.X = cols;
}
if (coord.Y < rows) {
coord.Y = rows;
}
if (!SetConsoleScreenBufferSize(hConOut, coord)) {
return FALSE;
}
}
return SetWindowPos(hWnd, NULL, rect.left, rect.top, cols*fi.dwFontSize.X+bw, rows*fi.dwFontSize.Y+bh, SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOOWNERZORDER | SWP_NOZORDER);
}
}
}
}
}
}
return FALSE;
}
/* usage */
SetConsoleSize(80, 40);
Get Console Window Size
static BOOL GetConsoleSize(int* cols, int* rows) {
HWND hWnd;
HANDLE hConOut;
CONSOLE_FONT_INFO fi;
int w, h;
RECT rect = {0, 0, 0, 0};
hWnd = GetConsoleWindow();
if (hWnd) {
hConOut = GetStdHandle(STD_OUTPUT_HANDLE);
if (hConOut && hConOut != (HANDLE)-1) {
if (GetCurrentConsoleFont(hConOut, FALSE, &fi)) {
if (GetClientRect(hWnd, &rect)) {
w = rect.right-rect.left;
h = rect.bottom-rect.top;
*cols = w / fi.dwFontSize.X;
*rows = h / fi.dwFontSize.Y;
return TRUE;
}
}
}
}
return FALSE;
}
/* usage */
int cols, rows;
GetConsoleSize(&cols, &rows);
Set Console Buffer Size
static BOOL SetConsoleBufferSize(int cols, int rows) {
HANDLE hConOut;
CONSOLE_SCREEN_BUFFER_INFO bi;
COORD coord = {0, 0};
hConOut = GetStdHandle(STD_OUTPUT_HANDLE);
if (hConOut && hConOut != (HANDLE)-1) {
if (GetConsoleScreenBufferInfo(hConOut, &bi)) {
coord.X = cols;
coord.Y = rows;
return SetConsoleScreenBufferSize(hConOut, coord);
}
}
return FALSE;
}
/* usage */
SetConsoleBufferSize(80, 300);
Get Console Buffer Size
static BOOL GetConsoleBufferSize(int* cols, int* rows) {
HANDLE hConOut;
CONSOLE_SCREEN_BUFFER_INFO bi;
hConOut = GetStdHandle(STD_OUTPUT_HANDLE);
if (hConOut && hConOut != (HANDLE)-1) {
if (GetConsoleScreenBufferInfo(hConOut, &bi)) {
*cols = bi.dwSize.X;
*rows = bi.dwSize.Y;
return TRUE;
}
}
return FALSE;
}
/* usage */
int cols, rows;
GetConsoleBufferSize(&cols, &rows);

Related

DrawText Refresh Problem - Text Disappears

I'm using Objective Grid and wanted to have a grid cell control that can show an icon and text. So I took RogueWave's example (https://rwkbp.makekb.com/entry/466/) and modified it as below.
Initially the text renders correctly, but if I do anything in the grid that refreshes the relevant cell, the text disappears. If I update the whole grid, or even refresh the cell by dragging it off screen and back again, the text reappears - until it disappears again. Seems like a paint or InvalidateRect problem - but I can't figure out how to fix it. Any ideas?
Thanks!
//------------------------------------------------------------------------------
void CGXIconControl::Draw(CDC* pDC, CRect rect, ROWCOL nRow, ROWCOL nCol, const CGXStyle& style, const CGXStyle* pStandardStyle)
//------------------------------------------------------------------------------
{
BOOL b;
ASSERT(pDC != NULL && pDC->IsKindOf(RUNTIME_CLASS(CDC)));
// ASSERTION-> Invalid Device Context ->END
ASSERT(nRow <= Grid()->GetRowCount() && nCol <= Grid()->GetColCount());
// ASSERTION-> Cell coordinates out of range ->END
ASSERT_VALID(pDC);
DrawBackground(pDC, rect, style);
pDC->SetBkMode(TRANSPARENT);
if (rect.right <= rect.left || rect.Width() <= 1 || rect.Height() <= 1)
{
return;
}
CString str = style.GetIncludeValue() ? style.GetValue() : _T("");
CString sTxtBefore = _T("");
CString sTxtAfter = _T("");
int nHAlign = style.GetHorizontalAlignment();
int nVAlign = style.GetVerticalAlignment();
// Save these value to restore them when done drawing
COLORREF crOldTextColor = pDC->GetTextColor();
COLORREF crOldBkColor = pDC->GetBkColor();
if (!style.GetEnabled())
{
pDC->SetTextColor(::GetSysColor(COLOR_GRAYTEXT));
}
CBrush Brush;
Brush.CreateSolidBrush(style.GetEnabled() ? ::GetSysColor(COLOR_WINDOWTEXT) : ::GetSysColor(COLOR_GRAYTEXT));
LOGBRUSH lBrush = { 0 };
Brush.GetLogBrush(&lBrush);
CBrush* pOldBrush = (CBrush*)pDC->SelectStockObject(NULL_BRUSH);
CPen linePen;
linePen.CreatePen(PS_SOLID, 1, &lBrush);
CPen* pOldPen = pDC->SelectObject(&linePen);
// Set font bold if necessary
CFont* pCurfont = pDC->GetCurrentFont();
LOGFONT lf;
CFont font;
if (pCurfont)
{
pCurfont->GetLogFont(&lf);
}
if (style.GetFont().GetBold())
{
lf.lfWeight = FW_BOLD;
}
font.CreateFontIndirect(&lf);
CFont* pOldFont = pDC->SelectObject(&font);
int nIcoStart = str.Find(_T("#ICO"));
if (nIcoStart == -1)
{
// We didn't find an icon indicator, so just draw the text
pDC->DrawText(str, rect, DT_SINGLELINE|nHAlign|nVAlign);
}
else
{
sTxtBefore = str.Left(nIcoStart);
CSize szBefore = pDC->GetTextExtent(sTxtBefore);
int nIconEnd = str.Find(_T(")"), nIcoStart);
CString strIDResource = str.Mid(nIcoStart + 5, nIconEnd - (nIcoStart + 5));
UINT nIDResource = _ttoi(strIDResource);
// Load the highest bit-depth available
HICON hIcon = NULL;
hIcon = LoadResourceIcon(nIDResource, m_nIconSize, m_nIconSize, 32);
hIcon == NULL ? LoadResourceIcon(nIDResource, m_nIconSize, m_nIconSize, 24) : NULL;
hIcon == NULL ? LoadResourceIcon(nIDResource, m_nIconSize, m_nIconSize, 16) : NULL;
hIcon == NULL ? LoadResourceIcon(nIDResource, m_nIconSize, m_nIconSize, 8) : NULL;
sTxtAfter = str.Right(str.GetLength() - nIconEnd - 1);
CSize szAfter = pDC->GetTextExtent(sTxtAfter);
CRect rectCell = CGXControl::GetCellRect(nRow, nCol, rect, &style);
CRect rectBefore = rectCell;
CRect rectAfter = rectCell;
int nTotalWidth = szBefore.cx + m_nIconSize + szAfter.cx;
// Calculate positions
int nTop, nLeft;
switch (nHAlign)
{
case DT_LEFT:
{
rectBefore.right = rectBefore.left + szBefore.cx;
nLeft = rectBefore.right;
rectAfter.left = nLeft + m_nIconSize;
rectAfter.right = rectAfter.left + szAfter.cx;
} break;
case DT_CENTER:
{
rectBefore.left = (rectCell.right - rectCell.Width() / 2) - nTotalWidth / 2;
rectBefore.right = rectBefore.left + szBefore.cx;
nLeft = rectBefore.right;
rectAfter.left = nLeft + m_nIconSize;
rectAfter.right = rectAfter.left + szAfter.cx;
} break;
case DT_RIGHT:
{
// Work from the right
rectAfter.right = rectCell.right;
rectAfter.left = rectAfter.right - szAfter.cx;
nLeft = rectAfter.left - m_nIconSize;
rectBefore.right = nLeft;
rectBefore.left = rectBefore.right - szBefore.cx;
} break;
}
switch (nVAlign)
{
case DT_TOP:
nTop = rectCell.top;
break;
case DT_VCENTER:
nTop = rectCell.top + (rectCell.Height() / 2 - m_nIconSize / 2);
break;
case DT_BOTTOM:
nTop = rectCell.bottom - m_nIconSize;
break;
}
if (!sTxtBefore.IsEmpty())
{
pDC->DrawText(sTxtBefore, rectBefore, DT_SINGLELINE|nHAlign|nVAlign);
}
b = ::DrawIconEx(pDC->m_hDC, nLeft, nTop, hIcon, m_nIconSize, m_nIconSize, 0, NULL, DI_NORMAL);
b = ::DestroyIcon(hIcon);
if (!sTxtAfter.IsEmpty())
{
pDC->DrawText(sTxtAfter, rectAfter, DT_SINGLELINE|nHAlign|nVAlign);
}
}
// Reset original values
pDC->SetTextColor(crOldTextColor);
pDC->SetBkColor(crOldBkColor);
pDC->SelectObject(pOldBrush);
pDC->SelectObject(pOldPen);
pDC->SelectObject(pOldFont);
// Child Controls: spin-buttons, hotspot, combobox btn, ...
CGXControl::Draw(pDC, rect, nRow, nCol, style, pStandardStyle);
}

WriteConsoleOutputCharacterW - Where are my new lines?

I'm trying to learn how to use a screen buffer, and I made a mistake that I do not understand. These are the settings for my screen buffer:
wchar_t* screen = new wchar_t[nScreenWidth * nScreenHeight];
for (int i = 0; i < nScreenWidth * nScreenHeight; i++) {
screen[i] = L' ';
}
HANDLE hConsole = CreateConsoleScreenBuffer(GENERIC_READ | GENERIC_WRITE, 0, NULL, CONSOLE_TEXTMODE_BUFFER, NULL);
SetConsoleActiveScreenBuffer(hConsole);
DWORD dwBytesWritten = 0;
WriteConsoleOutputCharacterW(hConsole, screen, (nScreenWidth * nScreenHeight), { 0,0 }, &dwBytesWritten);
I manage to print to it my 2D array but it is weird that it's lying flat in my terminal window (see link to print screen).
Small print screen of my failed 2D array
It's as if all the new lines have been removed. This is my loop that prints my 2D array to "screen".
int g = 0;
while (g < 100) {
WriteConsoleOutputCharacterW(hConsole, screen, (nScreenWidth * nScreenHeight), { 0,0 }, &dwBytesWritten);
for (int i = 0; i < field.difficulty; i++) {
std::this_thread::sleep_for(std::chrono::milliseconds(50));
}
for (int y = 0; y < field.nFieldHeight; y++) {
for (int x = 0; x < field.nFieldWidth; x++) {
screen[(y + 2) * field.nFieldWidth + (x + 2)] = field.matrix[x][y];
}
}
}
Is it possible that I need to write to a coordinate in the screen buffer every time I print a character?
By default the console window is re-sizable and the this causes the output to wrap. You can prevent this by using the following:
// Get console window
HWND hwndWindow = GetConsoleWindow();
// Prevent resize & maximize
LONG lFlags = GetWindowLong(hwndWindow , GWL_STYLE) & ~WS_MAXIMIZEBOX & ~WS_SIZEBOX & ~WS_HSCROLL;
SetWindowLong(hwndWindow , GWL_STYLE, lFlags);
// Get console handle
HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
// Set window and buffer size
_SMALL_RECT consoleRect = { 0, 0, SCREEN_W - 1, SCREEN_H - 1 };
SetConsoleScreenBufferSize(hConsole, { SCREEN_W, SCREEN_H });
SetConsoleWindowInfo(hConsole, TRUE, &consoleRect);

C++ console application using double buffer size limitation

# include "DISPLAY.h"
DoubleBuffer::DoubleBuffer()
{
COORD size = { WINDOW_X_SIZE , WINDOW_Y_SIZE };
SMALL_RECT rect;
rect.Left = 0;
rect.Right = WINDOW_X_SIZE - 1;
rect.Top = 0;
rect.Bottom = WINDOW_Y_SIZE - 1;
hBuffer[0] = CreateConsoleScreenBuffer(GENERIC_READ | GENERIC_WRITE, 0, NULL, CONSOLE_TEXTMODE_BUFFER, NULL);
SetConsoleScreenBufferSize(hBuffer[0], size);
SetConsoleWindowInfo(hBuffer[0], TRUE, &rect);
hBuffer[1] = CreateConsoleScreenBuffer(GENERIC_READ | GENERIC_WRITE, 0, NULL, CONSOLE_TEXTMODE_BUFFER, NULL);
SetConsoleScreenBufferSize(hBuffer[1], size);
SetConsoleWindowInfo(hBuffer[1], TRUE, &rect);
CONSOLE_CURSOR_INFO cursorinfo;
cursorinfo.dwSize = 1;
cursorinfo.bVisible = FALSE;
SetConsoleCursorInfo(hBuffer[0], &cursorinfo);
SetConsoleCursorInfo(hBuffer[1], &cursorinfo);
nBufferIndex = 0;
}
void DoubleBuffer::WriteBuffer(int x, int y, char *string)
{
DWORD dw;
COORD startposition = { x,y };
SetConsoleCursorPosition(hBuffer[nBufferIndex], startposition);
WriteFile(hBuffer[nBufferIndex], string, strlen(string), &dw, NULL);
}
void DoubleBuffer::FlippBuffer()
{
Sleep(1000);
SetConsoleActiveScreenBuffer(hBuffer[nBufferIndex]);
nBufferIndex = !nBufferIndex;
}
void DoubleBuffer::ClearBuffer()
{
COORD coord = { 0,0 };
DWORD dw;
FillConsoleOutputCharacter(hBuffer[nBufferIndex], ' ', WINDOW_X_SIZE*WINDOW_Y_SIZE, coord, &dw);
}
void DoubleBuffer::ReleaseBuffer()
{
CloseHandle(hBuffer[0]);
CloseHandle(hBuffer[1]);
}
I used this code to construct a double buffer.
The entire game map which would be printed at the function 'WriteBuffer' was designed as a 1D char array.
But a big problem is that if the length of string goes over 80, then the console just shows 80 character in one line regardless of the console window size.
I mean, for example the input string is
char 1D_map[90] = {'D'};
and the console window size is 5*20.
Then no matter what the window size is, it shows like behind.
DDDDD (.. the left 70 characters are hidden..)
DDDDD (.. the left 5 character are hidden..)
As I pull the side bar of window to its maximum, it shows like behind.
DDDDDDDDDDDDDDDD ... 80 characters ... DDDDDDDDDDDDDDDDD
DDDDDDDDDD
So when I use these codes, then I cannot adjust the game map size.
(concretely horizontal size.)
Because whatever I do, it just shows 80 characters in a line!!
Can I fix the problem? or Should I apply other double buffering method?

How to get the usable width of the window's main menu?

Say, I have a Win32 app with a menu added to the main window via CreateWindowEx method, and I need to know its usable width. The best way to illustrate the width I'm asking for is with this diagram:
So how do I calculate it?
When I try to do the following, it gives me the width of the client area:
MENUBARINFO mbi = {0};
mbi.cbSize = sizeof(mbi);
if(::GetMenuBarInfo(hWnd, OBJID_MENU, 0, &mbi))
{
int nUsableWifth = mbi.rcBar.right - mbi.rcBar.left;
}
If you want to know the width that is used for menu items, you can either:
use GetMenuItemRect() to get the screen coordinates of the last menu item, and then convert them to client coordinates within the parent window. The converted right edge coordinate will give you the width:
HMENU hMenu = ::GetMenu(hWnd);
int count = ::GetMenuItemCount(hMenu);
RECT r;
if (::GetMenuItemRect(hWnd, hMenu, count-1, &r))
{
::MapWindowPoints(NULL, hWnd, (LPPOINT)&r, 2);
int nUsedWidth = r.right;
...
}
the above assumes the menu starts at offset 0 within the window's client area. If you don't want to rely on that, you could instead get the screen coordinates of the 1st menu item and subtract it from the right-edge screen coordinate of the last menu item:
HMENU hMenu = ::GetMenu(hWnd);
int count = ::GetMenuItemCount(hMenu);
RECT rFirst, rLast;
if (::GetMenuItemRect(hWnd, hMenu, 0, &rFirst) &&
::GetMenuItemRect(hWnd, hMenu, count-1, &rLast))
{
int nUsedWidth = rLast.right - rFirst.left;
...
}
Either way, if you then want to know the width that is not used for menu items, simply get the menu's total width and subtract the above calculated width:
MENUBARINFO mbi = {0};
mbi.cbSize = sizeof(mbi);
if (::GetMenuBarInfo(hWnd, OBJID_MENU, 0, &mbi))
{
int nUsableWidth = (mbi.rcBar.right - mbi.rcBar.left) - nUsedWidth;
...
}
UPDATE: I didn't realize a window's menu can wrap its items vertically if the client area is too small to display them all on one line. In that case, you might have to do something more like this instead to calculate nUsedWidth:
HMENU hMenu = ::GetMenu(hWnd);
int count = ::GetMenuItemCount(hMenu);
int nUsedWidth = 0;
RECT r;
for(int idx = 0; idx < count; ++idx)
{
if (::GetMenuItemRect(hWnd, hMenu, idx, &r))
{
::MapWindowPoints(NULL, hWnd, (LPPOINT)&r, 2);
if (r.right > nUsedWidth)
nUsedWidth = r.right;
}
}
...
Or:
HMENU hMenu = ::GetMenu(hWnd);
int count = ::GetMenuItemCount(hMenu);
int nUsedWidth = 0;
RECT rFirst, r;
if (::GetMenuItemRect(hWnd, hMenu, 0, &rFirst))
{
nUsedWidth = rFirst.right - rFirst.left;
for (int idx = 1; idx < count; ++idx)
{
if (::GetMenuItemRect(hWnd, hMenu, idx, &r))
{
int nWidth = r.right - rFirst.left;
if (nWidth > nUsedWidth)
nUsedWidth = nWidth;
}
}
}
...

How to render CRichEditCtrl on CDC with transparent backgorund ? (MFC)

I need help with rendering CRichEditCtrl content with transparent background on graphical context which is displayed on screen and printed as well.
Now I have following code, which is working good except transparency issues:
CRichEditCtrl ctrl; // my CRichEditCtrl
CDC *dc; // - my graphical context
dc->SetBkMode(TRANSPARENT);
dc->DPtoHIMETRIC(&targetSize);
CRect cHiMetricRect( 0, 0, origSize.cx*factor,origSize.cy*factor);
CRect cTwipsRect( 0, 0, (TWIPS_INCH * targetSize.cx + HIMETRIC_INCH / 2) / HIMETRIC_INCH, (TWIPS_INCH * targetSize.cy + HIMETRIC_INCH / 2) / HIMETRIC_INCH);
CMetaFileDC metaFile;
metaFile.CreateEnhanced( dc, NULL, cHiMetricRect, NULL );
metaFile.SetBkMode(TRANSPARENT);
metaFile.SetAttribDC( dc->m_hDC );
FORMATRANGE stFR;
stFR.hdcTarget = stFR.hdc = metaFile.m_hDC;
stFR.rcPage = stFR.rc = cTwipsRect;
stFR.chrg.cpMin = 0;
stFR.chrg.cpMax = -1;
ctrl.FormatRange( &stFR, TRUE);
ctrl.FormatRange( NULL, TRUE);
HENHMETAFILE hMetaFile = metaFile.CloseEnhanced();
dc->PlayMetaFile(hMetaFile,&cr);
DeleteEnhMetaFile(hMetaFile);
I need to render this text with transparency because there are already things drawn on my DC.
I tried to search web for any help about metafiles and transparency but found nothing adequate. I will be thankful for any kind of help.
I'm not very sure about MetaFiles, but I've done something similar with EMFs with straight WinAPI which does work -- call EnumEnhMetaFile instead of PlayMetaFile, like:
BOOL bFirstTime = TRUE;
EnumEnhMetaFile(hDC, m_hEmf, (ENHMFENUMPROC)EmfEnumProc_Play_TranspBackground, &bFirstTime, &rc);
where the EnumProc is defined as
int CALLBACK EmfEnumProc_Play_TranspBackground(HDC hDC, LPHANDLETABLE lpHTable, LPENHMETARECORD lpEMFR, int nObj, LPARAM lpData)
{ BOOL bOK;
if (lpEMFR->iType == EMR_SETBKMODE)
{ EMRSETBKMODE* lpEMRBkMode = (EMRSETBKMODE*) lpEMFR;
if (lpEMRBkMode->iMode == OPAQUE)
{ EMRSETBKMODE EmrBkMode;
EmrBkMode.emr.iType = EMR_SETBKMODE;
EmrBkMode.emr.nSize = (sizeof(EmrBkMode) % 4 == 0 ? sizeof(EmrBkMode) : (((sizeof(EmrBkMode) / 4) + 1) * 4));
EmrBkMode.iMode = TRANSPARENT;
bOK = PlayEnhMetaFileRecord(hDC, lpHTable, (LPENHMETARECORD)&EmrBkMode, (UINT)nObj);
return bOK;
}
}
bOK = PlayEnhMetaFileRecord(hDC, lpHTable, lpEMFR, (UINT)nObj);
if (lpEMFR->iType == EMR_HEADER)
{ BOOL* pbFirstTime = (BOOL*)lpData;
if (*pbFirstTime)
{ EMRSETBKMODE EmrBkMode;
EmrBkMode.emr.iType = EMR_SETBKMODE;
EmrBkMode.emr.nSize = (sizeof(EmrBkMode) % 4 == 0 ? sizeof(EmrBkMode) : (((sizeof(EmrBkMode) / 4) + 1) * 4));
EmrBkMode.iMode = TRANSPARENT;
PlayEnhMetaFileRecord(hDC, lpHTable, (LPENHMETARECORD)&EmrBkMode, (UINT)nObj);
*pbFirstTime = FALSE;
}
}
return bOK;
}
I used EnumEnhMetaFile function:
EnumEnhMetaFile(dc->m_hDC, hMetaFile, myMetafileProc, NULL, rect );
Then I used callback procedure that retrives each record of metafile:
int metafileProc( HDC hDC, HANDLETABLE *lpHTable, const ENHMETARECORD *lpEMFR, int nObj, LPARAM lpData) {
if ( lpEMFR->iType == EMR_EXTTEXTOUTW ) {
COLORREF_STRUCT *c = (COLORREF_STRUCT *)&(lastBgColor);
EMR_EXTTEXTOUTW_STRUCT *s = (EMR_EXTTEXTOUTW_STRUCT *)lpEMFR;
s->textStruct.options = 0x00;
if ( bgColorFlag ) {
bgColorFlag = false;
renderRect( hDC, s->textStruct.rect, c );
}
PlayEnhMetaFileRecord( hDC, lpHTable, lpEMFR, nObj );
} else if ( lpEMFR->iType == EMR_SETBKCOLOR ) {
BYTE *ptr = (BYTE*)lpEMFR;
COLORREF temp = *((COLORREF*)(ptr+8));
if ( temp != 0xFFFFFF ) {
lastBgColor = temp;
bgColorFlag = true;
}
}
return 1;
}
where renderRect( hDC, s->textStruct.rect, c ) is function which draws transparent background in c color. I have to zero options flag for text becouse there was different behaviour in Windows XP and Windows 7 and 8 - after that everything works ok. Thanks for help. Best regards.