#include "stdafx.h" #include #include #include #include "DLUConv.h" HFONT _hBaseFont = 0; LPCWSTR _pszClass = L"StartNRT"; LRESULT CALLBACK WP(HWND, UINT, WPARAM, LPARAM); int _progress = 0; DWORD WINAPI TP(LPVOID param) { HANDLE hTargetProcess = (HANDLE)param; WaitForInputIdle(hTargetProcess, (DWORD)-1); return 0; } int WINAPI wWinMain(HINSTANCE hInst, HINSTANCE, LPWSTR, int) { assert(sizeof(DWORD) == sizeof(unsigned int)); srand(GetTickCount()); // Make base font for DLU sizing and alignment LOGFONT lfBaseFont = {0}; HFONT hStockFont = (HFONT)GetStockObject(DEFAULT_GUI_FONT); GetObject(hStockFont, sizeof(lfBaseFont), &lfBaseFont); #ifdef _DEBUG LOGFONT lfNull = {0}; assert(memcmp(&lfBaseFont, &lfNull, sizeof(lfBaseFont)) != 0); #endif _hBaseFont = CreateFontIndirect(&lfBaseFont); assert(_hBaseFont != 0); // Register window class WNDCLASS wc = {0}; wc.hCursor = LoadCursor(0, IDC_APPSTARTING); wc.hInstance = hInst; wc.lpszClassName = _pszClass; wc.lpfnWndProc = WP; ATOM atom = RegisterClass(&wc); assert(atom != 0); // // Start the main application // Note: according to MS COM should always be initialised // if using Shell functions such as ShellExecuteEx() // WCHAR szPathName[33792] = {0}; GetModuleFileName(0, szPathName, sizeof(szPathName) / sizeof(WCHAR)); WCHAR *p = wcsrchr(szPathName, L'\\'); assert(p != 0); wcscpy(p, L"\\NetworkResponseTester.exe"); CoInitializeEx(0, COINIT_APARTMENTTHREADED|COINIT_DISABLE_OLE1DDE); SHELLEXECUTEINFO i = {0}; i.cbSize = sizeof(i); i.fMask = SEE_MASK_NOCLOSEPROCESS|SEE_MASK_FLAG_NO_UI; i.lpVerb = L"Open"; i.lpFile = szPathName; i.nShow = SW_SHOW; ShellExecuteEx(&i); assert(i.hProcess != 0); // // Create secondary thread to exist parallel to // process becomming input-idle // DWORD dwID = 0; HANDLE hThread = CreateThread(0, 0, &TP, i.hProcess, CREATE_SUSPENDED, &dwID); assert(hThread != 0); SetThreadPriority(hThread, THREAD_PRIORITY_BELOW_NORMAL); ResumeThread(hThread); // Make (one-and-only) window const int cxDLU = 190; const int cyDLU = 80; HWND hwnd = CreateWindowEx(WS_EX_TOOLWINDOW, _pszClass, L"", WS_POPUP|WS_DLGFRAME, 0, 0, DLUConv::DLU2PxX(_hBaseFont, cxDLU), DLUConv::DLU2PxY(_hBaseFont, cyDLU), 0, 0, hInst, 0); assert(hwnd != 0); // Show and position window RECT rc; int cxPxS = GetSystemMetrics(SM_CXSCREEN); int cyPxS = GetSystemMetrics(SM_CYSCREEN); GetWindowRect(hwnd, &rc); OffsetRect(&rc, -rc.left, -rc.top); MoveWindow(hwnd, (cxPxS - rc.right) / 2, (cyPxS - rc.bottom) / 2, rc.right, rc.bottom, 0); ShowWindow(hwnd, SW_SHOWNORMAL); UpdateWindow(hwnd); // Move window to top of non-topmost z-order range SetWindowPos(hwnd, HWND_TOP, 0, 0, 0, 0, SWP_NOMOVE|SWP_NOSIZE); // Message loop for(;;) { // // Block thread until either secondary thread has finished // (meaning target process is input idle) or any system/app // event requiring a call to one of the message retrieval // functions // Note: PeekMessage() facilitates everything GetMessage() does // so anything the system needs to do as in a normal message loop // is fine // DWORD res = MsgWaitForMultipleObjects(1, &hThread, 0, (DWORD)-1, QS_ALLINPUT); if(res == WAIT_OBJECT_0) { // Since thread has finished we want to show 100% // so we set to 99 to cause next timer-induced // redraw to increase directly to 100%; if we set // to 100% here, no redraw would happen if(_progress < 99) _progress = 99; const DWORD dwFinal = 2000; // // Once 100% is reached, maintain the display // for [dwFinal] milliseconds // if(_progress == 100) { static BYTE bDone = 0; static DWORD dwDoneAt = 0; if(!bDone) { bDone = 1; dwDoneAt = GetTickCount(); } // Let user see 100% if(GetTickCount() - dwDoneAt > dwFinal) { SetWindowPos(hwnd, HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOMOVE|SWP_NOSIZE); DestroyWindow(hwnd); break; } } } MSG msg = {0}; while(PeekMessage(&msg, 0, 0, 0, PM_REMOVE)) { // Ignore all input messages if(msg.message >= WM_KEYFIRST && msg.message <= WM_KEYLAST) continue; if(msg.message >= WM_MOUSEFIRST && msg.message <= WM_MOUSELAST) continue; // No need for TranslateMessage() // since we are ignoring input DispatchMessage(&msg); } } // Clean up CloseHandle(hThread); CloseHandle(i.hProcess); DeleteObject(_hBaseFont); return 0; } void _Draw(HWND hwnd, HDC hdc) { _progress += (rand() % 4); if(_progress > 100) _progress = 100; RECT rc = {0}; GetClientRect(hwnd, &rc); HDC mdc = CreateCompatibleDC(0); HBITMAP mbm = CreateCompatibleBitmap(hdc, rc.right, rc.bottom); HBITMAP prevbm = (HBITMAP)SelectObject(mdc, mbm); BitBlt(mdc, 0, 0, rc.right, rc.bottom, hdc, 0, 0, SRCCOPY); int cxb = GetSystemMetrics(SM_CXBORDER); int cyb = GetSystemMetrics(SM_CYBORDER); RECT rcFill = rc; rcFill.left += cxb; rcFill.top += cyb; rcFill.bottom -= cyb; rcFill.right -= cxb; FillRect(mdc, &rcFill, GetSysColorBrush(COLOR_WINDOW)); RECT rcProgress = rc; rcProgress.left += DLUConv::DLU2PxX(_hBaseFont, 7); rcProgress.right -= DLUConv::DLU2PxX(_hBaseFont, 7); rcProgress.bottom -= DLUConv::DLU2PxY(_hBaseFont, 7); rcProgress.top = rcProgress.bottom - DLUConv::DLU2PxY(_hBaseFont, 10); SelectObject(mdc, (HPEN)GetStockObject(BLACK_PEN)); SelectObject(mdc, (HBRUSH)GetStockObject(WHITE_BRUSH)); Rectangle(mdc, rcProgress.left, rcProgress.top, rcProgress.right, rcProgress.bottom); int xoff = rcProgress.left; OffsetRect(&rcProgress, -xoff, 0); rcProgress.right *= _progress; rcProgress.right /= 100; OffsetRect(&rcProgress, xoff, 0); SelectObject(mdc, GetSysColorBrush(COLOR_HIGHLIGHT)); Rectangle(mdc, rcProgress.left, rcProgress.top, rcProgress.right, rcProgress.bottom); SelectObject(mdc, _hBaseFont); SetBkMode(mdc, TRANSPARENT); SetTextColor(mdc, GetSysColor(COLOR_WINDOWTEXT)); DrawText(mdc, L"\r\n Network Response Tester\r\n Copyright © Chris Millward 2011" L"\r\n www.chrismillward.com", -1, &rc, DT_WORDBREAK|DT_TOP); SelectObject(mdc, (HFONT)GetStockObject(SYSTEM_FONT)); SelectObject(mdc, (HBRUSH)GetStockObject(WHITE_BRUSH)); BitBlt(hdc, 0, 0, rc.right, rc.bottom, mdc, 0, 0, SRCCOPY); SelectObject(mdc, prevbm); } // // WP() // LRESULT CALLBACK WP(HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam) { switch(iMsg) { case WM_CREATE: SetTimer(hwnd, 1, 100, 0); break; case WM_ERASEBKGND: // Cheap enough to draw twice PostMessage(hwnd, WM_USER, 0, 0); _Draw(hwnd, (HDC)wParam); return 1; case WM_PAINT: { // Bypass any DWM-Windows caching // by always direct drawing PostMessage(hwnd, WM_USER, 0, 0); PAINTSTRUCT ps = {0}; BeginPaint(hwnd, &ps); EndPaint(hwnd, &ps); return 0; } case WM_USER: { // Purge message queue of duplicate calls MSG msg = {0}; while(PeekMessage(&msg, hwnd, iMsg, iMsg, PM_REMOVE)); // Whole client area is about to be drawn // so no reason for any 'pending WM_PAINT' // to get through ValidateRect(hwnd, 0); HDC hdc = GetDC(hwnd); _Draw(hwnd, hdc); ReleaseDC(hwnd, hdc); return 0; } case WM_TIMER: PostMessage(hwnd, WM_USER, 0, 0); return 0; case WM_CLOSE: return 0; case WM_DESTROY: KillTimer(hwnd, 1); PostQuitMessage(0); return 0; } return DefWindowProc(hwnd, iMsg, wParam, lParam); }