Handmade Hero » Forums » Code » Issue with switching between modes on Windows windows
brothir
Thomas Kingsvik
10 posts

None

#8481 Issue with switching between modes on Windows windows
11 months, 2 weeks ago Edited by Thomas Kingsvik on Sept. 7, 2016, 5:37 p.m.

Hello. I've been experimenting with borderless windows, and I've run into a snag. If you run the program below and press Enter, the program will switch from a borderless window to a normal window. If you press the left arrow key in either mode, the color of the small rectangle at the bottom will switch colors.

The problem comes when trying to switch back to a borderless window. The window now refuses to update, and switching colors no longer work. However, if you switch to the normal window mode it works again.

My suspicion was that the WM_SIZE message got processed before the window actually switched mode, but I've been trying to post extra SIZE messages without success.

Also, if there is some way to collapse code or upload it as a text file on these forums please tell me how, as I figure it's a bit annoying to have to scroll through a hundred lines of code.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
#include <windows.h>

struct RenderStruct {
	HWND Window;
	HDC DC;
	HBRUSH Brush;
	BITMAPINFO BMPInfo;
	void* Memory;
	__int64 Style;
	__int64 Width;
	__int64 Height;
}; RenderStruct Renderer;

struct V3 {
	__int64 x;
	__int64 y;
	__int64 z;
}; V3 Color = {255, 255, 0};

void MakeBrush(BYTE R, BYTE G, BYTE B) {
	Renderer.Brush = CreateSolidBrush(RGB(R, G, B));
	SelectObject(Renderer.DC, Renderer.Brush);
}

LRESULT CALLBACK TestWinProc(HWND Window, UINT Msg, WPARAM W, LPARAM L) {
	LRESULT Result = 0;
	static __int64 RepeatedInput = 0;
	switch (Msg) {{
		} case (WM_KEYDOWN): {
			switch (W) {{
				} case (VK_ESCAPE): {
					ExitProcess(0);
				} case (VK_RETURN): {
					if (!Renderer.Style) {
						SetWindowLong(Window, GWL_STYLE, 282001408); // WS_OVERLAPPEDWINDOW | WS_VISIBLE
						PostMessageA(Window, WM_SIZE, W, L);
						Renderer.Style = 282001408;
					} else {
						SetWindowLong(Window, GWL_STYLE, 0);
						PostMessageA(Window, WM_SIZE, W, L);
						Renderer.Style = 0;
					} break;
				} case (VK_LEFT): {
					if (!RepeatedInput) {
						int temp = Color.z;
						Color.z = Color.x;
						Color.x = temp;
					} if (L == 21692417) {
						RepeatedInput++;
					} else {
						RepeatedInput = 0;
					} break;
				}
			} break;
		} case (WM_KEYUP): {
			RepeatedInput = 0;
			break;
		} case (WM_SIZE): {
			if (Renderer.Memory) {
				VirtualFree(Renderer.Memory, 0, MEM_RELEASE);
				Renderer.Memory = 0;
			} RECT CR; GetClientRect(Window, &CR);
			Renderer.BMPInfo = {sizeof(BITMAPINFO), (LONG)(Renderer.Width = CR.right), (LONG)(Renderer.Height = CR.bottom), 1, 32, BI_RGB,};
			Renderer.Memory  = VirtualAlloc(0, Renderer.Width*Renderer.Height*4, MEM_COMMIT, PAGE_READWRITE);
			break;
		} case (WM_PAINT): {
			if (!Renderer.Brush) MakeBrush(122, 122, 122);
			PAINTSTRUCT P;
			BeginPaint(Window, &P);
			PatBlt(Renderer.DC, 0, 0, P.rcPaint.right, P.rcPaint.bottom, PATCOPY);
			EndPaint(Window, &P);
			break;
		} default: {
			Result = DefWindowProc(Window, Msg, W, L);
			break;
		}
	} return Result;
}

void DrawRectangle() {
	int* Pixel = (int*)Renderer.Memory + (int)0.4*Renderer.Width + (int)0.4*Renderer.Width*Renderer.Height;
	for (int i = 0; i < Renderer.Width * 10; i++) {
		*Pixel++ = RGB(Color.x, Color.y, Color.z);
	}
}

int CALLBACK WinMain(HINSTANCE Instance, HINSTANCE PrevInstance, LPSTR CmndLine, int ShowCmnd) {
	WNDCLASS WindowClass = {};
	WindowClass.lpszClassName = L"window class";
	WindowClass.lpfnWndProc = TestWinProc;
	WindowClass.hInstance = Instance;
	ATOM A = RegisterClass(&WindowClass);

	int CWD         = CW_USEDEFAULT;
	Renderer.Window = CreateWindow(WindowClass.lpszClassName, L"Test", 0, CWD, CWD, CWD, CWD, 0, 0, Instance, 0);
	Renderer.DC     = GetDC(Renderer.Window);

	SetWindowLong(Renderer.Window, GWL_STYLE, 0);
	PostMessageA(Renderer.Window, WM_SIZE, 0, 0);
	ShowWindow(Renderer.Window, SW_NORMAL);

	for (;;) {
		MSG Msg;
		PeekMessageA(&Msg, 0, 0, 0, PM_REMOVE);
		TranslateMessage(&Msg);
		DispatchMessage(&Msg);

		DrawRectangle();
		StretchDIBits(Renderer.DC, 0, 0, Renderer.Width, Renderer.Height, 
								   0, 0, Renderer.Width, Renderer.Height, 
								   Renderer.Memory, &Renderer.BMPInfo, DIB_RGB_COLORS, SRCCOPY
		); 
	}
}


None
mmozeiko
Mārtiņš Možeiko
1440 posts
1 project
#8483 Issue with switching between modes on Windows windows
11 months, 2 weeks ago Edited by Mārtiņš Možeiko on Sept. 7, 2016, 6:19 p.m.

There are two issues.

1)
1
SetWindowLong(Window, GWL_STYLE, 0);

This line is wrong. Window style includes couple more flags than just the ones you specify.
To correctly toggle overlapped window style do this instead:
1
2
3
4
5
6
case (VK_RETURN): {
	LONG style = GetWindowLong(Window, GWL_STYLE);
	style ^= WS_OVERLAPPEDWINDOW;
	SetWindowLong(Window, GWL_STYLE, style);
	break;
}


2) You need to call SetWindowPos with SWP_FRAMECHANGED flag after you change window style (after SetWindowLong):
1
SetWindowPos(Window, NULL, 0, 0, 0, 0, SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_NOREDRAW);

This will recalculate window size and automatically will send correct WM_SIZE message.

Here's code that does similar thing from Raymond Chen: https://blogs.msdn.microsoft.com/oldnewthing/20100412-00/?p=14353
It also resizes window to cover whole monitor, but you can ignore that part.

Also you should really process all pending messages in your main loop before rendering. Otherwise you'll have unresponsive/laggy window or input:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
	for (;;) {
		MSG Msg;
		while (PeekMessageA(&Msg, 0, 0, 0, PM_REMOVE))
		{
			TranslateMessage(&Msg);
			DispatchMessage(&Msg);
		}

		DrawRectangle();
		StretchDIBits(Renderer.DC, 0, 0, Renderer.Width, Renderer.Height, 
								   0, 0, Renderer.Width, Renderer.Height, 
								   Renderer.Memory, &Renderer.BMPInfo, DIB_RGB_COLORS, SRCCOPY
		); 
	}

brothir
Thomas Kingsvik
10 posts

None

#8487 Issue with switching between modes on Windows windows
11 months, 2 weeks ago Edited by Thomas Kingsvik on Sept. 8, 2016, 7:10 a.m.

Thanks a bunch mmozeiko. I've fiddled around a bit, and come to the following conclusion (this is more for posterity's sake than directed at you):

As mentioned,
1
SetWindowLong(Window, GWL_STYLE, 0);
is wrong, except at the start of the program. This is, as far as I gather, because of ShowWindow() and the fact that WS_VISIBLE is not used in CreateWindow(). My guess is that when ShowWindow is called after the incorrect usage of SetWindowLong(), it detects that the value has been set to something that doesn't make sense (WS_OVERLAPPED which is 0 but also only meaningfully used during CreateWindow(), apparently) and then reverts to the borderless configuration as a default or closest approximation.

If what I've written is at least partially correct, this is a fairly unfortunate example of API covering up incorrect usage of itself.

The code I now use at the start is:
1
2
3
4
Renderer.Window = CreateWindow(WindowClass.lpszClassName, L"Test", WS_OVERLAPPEDWINDOW, CWD, CWD, CWD, CWD, 0, 0, Instance, 0);
SetWindowLong(Renderer.Window, GWL_STYLE, 0);
PostMessageA(Renderer.Window, WM_SIZE, 0, 0);
ShowWindow(Renderer.Window, SW_NORMAL);


In SetWindowLong() above, it seems that pretty much any value that is not WS_OVERLAPPEDWINDOW causes the window to become borderless.

and for the toggling:
1
2
SetWindowLong(Window, GWL_STYLE, GetWindowLong(Window, GWL_STYLE) ^ WS_OVERLAPPEDWINDOW);
SetWindowPos(Window, NULL, 0, 0, 0, 0, SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_NOREDRAW);

None