Can't seem to make double-buffered painting with multiple monitors - c++

I'm trying to remake my Windows screensaver written with C++ and WinAPIs to work on multiple monitors. I found this article that gives the basics. But when I implement it in my own code, I get a weird result. Take a look at this code:
case WM_PAINT:
{
PAINTSTRUCT ps = {0};
HDC hdcE = BeginPaint(hWnd, &ps );
EnumDisplayMonitors(hdcE, NULL, MyPaintEnumProc, 0);
EndPaint(hWnd, &ps);
}
break;
BOOL CALLBACK MyPaintEnumProc(
HMONITOR hMonitor, // handle to display monitor
HDC hdc1, // handle to monitor DC
LPRECT lprcMonitor, // monitor intersection rectangle
LPARAM data // data
)
{
MONITORINFO mi = {0};
mi.cbSize = sizeof(mi);
if(GetMonitorInfo(hMonitor, &mi))
{
//Is it a primary monitor?
BOOL bPrimary = mi.dwFlags & MONITORINFOF_PRIMARY;
DoDrawing(bPrimary, hdc1, &mi.rcMonitor);
}
return 1;
}
void DoDrawing(BOOL bPrimaryMonitor, HDC hDC, RECT* pRcMonitor)
{
//#define DIRECT_PAINT //Comment out for double-buffering
int nMonitorW = abs(pRcMonitor->right - pRcMonitor->left);
int nMonitorH = abs(pRcMonitor->bottom - pRcMonitor->top);
HDC hMemDC = ::CreateCompatibleDC(hDC);
if(hMemDC)
{
HBITMAP hMemBmp = ::CreateCompatibleBitmap(hDC, nMonitorW, nMonitorH);
if(hMemBmp)
{
HBITMAP hOldBmp = (HBITMAP)SelectObject(hMemDC, hMemBmp);
COLORREF clr, clrBorder;
if(bPrimaryMonitor)
{
clr = RGB(0, 128, 0); //Green
clrBorder = RGB(255, 0, 0);
}
else
{
clr = RGB(128, 0, 0); //Red
clrBorder = RGB(0, 255, 0);
}
RECT rcRect;
#ifndef DIRECT_PAINT
//With double-buffering
rcRect.left = 0;
rcRect.top = 0;
rcRect.right = nMonitorW;
rcRect.bottom = nMonitorH;
#else
rcRect = *pRcMonitor;
#endif
HBRUSH hBrush = ::CreateSolidBrush(clr);
#ifndef DIRECT_PAINT
//With double-buffering
::FillRect(hMemDC, &rcRect, hBrush);
#else
::FillRect(hDC, &rcRect, hBrush);
#endif
#ifndef DIRECT_PAINT
//With double-buffering
::BitBlt(hDC, pRcMonitor->left, pRcMonitor->top, nMonitorW, nMonitorH, hMemDC, 0, 0, SRCCOPY);
#endif
//Debugging output
CString _s;
_s.Format(_T("%s\n")
_T("%s\n")
_T("hDC=0x%X\n")
_T("hMemDC=0x%X\n")
_T("RcMonitor: L=%d, T=%d, R=%d, B=%d")
,
bPrimaryMonitor ? _T("Primary") : _T("Secondary"),
#ifndef DIRECT_PAINT
_T("Double-buffering"),
#else
_T("Direct paint"),
#endif
hDC,
hMemDC,
pRcMonitor->left,
pRcMonitor->top,
pRcMonitor->right,
pRcMonitor->bottom);
::DrawText(hDC, _s, _s.GetLength(), pRcMonitor, DT_NOCLIP | DT_NOPREFIX);
SelectObject(hMemDC, hOldBmp);
::DeleteObject(hMemBmp);
}
::DeleteDC(hMemDC);
}
}
Painting always works on a primary monitor. But when I paint to the secondary monitor, I can only paint directly to its DC. When I use double-buffering technique (with DIRECT_PAINT pre-processor directive commented out) I only get a black screen on a secondary monitor when it should've been red.
I'm attaching two screenshots here.
First one with direct painting that works:
And then the one with double-buffering that fails:
Any ideas what am I doing wrong here?

Replace the code for WM_PAINT from
case WM_PAINT:
hdc = BeginPaint(hWnd, &ps);
EnumDisplayMonitors(hdc, NULL, MyPaintEnumProc, 0);
EndPaint(hWnd, &ps);
with
case WM_PAINT:
hdc = GetDC(NULL);
EnumDisplayMonitors(hdc, NULL, MyPaintEnumProc, 0);
ReleaseDC(NULL, hdc);
and it will work.
See this http://msdn.microsoft.com/en-us/library/windows/desktop/dd162610(v=vs.85).aspx.

Related

Not drawing bitmap

GIF::GIF(Bitmap* bit, HDC hdc) {
this->bit = bit;
}
void GIF::draw(Graphics* g,HDC hdc, Bitmap *bit)
{
this->position.x = 50;
this->position.y = 50;
this->CachedBitmap = PNGLoader::createCachedBitmap(this->bit, hdc);
g->DrawCachedBitmap(CachedBitmap.get(), position.x, position.y);
}
std::shared_ptr<CachedBitmap> PNGLoader::createCachedBitmap(Bitmap*
originalBitmap, HDC hdc)
{
Graphics graphics(hdc);
return std::make_shared<CachedBitmap>(originalBitmap,
&graphics);
}
paint:
splash->draw(gt, hdc,new Bitmap(L"resoureses/Sample.gif"));
if I pass the value to this->bit when creating an instance of the class, and I pass it to the function and draw, then nothing works, and if I use a variable from GIF::Draw(Bitmap* bit), then everything works fine
init.
Main.h:
std::shared_ptr<CachedBitmap> CachedBitmap;
GIF* splash;
1st variant
void loadResources()
{
Bitmap* bitmap = new Bitmap(L"resoureses/Sample.gif");
splash = new GIF(bitmap, hdc);
}
void Paint(HDC hdc)
{
Graphics* gt = new Graphics(hdc);
splash->draw(gt, hdc);
}
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hwnd, &ps);
Paint(hdc);
EndPaint(hwnd, &ps);
break;
}
WndProc(){
hwnd = CreateWindowExW(0, Wcc.lpszClassName, nullptr, WS_POPUP | WS_MINIMIZEBOX, x, y,
windowW, windowH, hWnd, NULL, (HINSTANCE)GetWindowLong(hParent, GWL_HINSTANCE), NULL);
loadResources(hwnd );
}
2nd variant
void loadResources()
{
Bitmap* bitmap = new Bitmap(L"resoureses/Sample.gif");
splash = new GIF(bitmap, hdc);
}
void Paint(HDC hdc)
{
Graphics* gt = new Graphics(hdc);
splash->draw(gt, hdc,new Bitmap(L"resoureses/Sample.gif"));
}
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hwnd, &ps);
Paint(hdc);
EndPaint(hwnd, &ps);
break;
}
WndProc(){
hwnd = CreateWindowExW(0, Wcc.lpszClassName, nullptr, WS_POPUP | WS_MINIMIZEBOX, x, y,
windowW, windowH, hWnd, NULL, (HINSTANCE)GetWindowLong(hParent, GWL_HINSTANCE), NULL);
loadResources(hwnd );
}
Looks like this line is causing the problem
this->CachedBitmap = PNGLoader::createCachedBitmap(this->bit, hdc);
This function GIF::draw is called repeatedly whenever windows have to be redrawn. So, when you assign a CachedBitmap first time with this->bit it should be ok. But as it is shared pointer, when you do it the second time, the stored CachedBitmap releases the pointer this->bit and it gets unusable before reassigning the shared_ptr newly returned by createCachedBitmap.
Instead, when you pass each time a new Bitmap before calling GIF::draw, you allocate new Bitmap so is no problem if you lose the old Bitmap
void GIF::draw(Graphics* g,HDC hdc, Bitmap *bit)
{
....
this->CachedBitmap = PNGLoader::createCachedBitmap(bit, hdc);
My suggestion is to assign this->CachedBitmap only once. For instance verify in GIF::draw if it is initialized, and if not assign, if yes do not assign. Or, do it outside draw function, in a place where you know it will be called only once.

How to draw text on a bitmap in memory [no MFC]

Can somebody explain how to draw text on a bitmap in memory?
I have the following code but i can't figure out how to do it.
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hWnd, &ps);
HDC buffDC = CreateCompatibleDC(hdc);
SelectObject(buffDC, hFnt);
SetTextColor(buffDC, RGB(1, 1, 1));
SetBkColor(buffDC, RGB(0, 255, 0));
RECT rc;
GetClientRect(hWnd, &rc);
HBITMAP buffBitmap = CreateCompatibleBitmap(buffDC, rc.right, rc.bottom);
int savedDC = SaveDC(buffDC);
HBRUSH hBrush = CreateSolidBrush(RGB(0, 255, 0));
FillRect(buffDC, &rc, hBrush);
DeleteObject(hBrush);
//This is the part where i would like to draw to the bitmap
TextOutA(buffDC, 0, 0, "Hello", 6);
SelectObject(buffDC, buffBitmap);
BitBlt(hdc, 0, 0, rc.right, rc.bottom, buffDC, 0, 0, SRCCOPY);
RestoreDC(buffDC, savedDC);
DeleteObject(buffBitmap);
DeleteDC(buffDC);
EndPaint(hWnd, &ps);
break;
}
I have seen a lot of different solutions mostly using MFC, however I would like to avoid that approach if possible.
EDIT: I have checked the other already asked questions but I couldn't find one wich would cover this without MFC.
My original problem was that I'm using a timer to call RedrawWindow and update the position of the text and make a sort of scrolling text which moves from right to left.
When I was in the testing process I have noticed that on some machines the app runs with up to 25% CPU usage and on some others it uses < 1%. I have tested the app one two machines with exactly the same specs running Windows 7 and the app ran on one with ~10% and the other with 0%.
(By the way my timer is getting called every 33ms and RedrawWindow uses RDW_UPDATENOW and I didn't handle the WM_ERASEBKGND message either :P
Since WM_TIMER (as far as I know) is a low priority message I was not concerned about the timer causeing the issue with the CPU usage.)
I satrted to think that maybe I should be using a bitmap and BitBlt it to the screen rather than just simply drawing to the dc and updating the x coordinate every time I repaint the screen.
Thanks
Before you can draw onto a bitmap, you have to select it into a memory device context.
Move SelectObject(buffDC, buffBitmap); before the first call to a drawing function, but usually as soon as possible after you created the bitmap.
In your sample code it appears suitable to insert it after the SaveDC() call so the original bitmap will be restored later when you call RestoreDC():
int savedDC = SaveDC(buffDC);
SelectObject(buffDC, buffBitmap);
As commenters noted, CreateCompatibleBitmap(buffDC, rc.right, rc.bottom) should be changed to CreateCompatibleBitmap(hdc, rc.right, rc.bottom).
From the reference of CreateCompatibleBitmap():
When a memory device context is created, it initially has a 1-by-1
monochrome bitmap selected into it. If this memory device context is
used in CreateCompatibleBitmap, the bitmap that is created is a
monochrome bitmap. To create a color bitmap, use the HDC that was used
to create the memory device context
Finally a suggestion: If you just need a temporary bitmap (as in your sample code), there is a more efficient API available since Windows Vista. It is called the buffered paint API. MSDN does not appear to provide a good overview, here is a tutorial and the reference (all functions that have "BufferedPaint" in their name).
Here is the Window Procedure which worked for me and is based on the answer from zett42.
This piece of code is just for testing purposses as I cannot post the original source code of the application I'm working on due to work.
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
static int xPos;
const bool bIsBufferedPaint = true;
switch (message)
{
case WM_CREATE:
{
BufferedPaintInit();
}
break;
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hWnd, &ps);
if(bIsBufferedPaint)
{
HDC newDC;
RECT rc;
RECT dstrc;
GetClientRect(hWnd, &rc);
dstrc = rc;
dstrc.left = rc.right + xPos;
HPAINTBUFFER hBufferedPaint = BeginBufferedPaint(hdc, &rc, BPBF_COMPATIBLEBITMAP, NULL, &newDC);
if(hBufferedPaint)
{
BufferedPaintClear(hBufferedPaint, NULL);
SetTextColor(newDC, RGB(0, 0, 0));
DrawText(newDC, L"Using buffered paint", -1, &dstrc, DT_SINGLELINE | DT_VCENTER | DT_LEFT);
Sleep(2);
EndBufferedPaint(hBufferedPaint, TRUE);
}
else
{
// buffer paint did not work.
}
}
else
{
HDC buffDC = CreateCompatibleDC(hdc);
SetTextColor(buffDC, RGB(0, 0, 0));
SetBkColor(buffDC, RGB(255, 255, 255));
RECT rc;
GetClientRect(hWnd, &rc);
HBITMAP buffBitmap = CreateCompatibleBitmap(hdc, rc.right, rc.bottom);
int savedDC = SaveDC(buffDC);
SelectObject(buffDC, buffBitmap);
HBRUSH hBrush = CreateSolidBrush(RGB(255, 255, 255));
FillRect(buffDC, &rc, hBrush);
DeleteObject(hBrush);
std::string testText = "Not using the buffered paint API";
TextOutA(buffDC, xPos, 0, testText.c_str(), testText.size());
BitBlt(hdc, 0, 0, rc.right, rc.bottom, buffDC, 0, 0, SRCCOPY);
RestoreDC(buffDC, savedDC);
DeleteObject(buffBitmap);
DeleteDC(buffDC);
}
EndPaint(hWnd, &ps);
}
break;
case WM_ERASEBKGND:
return 1;
case WM_TIMER:
{
switch(wParam)
{
case TIMER1:
{
xPos--;
RedrawWindow(hWnd, NULL, NULL, RDW_INVALIDATE | RDW_ERASE | RDW_ERASE);
if(bIsBufferedPaint)
{
if(xPos <= -500)
xPos = 0;
}
else
{
if(xPos <= -50)
xPos = 1000;
}
}
break;
}
}
break;
case WM_NCDESTROY:
{
BufferedPaintUnInit();
}
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}

WINAPI redrawing bitmaps in client area

I am attempting to make image viewer in winapi,
so basically it loads and draws my first image, but then i click button to navigate through .bmp images in given directory and they are not being drawn.
I would appreciate if you could help me and tell me where I am making mistake.
Here are my WndProc variables:
WIN32_FIND_DATA file;
HBITMAP img;
std::vector<WIN32_FIND_DATA> vFiles;
std::vector<WIN32_FIND_DATA>::iterator it;
HANDLE hFile = FindFirstFile(L"C:\\Users\\eclip7e\\Pictures\\*.bmp", &file);
if (hFile != INVALID_HANDLE_VALUE)
{
it = vFiles.begin();
it = vFiles.insert(it, file);
while (FindNextFile(hFile, &file))
{
it = vFiles.insert(it, file);
}
it = vFiles.begin();
}
Here is my button click:
case WM_COMMAND:
{
switch (LOWORD(wParam))
{
case IDC_RIGHT_BUTTON:
{
if (it<vFiles.end())
{
it++;
InvalidateRect(hWnd, NULL, TRUE);
}
else if (it == vFiles.end())
{
it = vFiles.begin();
InvalidateRect(hWnd, NULL, TRUE);
}
}
break;
}
}
break;
Here is my WM_PAINT:
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hWnd, &ps);
HDC hdcMem;
LPCWSTR d = L"C:\\Users\\eclip7e\\Pictures\\";
WIN32_FIND_DATA filex;
filex = *(it);
std::wstring df = std::wstring(d) + filex.cFileName;
LPCWSTR dfc = df.c_str();
img = (HBITMAP)LoadImage(NULL, dfc, IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE);
hdcMem = CreateCompatibleDC(hdc);
// Get the bitmap's parameters and verify the get
BITMAP qBitmap;
int iReturn = GetObject(reinterpret_cast<HGDIOBJ>(img), sizeof(BITMAP),
reinterpret_cast<LPVOID>(&qBitmap));
SelectObject(hdcMem, img);
BOOL bb =BitBlt(hdc, 0, 0, qBitmap.bmWidth, qBitmap.bmHeight, hdcMem, 0, 0, SRCCOPY);
DeleteDC(hdcMem);
DeleteObject(img);
EndPaint(hWnd, &ps);
}
break;

Splash Screen Make Wait box move

I have the following code that is supposed to paint a small box located on the bottom-left of a window, move 50 pixels to the right every 200 milliseconds, then reappear on the left once it reaches the right side.
Why doesn't my little rectangle move? It is painted in the same location all the time.
case WM_PAINT:
if (hBitmap != NULL)
{
// Paint the bitmap.
PAINTSTRUCT ps;
HDC hdc;
HDC hdcMem;
HGDIOBJ oldBitmap;
//
hdc = BeginPaint(hwnd, &ps);
// Create a dc in memory to paint on.
hdcMem = CreateCompatibleDC(hdc);
// Select the bitmap.
oldBitmap = SelectObject(hdcMem, hBitmap);
// Copy bitmap to splash screen window.
BitBlt(hdc, 0, 0, bmWidth, bmHeight, hdcMem, 0, 0, SRCCOPY);
// Fill rectangle.
HBRUSH hbr = CreateSolidBrush(RGB(42, 59, 87));
SelectObject(hdc, hbr);
FillRect(hdc, &rc, hbr);
// Cleanup.
SelectObject(hdcMem, oldBitmap);
DeleteObject(hbr);
DeleteObject(oldBitmap);
DeleteDC(hdcMem);
EndPaint(hwnd, &ps);
}
break;
case WM_TIMER:
timeCount++;
addLeft += 50;
if (addLeft == 300)
{
addLeft = 0;
}
// Move rectangle.
rc.left += addLeft;
rc.right += addLeft;
// Refresh the window.
UpdateWindow(hwnd);
// Timer and RECT from the top of the code page, and WinMain:
UINT_PTR ptrTimer;
const int TIMER_INTERVAL = 200;
const int MAX_TIME_COUNT = 100;
int timeCount;
// the timer works, but here's the code anyway.
ptrTimer = SetTimer(hwnd, 1, TIMER_INTERVAL, (TIMERPROC)NULL);
RECT rc;
rc.left = 141;
rc.top = 232;
rc.right = rc.left + 15;
rc.bottom = rc.top + 15;
Thanks for any replies,
Matt
Edit: Thanks hf.enigma, for the reply. This is what I ended up doing before I read your post. This works if anyone else wants to do this, but there are a couple more handles and GDI objects to clear. I am new to C++, so if anyone sees a memory leak here, please let me know. Thanks.
case WM_PAINT:
if (hBitmap != NULL)
{
// Paint the bitmap.
PAINTSTRUCT ps;
HDC hdc;
HDC hdcMem;
HGDIOBJ oldBitmap;
//
hdc = BeginPaint(hwnd, &ps);
hdcMem = CreateCompatibleDC(hdc); // a device context (dc) in memory to paint on.
oldBitmap = SelectObject(hdcMem, hBitmap);
// Copy bitmap to splash screen window.
BitBlt(hdc, 0, 0, bmWidth, bmHeight, hdcMem, 0, 0, SRCCOPY);
EndPaint(hwnd, &ps);
// Cleanup.
SelectObject(hdcMem, oldBitmap);
DeleteObject(oldBitmap);
DeleteDC(hdcMem);
HBRUSH hbr ;
// Fill rectangle.
RECT f;
GetClientRect(hwndBox, &f);
hdc = BeginPaint(hwndBox, &ps);
hdcMem = CreateCompatibleDC(hdc);
hbr = CreateSolidBrush(RGB(42, 59, 87));
SelectObject(hdc, hbr);
FillRect(hdc, &f, hbr);
EndPaint(hwnd, &ps);
// Cleanup.
SelectObject(hdcMem, oldBitmap);
DeleteObject(oldBitmap);
DeleteDC(hdcMem);
DeleteObject(hbr);
}
case WM_TIMER:
timeCount++;
if (addLeft == 60)
{
addLeft = 10000;
ival = 2;
}
// 'Hide' the box for 2/3 of timer interval when
// it reaches the right side.
if (addLeft == 10000)
{
addLeft = 0;
ival = 1;
}
//
switch (ival)
{
case 2:
addLeft += 12;
break;
case 3:
ival = 0;
break;
}
ival++;
if (ival == 2)
{
// Move rectangle.
MoveWindow(hwndBox, rcleft + addLeft, rctop, 12, 12, true);
}
if (timeCount == MAX_TIME_COUNT)
{
DestroyWindow(hwnd);
}
break;
You have to copy the bitmap after you have finished the painting, and you should paint with hdcMem, like this:
...
// Fill rectangle.
HBRUSH hbr = CreateSolidBrush(RGB(42, 59, 87));
SelectObject(hdcMem, hbr);
FillRect(hdcMem, &rc, hbr);
// Copy bitmap to splash screen window.
BitBlt(hdc, 0, 0, bmWidth, bmHeight, hdcMem, 0, 0, SRCCOPY);
// Cleanup.
....
This can be painted like hf.enigma has stated, but FillRect() must be called twice for each paint event. Without 'erasing' the previous block, the window ended up having a long rectangle along the bottom, similar to a progress bar.
I ended up making a sub-window the size of the block I wanted, and calling the MoveWindow() function. For me, this is the simplest and most maintainable way to accomplish this without having to use MFC or CLI.
If this can help anyone else, Here is all of the code that I am using for this.
//This displays a splash screen, and starts
//another application that takes a long time
//to load.
#pragma once
#include "resource.h"
#include "PathStatus.h"
#include "StartApp.h"
#include "CenterWindow.h"
#include <string>
#include <stdlib.h>
#include <iostream>
using namespace std;
const LPWSTR CLASS_NAME(L"TMS_Logs");
const LPWSTR PATH_SUFFIX (L"\\bin\\tl.exe");
const int LEN_APP_NAME = 12; // length of "TMS_Logs.exe".
bool b;
int ival;
char *appPath;
HWND hwnd; // Main window.
HBITMAP hBitmap = NULL; // Bitmap painted in window.
HWND hwndBox; // Moving box shows app. is loading.
HWND hwndButton; // Close box.
int bmWidth;
int bmHeight;
UINT_PTR ptrTimer;
const int TIMER_INTERVAL = 50;
const int MAX_TIME_COUNT = 650;
int timeCount;
int addLeft;
int rcleft, rctop, rcright, rcbottom;
RECT rcMover;
RECT rcMover2;
bool exitApp = false;
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PWSTR pCmdLine, int nCmdShow)
{
rcleft = 157;
rctop = 220;
rcright = rcleft + 15;
rcbottom = rctop + 15;
//
rcMover.left = rcleft;
rcMover.bottom = rcbottom;
rcMover.top = rctop;
rcMover.right = rcright;
//
rcMover2.bottom = rcbottom;
rcMover2.top = rctop;
rcMover.left + rcleft + 50;
rcMover.right = rcleft + 50 + 15;
// Get the commandline.
const wchar_t *args = GetCommandLineW();
wstring s(args);
// With no external arguments, GetCommandLineW() returns the
// full path with quotes around it, plus one space after the
// end qoute, e.g., '"c:\fullpath.exe" '.
// Remove the starting and ending quote chars, the space at
// the end, and and the exe path.
//
int len = s.length();
s = s.substr(1, len - 3 - LEN_APP_NAME) + PATH_SUFFIX;
// Convert the wide string to a regular char array.
// Convert the wstring to c-string.
const wchar_t *path = s.c_str();
// Convert the WCHAR to char* and store it in the
// 'appPath' variable.
const size_t BUFFER_SIZE = 200;
size_t i;
appPath = (char*)malloc(BUFFER_SIZE);
wcstombs_s(&i, appPath, BUFFER_SIZE, path, BUFFER_SIZE);
// Create window class objects.
WNDCLASSEX wc;
MSG msg;
wc.cbSize = sizeof(WNDCLASSEX);
wc.style = 0;
wc.lpfnWndProc = WndProc; // Sets callback function.
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = hInstance;
wc.hIcon = LoadIcon(NULL, MAKEINTRESOURCE(IDICN_WSDOT));
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
wc.lpszMenuName = NULL;
wc.lpszClassName = CLASS_NAME;
wc.hIconSm = wc.hIcon;
if (!RegisterClassEx(&wc))
{
MessageBox(NULL, L"Window Registration Failed!", L"Error!",
MB_ICONEXCLAMATION | MB_OK);
return -1;
}
// Load the resource bitmap.
hBitmap = LoadBitmap(hInstance, MAKEINTRESOURCE(IDBMP_TL7));
// Make a bitmap to hold the returned width and height.
// This bitmap will be deleted when DeleteObject is called
// for the hBitmap handle.
BITMAP bm;
if (hBitmap != NULL && GetObject(hBitmap, sizeof(bm), &bm))
{
bmWidth = bm.bmWidth;
bmHeight = bm.bmHeight;
}
else
{
MessageBox(hwnd, L"Error loading splash screen bitmap.",
L"Error", MB_ICONEXCLAMATION | MB_OK);
return -1;
}
// Center the window on the monitor
// where the mouse is.
RECT rc;
CenterRectToMonitor(&rc, bmWidth, bmHeight);
//
hwnd = CreateWindowEx(WS_EX_PALETTEWINDOW, wc.lpszClassName, // Uses the properties set in Window Class wc.
L"TMS Logs", WS_POPUP, rc.left, rc.top, bmWidth, bmHeight, NULL, NULL, hInstance, NULL);
if (hwnd == NULL)
{
MessageBox(NULL, L"Error creating Window.", L"Error",
MB_ICONEXCLAMATION | MB_OK);
return -1;
}
ShowWindow(hwnd, nCmdShow);
UpdateWindow(hwnd);
//bool started = true;
bool started = false;
bool ret;
int x, w, y, h;
x = 369;
y = 10;
w = 19;
h = 19;
// Make close box in upper-right corner.
hwndButton = CreateWindow(
L"BUTTON", // Predefined class.
L"X", // Button text
WS_TABSTOP | WS_VISIBLE | WS_CHILD | BS_DEFPUSHBUTTON, // Styles
369, // x position
10, // y position
19, // Button width
19, // Button height
hwnd, // Parent window
NULL, // No menu.
(HINSTANCE)GetWindowLong(hwnd, GWL_HINSTANCE),
NULL); // Pointer not needed.
// Make a box that moves along the bottom of
// the window while the other app. loads.
x = rcleft;
y = rctop;
w = 10;
h = 10;
hwndBox = CreateWindow(L"static", 0, WS_CHILD | WS_VISIBLE, x, y, w, h, hwnd, NULL,
(HINSTANCE) GetWindowLong (hwnd, GWL_HINSTANCE), NULL);
// Create a timer to close this window after
// several seconds, in case the main app. does
// not send the close window message to this app.
// the WM_TIMER message is handled in the WndProc
// callback function.
ptrTimer = SetTimer(hwnd, 1, TIMER_INTERVAL, (TIMERPROC)NULL);
// Start message Loop.
while ((ret = GetMessage(&msg, NULL, 0, 0)) != 0)
{
if (ret == -1)
{
MessageBox(NULL,
L"Error in Splash screen window message loop, at:\n\n'GetMessage(&msg, NULL, 0, 0))'.\n\nMessage return value was -1.",
L"TMS Logs", MB_ICONEXCLAMATION | MB_OK);
// Exit this app.
return -1;
}
// TranslateMessage(&Msg); // Not needed with what I have here.
DispatchMessage(&msg);
// Check app. path and start the other app.
if (!started)
{
started = true;
if (Path_Accessible(appPath))
{
// Start the main app.
if (!StartApplication(appPath))
{
// The folder or file is missing, or
// the user does not have security
// permissions to the sub-folder.
MessageBox(hwnd, L"Unexpected error at: StartApplication().",
L"Error", MB_ICONEXCLAMATION | MB_OK);
if (appPath)
{
free(appPath);
}
return -1;
}
}
else
{
// Display an error message.
char msg[350];
char* prefix = "Error: Can't find 'tl.exe'.\n\n It's missing from: '";
//char* suffix = " '\ncannot be accessed, or does not exist.";
int count = sizeof(msg);
strncpy(msg, prefix, count);
strncat(msg, appPath, count);
//strncat(msg, suffix, count);
// Convert c-string to wide char. string.
count = strlen(msg) + 1;
wchar_t* wMsg = new wchar_t[count];
size_t returnCount;
int ret = mbstowcs_s(&returnCount, wMsg, count, msg, _TRUNCATE);
if (ret == 0 && returnCount == count)
{
MessageBox(hwnd, wMsg, L"Error", MB_ICONEXCLAMATION | MB_OK);
}
else
{
// Error
MessageBox(hwnd, L"Error: The application path cannot be accessd, or does not exist.", L"Error", MB_ICONEXCLAMATION | MB_OK);
}
delete[] wMsg;
if (appPath)
{
free(appPath);
}
return -1;
}
}
}
if (appPath)
{
free(appPath);
}
return msg.wParam;
}
// Process messages.
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch (msg)
{
case WM_COMMAND:
// The close button is the only child window,
// so no need to check wparam.
DestroyWindow(hwnd);
break;
case WM_CLOSE:
DestroyWindow(hwnd);
break;
case WM_DESTROY:
DestroyWindow(hwndButton);
DestroyWindow(hwndBox);
if (hBitmap != NULL)
{
if (!DeleteObject(hBitmap))
{
MessageBox(hwnd, L"Error at: WndProc(). Failed to delete the application bitmap.",
L"Error", MB_OK);
}
}
if (ptrTimer)
{
if (!KillTimer(hwnd, ptrTimer))
{
MessageBox(hwnd, L"Error at :WndProc(). Failed to free the application timer.",
L"Error", MB_OK);
}
}
PostQuitMessage(0);
break;
case WM_PAINT:
if (hBitmap != NULL)
{
// Paint the bitmap.
PAINTSTRUCT ps;
HDC hdc;
HDC hdcMem;
HGDIOBJ oldBitmap;
//
hdc = BeginPaint(hwnd, &ps);
hdcMem = CreateCompatibleDC(hdc); // a device context (dc) in memory to paint on.
oldBitmap = SelectObject(hdcMem, hBitmap);
// Copy bitmap to splash screen window.
BitBlt(hdc, 0, 0, bmWidth, bmHeight, hdcMem, 0, 0, SRCCOPY);
EndPaint(hwnd, &ps);
// Cleanup.
SelectObject(hdcMem, oldBitmap);
DeleteObject(oldBitmap);
DeleteDC(hdcMem);
HBRUSH hbr ;
// Fill rectangle.
RECT f;
GetClientRect(hwndBox, &f);
hdc = BeginPaint(hwndBox, &ps);
hdcMem = CreateCompatibleDC(hdc);
hbr = CreateSolidBrush(RGB(42, 59, 87));
SelectObject(hdc, hbr);
FillRect(hdc, &f, hbr);
EndPaint(hwnd, &ps);
// Cleanup.
SelectObject(hdcMem, oldBitmap);
DeleteObject(oldBitmap);
DeleteDC(hdcMem);
DeleteObject(hbr);
}
break;
case WM_TIMER:
timeCount++;
if (addLeft == 60)
{
addLeft = 10000;
ival = 2;
}
// 'Hide' the box for 2/3 of timer interval when
// it reaches the right side.
if (addLeft == 10000)
{
addLeft = 0;
ival = 1;
}
//
switch (ival)
{
case 2:
addLeft += 12;
break;
case 3:
ival = 0;
break;
}
ival++;
if (ival == 2)
{
// Move rectangle.
MoveWindow(hwndBox, rcleft + addLeft, rctop, 12, 12, true);
}
if (timeCount == MAX_TIME_COUNT)
{
DestroyWindow(hwnd);
}
break;
default:
return DefWindowProc(hwnd, msg, wParam, lParam);
}
return 0;
}
Here is the code for referenced H files.
StartApp.H
#pragma once
#define WIN32_LEAN_AND_MEAN // Exclude DDE, RPC, Shell, Sockets, etc.
#define NOCOMM // Exclude serial communication APIs.
#include <Windows.h>
#include <cstdlib>
// Calls the CreateProcess function to start another app.
// ref: https://msdn.microsoft.com/en-us/library/windows/desktop/ms682512(v=vs.85).aspx
bool StartApplication(const char* appPath)
{
// The CreateProcess function requires
// wide char. array, so convert appPath.
size_t count = strlen(appPath) + 1;
wchar_t* path = new wchar_t[count];
size_t returnCount;
int ret = mbstowcs_s(&returnCount, path, count, appPath, _TRUNCATE);
if (ret != 0 || returnCount != count)
{
// Error converting C-string.
delete[] path;
return false;
}
// Required objects.
STARTUPINFO si;
PROCESS_INFORMATION pi;
// Set the size of the objects.
ZeroMemory(&si, sizeof(si));
si.cb = sizeof(si);
ZeroMemory(&pi, sizeof(pi));
// Start the program at 'path'.
bool ok = CreateProcess(
path, // module name/app. path.
NULL, // Command line/path, if null app. path is used.
NULL, // Process handle, use null.
NULL, // Thread handle, use null.
FALSE, // Set handle inheritance to FALSE.
0, // No creation flags.
NULL, // Use parent's environment block.
NULL, // Use parent's starting directory.
&si, // Pointer to STARTUPINFO structure.
&pi); // Pointer to PROCESS_INFORMATION structure.
// Close process and thread handles.
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
delete[] path;
return ok;
}
CenterWindow.H
void CenterRectToMonitor(LPRECT prc, int rcWidth, int rcHeight)
{
POINT pt;
GetCursorPos(&pt);
HMONITOR mon;
mon = MonitorFromPoint(pt, MONITOR_DEFAULTTONEAREST);
MONITORINFO mi;
mi.cbSize = sizeof(mi);
GetMonitorInfo(mon, &mi);
*prc = mi.rcMonitor;
//
// Center the window rectangle to the monitor rectangle.
prc->left = prc->left + (prc->right - prc->left - rcWidth) / 2;
prc->top = prc->top + (prc->bottom - prc->top - rcHeight) / 2;
prc->right = prc->left + rcWidth;
prc->bottom = prc->top + rcHeight;
}
PathStatus.H
#include <sys/stat.h>
const int SUCCESS = 0; // Indicates stat buffer was successfully set.
// Method to check if a file or folder
// exists and is accessible, using the stat
// structure and stat function, ref:
// http://pubs.opengroup.org/onlinepubs/009695399/functions/stat.html
bool Path_Accessible(const char* path)
{
if (path == 0)
{
return false;
}
struct stat path_status;
int result = stat(path, &path_status);
//
// The _S_IFREG flag indicates the path is a
// regular file and not a directory.
//
// To check for a directory, use '_S_IFDIR'.
return result == SUCCESS &&
(path_status.st_mode & _S_IFREG);
}
This is what my splash screen looks like. Note that the small box starts a little to the left of where it is now, moves a little to the right, and this repeats until the other app. this one started sends the WM_CLOSE message to this window.

Include mouse cursor in screen capture

I use CreateDC / BitBlt / GetDIBits etc. to capture the screen, but the cursor is not captured. Is there some simple argument or something to have it included?
#include <Windows.h>
#include <stdio.h>
#include <assert.h>
void scrshot() {
HWND hwnd = GetDesktopWindow();
HDC hdc = GetWindowDC(hwnd);
HDC hdcMem = CreateCompatibleDC(hdc);
int cx = GetDeviceCaps(hdc, HORZRES);
int cy = GetDeviceCaps(hdc, VERTRES);
HBITMAP hbitmap(NULL);
hbitmap = CreateCompatibleBitmap(hdc, cx, cy);
SelectObject(hdcMem, hbitmap);
BitBlt(hdcMem, 0, 0, cx, cy, hdc, 0, 0, SRCCOPY);
CURSORINFO cursor = { sizeof(cursor) };
GetCursorInfo(&cursor);
if (cursor.flags == CURSOR_SHOWING) {
RECT rect;
GetWindowRect(hwnd, &rect);
ICONINFO info = { sizeof(info) };
GetIconInfo(cursor.hCursor, &info);
const int x = cursor.ptScreenPos.x - rect.left - rect.left - info.xHotspot;
const int y = cursor.ptScreenPos.y - rect.top - rect.top - info.yHotspot;
BITMAP bmpCursor = { 0 };
GetObject(info.hbmColor, sizeof(bmpCursor), &bmpCursor);
DrawIconEx(hdcMem, x, y, cursor.hCursor, bmpCursor.bmWidth, bmpCursor.bmHeight,
0, NULL, DI_NORMAL);
}
}
int main(){
scrshot();
return 0;
}
Further to the discussion that occurred in the comments, I had the chance to further investigate the question. As a result, I came up with the following code that will grab the current cursor's HBITMAP and draw it to the screen.
Since the cursor is actually an HICON, it comes with a mask. Initially, I just did a simple BitBlt - however, I got a 32x32 black sqaure with the cursor in the top left 1/4 or so.
I then investigated using MaskBlt. Depending on where the cursor is when the app is started, I get either the wait cursor, a NS resize cursor, or the standard pointer. I guess you could start a timer and add a WM_TIMER handler to fire a couple of times a second in order to get a real-time update of the cursor as it was used in other windows in the system. It seemed like a mere curiosity to do something like that so I didn't bother.
EDIT: I actually did start a timer in WM_INITDIALOG and handle it in WM_TIMER. You can now see the image updated 10 times a second. For some reason, the I-beam cursor doesn't seem to be displayed at all - a case for further investigation as needed, I guess.
Here's the complete listing (except for resource.rc and resource.h - just create a dialog app and make sure the dialog's resource ID is used inside Main in the call to DialogBox)
#include <windows.h>
#include <commctrl.h>
#include <stdio.h>
#include "resource.h"
HINSTANCE hInst;
HBITMAP getCursorHBITMAP(HBITMAP *maskBmp)
{
CURSORINFO pci;
ICONINFO iconinfo;
HBITMAP result;
pci.cbSize = sizeof(pci);
GetCursorInfo(&pci);
if (GetIconInfo(pci.hCursor,&iconinfo))
{
result = iconinfo.hbmColor;
if (maskBmp)
*maskBmp = iconinfo.hbmMask;
}
else
result = NULL;
return result;
}
BOOL CALLBACK DlgMain(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch(uMsg)
{
case WM_INITDIALOG:
{
SetTimer(hwndDlg, 1, 100, NULL);
}
return TRUE;
case WM_TIMER:
{
InvalidateRect(hwndDlg, NULL, true);
}
return 0;
case WM_ERASEBKGND:
{
HDC hdc = (HDC)wParam;
RECT mRect;
GetClientRect(hwndDlg, &mRect);
FillRect(hdc, &mRect, (HBRUSH)GetStockObject(GRAY_BRUSH));
}
return 1;
case WM_PAINT:
{
HBITMAP oldBm, cursorBmp, maskBmp;
cursorBmp = getCursorHBITMAP(&maskBmp);
if (cursorBmp)
{
HDC hdc;
PAINTSTRUCT ps;
HDC memDC;
BITMAP bm;
hdc = BeginPaint(hwndDlg, &ps);
memDC = CreateCompatibleDC(hdc);
oldBm = (HBITMAP) SelectObject(memDC, cursorBmp);
GetObject(cursorBmp, sizeof(bm), &bm);
// printf("Cursor size: %d x %d\n", bm.bmWidth, bm.bmHeight);
// BitBlt(hdc, 10,10, 32,32, memDC, 0,0, SRCCOPY);
MaskBlt(hdc, 10,10, bm.bmWidth, bm.bmHeight, memDC, 0,0, maskBmp, 0,0, MAKEROP4(SRCPAINT,SRCCOPY) );
SelectObject(memDC, oldBm);
DeleteDC(memDC);
EndPaint(hwndDlg, &ps);
}
}
return 0;
case WM_CLOSE:
{
EndDialog(hwndDlg, 0);
}
return TRUE;
case WM_COMMAND:
{
switch(LOWORD(wParam))
{
}
}
return TRUE;
}
return FALSE;
}
int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd)
{
hInst=hInstance;
InitCommonControls();
return DialogBox(hInst, MAKEINTRESOURCE(DLG_MAIN), NULL, (DLGPROC)DlgMain);
}