I have a program that is supposed to display a count that increments every second. The counter is in a separate thread. I call WindowUpdate when a change has occurred but the count in the window does not update unless I hover the mouse pointer over the window or resize the window. I have tried InvalidateRect and RedrawWindow but they don't work either.
Why won't the counter updates display?
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
#include <sdkddkver.h>
#include <Windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <tchar.h>
#include <strsafe.h>
typedef struct DataTransfer {
BOOL Updated;
int Counter;
} TRANSFER, *PTRANSFER;
TRANSFER Payload;
PTRANSFER pPayload;
DWORD dwThreadId;
HANDLE hThread;
LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
void DisplayData(HDC hDC);
void ErrorHandler(LPTSTR lpszFunction);
DWORD WINAPI Counter(LPVOID lpParam);
int
APIENTRY
wWinMain(
HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPWSTR lpCmdLine,
int nShowCmd)
{
MSG msg;
WNDCLASSEX wcex;
ZeroMemory(&wcex, sizeof(wcex));
wcex.cbSize = sizeof(wcex);
wcex.style = CS_HREDRAW | CS_VREDRAW;
wcex.lpszClassName = TEXT("MYFIRSTWINDOWCLASS");
wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
wcex.hCursor = LoadCursor(hInstance, IDC_ARROW);
wcex.lpfnWndProc = WndProc;
wcex.hInstance = hInstance;
if (!RegisterClassEx(&wcex))
return 1;
CREATESTRUCT cs;
ZeroMemory(&cs, sizeof(cs));
cs.x = 0;
cs.y = 0;
cs.cx = 200;
cs.cy = 300;
cs.hInstance = hInstance;
cs.lpszClass = wcex.lpszClassName;
cs.lpszName = TEXT("Test");
cs.style = WS_OVERLAPPEDWINDOW;
HWND hWnd = ::CreateWindowEx(
cs.dwExStyle,
cs.lpszClass,
cs.lpszName,
cs.style,
cs.x,
cs.y,
cs.cx,
cs.cy,
cs.hwndParent,
cs.hMenu,
cs.hInstance,
cs.lpCreateParams);
if (!hWnd)
return 1;
DWORD dwThreadId;
HANDLE hThread;
pPayload = (PTRANSFER)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(TRANSFER));
if (pPayload == NULL)
ExitProcess(2);
pPayload->Updated = FALSE;
pPayload->Counter = 0;
// Display the window.
ShowWindow(hWnd, SW_SHOWDEFAULT);
UpdateWindow(hWnd);
hThread = CreateThread(
NULL,
0,
Counter,
pPayload,
0,
&dwThreadId);
if (hThread == NULL)
ExitProcess(2);
while (1)
{
if (pPayload->Updated == TRUE)
{
// InvalidateRect(hWnd, NULL, FALSE);
// RedrawWindow(hWnd, NULL, NULL, RDW_INVALIDATE | RDW_UPDATENOW);
UpdateWindow(hWnd);
}
if (GetMessage(&msg, NULL, 0, 0) > 0)
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
::UnregisterClass(wcex.lpszClassName, hInstance);
return (int)msg.wParam;
}
LRESULT
CALLBACK
WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
PAINTSTRUCT paintStruct;
HDC hDC;
switch (uMsg)
{
case WM_DESTROY:
::PostQuitMessage(0);
break;
case WM_PAINT:
hDC = BeginPaint(hWnd, &paintStruct);
DisplayData(hDC);
EndPaint(hWnd, &paintStruct);
return 0;
break;
default:
return ::DefWindowProc(hWnd, uMsg, wParam, lParam);
}
return 0;
}
void DisplayData(HDC hDC)
{
char OutputStr[32];
sprintf_s(OutputStr, sizeof(OutputStr) - 1, "%d", pPayload->Counter);
TextOut(hDC, 100, 100, OutputStr, strlen(OutputStr));
}
DWORD WINAPI Counter(LPVOID lpParam)
{
PTRANSFER pTransfer;
pTransfer = (PTRANSFER)lpParam;
while (1)
{
pTransfer->Counter++;
pTransfer->Updated = TRUE;
Sleep(1000);
}
}
void ErrorHandler(LPTSTR lpszFunction)
{
LPVOID lpMsgBuf;
LPVOID lpDisplayBuf;
DWORD dw = GetLastError();
FormatMessage(
FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_FROM_SYSTEM |
FORMAT_MESSAGE_IGNORE_INSERTS,
NULL,
dw,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
(LPTSTR)&lpMsgBuf,
0, NULL);
lpDisplayBuf = (LPVOID)LocalAlloc(LMEM_ZEROINIT,
(lstrlen((LPCTSTR)lpMsgBuf) + lstrlen((LPCTSTR)lpszFunction) + 40) * sizeof(TCHAR));
StringCchPrintf((LPTSTR)lpDisplayBuf,
LocalSize(lpDisplayBuf) / sizeof(TCHAR),
TEXT("%s failed with error %d: %s"),
lpszFunction, dw, lpMsgBuf);
MessageBox(NULL, (LPCTSTR)lpDisplayBuf, TEXT("Error"), MB_OK);
LocalFree(lpMsgBuf);
LocalFree(lpDisplayBuf);
}
(Disclaimer: This answer was written by the seat of my pants. Please correct me if I made an error somewhere.)
You are not doing what you are trying to do properly.
First, as Jonathan Potter has noted, UpdateWindow() by itself will not update the window unless it has an invalid region. The InvalidateRect() call will invalidate a rectangle. So you need both...
...but in reality you don't really want to call UpdateWindow() directly, because it bypasses the Windows drawing model. After calling InvalidateRect(), if there are no pending messages next time GetMessage() is called, and Windows itself decides it's time to refresh the screen contents with new data, you'll get a WM_PAINT message. The system knows when it's best to paint; you'll make your life easier by using it. Only use UpdateWindow() when it is vital that you want the window to redraw right now.
(The point of the invalid region is also due to the Windows drawing model: drawing can be very expensive, so Windows tries to optimize drawing by only redrawing what is needed. Invalidate only the part of your window that needs to be updated, and you'll get better performance.)
But there is a deeper issue: you do not implement multithreading properly.
Your worker thread is generating data every second and overwriting a shared structure. Your window is reading from that shared structure. There is nothing in place to ensure that only one thread is accessing that shared structure at a time. As a result, you'll wind up with ptential mixed state if your worker thread ever grows from a simple integer to a large and complex data structure.
You need to synchronize your data accesses. Communicate between threads.
How? The obvious method is to use a synchronization object, like a mutex. And you can totally do that.
But there's a better way: since one of your threads has a window, just use a window message! Window messages sent using SendMessage() will be received on the window's thread (with the calling thread blocked); messages posted using PostMessage() will be placed on the window's thread's message queue.
Plus you can pass not one but two pieces of information in that message! Both wParam and lParam are pointer-sized integers, so just stuff a pointer in one of them and use SendMessage() or PostMessage()!
But what message do you use? Every* message in the range [WM_USER, WM_APP) is available to the window class to decide what to use it for, and every message in the range [WM_APP, 0xC000) is for the application to decide what to use it for. So just pick one and use it!
Just remember how SendMessage() and PostMessage() work. If the data is allocated on the heap, and each piece of data is allocated separately, then it doesn't matter; if the data is a pointer to a local variable, SendMessage() is the correct answer; if the data is a numeric value that can fit in a pointer-sized integer, either will work. If you need a response, your worker thread will either need a window of its own or the use of SendMessage() to get a value back from the window.
There are lots of ways to do this. It all depends on what data you need to communicate. As the Go programming language's designers say (altered somewhat): don't communicate by sharing data; share data by communicating.
*until you call IsDialogMessage(), at which point you lose WM_USER and WM_USER + 1 (and possibly WM_USER + 2 as well?). But that's only three messages out of over 32,000.
Your problem is in your message loop:
while (1)
{
if (pPayload->Updated == TRUE) {
UpdateWindow(hWnd);
}
if (GetMessage(&msg, NULL, 0, 0) > 0) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
GetMessage is synchronous, and doesn't return until a message is available for retrieval. The code sits there, waiting for a message to arrive most of the time, and will not evaluate pPayload->Updated. Consequently, no updates happen, until a message arrives. But even then, nothing happens, since UpdateWindow sends "a WM_PAINT message to the window if the window's update region is not empty." UpdateWindow by itself doesn't do anything useful.1)
You could solve this by reworking your message loop. The following issues need to be addressed:
A call to InvalidateRect must precede the call to UpdateWindow.
Access to the shared data must be synchronized.
The message loop needs to terminate, when GetMessage returns 0 or -1.
None of this is necessary, though. You don't need a background thread. A single-threaded application will do just fine. Just use a Timer to trigger updates to the stored value, and the visual representation. Here is a rough sketch for a window procedure implementing a timer-based solution:
LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
static unsigned __int64 startTime = ::GetTickCount64();
const UINT_PTR timerId = 1;
switch (uMsg)
{
case WM_CREATE:
// Start timer when window is created
::SetTimer(hWnd, timerId, 1000, nullptr);
return ::DefWindowProc(hWnd, uMsg, wParam, lParam);
case WM_TIMER: {
unsigned __int64 currTime = ::GetTickCount64();
Counter = (currTime - startTime) / 1000;
// Value changed -> initiate window update
::InvalidateRect(hWnd, nullptr, FALSE);
// Re-start timer
::SetTimer(hWnd, timerId, 1000, nullptr);
}
break;
case WM_DESTROY:
::KillTimer(hWnd, timerId);
::PostQuitMessage(0);
break;
case WM_PAINT: {
PAINTSTRUCT paintStruct = {0};
HDC hDC = BeginPaint(hWnd, &paintStruct);
DisplayData(hDC);
EndPaint(hWnd, &paintStruct);
}
return 0;
default:
return ::DefWindowProc(hWnd, uMsg, wParam, lParam);
}
return 0;
}
A few notes on the implementation:
The timer uses a fixed time-out value of 1000 milliseconds. This will occasionally skip an update. The error doesn't accumulate, though, as the Counter is re-evaluated whenever the timer expires. If you more steady updates, calculate the remaining time-out value based on the Counter, startTime, and currTime.
Counter is the variable that holds the current time in seconds. It is a replacement for TRANSFER::Counter, that needs to be accessible from both the window procedure and the rendering code.
1) There is another issue with your message loop: It never terminates, and neither does your process. The GUI may disappear, but the process will still show up in Task Manager.
i marked my alterations with comment <<updated
instead of setting flag Updated, i changed it to be hWnd, and then update the window instantly and directly from the thread
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
#include <sdkddkver.h>
#include <Windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <tchar.h>
#include <strsafe.h>
typedef struct DataTransfer {
//BOOL Updated; // << updated
int Counter;
HWND hWnd; // << updated
} TRANSFER, *PTRANSFER;
TRANSFER Payload;
PTRANSFER pPayload;
DWORD dwThreadId;
HANDLE hThread;
LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
void DisplayData(HDC hDC);
void ErrorHandler(LPTSTR lpszFunction);
DWORD WINAPI Counter(LPVOID lpParam);
int
APIENTRY
wWinMain(
HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPWSTR lpCmdLine,
int nShowCmd)
{
MSG msg;
WNDCLASSEX wcex;
ZeroMemory(&wcex, sizeof(wcex));
wcex.cbSize = sizeof(wcex);
wcex.style = CS_HREDRAW | CS_VREDRAW;
wcex.lpszClassName = TEXT("MYFIRSTWINDOWCLASS");
wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
wcex.hCursor = LoadCursor(NULL, IDC_ARROW); //<< updated
wcex.lpfnWndProc = WndProc;
wcex.hInstance = hInstance;
if (!RegisterClassEx(&wcex))
return 1;
CREATESTRUCT cs;
ZeroMemory(&cs, sizeof(cs));
cs.x = 0;
cs.y = 0;
cs.cx = 200;
cs.cy = 300;
cs.hInstance = hInstance;
cs.lpszClass = wcex.lpszClassName;
cs.lpszName = TEXT("Test");
cs.style = WS_OVERLAPPEDWINDOW;
HWND hWnd = CreateWindowEx(
cs.dwExStyle,
cs.lpszClass,
cs.lpszName,
cs.style,
cs.x,
cs.y,
cs.cx,
cs.cy,
cs.hwndParent,
cs.hMenu,
cs.hInstance,
cs.lpCreateParams);
if (!hWnd)
return 1;
DWORD dwThreadId;
HANDLE hThread;
pPayload = (PTRANSFER)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(TRANSFER));
if (pPayload == NULL)
ExitProcess(2);
//pPayload->Updated = FALSE; //<< updated
pPayload->hWnd=hWnd;
pPayload->Counter = 0;
// Display the window.
ShowWindow(hWnd, SW_SHOWDEFAULT);
UpdateWindow(hWnd);
hThread = CreateThread( NULL, 0, Counter, pPayload, 0, &dwThreadId);
if (hThread == NULL)
ExitProcess(2);
while (1)
{
// ____[ updated ]_____
/*
if (pPayload->Updated == TRUE)
{
UpdateWindow(hWnd);
pPayload->Updated=FALSE;
}
*/
int ret=GetMessage(&msg, NULL, 0, 0);//<< updated
if(ret==0 || ret==-1)
break;
TranslateMessage(&msg);
DispatchMessage(&msg);
}
UnregisterClass(wcex.lpszClassName, hInstance);
return (int)msg.wParam;
}
LRESULT
CALLBACK
WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
PAINTSTRUCT paintStruct;
HDC hDC;
switch (uMsg)
{
case WM_DESTROY:
PostQuitMessage(0);
break;
case WM_PAINT:
hDC = BeginPaint(hWnd, &paintStruct);
DisplayData(hDC);
EndPaint(hWnd, &paintStruct);
break;
default:
return DefWindowProc(hWnd, uMsg, wParam, lParam);
}
return 0;
}
void DisplayData(HDC hDC)
{
char OutputStr[32];
sprintf_s(OutputStr, sizeof(OutputStr) - 1, "%d", pPayload->Counter);
TextOut(hDC, 100, 100, OutputStr, strlen(OutputStr));
}
DWORD WINAPI Counter(LPVOID lpParam)
{
PTRANSFER pTransfer;
pTransfer = (PTRANSFER)lpParam;
while (1)
{
pTransfer->Counter++;
InvalidateRect(pTransfer->hWnd,NULL,1);
Sleep(1000);
}
}
void ErrorHandler(LPTSTR lpszFunction)
{
LPVOID lpMsgBuf;
LPVOID lpDisplayBuf;
DWORD dw = GetLastError();
FormatMessage(
FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_FROM_SYSTEM |
FORMAT_MESSAGE_IGNORE_INSERTS,
NULL,
dw,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
(LPTSTR)&lpMsgBuf,
0, NULL);
lpDisplayBuf = (LPVOID)LocalAlloc(LMEM_ZEROINIT,
(lstrlen((LPCTSTR)lpMsgBuf) + lstrlen((LPCTSTR)lpszFunction) + 40) * sizeof(TCHAR));
StringCchPrintf((LPTSTR)lpDisplayBuf,
LocalSize(lpDisplayBuf) / sizeof(TCHAR),
TEXT("%s failed with error %d: %s"),
lpszFunction, dw, lpMsgBuf);
MessageBox(NULL, (LPCTSTR)lpDisplayBuf, TEXT("Error"), MB_OK);
LocalFree(lpMsgBuf);
LocalFree(lpDisplayBuf);
}
Related
I'm trying to make a screenshot helper that takes screenshots when it receives signal from other application. In my design, the higher layer application will send a window message to the helper, and the helper will then take a screenshot for user (by simulating Win+Prtsc). I've done this without any problem, but when I try to add a feature that implements by detecting the gap between two message, I found that GetMessageTime() function works weird.
The problem is that I added a MessageBox for debugging, and GetMessageTime returns the proper ticks the message was sent at. But when I removed that MessageBox, GetMessageTime kept returning 0. I searched the web and MSDN documents but found nothing about this. So I'm just wondering what happened and what should I do, without changing the architecture that using HWND_MESSAGE window to receive message.
MessageReceiver
#include <Windows.h>
#include <stdio.h>
HWND hwndMain;
UINT hotkeyTriggeredMessageId;
INPUT winPlusPrtscKeyCombo[4];
LONG lastHotkeyTime = -1000;
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) {
if (message == hotkeyTriggeredMessageId) {
printf("last:%d,this:%d,diff:%d\n", lastHotkeyTime, GetMessageTime(), lastHotkeyTime - GetMessageTime());
MessageBox(NULL, L"triggered!", L"", NULL);
printf("last:%d,this:%d,diff:%d\n", lastHotkeyTime, GetMessageTime(), lastHotkeyTime - GetMessageTime());
SendInput(4, winPlusPrtscKeyCombo, sizeof(INPUT));
}
return DefWindowProc(hWnd, message, wParam, lParam);
}
int main() {
MSG msg;
BOOL bRet;
WNDCLASS wc = { 0 };
memset(winPlusPrtscKeyCombo, 0, sizeof(INPUT) * 4);
winPlusPrtscKeyCombo[0].type = INPUT_KEYBOARD;
winPlusPrtscKeyCombo[0].ki.wVk = VK_LWIN;
winPlusPrtscKeyCombo[1].type = INPUT_KEYBOARD;
winPlusPrtscKeyCombo[1].ki.wVk = VK_SNAPSHOT;
winPlusPrtscKeyCombo[2].type = INPUT_KEYBOARD;
winPlusPrtscKeyCombo[2].ki.wVk = VK_SNAPSHOT;
winPlusPrtscKeyCombo[2].ki.dwFlags = KEYEVENTF_KEYUP;
winPlusPrtscKeyCombo[3].type = INPUT_KEYBOARD;
winPlusPrtscKeyCombo[3].ki.wVk = VK_LWIN;
winPlusPrtscKeyCombo[3].ki.dwFlags = KEYEVENTF_KEYUP;
HINSTANCE hInstance = GetModuleHandle(NULL);
wc.lpfnWndProc = (WNDPROC)WndProc;
wc.hInstance = hInstance;
wc.lpszClassName = L"MessageReceiverWindow";
if (!RegisterClass(&wc)) {
return FALSE;
}
hwndMain = CreateWindow(L"MessageReceiverWindow", L"MessageReceiverWindow", 0, 0, 0, 0, 0, HWND_MESSAGE, 0, hInstance, 0);
hotkeyTriggeredMessageId = RegisterWindowMessage(L"MESSAGE_HOTKEY_TRIGGERED");
ChangeWindowMessageFilterEx(hwndMain, hotkeyTriggeredMessageId, MSGFLT_ALLOW, NULL);
UpdateWindow(hwndMain);
while (GetMessage(&msg, NULL, 0, 0)) {
DispatchMessage(&msg);
}
return 0;
}
MessageSender
#include <stdio.h>
#include <Windows.h>
UINT hotkeyTriggeredMessageId;
HWND targetWindow;
int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdLine, int nCmdShow) {
hotkeyTriggeredMessageId = RegisterWindowMessage(L"MESSAGE_HOTKEY_TRIGGERED");
targetWindow = FindWindowEx(HWND_MESSAGE, NULL, L"MessageReceiverWindow", NULL);
SendMessage(targetWindow, hotkeyTriggeredMessageId, NULL, NULL);
return 0;
}
I'm wondering if it's possible to programmatically restart an application that has been terminated in the beginning of a shutdown that will be canceled later.
On Windows, if an application calls ShutdownBlockReasonCreate on shutdown , it can know whether the user has cancelled it or not, by checking if the ENDSESSION_CRITICAL bit is set in the lParam.
In my case I don't want to block any shutdown .
Is this possible ? Thanks for any suggestion.
I think I found a solution :
I created a hidden window using CreateWindowEx to handle the WM_QUERYENDSESSION and WM_ENDSESSION messages:
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch(msg)
{
case WM_QUERYENDSESSION:
{
//clean up ...
ShutdownBlockReasonCreate(hwnd, L" ");
// What ? You said you don't want to block the shutdown ?
// Yes I do. It seems to work different with a hidden window.
return 1;
}
case WM_ENDSESSION:
{
// GetSystemMetrics(SM_SHUTTINGDOWN) return Nonzero if the current session is shutting down
// This loop waits for the user to cancel the shutdown
while(GetSystemMetrics(SM_SHUTTINGDOWN) != 0)
Sleep(100);
// The user has cancelled the shutdown here
// I can run the process again using CreateProcess or what ever...
// Once this line is executed this instance will be terminated
return 0;
}
default:
return DefWindowProc(hwnd, msg, wParam, lParam);
}
}
The return values are very important (see this).
As I didn't expected, a process without a top level visible window, even if it calls ShutdownBlockReasonCreate (with a hidden window handle), it will not be shown in the list of applications blocking the shutdown. And if there's no other application blocking the shutdown, Windows will only wait for a few seconds, and then shutdown.
For the loop, I took the idea from here. I don't know if there's a better alternative.
That's it.
Please test it, and correct me if I'm wrong.
Thanks.
[EDIT]
Here's the program main for a ready test:
#include <string>
#include <Windows.h>
using namespace std;
string getExePath()
{
TCHAR wpath[MAX_PATH];
GetModuleFileName (NULL, wpath, MAX_PATH);
size_t i;
char path[MAX_PATH];
wcstombs_s(&i, path, MAX_PATH, wpath, MAX_PATH);
return path;
}
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch(msg)
{
case WM_QUERYENDSESSION:
{
// A shutdown has been initiated
ShutdownBlockReasonCreate(hwnd, L" ");
return 1;
}
case WM_ENDSESSION:
{
// GetSystemMetrics(SM_SHUTTINGDOWN) return Nonzero if the current session is shutting down
// This loop waits for the user to cancel the shutdown
while(GetSystemMetrics(SM_SHUTTINGDOWN) != 0)
Sleep(100);
// The user has cancelled the shutdown here
// I can run the process again
WinExec(string("\"" + getExePath() + "\"").c_str(), SW_HIDE);
TerminateProcess(OpenProcess(PROCESS_TERMINATE, FALSE, GetCurrentProcessId()), 0);
return 0;
}
default:
return DefWindowProc(hwnd, msg, wParam, lParam);
}
}
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
HWND hwnd;
WNDCLASSEXA wc;
wc.cbSize = sizeof(WNDCLASSEX);
wc.style = 0;
wc.lpfnWndProc = WndProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = hInstance;
wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
wc.lpszClassName = "TestShutDownAbortClass";
wc.hIconSm = LoadIcon(NULL, IDI_APPLICATION);
if( RegisterClassExA(&wc) == 0 )
return 1;
hwnd = CreateWindowExA(0,"TestShutDownAbortClass", "", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,NULL, NULL, hInstance, NULL);
if(hwnd == NULL)
return 1;
MSG msg;
while(GetMessageA(&msg, hwnd, 0, 0) > 0)
{
TranslateMessage(&msg);
DispatchMessageA(&msg);
}
return 0;
}
The DATETIMEPICKERINFOstructure obtained by sending the DTM_GETDATETIMEPICKERINFOmessage has a field hwndEdit which might be what I'm looking for. However, I'm getting always NULL for it so I'm wondering what's its actual meaning. If not, is there a way to get the handle of the entry field?
hwndEdit only seems to be valid when the control has the DTS_APPCANPARSE style and you click the date text with the mouse (I tested this with OutputDebugString and a timer). The edit control is created and destroyed dynamically. The hwndUD handle is only valid if DTS_UPDOWN is set and the hwndDropDown is only valid while the dropdown is visible.
It is not called out in the documentation but DTM_GETDATETIMEPICKERINFO is marked Vista+ and this often means the feature is only implemented in ComCtl32 v6 so you also have to make sure you have a manifest that requests this version.
To change the color you can try DTM_SETMCCOLOR but only MCSC_BACKGROUND is documented to work when Visual Styles are active.
I'm afraid there is no way to get what you wanted. I just created a simple Win32 application just to test the possibility. If I use the DTM_GETDATETIMEPICKERINFO, hwndDropDown, hwndEdit and hwndUD give me NULL. If I try to enum child window, well before I do so I check it with Spy++, no luck, there is no child window associated with it.
Finally, I tried GetFocus() and WindowFromPoint(), both give me the HWND of the DateTimePicker itself only.
Here is my testing code:
#pragma comment(lib, "comctl32.lib")
#include <windows.h>
#include <tchar.h>
#include <commctrl.h>
enum MYID {
MYID_FIRST = WM_APP,
MYID_DTP
};
LPCTSTR const g_MyWndClass = _T("DTPTest");
LPCTSTR const g_MyWndTitle = _T("DTPTest");
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
void OnWindowCreate(HWND);
void OnTimer(HWND);
int APIENTRY WinMain(HINSTANCE, HINSTANCE, LPSTR, int nCmdShow)
{
INITCOMMONCONTROLSEX icex{};
icex.dwSize = sizeof(icex);
icex.dwICC = ICC_DATE_CLASSES;
InitCommonControlsEx(&icex);
WNDCLASSEX wcex{};
wcex.cbSize = sizeof(wcex);
wcex.hbrBackground = GetSysColorBrush(COLOR_3DFACE);
wcex.hCursor = LoadCursor(nullptr, IDC_ARROW);
wcex.lpfnWndProc = WndProc;
wcex.lpszClassName = g_MyWndClass;
wcex.style = CS_HREDRAW | CS_VREDRAW;
RegisterClassEx(&wcex);
HWND hwnd = CreateWindowEx(0,
g_MyWndClass, g_MyWndTitle,
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, 0, 600, 400,
nullptr, nullptr, nullptr, nullptr);
if (!hwnd) { return 99; }
SetTimer(hwnd, 0, 100, nullptr);
ShowWindow(hwnd, nCmdShow);
MSG msg{};
while (GetMessage(&msg, nullptr, 0, 0)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return static_cast<int>(msg.wParam);
}
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM w, LPARAM l)
{
switch (msg) {
case WM_CREATE:
OnWindowCreate(hwnd);
break;
case WM_TIMER:
OnTimer(hwnd);
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hwnd, msg, w, l);
}
return 0;
}
void OnWindowCreate(HWND hwnd)
{
HWND hwndDTP = CreateWindowEx(0, DATETIMEPICK_CLASS, nullptr,
WS_CHILD | WS_VISIBLE | DTS_SHOWNONE,
20, 50, 220, 20,
hwnd, reinterpret_cast<HMENU>(MYID_DTP), nullptr, nullptr);
DATETIMEPICKERINFO info{};
info.cbSize = sizeof(DATETIMEPICKERINFO);
SendMessage(hwndDTP, DTM_GETDATETIMEPICKERINFO, 0,
reinterpret_cast<LPARAM>(&info));
if (!info.hwndDropDown && !info.hwndEdit && !info.hwndUD)
{
MessageBox(hwnd, _T("No luck with DTM_GETDATETIMEPICKERINFO"),
nullptr, MB_ICONERROR);
}
}
void OnTimer(HWND hwnd)
{
POINT pt{};
GetCursorPos(&pt);
HWND hwndPoint = WindowFromPoint(pt);
HWND hwndFocus = GetFocus();
TCHAR buf[99]{};
wsprintf(buf, _T("Pointing at %p, focusing %p"),
hwndPoint, hwndFocus);
SetWindowText(hwnd, buf);
}
Using Visual Studio 2013
I have an application that could potentially use up to about 20 window classes, but not all at the same time. In order to save on space I decided to Unregister those not needed any more before starting another batch of window classes, but I could not make the UnregisterClass function work.
I called Unregister at WM_DESTROY and/or WM_NCDESTROY but it always returned error message 1412 'Class still has open window'. Perhaps the Unregister call in WM_DESTROY failed because the window had not been destroyed yet, but I did not expect the call in WM_NCDESTROY to fail since this message is sent after destruction of the window.
The only way I could make UnregisterClass work was to call PostQuitMessage at either WM_DESTROY or WM_NCDESTROY. Then UnregisterClass would work after the message loop just before the whole application exits, but I want to start another batch of classes from inside the application, not to have to start it all over.
I am submitting a test program that shows the problem. It is Win32Project7, a program provided by Visual Studio 2013 with two tiny additions by myself - wrapped Messagebox (mbox) and a procedure to call unregister (tryunreg).
One extreme would be to register 20 window classes just to have them ready when needed, another would be to use a single windowclass and multiplex on HWND. Not too keen on any of these.
Questions:
Have I made mistakes or wrong assumptions in this program?
Is there a way to make unregisterclass work without having to close the program?
how much space would a typical windowclass register need? Is it likely to be KB or MB? Any way to experiment to find out?
Have Googled on this. Did not find anything that is not already in the documentation, e.g. like unregister is automatic on exit from application. Stackoverflow has two posts similar to this, but with no answers.
The code:
I placed the code like this:
<pre>
program fragments
</pre>
enclosed between html pre tags
but the post was not sent. Error message said the text was formatted like
a program but the indentation was not 4 spaces. Originally it wasn't but I changed it, but it still was not sent.
Have never sent questions on this forum, so I am doing something wrong. What?
Here is the code I did not know how to send in my original post.
Better late than never.
// Win32Project7.cpp : Defines the entry point for the application.
#include "stdafx.h"
#include "Win32Project7.h"
#define MAX_LOADSTRING 100
// Global Variables:
HINSTANCE hInst;
TCHAR szTitle[MAX_LOADSTRING];
TCHAR szWindowClass[MAX_LOADSTRING];
// Forward declarations of functions included in this code module:
ATOM MyRegisterClass(HINSTANCE hInstance);
BOOL InitInstance(HINSTANCE, int);
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
INT_PTR CALLBACK About(HWND, UINT, WPARAM, LPARAM);
static void mbox(const wchar_t * msg) // added
{
int errcode;
const wchar_t * caption = L"Info";
int res = MessageBox(NULL, msg, caption, 0);
if (res == 0)
{
errcode = GetLastError();
return; // was setting breakpoint, but never got here
// but mbox does not give any output after postquit
}
}
static void tryunreg(const wchar_t * where) // added
{
int errcode;
wchar_t outmsg[100];
BOOL b = UnregisterClass(szWindowClass, hInst);
if (!b)
{
errcode = GetLastError();
wsprintf(outmsg, L"%s: Unreg failed for classname %s errcode %d",
where, szWindowClass, errcode);
}
else
{
wsprintf(outmsg, L"%s: Unreg worked", where);
}
mbox(outmsg);
}
int APIENTRY _tWinMain(_In_ HINSTANCE hInstance,
_In_opt_ HINSTANCE hPrevInstance,
_In_ LPTSTR lpCmdLine,
_In_ int nCmdShow)
{
UNREFERENCED_PARAMETER(hPrevInstance);
UNREFERENCED_PARAMETER(lpCmdLine);
// TODO: Place code here.
MSG msg;
HACCEL hAccelTable;
// Initialize global strings
LoadString(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);
LoadString(hInstance, IDC_WIN32PROJECT7, szWindowClass, MAX_LOADSTRING);
MyRegisterClass(hInstance);
// Perform application initialization:
if (!InitInstance (hInstance, nCmdShow))
{
return FALSE;
}
hAccelTable = LoadAccelerators(hInstance,
MAKEINTRESOURCE(IDC_WIN32PROJECT7));
// Main message loop:
while (GetMessage(&msg, NULL, 0, 0))
{
if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
tryunreg(L"After message loop" ); // added this
return (int) msg.wParam;
}
//
// FUNCTION: MyRegisterClass()
//
// PURPOSE: Registers the window class.
//
ATOM MyRegisterClass(HINSTANCE hInstance)
{
WNDCLASSEX wcex;
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.style = CS_HREDRAW | CS_VREDRAW;
wcex.lpfnWndProc = WndProc;
wcex.cbClsExtra = 0;
wcex.cbWndExtra = 0;
wcex.hInstance = hInstance;
wcex.hIcon = LoadIcon(hInstance,
MAKEINTRESOURCE(IDI_WIN32PROJECT7));
wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
wcex.lpszMenuName = MAKEINTRESOURCE(IDC_WIN32PROJECT7);
wcex.lpszClassName = szWindowClass;
wcex.hIconSm = LoadIcon(wcex.hInstance,
MAKEINTRESOURCE(IDI_SMALL));
return RegisterClassEx(&wcex);
}
//
// FUNCTION: InitInstance(HINSTANCE, int)
//
// PURPOSE: Saves instance handle and creates main window
//
// COMMENTS:
//
// In this function, we save the instance handle in a global
// variable and
// create and display the main program window.
//
BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
HWND hWnd;
hInst = hInstance; // Store instance handle in our global variable
hWnd = CreateWindow(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInstance, NULL);
if (!hWnd)
{
return FALSE;
}
ShowWindow(hWnd, nCmdShow);
UpdateWindow(hWnd);
return TRUE;
}
//
// FUNCTION: WndProc(HWND, UINT, WPARAM, LPARAM)
//
// PURPOSE: Processes messages for the main window.
//
// WM_COMMAND - process the application menu
// WM_PAINT - Paint the main window
// WM_DESTROY - post a quit message and return
//
//
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam,
LPARAM lParam)
{
int wmId, wmEvent;
PAINTSTRUCT ps;
HDC hdc;
BOOL b;
int errcode;
wchar_t msg[100];
switch (message)
{
case WM_COMMAND:
wmId = LOWORD(wParam);
wmEvent = HIWORD(wParam);
// Parse the menu selections
switch (wmId)
{
case IDM_ABOUT:
DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX),
hWnd, About);
break;
case IDM_EXIT:
DestroyWindow(hWnd);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
break;
case WM_PAINT:
hdc = BeginPaint(hWnd, &ps);
// TODO: Add any drawing code here...
EndPaint(hWnd, &ps);
break;
case WM_CLOSE: // added
// mbox(L"#wm_close before destroywindow");
DestroyWindow(hWnd);
break;
case WM_DESTROY:
tryunreg(L"#wm_destroy before postquit"); // added
PostQuitMessage(0); // in original MS code
tryunreg(L"#wm_destroy after postquit"); // added
break;
case WM_NCDESTROY: // added
tryunreg(L"#wm_NCdestroy before postquit"); // added
//PostQuitMessage(0);
tryunreg(L"#wm_NCdestroy after postquit"); // added
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
// Message handler for about box.
INT_PTR CALLBACK About(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
UNREFERENCED_PARAMETER(lParam);
switch (message)
{
case WM_INITDIALOG:
return (INT_PTR)TRUE;
case WM_COMMAND:
if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL)
{
EndDialog(hDlg, LOWORD(wParam));
return (INT_PTR)TRUE;
}
break;
}
return (INT_PTR)FALSE;
}
The time that UnregisterClass is needed is a dynamically loaded DLL that registers a window class. Such a library needs to ensure that the class is unregistered before it unloads, otherwise a CreateWindow for that class would make a call to code that is no longer present.
If you do choose to unregister window classes a delay can be introduced by using QueueUserAPC, however that does require changing the message loop (to one based around MsgWaitForMultipleObjectsEx and an embedded PeekMessage loop). Or you could use a thread message.
I prefer the APC because it allows for decoupling the code to be invoked from the rest of the program. For example in MFC using a thread message would require changing the message map for the thread class (the CWinApp in most cases).
I have a Win32 application with a static control that I want to render to using OpenGL. The problem is when I run the application, my computer comes to a screeching halt and more than once now I've had to hard-reboot it. It takes less than a second and it's really brutal. Most of my code here is ripped off of the internet or from my OpenGL bluebook. I have tried doing this several other ways, some with the same results and some with different but no more desirable results, including making the static control owner-draw, subclassing the static control, and just rendering to the control in it's parent's (the main window's) WM_PAINT message. I'm not even rendering any geometry yet, I just want to see the clear color. Most if not all of the resources I've found online either don't cover rendering to a control or using MFC instead of native Win32 API calls.
I have determined that it only happens because I am calling glClear(). If I don't call it, I don't experience the slow down, but I also don't see anything in my static control.
Some more information:
Visual Studio 2010 Ultimate
Windows SDK v7.0a
GLEW 1.7
nVidia Quadro 1600M with the latest drivers
Windows 7 x64 Ultimate
Here is my code:
#define WINVER 0x0600
#define _WIN32_WINNT 0x0600
#define GLEW_STATIC
#include <windows.h>
#include <gl\glew.h>
#include <gl\wglew.h>
#include <Uxtheme.h>
#include <commctrl.h>
#include <tchar.h>
#include "resource.h"
//#define WIN32_LEAN_AND_MEAN
#define IDC_THE_BUTTON 9001
#pragma comment(linker,"\"/manifestdependency:type='win32' \
name='Microsoft.Windows.Common-Controls' version='6.0.0.0' \
processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"")
struct GlobalSection
{
HINSTANCE app;
HWND wnd;
HWND button;
HWND panel;
HWND text;
HGLRC glrc;
HDC fdc;
} g;
bool initWindow(int cmdShow);
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam);
void Init(HWND wnd);
void Render();
int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hPrevInst, LPSTR lpCmd, int nCmdShow)
{
g.app = hInst;
INITCOMMONCONTROLSEX iccex;
iccex.dwSize = sizeof(iccex);
iccex.dwICC = ICC_BAR_CLASSES|ICC_COOL_CLASSES|ICC_LISTVIEW_CLASSES|ICC_PROGRESS_CLASS|ICC_STANDARD_CLASSES|ICC_TAB_CLASSES;
InitCommonControlsEx(&iccex);
initWindow(nCmdShow);
MSG m;
ZeroMemory(&m, sizeof(m));
while(m.message != WM_QUIT)
{
Render();
if(PeekMessage(&m, NULL, 0, 0, PM_REMOVE) && !IsDialogMessage(g.wnd, &m))
{
TranslateMessage(&m);
DispatchMessage(&m);
}
}
}
bool initWindow(int cmdShow)
{
WNDCLASSEX wc;
ZeroMemory(&wc, sizeof(wc));
wc.cbSize = sizeof(wc);
wc.lpfnWndProc = (WNDPROC)WndProc;
wc.lpszClassName = _T("Window Party");
wc.hInstance = g.app;
wc.hbrBackground = (HBRUSH)COLOR_WINDOW;
wc.lpszMenuName = MAKEINTRESOURCE(IDR_MENU1);
wc.hIcon = LoadIcon(g.app, MAKEINTRESOURCE(IDI_ICON2));
wc.hIconSm = wc.hIcon;
RegisterClassEx(&wc);
g.wnd = CreateWindowEx(WS_EX_CLIENTEDGE, _T("Window Party"), _T("Window Party"), WS_SYSMENU|WS_CLIPCHILDREN|WS_OVERLAPPED, CW_USEDEFAULT, CW_USEDEFAULT, 1280, 800, NULL, NULL, g.app, NULL);
ShowWindow( g.wnd, cmdShow ); // technically should be nCmdShow
return true;
}
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam)
{
switch(message)
{
case WM_CREATE:
Init(hwnd);
return DefWindowProc(hwnd, message, wparam, lparam);
case WM_DESTROY:
PostQuitMessage(0);
break;
case WM_COMMAND:
{
int wmId = LOWORD(wparam);
int wmEvent = HIWORD(wparam);
switch(wmId)
{
case IDM_FILE_CLOSE:
PostQuitMessage(0);
break;
case IDOK:
case IDC_THE_BUTTON:
{
MessageBox(hwnd, _T("You have pressed the button!"), _T("Congraturation"), MB_OK);
}
break;
default:
return DefWindowProc(hwnd, message, wparam, lparam);
}
}
break;
case WM_KEYDOWN:
{
TCHAR buff[64] = {0};
wsprintf(buff, _T("Key Pressed: 0x%X"), (unsigned)wparam);
MessageBox(hwnd, buff, _T("YOYOYO"), MB_OK);
break;
}
default:
return DefWindowProc(hwnd, message, wparam, lparam);
}
return 0;
}
void Init(HWND wnd)
{
//Creating controls
RECT cr;
GetClientRect(wnd, &cr);
g.panel = CreateWindow(_T("STATIC"), NULL, WS_CHILDWINDOW|WS_VISIBLE|CS_OWNDC|CS_VREDRAW|CS_HREDRAW, 0, 0, cr.right, cr.bottom-26, wnd, NULL, g.app, NULL);
g.button = CreateWindow(_T("BUTTON"), _T("The Button"), WS_CHILDWINDOW|BS_DEFPUSHBUTTON|WS_VISIBLE, cr.right-160, cr.bottom-26, 160, 26, wnd, (HMENU)IDC_THE_BUTTON, g.app, NULL);
g.text = CreateWindowEx(WS_EX_CLIENTEDGE, _T("EDIT"), NULL, WS_CHILDWINDOW|ES_LEFT|WS_VISIBLE, 0, cr.bottom-26, cr.right-160, 26, wnd, NULL, g.app, NULL);
//Setting fonts
HFONT hf = NULL;
hf = (HFONT)GetStockObject(DEFAULT_GUI_FONT);
SendMessage(g.button, WM_SETFONT, (WPARAM)hf, TRUE);
SendMessage(g.text, WM_SETFONT, (WPARAM)hf, TRUE);
//Creating wgl context
g.fdc = GetDC(g.panel);
PIXELFORMATDESCRIPTOR pfd = {0};
pfd.nSize = sizeof(PIXELFORMATDESCRIPTOR);
pfd.nVersion = 1;
pfd.dwFlags = PFD_DRAW_TO_WINDOW|PFD_SUPPORT_OPENGL|PFD_DOUBLEBUFFER;
pfd.iPixelType = PFD_TYPE_RGBA;
pfd.cColorBits = 32;
pfd.cDepthBits = 16;
pfd.cStencilBits = 8;
SetPixelFormat(g.fdc, 1, &pfd);
g.glrc = wglCreateContext(g.fdc);
wglMakeCurrent(g.fdc, g.glrc);
RECT pcr;
GetClientRect(g.panel, &pcr);
glViewport(0, 0, pcr.right, pcr.bottom);
glewInit();
glClearColor(0.0, 0.0, 0.0, 1.0);
UpdateWindow(wnd);
}
void Render()
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
SwapBuffers(g.fdc);
}
You must not call Render() for every single window message! Only call Render() for WM_PAINT messages.
This sounds like a memory leak somewhere. The computer will come to a screeching stop if the PF usage gets too high.
Open up your task manager and look at the PF Usage before and then again during running the program. If it jumps up .5GB or more and keeps climbing then you've got a memory leak on your hands.
If it behaves in the manner described above the most likely culprit is that you are either making a new object (or texture) multiple times causing Windows to need to allocate more PF space for you, or you could have your VSYNC turned off.
I probably don't have to tell you what happens with a memory leak, but with the VSYNC turned off the program is going to try to run as fast as the CPU lets it. It will max out your CPU and it will do it fast.
Hope this helps.