I created an application by slightly modifying the Magnification API sample provided by Microsoft to implement a custom transformation of each frame captured and displayed in the Magnifier window.
I used the MagSetImageScalingCallback to set the my function as a callback. The callback is invoked without problem and the source and destination image can be easily manipulated because the raw bits are passed to the callback as pointers (srcdata and destdata).
The window is refreshed with a timer which is set to 16 ms (~60Hz). The refresh is invoked by using the InvalidateRect API. The problem is that when the magnifier window suffer from flickering. This happens especially when the start menu appears, if "Peek" is enabled or every time there is a window on foreground which has dynamic content.
I tried to intercept the WM_ERASEBKGND and invoke the InvalidateRect with FALSE as third parameter but this didn't help. I tried to add an UpdateWindow invocation before the invalidate but nothing changed.
The Magnifier application provided with Windows 10 doesn't have the same problem. I'm wondering why this is happening and how can I get rid of flickering.
In order to reproduce che problem download the Magnification API sample from the link above then replace the content in the file MagnifierSample.cpp with this source code:
// Ensure that the following definition is in effect before winuser.h is included.
#ifndef _WIN32_WINNT
#define _WIN32_WINNT 0x0501
#endif
#include <windows.h>
#include <wincodec.h>
#include <magnification.h>
// For simplicity, the sample uses a constant magnification factor.
#define RESTOREDWINDOWSTYLES WS_POPUP
// Global variables and strings.
HINSTANCE hInst;
const TCHAR WindowClassName[]= TEXT("MagnifierWindow");
const TCHAR WindowTitle[]= TEXT("Screen Magnifier Sample");
const UINT timerInterval = 16; // close to the refresh rate #60hz
HWND hwndMag;
HWND hwndHost;
RECT magWindowRect;
RECT hostWindowRect;
// Forward declarations.
ATOM RegisterHostWindowClass(HINSTANCE hInstance);
BOOL SetupMagnifier(HINSTANCE hinst);
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
void CALLBACK UpdateMagWindow(HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime);
BOOL isFullScreen = FALSE;
//
// FUNCTION: WinMain()
//
// PURPOSE: Entry point for the application.
//
int APIENTRY WinMain(_In_ HINSTANCE hInstance,
_In_opt_ HINSTANCE /*hPrevInstance*/,
_In_ LPSTR /*lpCmdLine*/,
_In_ int nCmdShow)
{
if (FALSE == MagInitialize())
{
return 0;
}
if (FALSE == SetupMagnifier(hInstance))
{
return 0;
}
ShowWindow(hwndHost, nCmdShow);
UpdateWindow(hwndHost);
// Create a timer to update the control.
UINT_PTR timerId = SetTimer(hwndHost, 0, timerInterval, UpdateMagWindow);
// Main message loop.
MSG msg;
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
// Shut down.
KillTimer(NULL, timerId);
MagUninitialize();
return (int) msg.wParam;
}
//
// FUNCTION: HostWndProc()
//
// PURPOSE: Window procedure for the window that hosts the magnifier control.
//
LRESULT CALLBACK HostWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_ERASEBKGND:
return TRUE;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
//
// FUNCTION: RegisterHostWindowClass()
//
// PURPOSE: Registers the window class for the window that contains the magnification control.
//
ATOM RegisterHostWindowClass(HINSTANCE hInstance)
{
WNDCLASSEX wcex = {};
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.style = CS_HREDRAW | CS_VREDRAW;
wcex.lpfnWndProc = HostWndProc;
wcex.hInstance = hInstance;
wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
wcex.hbrBackground = (HBRUSH)(1 + COLOR_BTNFACE);
wcex.lpszClassName = WindowClassName;
return RegisterClassEx(&wcex);
}
static BOOL CALLBACK myCallBack(HWND hwnd, void *srcdata, MAGIMAGEHEADER srcheader, void *destdata, MAGIMAGEHEADER destheader, RECT unclipped, RECT clipped, HRGN dirty) {
UINT row;
UINT column;
BYTE *matrix;
memset(destdata, 0, destheader.cbSize);
matrix = (BYTE *) destdata;
//Set alpha bits for the resulting image to 255 (fully opaque)
for (row = 0; row < destheader.height; row++) {
for (column = 3; column < destheader.width; column += 4) {
matrix[(row * destheader.width) + column] = 0xFF;
}
}
Sleep(20);
return TRUE;
}
//
// FUNCTION: SetupMagnifier
//
// PURPOSE: Creates the windows and initializes magnification.
//
BOOL SetupMagnifier(HINSTANCE hinst)
{
// Set bounds of host window according to screen size.
hostWindowRect.top = 0;
hostWindowRect.bottom = GetSystemMetrics(SM_CYSCREEN);
hostWindowRect.left = 0;
hostWindowRect.right = GetSystemMetrics(SM_CXSCREEN);
// Create the host window.
RegisterHostWindowClass(hinst);
hwndHost = CreateWindowEx(WS_EX_TOPMOST | WS_EX_LAYERED, WindowClassName, WindowTitle, RESTOREDWINDOWSTYLES, 0, 0, hostWindowRect.right, hostWindowRect.bottom, NULL, NULL, hInst, NULL);
if (!hwndHost)
{
return FALSE;
}
// Make the window opaque.
SetLayeredWindowAttributes(hwndHost, 0, 255, LWA_ALPHA);
SetWindowLong(hwndHost, GWL_EXSTYLE, GetWindowLong(hwndHost, GWL_EXSTYLE) | WS_EX_LAYERED | WS_EX_TRANSPARENT | WS_EX_TOPMOST | WS_EX_NOACTIVATE);
// Create a magnifier control that fills the client area.
magWindowRect = hostWindowRect;
hwndMag = CreateWindow(WC_MAGNIFIER, TEXT("MagnifierWindow"), WS_CHILD | WS_VISIBLE, magWindowRect.left, magWindowRect.top, magWindowRect.right, magWindowRect.bottom, hwndHost, NULL, hInst, NULL );
if (!hwndMag)
{
return FALSE;
}
MagSetImageScalingCallback(hwndMag, &myCallBack);
// Set the source rectangle for the magnifier control.
MagSetWindowSource(hwndMag, magWindowRect);
return 1;
}
//
// FUNCTION: UpdateMagWindow()
//
// PURPOSE: Sets the source rectangle and updates the window. Called by a timer.
//
void CALLBACK UpdateMagWindow(HWND /*hwnd*/, UINT /*uMsg*/, UINT_PTR /*idEvent*/, DWORD /*dwTime*/)
{
// Reclaim topmost status, to prevent unmagnified menus from remaining in view.
SetWindowPos(hwndHost, HWND_TOPMOST, 0, 0, magWindowRect.right, magWindowRect.bottom, SWP_SHOWWINDOW | SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOREDRAW);
// Force redraw.
InvalidateRect(hwndMag, NULL, FALSE);
}
Notice that I added the code to set the callback
BOOL ret = MagSetImageScalingCallback(hwndMag, &myCallBack);
then I created this callback:
static BOOL CALLBACK myCallBack(HWND hwnd, void *srcdata, MAGIMAGEHEADER srcheader, void *destdata, MAGIMAGEHEADER destheader, RECT unclipped, RECT clipped, HRGN dirty)
The Sleep statement "simulate" the time taken by my custom transformation.
If you open a youtube video with Edge and then you run the MagnifierSample.exe executable you should see a black screen flickering sometime and when the screen flickers you should see the content behind the magnifier window (the black screen). This is exactly what is happening in my application. The sleep value is set to 20 but I don't known how long the callback is actually taking. I just guessed that it might take more than 16 ms.
To reproduce the problem perform the following steps:
Run the MagnifierSample.exe manually (don't use the debugger to run it)
You should see a black screen
Press the window key
Set a window with a video or a dynamic content in foreground
Click on the black window to let the taskbar disappear
You should see the screen flashing sometime
Is it possible to set a callback similar to the MagSetImageScalingCallback for a normal window handle ? I know I can use the WindowProc callback to intercept the messages sent to the window however I don't have access to the rawbits unless I use CreateDIBSection, SelectObject etc ...but at that time the image has been already sent to the destination window and I don't have the chance to transform it.
Related
MSDN describes that lParam of WM_MOUSEMOVE is 2 shorts due to it needing to be compatible with virtual coordinates because it acts as a redirected event if capture is set, which is clear. However, negative coordinates are still received under normal circumstances when moving the mouse slightly outside of the window, to be exact a bonus 5 pixels in all directions except up (which is where the caption is, and it'd go to WM_NCMOUSEMOVE instead).
I initially suspected this was to do with the drop shadow technically being part of the window (like with the output of AdjustWindowRectEx's rectangle including it) since you can receive events in some of the shadow, but the values of that don't match up, and clamping the values to the client area's size doesn't feel intended. Where do the bonus 5 pixels (on my system, Windows 10 Education 2004) come from, especially considering that shouldn't even be a part of the client area to my knowledge, and is there a clean/intended way to dodge unwanted values?
I've seen some discussion about the area you can grab to resize the window potentially being related, but my window isn't resizable (doesn't have a thick frame).
Edit: After tinkering, it seems this is related to the window style. Here's a reproducible sample (hopefully on other machines, too):
#include <Windows.h>
#include <stdio.h>
// Remove `^ WS_THICKFRAME` and the bug vanishes!
#define WINDOW_STYLE ((WS_OVERLAPPEDWINDOW | WS_VISIBLE) ^ WS_THICKFRAME)
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) {
if (message == WM_MOUSEMOVE) {
POINTS mouse = MAKEPOINTS(lParam);
printf("WM_MOUSEMOVE # x=%hd, y=%hd\n", mouse.x, mouse.y);
} else if (message == WM_DESTROY) {
PostQuitMessage(0);
}
return DefWindowProcW(hWnd, message, wParam, lParam);
}
int main(void) {
WNDCLASSEXW wcex;
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.style = CS_HREDRAW | CS_VREDRAW;
wcex.lpfnWndProc = WndProc;
wcex.cbClsExtra = 0;
wcex.cbWndExtra = 0;
wcex.hInstance = GetModuleHandle(NULL);
wcex.hIcon = NULL;
wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
wcex.lpszMenuName = NULL;
wcex.lpszClassName = L"SO_Example";
wcex.hIconSm = NULL;
RECT windowRect = { 0, 0, 600, 600 };
AdjustWindowRectEx(&windowRect, WINDOW_STYLE, FALSE, 0);
ATOM windowClass = RegisterClassExW(&wcex);
HWND hWnd = CreateWindowExW(
0,
(LPCWSTR)windowClass,
L"hello stackoverflow!",
WINDOW_STYLE,
CW_USEDEFAULT, CW_USEDEFAULT,
windowRect.right - windowRect.left,
windowRect.bottom - windowRect.top,
NULL,
NULL,
GetModuleHandle(NULL),
NULL
);
MSG msg;
while (GetMessageW(&msg, NULL, 0, 0) != 0) {
TranslateMessage(&msg);
DispatchMessageW(&msg);
}
return 0;
}
Here's that sample in action: https://i.imgur.com/LfCe9od.mp4
It appears 5 is the border size (1) and the border padding (4) added up (aka the area the user can use to resize the window), so it's safe to discard values outside of the client rectangle, even if it's a little strange that Win32 is reporting events in that area.
I am searching for the fastest possible way in C to determine if any key on the keyboard has been pressed.
I am not looking for how to determine if a specific key is pressed (in that case, GetAsyncKeyState() would work).
Also, it needs to work in the background, so without the program window having focus (the program will be running in the background).
EDIT: The program will react on every keypress with a sound output. I want it to output a sound everytime I type something (like in Word and such). That's also why it needs to run in the background. I want it to be fast, so I can minimize the delay between keypress and sound output.
EDIT2: I am searching for something else than Windows Hooks. While it does work for getting key presses in the background, I am looking for something that is faster (least delay possible).
For example: GetAsyncKeyState() works for reacting on specific keypresses, while the program window doesn't have focus. I am looking for something like that, but with the ability to react on any key press, not a specific one.
As comment, you could use RegisterRawInputDevices as this sample.
Create a Message-Only Window.
Set RAWINPUTDEVICE.hwndTarget to the window create in step 1, so that you don't need to focus on the window.
Call GetRawInputData to get the input data.
Sample(removed the error checking):
#include <windows.h>
#include <iostream>
using namespace std;
LRESULT CALLBACK WindProc(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam)
{
if (Msg == WM_INPUT)
{
HRAWINPUT hRawInput = (HRAWINPUT)lParam;
RAWINPUT input = { 0 };
UINT size = sizeof(input);
GetRawInputData(hRawInput, RID_INPUT,&input,&size,sizeof(RAWINPUTHEADER));
printf("vkey: %x, flag: %d\n",input.data.keyboard.VKey, input.data.keyboard.Flags);
}
return DefWindowProc(hWnd, Msg, wParam, lParam);
}
int main()
{
WNDCLASSEX wcx = { 0 };
wcx.cbSize = sizeof(WNDCLASSEX);
wcx.lpfnWndProc = WindProc;
wcx.hInstance = GetModuleHandle(NULL);
wcx.lpszClassName = TEXT("RawInputClass");
RegisterClassEx(&wcx);
HWND hWnd = CreateWindowEx(0, TEXT("RawInputClass"), NULL, 0, 0, 0, 0, 0, HWND_MESSAGE, NULL, GetModuleHandle(NULL), NULL);
RAWINPUTDEVICE rid = { 0 };
rid.usUsagePage = 0x01;
rid.usUsage = 0x06; //keyboard
rid.dwFlags = RIDEV_INPUTSINK;
rid.hwndTarget = hWnd;
RegisterRawInputDevices(&rid, 1, sizeof(RAWINPUTDEVICE));
MSG msg;
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return 0;
}
I have a main window with an edit control and a custom button. When the edit control has focus and I press the Tab key, the button control gets the focus but it never receives WM_UPDATEUISTATE? I'm using IsDialogMessage() in the message loop of main window. Any ideas on why this is happening?
Edit: Why isn't the custom button control receiving WM_UPDATEUISTATE?
Edit: IsDialogMessage() is not sending WM_UPDATEUISTATE or WM_CHANGUISTATE in this example
#include <windows.h>
#include <tchar.h>
HINSTANCE g_hInst;
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
static HWND hBtn1, hBtn2;
switch(msg)
{
case WM_CREATE:
hBtn1 = CreateWindowEx(0, TEXT("Button"), TEXT("Button 1"), WS_VISIBLE | WS_CHILD | BS_PUSHBUTTON,
4, 4, 100, 40, hwnd, 0, g_hInst, 0);
if(!hBtn1) return -1;
hBtn2 = CreateWindowEx(0, TEXT("Button"), TEXT("Button 2"), WS_VISIBLE | WS_CHILD | BS_PUSHBUTTON,
114, 4, 100, 40, hwnd, 0, g_hInst, 0);
if(!hBtn1) return -1;
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hwnd, msg, wParam, lParam);
}
return 0;
}
int WINAPI _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow)
{
const TCHAR szClassName[] = TEXT("Main////");
WNDCLASSEX wc;
HWND hwnd;
MSG msg;
SecureZeroMemory(&wc, sizeof(WNDCLASSEX));
wc.cbSize = sizeof(WNDCLASSEX);
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
wc.hCursor = LoadCursor(0, IDC_ARROW);
wc.hIcon = LoadIcon(0, IDI_APPLICATION);;
wc.hInstance = hInstance;
wc.lpfnWndProc = WndProc;
wc.lpszClassName = szClassName;
if(!RegisterClassEx(&wc)) return 0;
g_hInst = hInstance;
hwnd = CreateWindowEx(0, szClassName, TEXT("Main"), WS_OVERLAPPEDWINDOW, 140, 140, 440, 240, 0, 0, hInstance, 0);
if(!hwnd) return 0;
ShowWindow(hwnd, nCmdShow);
while(GetMessage(&msg, 0, 0, 0) > 0)
{
if(!IsDialogMessage(hwnd, &msg))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
return (int)msg.wParam;
}
I've used spy++ for the main window and the 2 buttons when pressing the Tab key and I don't see neither WM_UPDATEUISTATE nor WM_CHANGEUISTATE anywhere, but the standard button control draws the focus rect. How does the standard button know when to draw the focus rect?
A window should show its focus indicator when it has focus. For that, a control should watch for WM_SETFOCUS and WM_KILLFOCUS. When it has the focus, a well-behaved control will check the UI state by sending WM_QUERYUISTATE to itself, and, if the UI state has the UISF_HIDEFOCUS flag set, the control would not draw the focus indicator.
The WM_CHANGEUISTATE and WM_UPDATEUISTATE are responsible for controlling whether a tree of windows (including controls) should display keyboard "cues" and focus indicators in general. For example, if the cues aren't showing and you tap the ALT key, these messages will update the UI state of the windows in the tree so that they start showing the cues. These messages are not used for hiding the focus indicator from one control and showing it on the next as the user TABs through the controls. That effect happens because the controls pay attention to the focus change messages.
Generally speaking, a custom control should watch for the focus change messages and let DefWindowProc handle the UI state messages.
Raymond Chen has a series of blog posts about the UI state and how these messages propagate.
Untangling the confusingly-named WM_UPDATEUISTATE and WM_CHANGEUISTATE messages
Who sends the initial WM_UPDATEUISTATE message?
Why does tapping the Alt key cause my owner-draw static control to repaint?
What is the documentation for SetParent trying to tell me about synchronizing the UI state?
Demonstrating what happens when a parent and child window have different UI states
Getting a parent and child window to have the same UI states
I found these helpful when I recently tried to work all this out again. The current state of the official documentation on this topic is a bit vague and ambiguous in places.
Note: The terms "mnemonics," "accelerators," and "hot keys" have blurred together, even in the official Microsoft documentation and some of the API names. When I say "keyboard cues," I'm mostly referring to mnemonics (sometimes called "shortcuts" or "access keys"). Those are the underlined characters in control labels and menu items that let the user know that ALT+<character> will select the labeled item.
The aim is to resize the Window of the DialogEx that best fit the configured Systemmetrics screenheight and screendepth on the object machine:
int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PWSTR pCmdLine, int nCmdShow)
{
return DialogBoxW(hInstance, MAKEINTRESOURCEW(IDD_MAIN), nullptr, DlgProc);
}
IDD_MAIN is setup as the default in 768p. Let's call it IDD_760Pinstead and use its resource file config as the base to work on.
IDD_768P DIALOGEX 0, 0, 701, 191
STYLE DS_SETFONT | DS_MODALFRAME | DS_CENTER | WS_POPUP | WS_CAPTION | WS_SYSMENU
CAPTION "MahProject"
FONT 8, "MS Sans Serif", 0, 0, 0x0
BEGIN
LTEXT "Add",IDC_STATIC,506,10,14,8
EDITTEXT IDC_TEXT,528,7,120,14,ES_AUTOHSCROLL
EDITTEXT IDC_NUMBER,647,7,21,14,ES_NUMBER
LTEXT "times.",IDC_STATIC,671,10,23,8
LISTBOX IDC_LIST,7,22,641,148,LBS_NOINTEGRALHEIGHT | LBS_EXTENDEDSEL | WS_VSCROLL | WS_TABSTOP //principal, main, or chief control on form
PUSHBUTTON "&Add",IDC_ADD,650,30,46,14
PUSHBUTTON "&Up",IDC_UP,650,47,47,14
PUSHBUTTON "&Down",IDC_DOWN,650,63,47,14
PUSHBUTTON "&Sideways",IDC_CREATE,650,80,47,14
PUSHBUTTON "&UpsideDown",IDC_REMOVE,650,97,47,14
PUSHBUTTON "&Less",IDC_CLEAR,650,114,47,14
PUSHBUTTON "&More",IDC_LOGON,650,131,47,14
PUSHBUTTON "&NoMore",IDC_NOLOGON,650,148,47,14
LTEXT "Great",IDC_STATIC_ONE,530,180,70,8
CTEXT "-",IDC_SHOWCOUNT,600,180,25,8
LTEXT "Fantastic",IDC_STATIC_TWO,625,180,30,8
END
These controls can be separately recalibrated with CreateWindow according to this post but that's a lot of code. Is there a way of subclassing the form to use extra resource file presets based on the above for IDD_1080P, IDD_2160P, IDD_4320P& beyond?
Where in the code would we place the GetSystemMetrics(SM_CXSCREEN) and GetSystemMetrics(SM_CYSCREEN) functions?
You can resize the dialog in WM_INITDIALOG
GetSystemMetrics(SM_CXSCREEN)/GetSystemMetrics(SM_CYSCREEN) gives the full screen width/height. This would try to overlap the toolbar. You probably don't want that.
SystemParametersInfo(SPI_GETWORKAREA, NULL, &rcDesktop, NULL); will get the rectangle for desktop, or you can just show maximized window
BOOL CALLBACK DlgProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp)
{
if (msg == WM_INITDIALOG)
{
ShowWindow(hwnd, SW_MAXIMIZE);
return 0;
}
...
return FALSE;
}
You should change the dialog style to the following:
STYLE DS_SETFONT | DS_FIXEDSYS | DS_CENTER | WS_MAXIMIZEBOX | WS_POPUP | WS_CLIPCHILDREN | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME
Note, you will have to move/resize all the child controls. When moving child controls, not that you have to its position in screen coordinates, then convert to client coordinate's of parent window.
Example
#include <Windows.h>
#include "resource.h"
#define rcwidth(rc) (rc.right - rc.left)
#define rcheight(rc) (rc.bottom - rc.top)
void move_resize(HWND child, int dx, int dy, int dw, int dh)
{
if (!child) return;
if (!IsWindow(child)) return;
if (!GetParent(child)) return;
//find child window's coordinates relative to top-left of parent:
RECT rc;
GetWindowRect(child, &rc);
//rc is now child control's rectangle in screen coordinates
POINT pt = { 0 };
ScreenToClient(GetParent(child), &pt);
OffsetRect(&rc, pt.x, pt.y);
//rc is now child control's rectangle relative to parent window
//prevent negative size
if ((rcwidth(rc) + dw) < 0) dw = -rcwidth(rc);
if ((rcheight(rc) + dh) < 0) dh = -rcheight(rc);
MoveWindow(child, rc.left + dx, rc.top + dy, rcwidth(rc) + dw, rcheight(rc), TRUE);
}
BOOL CALLBACK DlgProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
static RECT save_rect;
switch (msg)
{
case WM_INITDIALOG:
GetClientRect(hwnd, &save_rect);
ShowWindow(hwnd, SW_MAXIMIZE);
break;
case WM_SIZE:
if (lParam)
{
int cx = LOWORD(lParam);
int cy = HIWORD(lParam);
int dx = cx - save_rect.right;
int dy = cy - save_rect.bottom;
//change x/y position of OK/Cancel button
move_resize(GetDlgItem(hwnd, IDCANCEL), dx, dy, 0, 0);
move_resize(GetDlgItem(hwnd, IDOK), dx, dy, 0, 0);
//change width/height of listbox:
move_resize(GetDlgItem(hwnd, IDC_LIST1), 0, 0, dx, dy);
GetClientRect(hwnd, &save_rect);
}
break;
case WM_COMMAND:
if (LOWORD(wParam) == IDOK)
EndDialog(hwnd, wParam);
if (LOWORD(wParam) == IDCANCEL)
EndDialog(hwnd, wParam);
break;
}
return FALSE;
}
int WINAPI wWinMain(HINSTANCE hinst, HINSTANCE, LPTSTR, int)
{
DialogBox(hinst, MAKEINTRESOURCE(IDD_DIALOG1), 0, DlgProc);
return 0;
}
In the "old" days there were avid discussions about the use of static data in resource files as opposed to the then "limited" processing power. Doesn't appear to be such an issue these days, however in this case the processing for huge DPIs might be an issue.
I believe Barmak's answer is the way to go, but added this as an expedient, but cheaper and less precise alternative.
Unfortunately we cannot use the preprocessor for the different presets, so the idea is to enumerate lpTemplate values for LPCDLGTEMPLATE just after the entry point:
int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PWSTR pCmdLine, int nCmdShow)
{
Create_Temporary_hwnd_here
...
lpTemplate = DoSystemParametersInfoStuff(hwnd);
...
Destroy__Temporary_hwnd
...
return DialogBoxW(hInstance, MAKEINTRESOURCEW(lpTemplate), nullptr, DlgProc);
}
DoSystemParametersInfostuff returns the appropriate strings IDD_1080P, IDD_2160P etc. Provided the template structure and associated data for each template in the resource file is not changed there shouldn't be a problem.
Edit1: Checking for primary/secondary display might be a little awkward if calling the function MonitorFromWindow before we get a handle for our DlgProc window using the alternative method to Barmak's. Well it appears that it is not possible to ever return to the wWinMain routine once having left it, so the next option is to create another ephemeral "cheaper" hwnd from wWinMain to get the appropriate templates.
Edit 2: The form templates for the different resolutions are all set up in a spreadsheet downloadable here thanks to AOO.
Normally, even when using double buffering, when resizing a window, it seems that it's inevitable that the flickering will happen.
Step 1, the original window.
Step 2, the window is resized, but the extra area hasn't been painted.
Step 3, the window is resized, and the extra area has been painted.
Is it possible somehow to hide setp 2? Can I suspend the resizing process until the painting action is done?
Here's an example:
#include <Windows.h>
#include <windowsx.h>
#include <Uxtheme.h>
#pragma comment(lib, "Uxtheme.lib")
LRESULT CALLBACK WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
BOOL MainWindow_OnCreate(HWND hWnd, LPCREATESTRUCT lpCreateStruct);
void MainWindow_OnDestroy(HWND hWnd);
void MainWindow_OnSize(HWND hWnd, UINT state, int cx, int cy);
void MainWindow_OnPaint(HWND hWnd);
int APIENTRY wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, int nCmdShow)
{
WNDCLASSEX wcex = { 0 };
HWND hWnd;
MSG msg;
BOOL ret;
wcex.cbSize = sizeof(wcex);
wcex.lpfnWndProc = WindowProc;
wcex.hInstance = hInstance;
wcex.hIcon = (HICON)LoadImage(NULL, IDI_APPLICATION, IMAGE_ICON, 0, 0, LR_SHARED);
wcex.hCursor = (HCURSOR)LoadImage(NULL, IDC_ARROW, IMAGE_CURSOR, 0, 0, LR_SHARED);
wcex.lpszClassName = TEXT("MainWindow");
wcex.hIconSm = wcex.hIcon;
if (!RegisterClassEx(&wcex))
{
return 1;
}
hWnd = CreateWindow(wcex.lpszClassName, TEXT("CWin32"), WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, HWND_DESKTOP, NULL, hInstance, NULL);
if (!hWnd)
{
return 1;
}
ShowWindow(hWnd, nCmdShow);
UpdateWindow(hWnd);
while ((ret = GetMessage(&msg, NULL, 0, 0)) != 0)
{
if (ret == -1)
{
return 1;
}
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return msg.wParam;
}
LRESULT CALLBACK WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch (uMsg)
{
HANDLE_MSG(hWnd, WM_CREATE, MainWindow_OnCreate);
HANDLE_MSG(hWnd, WM_DESTROY, MainWindow_OnDestroy);
HANDLE_MSG(hWnd, WM_SIZE, MainWindow_OnSize);
HANDLE_MSG(hWnd, WM_PAINT, MainWindow_OnPaint);
default:
return DefWindowProc(hWnd, uMsg, wParam, lParam);
}
}
BOOL MainWindow_OnCreate(HWND hWnd, LPCREATESTRUCT lpCreateStruct)
{
BufferedPaintInit();
return TRUE;
}
void MainWindow_OnDestroy(HWND hWnd)
{
BufferedPaintUnInit();
PostQuitMessage(0);
}
void MainWindow_OnSize(HWND hWnd, UINT state, int cx, int cy)
{
InvalidateRect(hWnd, NULL, FALSE);
}
void MainWindow_OnPaint(HWND hWnd)
{
PAINTSTRUCT ps;
HPAINTBUFFER hpb;
HDC hdc;
BeginPaint(hWnd, &ps);
hpb = BeginBufferedPaint(ps.hdc, &ps.rcPaint, BPBF_COMPATIBLEBITMAP, NULL, &hdc);
FillRect(hdc, &ps.rcPaint, GetStockBrush(DKGRAY_BRUSH));
Sleep(320); // This simulates some slow drawing actions.
EndBufferedPaint(hpb, TRUE);
EndPaint(hWnd, &ps);
}
Is it possible to eliminate the flickering?
When the window is updated during a drag operation, then the OS has to show something in the extended window region. If you can't provide anything then it will show the background until you do. Since you didn't specify any background you get blackness. Surely you ought to be specifying a background brush? Simply adding the following to your code makes the behaviour more palatable:
wcex.hbrBackground = GetStockBrush(DKGRAY_BRUSH);
However, if you take as long as 320ms to respond to a WM_PAINT then you ruin the resize UI for the user. It becomes jerky and unresponsive. The system is designed around the assumption that you can paint the window quickly enough for dragging to feel smooth. The right way to fix your problem is to make WM_PAINT run in a reasonable time.
If you really can't achieve quick enough painting for smooth dragging then I suggest a couple of alternatives:
Disable window updates during dragging. I'm sure this can be done for individual windows, but I can't remember how to do it off the top of my head.
Paint something fake whilst a resize/drag is active, and postpone the real painting until when the resize/drag has completed. Listening for WM_ENTERSIZEMOVE and WM_EXITSIZEMOVE are the keys to this. This Microsoft sample program illustrates how to do that: https://github.com/microsoft/Windows-classic-samples/blob/master/Samples/Win7Samples/winui/fulldrag/
Use WM_SIZING instead of WM_SIZE and don't forget about WM_ERASEBKGND.
If you go into the System Properties control panel applet, choose the Advanced tab, and then click Settings... in the Performance group box, you'll see a checkbox setting called Show window contents while dragging. If you uncheck that and try resizing a window, you'll see that only the window frame moves until you complete the drag operation, and then the window repaints just once at the new size. This is how window sizing used to work when we had slow, crufty computers.
Now we don't really want to change the setting globally (which you would do by calling SystemParametersInfo with SPI_SETDRAGFULLWINDOWS, but don't really do that because your users won't like it).
What happens when the user grabs the resize border is that the thread enters a modal loop controlled by the window manager. Your window will get WM_ENTERSIZEMOVE as that loop begins and WM_EXITSIZEMOVE when the operation is complete. At some point you'll also get a WM_GETMINMAXINFO, which probably isn't relevant to what you need to do. You'll also get WM_SIZING, WM_SIZE messages rapidly as the user drags the sizing frame (and the rapid WM_SIZEs often lead to WM_PAINTs).
The global Show window contents while dragging setting is responsible for getting the rapid WM_SIZE messages. If that setting is off, you'll just get one WM_SIZE message when it's all over.
If your window is complicated, you probably have layout code computing stuff (and maybe moving child windows) in the WM_SIZE handler and a lot of painting code in the WM_PAINT handler. If all that code is too slow (as your sample 320 ms delay suggests), then you'll have a flickery, jerky experience.
We really don't want to change the global setting, but it does inspire a solution to your problem:
Do simpler drawing during the resize operation and then do your (slower) complex drawing just once when the operation is over.
Solution:
Set a flag when you see the WM_ENTERSIZEMOVE.
Change your WM_SIZE handler to check the flag and do nothing if it's set.
Change your WM_PAINT handler to check the flag and do a simple, fast fill of the window in a solid color if it's set.
Clear the flag when you see WM_EXITSIZEMOVE, and then trigger your layout code and invalidate your window so that everything gets updated based on the final size.
If your slow window is a child rather than your application's top-level window, you'll have to signal the child window when the top-level window gets the WM_ENTERSIZEMOVE and WM_EXITSIZEMOVE in order to implement steps 1 and 4.
Yes you can entirely delete flickering :)
You can do all the window message handling in one thread, and painting its context in another. Your window always keeps responsive. It works great, can not understand why this is not established best practice.
If you bind a Direct3D context for example, it can have an instant scaling while resizing, completely without having the context updated!
My code looks like this:
int WINAPI wWinMain( HINSTANCE a_hInstance, HINSTANCE a_hPrevInstance, LPWSTR a_lpCmdLine, int a_nCmdShow )
{
Win32WindowRunnable* runnableWindow=new Win32WindowRunnable(a_hInstance, a_nCmdShow);
IThread* threadWindow=new Win32Thread(runnableWindow);
threadWindow->start();
Scene1* scene=new Scene1(runnableWindow->waitForWindowHandle());
IThread* threadRender=new Win32Thread(scene);
threadRender->start();
threadWindow->join();
threadRender->pause();
threadRender->kill();
delete runnableWindow;
return 0;
}
Full source example here:
https://github.com/TheWhiteAmbit/TheWhiteAmbit/blob/master/Win32App/Win32Main.cpp