Processing NM_CUSTOMDRAW on a pushbutton - but Windows still draws the text - winapi

I'm experimenting with using NM_CUSTOMDRAW instead of WM_DRAWITEM for bitmap buttons in my win32 app. The WM_DRAWITEM stuff works fine - except that it doesn't work under WINE with a desktop theme enabled (for some reason, with a theme enabled, WINE only sends WM_DRAWITEM when you click a pushbutton).
Anyway, I tried removing the BS_OWNERDRAW from the OK button below - leaving the others alone. To test processing the WM_NOTIFY, I just copy the fields I need from the NMCUSTOMDRAW struct to a DRAWITEMSTRUCT and pass that to my existing WM_DRAWITEM handler. The button draws fine, but then Windows draws the OK text over mine (my text is shifted to make room for the checkmark). I've pasted the code below. I thought that if I returned CDRF_SKIPDEFAULT in response to all NM_CUSTOMDRAW notifications, Windows wouldn't try to draw anything. There's obviously something else I need to do...
case WM_NOTIFY:
// Only intrested in NM_CUSTOMDRAW messages here.
nmh = (LPNMHDR) lParam;
if (nmh->code != NM_CUSTOMDRAW)
break;
// Only interested in CDDS_PREPAINT.
lpNMC = (LPNMCUSTOMDRAW) lParam;
if (lpNMC->dwDrawStage != CDDS_PREPAINT)
return(CDRF_SKIPDEFAULT);
// Copy fields we need from NMCUSTOMDRAW to a DRAWITEMSTRUCT.
memset(&dis, 0, sizeof(dis));
dis.hwndItem = nmh->hwndFrom;
dis.hDC = lpNMC->hdc;
dis.rcItem = lpNMC->rc;
if (lpNMC->uItemState & CDIS_FOCUS)
dis.itemState |= ODS_FOCUS;
if (lpNMC->uItemState & CDIS_SELECTED)
dis.itemState |= ODS_SELECTED;
if (lpNMC->uItemState & CDIS_DEFAULT)
dis.itemState |= ODS_DEFAULT;
if (lpNMC->uItemState & CDIS_DISABLED)
dis.itemState |= ODS_DISABLED;
DrawBitmapButtonOnWindowsDialog(wParam, (LPARAM) &dis, -1);
return(CDRF_SKIPDEFAULT);
case WM_DRAWITEM:
DrawBitmapButtonOnWindowsDialog(wParam, lParam, -1);
break;

Related

Any suggestions to effectively update the status bar of an application?

The status bar window of this program needs to be updated every time the user press a key that is likely to move the caret of the EDIT control, and the code below works like a charm! In a nutshell, pressing a key on the keyboard will update some values and send a message "ECM_GETLINEINFOS" that is next processed in the main window procedure (code below)
However, there is flickering that is not disturbing, of course, but I wonder if it's related to how I set the text on the status bar (maybe too many updates ?) or just a problem with the drawing part.
PS: The flickering occurs on the text, not the status bar in itself, so that is why I'm questioning how I should manage the update of my window.
constexpr int failed_val = -1;
LRESULT MainWindow::HandleMessage(UINT msg, WPARAM wParam, LPARAM lParam)
{
switch (msg)
{
// Custom message sent by an EDIT control, I
// use this message to tell the status bar it must update its text.
case CEM_GETLINEINFO:
{
const size_t buffSz = 24;
std::wstring buffer(buffSz, L'\0');
int line = LOWORD(wParam);
int column = HIWORD(wParam);
int count = _snwprintf_s(buffer.data(), buffer.size(),
_TRUNCATE, L"Ln %d, Col %d", line, column);
if (count != failed_val) {
// Param 1 : The text to be displayed
// Param 2 : Which status bar part
m_statusBar->SetText(buffer, 0);
}
}
return 0;
}
}
Just as Flicker-Free Displays Using an Off-Screen DC directed by the answer said,
What makes this window flicker when we update it frequently? The
answer is that Windows asks the window procedure to repaint the window
as a two-step process. First, it sends a WM_ERASEBKGND message and
then a WM_PAINT message. The default handling for the WM_ERASEBKGND
message is to fill the area with the current window background color.
So the sequence of events is first to fill the area with solid color
and then to draw the text on top. The net result of doing this
frequently is that the window state alternates between its erased
state and its drawn state—it flickers.
And
To prevent the control from flickering when we update it frequently,
we need to make two changes to how the control handles messages.
First, we need to prevent Windows from providing the default handling
of WM_ERASEBKGND messages. Secondly, we need to handle WM_PAINT
messages so that the background is painted with the window background
color and so that the changes to the control's client area happen at
once.
A status bar flicker free solution in .NET: Searching Visual Studio .NET style status bar. Or Simple Mode Status Bars could be enough.

Can't change edit text background when using Common Controls 6.0

I'm working on a Win32 C++ GUI Desktop application.
All the edit text are created statically in the resource file and their background is changed in the DialogBox routine using the WM_CTLCOLOREDIT and WM_CTLCOLORSTATIC messages.
case WM_CTLCOLOREDIT:
if (lParam == (LPARAM)Edit1Hwnd)
return SetBkColor((HDC)wParam, GuiColors[RED]);
else if (lParam == (LPARAM)Edit2Hwnd)
return SetBkColor((HDC)wParam, GuiColors[GREEN]);
break;
case WM_CTLCOLORSTATIC:
if (lParam == (LPARAM)Edit3Hwnd)
return SetBkColor((HDC)wParam, GuiColors[BLUE]);
else if (lParam == (LPARAM)Edit4Hwnd)
return SetBkColor((HDC)wParam, GuiColors[YELLOW]);
break;
When the window loads I can see that all the edit have the correct background color.
The problem rises when I use Common Controls 6.0 (which I need in order to load a BMP as a list-view background).
In order to enable Common Controls 6.0 I do:
#pragma comment(linker,"\"/manifestdependency:type='win32' \name='Microsoft.Windows.Common-Controls' version='6.0.0.0' \processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"")
If I run the program with the Common Controls enabled, all the edit text appear with a white background. But I can change the edit color selecting it with the mouse cursor.
Searching on the site I found this question.
It confirms the behaviour I experience, but it doesn't give a solution for changing the edit background color when it doesn't have the focus.
Any help is appreciated.
EDIT:
Thanks RbMm for your answer, I changed the code to
case WM_CTLCOLOREDIT:
if (lParam == (LPARAM)Edit1Hwnd)
return (LRESULT)redBrush;
break;
Where redBrush is declared and created like this
static HBRUSH redBrush = NULL;
redBrush = CreateSolidBrush(GuiColors[RED]);
The edit background color is properly red painted at the start, but when the edit gets the focus it turns white again (it turns back to red when the focus is lost).
EDIT 2:
Thanks zett42, now it works!
case WM_CTLCOLOREDIT:
if (lParam == (LPARAM)Edit1Hwnd){
SetBkColor((HDC)wParam, GuiColors[RED]);
return (LRESULT)redBrush;
}
break;

Deactivating desktop background when a pop up is shown to user

I have a win32 application that runs full screen when started. The application has some button which invoke pop up dialogs.
Is there a way to make the entire desktop (except the pop up) go transparent black unless the pop up is dismissed by the user? what I am talking of is similar to windows 7 UAC pop ups and the background it causes.
Is it possible to do similar stuff for a full screened window app?
It is possible do this…sort of. Perhaps I should say, you can simulate this effect. It won't actually be like the UAC dialog, as the user will still be able to interact with other running applications. There is no such concept as "system modal" available to applications. That's by design, of course. But you can certainly show a "light box" that dims out the rest of the desktop and forces focus on your app's dialog box.
The way I would do it is to create a giant layered window that sits on top of all other windows and covers the entire screen, fill it with black, and set the opacity as desired. Then, before you show a modal dialog (either by calling the MessageBox function or using the DialogBox function to show one of your own custom dialogs), display your light box window. Finally, after the user dismisses the modal dialog, you will destroy the light box window.
Here's some sample code. Error checking is omitted for brevity. So is other good style, like wrapping this mess up in one or more classes.
INT_PTR ShowLightBoxedDialog(HINSTANCE hInstance,
LPCTSTR pDlgTemplate,
HWND hwndParent,
DLGPROC pDlgProc,
BYTE opacityLevel)
{
const TCHAR szLightBoxClass[] = TEXT("LightBoxWndClass");
// Register the light box window class.
static bool lightBoxRegistered = false;
if (!lightBoxRegistered)
{
WNDCLASSEX wcex;
wcex.cbSize = sizeof(wcex);
wcex.style = CS_NOCLOSE | CS_SAVEBITS;
wcex.lpfnWndProc = LightBoxWndProc;
wcex.cbClsExtra = 0;
wcex.cbWndExtra = 0;
wcex.hInstance = hInstance;
wcex.hIcon = NULL;
wcex.hIconSm = NULL;
wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
wcex.hbrBackground = NULL;
wcex.lpszMenuName = NULL;
wcex.lpszClassName = szLightBoxClass;
RegisterClassEx(&wcex);
lightBoxRegistered = true;
}
// Create and display the light box window.
HWND hwndLightBox = CreateWindowEx(WS_EX_NOACTIVATE | WS_EX_LAYERED,
szLightBoxClass,
NULL,
WS_POPUPWINDOW,
0, 0, 0, 0,
hwndParent,
NULL,
hInstance,
NULL);
SetLayeredWindowAttributes(hwndLightBox, 0, opacityLevel, LWA_ALPHA);
SetWindowPos(hwndLightBox,
HWND_TOP,
GetSystemMetrics(SM_XVIRTUALSCREEN),
GetSystemMetrics(SM_YVIRTUALSCREEN),
GetSystemMetrics(SM_CXVIRTUALSCREEN),
GetSystemMetrics(SM_CYVIRTUALSCREEN),
SWP_SHOWWINDOW);
// Display the modal dialog box (as you normally would).
// NOTE: The dialog box won't appear centered on the screen.
// For that, you will need to write centering code in response
// to the WM_INITDIALOG message in the dialog procedure.
INT_PTR result = DialogBox(hInstance, pDlgTemplate, hwndLightBox, pDlgProc);
//
// For demonstration purposes, I used the following code:
// INT_PTR result = MessageBox(hwndLightBox,
// TEXT("OH NOEZ!\n\nYour system is kaput! Abandon þe all hope."),
// NULL,
// MB_ABORTRETRYIGNORE | MB_ICONERROR);
// Destroy the light box window.
DestroyWindow(hwndLightBox);
// Return the result of the modal dialog box.
return result;
}
You'll notice that basically what I've done is created a wrapper around the DialogBox function, which you use whenever you want a dialog box with a "light box" effect. It takes all of the same parameters (the first 4), and then there's an additional one tacked on the end that allows you to specify the opacity level used for the "light box" effect. Something in the range of 150–200 is probably good. Naturally, you could pick something and hard-code it, but I suffer from severe allergies to hard-coded values. Anyway, it's super easy to call this function from anywhere:
ShowLightBoxedDialog(hInstance, /* your application instance */
MAKEINTRESOURCE(IDD_SAMPLE), /* your dialog template */
hWnd, /* parent window for dialog */
SampleWndProc, /* ptr to dialog procedure */
175); /* light box opacity level */
Because the code takes advantage of how modal dialogs already work in Windows, the user won't be able to interact with any other pieces of your application until they dismiss the dialog box. And because the "light box" window is positioned on top of everything else, it eats all mouse clicks and prevents setting focus to any other application. But it is trivial to work around using something like Alt+Tab.
So this is not a security feature! It is merely a visual effect!
And because it's just a silly visual effect, it's likely to be a frustrating one for your users. I don't actually recommend using it. But now you know how to do it. Wield such power responsibly, etc.

How can I notify an application of a programmatically set scrollbar value?

My code involves standard Scroll Bar control and it happens that I need to change its value programmatically in some cases. I do this using SetScrollInfo function, as in this example:
void setScrollBarValue( HWND scrollBar, int value )
{
SCROLLINFO si = { sizeof( SCROLLINFO ); }
si.fMask = SIF_POS;
si.nPos = value;
::SetScrollInfo( scrollBar, SB_CTL, &si, true /* redraw */ );
}
This appears to work fine (the thumb of the scrollbar moves around) but it fails to notify the rest of the application of the new scrollbar value. For instance, an edit control which uses the scroll bar (much like in the Windows notepad application) fails to scroll around because it doesn't get notified about the new scrollbar value.
In case it matters: the scrollbar I'm modifying is not in the same process as the above
setScrollBarValue function.
Does anybody know how to achieve this?
Edit: I found out how to do this with default window scrollbars (those of type SB_VERT or SB_HORZ). I can send the WM_HSCROLL and WM_VSCROLL to the window like this:
::SendMessage( windowContainingScrollBar,
WM_HSCROLL,
MAKEWPARAM( SB_THUMBPOSITION, si.nPos ), NULL );
However, in my case the scroll bar has a window handle of its own (it has the type SB_CTL). This means that I don't know the orientation of the scroll bar (so I cannot tell whether to send WM_HSCROLL or WM_VSCROLL) and I don't know what window to send the message to.
Try sending the WM_VSCROLL message after calling SetScrollInfo().

Capturing a Window that is hidden or minimized

I followed this tutorial (there's a bit more than what's listed here because in my code I get a window via mouse click) for grabbing a window as a bitmap and then rendering that bitmap in a different window.
My question:
When that window is minimized or hidden (SW_HIDE) my screen capture doesn't work, so is it possible to capture a window when it is minimized or hidden?
The PrintWindow api works well, I use it for capturing thumbnails for hidden windows. Despite the name, it is different than WM_PRINT and WM_PRINTCLIENT, it works with pretty much every window except for Direct X / WPF windows.
I added some code (C#) but after reviewing how I used the code, I realized that the window isn't actually hidden when I capture its bitmap, its just off screen so this may not work for your case. Could you show the window off screen, do a print and then hide it again?
public static Bitmap PrintWindow(IntPtr hwnd)
{
RECT rc;
WinUserApi.GetWindowRect(hwnd, out rc);
Bitmap bmp = new Bitmap(rc.Width, rc.Height, PixelFormat.Format32bppArgb);
Graphics gfxBmp = Graphics.FromImage(bmp);
IntPtr hdcBitmap = gfxBmp.GetHdc();
bool succeeded = WinUserApi.PrintWindow(hwnd, hdcBitmap, 0);
gfxBmp.ReleaseHdc(hdcBitmap);
if (!succeeded)
{
gfxBmp.FillRectangle(new SolidBrush(Color.Gray), new Rectangle(Point.Empty, bmp.Size));
}
IntPtr hRgn = WinGdiApi.CreateRectRgn(0, 0, 0, 0);
WinUserApi.GetWindowRgn(hwnd, hRgn);
Region region = Region.FromHrgn(hRgn);
if (!region.IsEmpty(gfxBmp))
{
gfxBmp.ExcludeClip(region);
gfxBmp.Clear(Color.Transparent);
}
gfxBmp.Dispose();
return bmp;
}
There are WM_PRINT and WM_PRINTCLIENT messages you can send to the window, which cause its contents to be rendered into the HDC of your choice.
However, these aren't perfect: while the standard Win32 controls handle these correctly, any custom controls in the app might not.
I am trying to get the bitmap of partially hidden controls.
I used code before that did the drawing, but included windows overlapping it. So.. maybe you want to try this.
The WM_PRINTCLIENT should (in my understanding) redraw all inside the control, even if it is not really visible.
const int WM_PRINT = 0x317, WM_PRINTCLIENT = 0x318, PRF_CLIENT = 4,
PRF_CHILDREN = 0x10, PRF_NON_CLIENT = 2,
COMBINED_PRINTFLAGS = PRF_CLIENT | PRF_CHILDREN | PRF_NON_CLIENT;
SendMessage(handle, WM_PRINTCLIENT, (int)hdc, COMBINED_PRINTFLAGS);
//GDIStuff.BitBlt(hdc, 0, 0, width, height, hdcControl, 0, 0, (int)GDIStuff.TernaryRasterOperations.SRCCOPY);
The before code is commented out now. It is based on the code found here: Pocket PC: Draw control to bitmap (accepted answer). It is basically the same as Tim Robinson suggests in this thread.
Also, have a look here
http://www.tcx.be/blog/2004/paint-control-onto-graphics/

Resources