How to get correct hDevMode values from CPrintDialogEx (PrintDlgEx)? - windows

I'm displaying a CPrintDialogEx dialog to choose a printer and modify the settings. I set the hDevNames member so that a default printer will be selected, but I leave hDevMode set to NULL. On successful return I pull some values such as paper size out of the returned DEVMODE structure from hDevMode.
I'm having a problem because hDevMode appears to be initialized with the values from the default printer that I passed in, not the printer that was finally selected. How do I get the parameters from the actual selected printer?
As requested here's the relevant part of the code. I've deleted some of it in the interest of space. TOwnedHandle is a smart pointer I wrote for holding a memory handle and locking it automatically.
CPrintDialogEx dlg(PD_ALLPAGES | PD_NOCURRENTPAGE | PD_NOPAGENUMS | PD_NOSELECTION, this);
ASSERT(dlg.m_pdex.hDevMode == NULL);
ASSERT(dlg.m_pdex.hDevNames == NULL);
dlg.m_pdex.hDevNames = GlobalAlloc(GHND, sizeof(DEVNAMES) + iSizeName);
DEVNAMES * pDevNames = (DEVNAMES *) GlobalLock(dlg.m_pdex.hDevNames);
// ...
GlobalUnlock(dlg.m_pdex.hDevNames);
if ((dlg.DoModal() == S_OK) && (dlg.m_pdex.dwResultAction == PD_RESULT_PRINT))
{
TOwnedHandle<DEVMODE> pDevMode = dlg.m_pdex.hDevMode;
TRACE("Printer config = %dx%d %d\n", (int)pDevMode->dmPaperWidth, (int)pDevMode->dmPaperLength, (int)pDevMode->dmOrientation);
// ...
}
Edit: I've determined that I don't get the problem if I don't set the hDevNames parameter. I wonder if I've discovered a Windows bug? This is in XP, I don't have a more recent version of Windows handy to test with.
I've distilled the code into a test that doesn't use MFC, this is strictly a Windows API problem. This is the whole thing, nothing left out except the definition of pDefaultPrinter - but of course it doesn't do anything useful anymore.
PRINTDLGEX ex = {sizeof(PRINTDLGEX)};
ex.hwndOwner = m_hWnd;
ex.Flags = PD_ALLPAGES | PD_NOCURRENTPAGE | PD_NOPAGENUMS | PD_NOSELECTION;
ex.nStartPage = START_PAGE_GENERAL;
#if 1
int iSizeName = (strlen(pDefaultPrinter) + 1) * sizeof(char);
ex.hDevNames = GlobalAlloc(GHND, sizeof(DEVNAMES) + iSizeName);
DEVNAMES * pDevNames = (DEVNAMES *) GlobalLock(ex.hDevNames);
ASSERT(pDevNames != NULL);
pDevNames->wDeviceOffset = sizeof(DEVNAMES);
strcpy((char *)pDevNames + pDevNames->wDeviceOffset, pDefaultPrinter);
GlobalUnlock(ex.hDevNames);
#endif
HRESULT hr = PrintDlgEx(&ex);
if ((hr == S_OK) && (ex.dwResultAction == PD_RESULT_PRINT))
{
DEVMODE * pdm = (DEVMODE *) GlobalLock(ex.hDevMode);
ASSERT(pdm != NULL);
TRACE("Printer config = %dx%d %d\n", (int)pdm->dmPaperWidth, (int)pdm->dmPaperLength, (int)pdm->dmOrientation);
GlobalUnlock(ex.hDevMode);
DEVNAMES * pdn = (DEVNAMES *) GlobalLock(ex.hDevNames);
ASSERT(pdn != NULL);
TRACE(_T("Printer device = %s\n"), (char *)pdn + pdn->wDeviceOffset);
GlobalUnlock(ex.hDevNames);
}
If I can't get a fix, I'd love to hear of a work-around.

After much head scratching I think I've figured it out.
When the dialog comes up initially, the hDevMode member gets filled with the defaults for the printer that is initially selected. If you select a different printer before closing the dialog, that DEVMODE structure is presented to the new printer driver; if the paper size doesn't make sense to the driver it may change it, and the drivers are not consistent.
The reason this tripped me up is that I was switching between three printers: two label
printers with very different characteristics, and a laser printer with US Letter paper.
The laser printer always responds with the proper dimensions but may indicate a wrong paper size code.
The first label printer will override the size provided by the laser printer but not the other label printer.
The second label printer will accept the size provided by the first label printer, because it's capable of using that size even though it's not loaded and not configured. It modifies the size provided by the laser printer by returning the maximum width and the Letter size length of 11 inches.
I determined two ways to work around the problem. The first is to implement IPrintDialogCallback and respond to SelectionChange calls by reloading the default DEVMODE for the newly selected printer. EDIT: I tried this and it does not work. CPrintDialogEx already implements an IPrintDialogCallback interface, making this easy. It appears that PrintDlgEx has its own internal handle that it uses to track the current DEVMODE structure and only uses the one in the PRINTDLGEX structure for input/output. There's no way to affect the DEVMODE while the dialog is up, and by the time it returns it's too late.
The second solution is to ignore the returned results entirely and work from the default paper configuration for the printer. Any changes made from the printer defaults within the dialog are lost completely, but for my application this is acceptable.
bool MyDialog::GetPaperSize(const TCHAR * pPrinterName, double & dPaperWidth, double & dPaperLength)
{
// you need to open the printer before you can get its properties
HANDLE hPrinter;
if (OpenPrinter((TCHAR *)pPrinterName, &hPrinter, NULL))
{
// determine how much space is needed for the DEVMODE structure by the printer driver
int iDevModeSize = DocumentProperties(m_hWnd, hPrinter, (TCHAR *)pPrinterName, NULL, NULL, 0);
ASSERT(iDevModeSize >= sizeof(DEVMODE);
// allocate a DEVMODE structure and initialize it to a clean state
std::vector<char> buffer(iDevModeSize, 0);
DEVMODE * pdm = (DEVMODE *) &buffer[0];
pdm->dmSpecVersion = DM_SPECVERSION;
DocumentProperties(m_hWnd, hPrinter, (TCHAR *)pPrinterName, pdm, NULL, DM_OUT_BUFFER);
ClosePrinter(hPrinter);
// convert paper size from tenths of a mm to inches
dPaperWidth = pdm->dmPaperWidth / 254.;
dPaperLength = pdm->dmPaperLength / 254.;
return true;
}
return false;
}

Related

Questions regarding GetWindowPlacement return data

I'm a bit unsure of the meaning of some of the return values from a call to the GetWindowPlacement() function, so I'd like your help, please.
I'll be calling this to obtain the normal dimensions of a hidden window.
First, where do the values of the showCmd field come from? In the Microsoft documentation of the return structure (WINDOWPLACEMENT structure, all the descriptions of the possible values use verbs/action words; e.g., "SW_MAXIMIZE: Maximizes the specified window", or "SW_SHOWNOACTIVATE: Displays a window in its most recent size and position."
I want to obtain the dimensions of the hidden window without unhiding/restoring it first, so with the verbs it seems that I would have to call SetWindowPlacement() with showCmd set to SW_SHOWNOACTIVATE before calling GetWindowPlacement. Is that correct?
So do I understand correctly that the primary (and perhaps only) way that field gets its various values is by an explicit call to SetWindowPlacement() somewhere?
My second question relates to the rcNormalPosition return values. Do those data include the window decorations, or are they client values?
Thank you for your time!
The meaning of the showCmd member of the WINDOWPLACEMENT struct is a bit confusing because Win32 is reusing the SW_* commands used by ShowWindow().
Luckily, the meaning is documented on the GetWindowPlacement() function.
If the window identified by the hWnd parameter is maximized, the
showCmd member is SW_SHOWMAXIMIZED. If the window is minimized,
showCmd is SW_SHOWMINIMIZED. Otherwise, it is SW_SHOWNORMAL.
So, based on which of those 3 values is returned, you can tell whether the window is currently maximized, minimized or, normal (restored). And if you'd like to know what the normal placement is, you can just use the rcNormalPosition member. You do not need to call SetWindowPlacement() at all.
However, heed the warning that GetWindowPlacement() returns workspace coordinates rather than screen coordinates, which differ based on taskbar position and size. This is not a problem if you are only using the coordinates returned by GetWindowPlacement() to call SetWindowPlacement(). Otherwise, you might have to find a way to convert from workspace to screen coordinates.
I found these 2 functions to work for me.
void MyDialog::LoadDialogPlacement()
{
static WINDOWPLACEMENT last_wp = {};
// Load last stored DB version
WINDOWPLACEMENT *wp = new WINDOWPLACEMENT;
GetStoredWindowPlacement(&wp);
if (memcmp((void *)&last_wp, (const void *)wp, sizeof(WINDOWPLACEMENT)) == 0) return;
memcpy((void *)&last_wp, (const void *)wp, sizeof(WINDOWPLACEMENT));
SetWindowPlacement(wp);
delete[] wp;
}
void MyDialog::SaveDialogPlacement()
{
static WINDOWPLACEMENT last_wp = {};
if (IsWindowVisible())
{
WINDOWPLACEMENT wp = {};
wp.length = sizeof(WINDOWPLACEMENT);
GetWindowPlacement(&wp);
if (memcmp((void *)&last_wp, (const void *)&wp, wp.length) == 0) return;
memcpy((void *)&last_wp, (const void *)&wp, wp.length);
StoreWindowPlacement(&wp);
}
}

Windows change system resolution c++

I am trying to change system resolution programatically. What I need to achieve is set system resolution to maximum value.
Below is code the code which change the screen resolution to maximum available value.
DEVMODE devmode = { 0 };
int i=0;
QList<int>widths;
QList<int>heights;
while (1) {
if(EnumDisplaySettings( NULL,i, &devmode )!=true)
break;
widths.append(devmode.dmPelsWidth);
heights.append(devmode.dmPelsHeight);
qDebug()<<devmode.dmPelsWidth<<" X "<<devmode.dmPelsHeight;
i++;
}
EnumDisplaySettings( NULL,ENUM_CURRENT_SETTINGS, &devmode );
devmode.dmSize = sizeof(DEVMODE);
devmode.dmPelsWidth = widths.at(widths.size()-1); //take last item maximum value
devmode.dmPelsHeight = heights.at(widths.size()-1); //take last item maximum value
long result = ChangeDisplaySettings(&devmode, DM_PELSWIDTH || DM_PELSHEIGHT);
And it works fine when,
When the system resolution is maximum(1920x1080) I change to the same value.
When the system resolution is higher and I am changed to lower value.
But it doesn't work when I am changing from lower value to higher, like current resolution is 1600X900 and when I am changing to 1920x1080 then it wont work.
And I am getting -2 as return value and which stand for The graphics mode is not supported on the doc.
There are 2 issues with the way you're calling the function.
First, there is a typo. DM_PELSWIDTH || DM_PELSHEIGHT evaluates to 1, not the combination of the two flags. You probably meant DM_PELSWIDTH | DM_PELSHEIGHT
Second, those are not the flags you should be passing to ChangeDisplaySettings. Here's the correct way to call the function:
DEVMODE desiredMode = { 0 };
desiredMode.dmSize = sizeof(DEVMODE);
desiredMode.dmPelsWidth = 1920;
desiredMode.dmPelsHeight = 1080;
desiredMode.dmFields = DM_PELSHEIGHT | DM_PELSWIDTH;
LONG res = ChangeDisplaySettings(&desiredMode, CDS_UPDATEREGISTRY | CDS_GLOBAL | CDS_RESET);
The dmFields member of the DEVMODE structure is where you tell the system which fields you want to change. The flags on ChangeDisplaySettings specify how those settings are applied. The reason why the call with DM_PELSWIDTH || DM_PELSHEIGHT didn't fail with DISP_CHANGE_BADFLAGS is that 1 is the value of the CDS_UPDATEREGISTRY flag.
As a side note: if you are just temporarily changing resolution for your application (like games do) then pass the flag CDS_FULLSCREEN by itself and it will revert the settings when your application exits. The combination of flags in the example above, sets the settings for all users, stores it in the registry, and applies the change immediately.

Getting the exact edit box dimensions for the given text

I need to be able to determine the size of the edit box according to the text I have, and a maximum width.
There are similar questions and answers, which suggest GetTextExtentPoint32 or DrawTextEx.
GetTextExtentPoint32 doesn't support multiline edit controls, so it doesn't fit.
DrawTextEx kind of works, but sometimes the edit box turns out to be larger than necessary, and, what's worse, sometimes it's too small.
Then there's EM_GETRECT and EM_GETMARGINS. I'm not sure whether I should use one of them, or maybe both.
What is the most accurate method for calculating the size? This stuff is more complicated then it should be... and I prefer not to resort to reading the source code of Wine or ReactOS.
Thanks.
Edit
Here's my code and a concrete example:
bool AutoSizeEditControl(CEdit window, LPCTSTR lpszString, int *pnWidth, int *pnHeight, int nMaxWidth = INT_MAX)
{
CFontHandle pEdtFont = window.GetFont();
if(!pEdtFont)
return false;
CClientDC oDC{ window };
CFontHandle pOldFont = oDC.SelectFont(pEdtFont);
CRect rc{ 0, 0, nMaxWidth, 0 };
oDC.DrawTextEx((LPTSTR)lpszString, -1, &rc, DT_CALCRECT | DT_EDITCONTROL | DT_WORDBREAK);
oDC.SelectFont(pOldFont);
::AdjustWindowRectEx(&rc, window.GetStyle(), (!(window.GetStyle() & WS_CHILD) && (window.GetMenu() != NULL)), window.GetExStyle());
UINT nLeftMargin, nRightMargin;
window.GetMargins(nLeftMargin, nRightMargin);
if(pnWidth)
*pnWidth = rc.Width() + nLeftMargin + nRightMargin;
if(pnHeight)
*pnHeight = rc.Height();
return true;
}
I call it with nMaxWidth = 143 and the following text (below), and get nHeight = 153, nWidth = 95. But the numbers are too small for the text to fit, on both axes.
The text (two lines):
Shopping
https://encrypted.google.com/search?q=winapi+resize+edit+control+to+text+size&source=lnms&tbm=shop&sa=X&ved=0ahUKEwiMyNaWxZjLAhUiLZoKHQcoDqUQ_AUICigE
Edit 2
I found out that the word wrapping algorithm of DrawTextEx and of the exit control are different. For example, the edit control wraps on ?, DrawTextEx doesn't. What can be done about it?

CreateIconFromResource return NULL and UI crashed after being called thousands of times

Anybody got this problem, anyway I didn't find an answer. The code is simple:
void CbDlg::OnBnClickedOk()
{
for(int i=0; i<1000; i++)
{
HRSRC hRes = ::FindResource(NULL, MAKEINTRESOURCE(IDR_MAINFRAME), RT_GROUP_ICON);
HGLOBAL hResLoad = ::LoadResource(NULL, hRes);
BYTE* pIconBytes = (BYTE*)::LockResource(hResLoad);
int nId = ::LookupIconIdFromDirectory(pIconBytes, TRUE);
hRes = ::FindResource(NULL, MAKEINTRESOURCE(nId), RT_ICON);
DWORD read = ::SizeofResource(NULL ,hRes);
hResLoad = ::LoadResource(NULL, hRes);
pIconBytes = (BYTE*)::LockResource(hResLoad);
if(pIconBytes != NULL)
{
HICON hIcon = ::CreateIconFromResource(pIconBytes, read, TRUE, 0x00030000);
DWORD e = ::GetLastError();
if(hIcon != NULL)
{
::DestroyIcon(hIcon);
}
}
}
}
If I click the Ok button four times (On my computer), CreateIconFromResource start to return NULL (It worked fine before and I could even draw out the icon). As to the GetLastError, it's always return 6 whatever CreateIconFromResource return NULL or not.
When this problem happened, if I drag the title bar to move, UI crashed, see the pictrue.
Of course you can understand this piece of code is just a demo, my real business need to call CreateIconFromResource thousands of times just like this.
UPDATE:
According to Hans' suggestion, I keep tracking the Handles/USER Objects/GDI objects, and found that USER Objects grows 1000 and GDI objects grows 2000 against each clicking to OK button (handles didn't grow), and GDI objects is 9999 when problem happens. But how to release them correctly, when I finish to use? I didn't use that much at one time, but need to load, release, load again, release again... Just like this demo. As MSDN document, I called DestroyIcon for every HICON. What else do I need to do, to finally release the USER/GDI objects?
I found the answer. The success or failure is all due to MSDN.
It says:
"The CreateIconFromResource function calls CreateIconFromResourceEx passing LR_DEFAULTSIZE|LR_SHARED as flags" AND "Do not use this function(DestroyIcon) to destroy a shared icon"
But It also says:
"When you are finished using the icon, destroy it using the DestroyIcon function" in CreateIconFromResource's document.
Actually, the second statement is WRONG.
So, the solution is, using CreateIconFromResourceEx without LR_SHARED, and DestroyIcon every HICON after using.

Creating X11 window to span multiple displays

I'm having the exact problem described here. How to make X11 window span multiple monitors
I have six monitors and am trying to create a window larger than the size of one of the monitors. It keeps getting resized by the window manager.
Apologize if I should post within that thread, the etiquette is not clear to me.
Anhow, I do the following in my code:
/* Pass some information along to the window manager to size the window */
sizeHints.flags = USSize; // | PMinSize;
sizeHints.width = sizeHints.base_width = width;
sizeHints.height = sizeHints.base_height = height;
// sizeHints.min_width = width;
// sizeHints.min_height = height;
// sizeHints.max_width = mScreenWidth;
// sizeHints.max_height = mScreenHeight;
if (geometry->x != DONT_CARE && geometry->y != DONT_CARE) {
sizeHints.x = geometry->x;
sizeHints.y = geometry->y;
sizeHints.flags |= USPosition;
}
XSetNormalHints(mDisplay, mWindow, &sizeHints);
SetTitle(suggestedName);
XSetStandardProperties(mDisplay, mWindow,
suggestedName.toAscii(), suggestedName.toAscii(),
None, (char **)NULL, 0, &sizeHints);
/* Bring it up; then wait for it to actually get here. */
XMapWindow(mDisplay, mWindow);
The problem I'm having is that if I set min_width and min_height, the user cannot resize the window, which is not what I want. But if I don't, then when I do any X11 call later, such as
XGetWindowAttributes(mDisplay, mWindow, &win_attributes);
the window manager resizes my window to fit into one monitor instead of being larger than the monitor. I cannot just get a window of the desired size for some reason. Note that WidthOfScreen and HeightOfScreen give me the combined width and height of all monitors as expected.
Can anyone help? I hope I'm explaining myself clearly enough.

Resources