PrintWindow causes flicker, strange artifacts in titlebar - c++

I'm trying to take a screenshot of a Chrome window. It looks like this:
When I use PrintWindow to get the screenshot, I can see a flicker on the window titlebar/Chrome tab area. The captured screenshot contains a strange rendering of a titlebar in Windows Basic style (even though my machine runs the Aero theme):
I've noticed that some other apps also exhibit a similar behavior where they flicker on-screen but the titlebar artifact is not visible in the captured screenshot. Apps that do this include Office 2010, IE 10, and the Trillian tabbed chat window — in other words, windows that extend the non-client area seem to have this issue.
The code that reproduces this is simple:
void Screenshot(HWND hWnd) {
RECT rc;
GetClientRect(hWnd, &rc);
HDC hdcScreen = GetDC(NULL);
HDC hdc = CreateCompatibleDC(hdcScreen);
HBITMAP hbmp = CreateCompatibleBitmap(hdcScreen,
rc.right - rc.left, rc.bottom - rc.top);
SelectObject(hdc, hbmp);
//Print to memory hdc
PrintWindow(hWnd, hdc, PW_CLIENTONLY);
}
Why am I seeing flickering and strange visual artifacts? What can I do to stop it?

For those, who have the same problem, do this:
const uint PW_RENDERFULLCONTENT = 0x00000002;
PrintWindow(hWnd, hDC, PW_RENDERFULLCONTENT);

If Aero is enabled, use BitBlt instead.
This comment in the chromium desktop capture source code was especially helpful:
// When desktop composition (Aero) is enabled each window is rendered to a
// private buffer allowing BitBlt() to get the window content even if the
// window is occluded. PrintWindow() is slower but lets rendering the window
// contents to an off-screen device context when Aero is not available.

Related

Capturing invisible window with win32 API

Trying to capture contents of windows using BitBlt. In this particular case, which is probably important, i am speaking of invisible windows/invisible areas on windows - invisible because of z-order, they are visible but covered with other windows. All i get is a black box.
HDC winDC = GetWindowDC(hwnd);
HDC hdc_offscreen = CreateCompatibleDC(winDC);
HBITMAP bmp = CreateCompatibleBitmap(winDC, areaWidth, areaHeight);
HGDIOBJ origHandle = SelectObject(hdc_offscreen, bmp);
BitBlt(hdc_offscreen, rect.left, rect.top,
rect.right - rect.left,
rect.bottom - rect.top,
winDC, copy_from.left, copy_from.top, SRCCOPY);
// at this point i expect pixels to be there, but nah!
ReleaseDC(hwnd, winDC);
SelectObject(hdc_offscreen, origHandle);
DeleteDC(hdc_offscreen);
ReleaseDC(hWnd_main, winDC);
hwnd is the window i am trying to capture. Same things works like a charm with Aero theme enabled. I am using Windows 7 Professional.
Any idea what i am missing?
Your best bet (though it's not guaranteed to work) is to ask the other window to paint itself to your DC by sending it WM_PRINT.
HDC returned by GetWindowDC just doesn't contain a window's image. I can imagine that this HDC refers to the same "bitmap" as other windows. When Aero enabled, they apparently doesn't share the same "bitmap" and that's why you get a good result.
I would rather try to force window to paint to your HDC instead of reading HDC returned by GetWindowDC. To get client area you can try to call BeginPaint with your HDC, but who knows what if a window will check whether it (or it's part) is visible or not?
To get non client area you could pass WM_NCPAINT with your HDC.
Standard controls support HDC passed with WM_PAINT. Other windows may support it, but it's not required. If I remember correctly PrintWindow relies on this behavior.
In general I would say that no a 100% way to get a window's image but BeginPaint and WM_NCPAINT should be a good point of start.

Changing taskbar icon programmatically (Win32,C++) [duplicate]

This question already has an answer here:
Is there a Windows API for adding badges to taskbar icons?
(1 answer)
Closed 9 years ago.
I have a C++ win32 program, and I'd like to edit the taskbar icon at runtime to display alerts, etc about the program, however I'm not too experienced with the win32 api, and I haven't been able to find anything online. The closest I've found is http://www.windows-tech.info/17/52a5bfc45dac0ade.php which tells how to load the icon off the disc at runtime and change it.
I would like to do what they do in this question: Create an icon in memory with win32 in python but in C++ and without an external library.
I would like to do what they do in this question: Create an icon in memory with win32 in python but in C++ and without an external library
Since the accepted answer uses the wxWidgets library, which is just a wrapper of the Win32 API, the solution translates pretty nicely.
All you need to do is create a bitmap in memory using the CreateCompatibleBitmap function. Then you can draw into that bitmap using the standard GDI drawing functions. Finally, you create the icon using the CreateIconIndirect function.
The hardest part is keeping track of your resources and making sure that you free them all when you're finished to prevent memory leaks. It's way better if it's all wrapped up in a library that makes use of RAII to ensure the objects are properly freed, but if you're writing C code in C++, it would look like this:
HICON CreateSolidColorIcon(COLORREF iconColor, int width, int height)
{
// Obtain a handle to the screen device context.
HDC hdcScreen = GetDC(NULL);
// Create a memory device context, which we will draw into.
HDC hdcMem = CreateCompatibleDC(hdcScreen);
// Create the bitmap, and select it into the device context for drawing.
HBITMAP hbmp = CreateCompatibleBitmap(hdcScreen, width, height);
HBITMAP hbmpOld = (HBITMAP)SelectObject(hdcMem, hbmp);
// Draw your icon.
//
// For this simple example, we're just drawing a solid color rectangle
// in the specified color with the specified dimensions.
HPEN hpen = CreatePen(PS_SOLID, 1, iconColor);
HPEN hpenOld = (HPEN)SelectObject(hdcMem, hpen);
HBRUSH hbrush = CreateSolidBrush(iconColor);
HBRUSH hbrushOld = (HBRUSH)SelectObject(hdcMem, hbrush);
Rectangle(hdcMem, 0, 0, width, height);
SelectObject(hdcMem, hbrushOld);
SelectObject(hdcMem, hpenOld);
DeleteObject(hbrush);
DeleteObject(hpen);
// Create an icon from the bitmap.
//
// Icons require masks to indicate transparent and opaque areas. Since this
// simple example has no transparent areas, we use a fully opaque mask.
HBITMAP hbmpMask = CreateCompatibleBitmap(hdcScreen, width, height);
ICONINFO ii;
ii.fIcon = TRUE;
ii.hbmMask = hbmpMask;
ii.hbmColor = hbmp;
HICON hIcon = CreateIconIndirect(&ii);
DeleteObject(hbmpMask);
// Clean-up.
SelectObject(hdcMem, hbmpOld);
DeleteObject(hbmp);
DeleteDC(hdcMem);
ReleaseDC(NULL, hdcScreen);
// Return the icon.
return hIcon;
}
Adding the error checking and the code to draw something interesting onto the bitmap is left as an exercise for the reader.
As I said in a comment above, once you have created the icon, you can set the icon for a window by sending it a WM_SETICON message and passing the HICON as the LPARAM:
SendMessage(hWnd, WM_SETICON, ICON_BIG, (LPARAM)hIcon);
You can also specify ICON_SMALL in order to set the window's small icon. If you set only a big icon, it will be scaled down to create the small icon automatically. However, if you set only the small icon, the window will continue to use the default icon as its big icon. Big icons typically have a dimension of 32x32, while small icons typically have a dimension of 16x16. This is not, however, guaranteed, so do not hardcode these values. If you need to determine them, call the GetSystemMetrics function with SM_CXICON and SM_CYICON to retrieve the width and height of big icons, or SM_CXSMICON and SM_CYSMICON to retrieve the width and height of small icons.
A fairly good tutorial on drawing in Windows using GDI is available here. I recommend reading it thoroughly if this is your first time doing this and have no prior experience with GDI.

Applying Windows theme to custom control with WS_BORDER style

I have a custom control created using CreateWindowEx with the WS_BORDER style. Everything works fine apart from the border appearing in a different colour to other controls in the dialog box. The border in my control is black, the other controls have a blue border. I've tried calling EnableThemeDialogTexture(_dialogHandle, ETDT_ENABLE) after creating the control, as well as the logic from http://www.patchou.com/projects/richedit/ but to no avail. I'm using C++ and the Winapi. ie. no MFC, no .Net. Any guidance very much appreciated.
EDIT: Here's the logic that worked for me:
HDC hdc = GetWindowDC(hwnd);
HTHEME themeHandle = OpenThemeData(hwnd, L"Edit");
if(themeHandle)
{
int cxBorder = GetSystemMetrics(SM_CXBORDER);
int cyBorder = GetSystemMetrics(SM_CYBORDER);
RECT rc;
GetClientRect(hwnd, &rc);
OffsetRect(&rc, cxBorder, cyBorder);
ExcludeClipRect(hdc, rc.left, rc.top, rc.right, rc.bottom);
InflateRect(&rc, cxBorder, cyBorder);
DrawThemeBackground(themeHandle, hdc, 0, 0, &rc, NULL);
CloseThemeData(themeHandle);
}
ReleaseDC(hwnd, hdc);
You have to draw the border yourself, using the theme from another control (for example, the listview or treeview control). For a custom child control, drawing the border is quite easy - simply handle the WM_NCPAINT message. The part ID and state ID when you draw the border should both be 0.

screenshots of covered/minimized windows

I have the following code to take screenshots of a window:
HDC WinDC;
HDC CopyDC;
HBITMAP hBitmap;
RECT rt;
GetClientRect (hwnd, &rt);
WinDC = GetDC (hwnd);
CopyDC = CreateCompatibleDC (WinDC);
hBitmap = CreateCompatibleBitmap (WinDC,
rt.right - rt.left, //width
rt.bottom - rt.top);//height
SelectObject (CopyDC, hBitmap);
//Copy the window DC to the compatible DC
BitBlt (CopyDC, //destination
0,0,
rt.right - rt.left, //width
rt.bottom - rt.top, //height
WinDC, //source
0, 0,
SRCCOPY);
ReleaseDC(hwnd, WinDC);
ReleaseDC(hwnd, CopyDC);
It is someone elses code, slightly modified, as I am not really familiar with DC and how windows draws stuff to screen.
When I have one window slightly covering another, the covering window appears on screenshots of the covered one, which is kind of inconvenient. Also, when the window is minimized, this code produces nothing much interesting.
Is there any way around this? I would imagine that taking screenshots of a minimized application would be quite difficult, but I hope that getting screenshots of covered windows is possible. Perhaps there is a different method of implementing this to get around these problems?
No, a screenshot is exactly what it sounds like. You'll read the pixels out of the video adapter, what you get is what you see. You'll have to restore the window and bring it to the foreground to get the full view. WM_SYSCOMMAND+SC_RESTORE and SetForegroundWindow() respectively. Plus some time to allow the app to repaint its window if necessary.
The WM_PRINT message is available to ask a window to draw itself into a memory context. That could handle the overlapped window problem. But that can only work if is your window. And rarely has the expected result, programmers don't often implement WM_PRINT correctly.

How to draw in the nonclient area?

I'd like to be able to do some drawing to the right of the menu bar, in the nonclient area of a window.
Is this possible, using C++ / MFC?
Charlie hit on the answer with WM_NCPAINT. If you're using MFC, the code would look something like this:
// in the message map
ON_WM_NCPAINT()
// ...
void CMainFrame::OnNcPaint()
{
// still want the menu to be drawn, so trigger default handler first
Default();
// get menu bar bounds
MENUBARINFO menuInfo = {sizeof(MENUBARINFO)};
if ( GetMenuBarInfo(OBJID_MENU, 0, &menuInfo) )
{
CRect windowBounds;
GetWindowRect(&windowBounds);
CRect menuBounds(menuInfo.rcBar);
menuBounds.OffsetRect(-windowBounds.TopLeft());
// horrible, horrible icon-drawing code. Don't use this. Seriously.
CWindowDC dc(this);
HICON appIcon = (HICON)::LoadImage(AfxGetResourceHandle(), MAKEINTRESOURCE(IDR_MAINFRAME), IMAGE_ICON, 16, 16, LR_DEFAULTCOLOR);
::DrawIconEx(dc, menuBounds.right-18, menuBounds.top+2, appIcon, 0,0, 0, NULL, DI_NORMAL);
::DestroyIcon(appIcon);
}
}
In order to draw in the non-client area, you need to get the "window" DC (rather than "client" DC), and draw in the "window" DC.
You should try handling WM_NCPAINT. This is similar to a normal WM_PAINT message, but deals with the entire window, rather than just the client area. The MSDN documents on WM_NCPAINT provide the following sample code:
case WM_NCPAINT:
{
HDC hdc;
hdc = GetDCEx(hwnd, (HRGN)wParam, DCX_WINDOW|DCX_INTERSECTRGN);
// Paint into this DC
ReleaseDC(hwnd, hdc);
}
This code is intended to be used in the message loop of your applicaton, which is canonically organized using a large 'switch' statement.
As noted in the MFC example from Shog, make sure to call the default version, which in this example would mean a call to DefWindowProc.
If you just want something in the menu bar, maybe it is easier/cleaner to add it as a right-aligned menu item. This way it'll also work with different Windows themes, etc.