This tutorial is part of a Collection: DirectX12 Tutorial
rate up
1
rate down
1901
views
bookmark
S01E02 - First Window

How to open a window :)

397 downloads
##Hello there :)## This time lets create a window that will display all our stuff. but let's look how we can do that: 1) A simple window code written C-style 2) A window putted into a class with WindowProcedure outside of a class 3) A window putted into a class with WindowProcedure as a lambda 4) A window putted into a class with WindowProcedure Why 4 of them and what are the differences between them. Let's start writing: ##1) A simple window code written C-style## This one will be created in one file. #include <Windows.h> LRESULT CALLBACK WindowProcedure(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam) { switch (Msg) { case WM_CREATE: return 0; case WM_CLOSE: case WM_DESTROY: case WM_QUIT: PostQuitMessage(0); return 0; } return DefWindowProc(hWnd, Msg, wParam, lParam); } int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int) { WNDCLASSEX wndClass{}; wndClass.cbSize = sizeof(WNDCLASSEX); wndClass.hInstance = GetModuleHandle(nullptr); wndClass.lpszClassName = L"Window"; wndClass.lpfnWndProc = (WNDPROC)WindowProcedure; if (!RegisterClassEx(&wndClass)) { return false; } HWND MainWnd = CreateWindowEx(0, L"Window", L"Window", WS_OVERLAPPEDWINDOW, 0, 0, 800, 600, nullptr, nullptr, GetModuleHandle(nullptr), nullptr); if (!MainWnd) { return false; } ShowWindow(MainWnd, SW_NORMAL); UpdateWindow(MainWnd); MSG uMsg{}; while (1) { if (PeekMessage(&uMsg, nullptr, 0, 0, PM_REMOVE)) { TranslateMessage(&uMsg); DispatchMessage(&uMsg); if (uMsg.message == WM_QUIT) break; } } return static_cast<int>(uMsg.wParam); } Quite easy and you should know that if you start programming in Windows :) This can be enough for creation of window but we want more, we want C++ not C-Style. ##2) A window putted into a class with WindowProcedure outside of a class## #include <Windows.h> #include <memory> class Window { private: HWND MainWnd{}; WNDCLASSEX wndClass{}; public: bool Create(); int MessageLoop(); }; LRESULT CALLBACK WindowProcedure(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam) { switch (Msg) { case WM_CREATE: return 0; case WM_CLOSE: case WM_DESTROY: case WM_QUIT: PostQuitMessage(0); return 0; } return DefWindowProc(hWnd, Msg, wParam, lParam); } bool Window::Create() { wndClass.cbSize = sizeof(WNDCLASSEX); wndClass.hInstance = GetModuleHandle(nullptr); wndClass.lpszClassName = L"Window"; wndClass.lpfnWndProc = (WNDPROC)WindowProcedure; if (!RegisterClassEx(&wndClass)) { return false; } MainWnd = CreateWindowEx(0, L"Window", L"Window", WS_OVERLAPPEDWINDOW, 0, 0, 800, 600, nullptr, nullptr, GetModuleHandle(nullptr), nullptr); if (!MainWnd) { return false; } ShowWindow(MainWnd, SW_NORMAL); UpdateWindow(MainWnd); return true; } int Window::MessageLoop() { MSG uMsg{}; while (1) { if (PeekMessage(&uMsg, nullptr, 0, 0, PM_REMOVE)) { TranslateMessage(&uMsg); DispatchMessage(&uMsg); if (uMsg.message == WM_QUIT) break; } } return static_cast<int>(uMsg.wParam); } int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int) { auto Win = std::make_unique<Window>(); Win->Create(); return Win->MessageLoop(); } The code is almost identical but putted into a class. The problem is that in this case the WindowProcedure is outside a class. What if we put it into a class. When we put WindowProcedure into a class and make needed changes: class Window { private: HWND MainWnd{}; WNDCLASSEX wndClass{}; public: bool Create(); int MessageLoop(); LRESULT CALLBACK WindowProcedure(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam); }; LRESULT CALLBACK Window::WindowProcedure(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam) { the program won't even compile: error C2440: 'type cast': cannot convert from 'overloaded-function' to 'WNDPROC' OK, lets fix that. static LRESULT CALLBACK WindowProcedure(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam); and it is fixed. The program compile, it runs and everything is perfect. But it isn't. The problem is with static functions. Try to access other data from our class and see it by yourself :) ##3) A window putted into a class with WindowProcedure as a lambda## The code is almost identical with one change, the program compile and runs: wndClass.lpfnWndProc = [](HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam)->LRESULT { switch (Msg) { case WM_CREATE: return 0; case WM_CLOSE: case WM_DESTROY: case WM_QUIT: PostQuitMessage(0); return 0; } return DefWindowProc(hWnd, Msg, wParam, lParam); }; the only problem here is as you can see we don't have any access to external data. So having a lambda is useless in this case. But there is a solution for this problem and for aspect that I've showed above. ##4) A window putted into a class with WindowProcedure## In this case lets write this in our class in provate section: static LRESULT CALLBACK WindowProcedure(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam); LRESULT WinProc(UINT Msg, WPARAM wParam, LPARAM lParam); Looks familiar? Why two? one is for handling our messages and the second is for gaining information about our window. It is necessary to obtain those data if we want to have a WindowProcedure function in our class. Here we are gonna to manage all our messages: LRESULT Window::WinProc(UINT Msg, WPARAM wParam, LPARAM lParam) { switch (Msg) { case WM_CREATE: return 0; case WM_CLOSE: case WM_DESTROY: case WM_QUIT: PostQuitMessage(0); return 0; } return 0; } Here we are gonna obtain our data of the window. As you can see all data are created before WM_CREATE is even called so that we can get them when the window is created. Since we cannot use data from outside our static method we check if our pointer is valid and call another function which has full access: LRESULT CALLBACK Window::WindowProcedure(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam) { Window *AppWin{}; if (Msg == WM_NCCREATE) { CREATESTRUCT * cs = (CREATESTRUCT*)lParam; AppWin = (Window*)cs->lpCreateParams; SetLastError(0); if (SetWindowLongPtr(hWnd, GWLP_USERDATA, (LONG_PTR)AppWin) == 0) { if (GetLastError() != 0) return false; } } else { AppWin = (Window*)GetWindowLongPtr(hWnd, GWLP_USERDATA); } if (AppWin) AppWin->WinProc(Msg, wParam, lParam); return DefWindowProc(hWnd, Msg, wParam, lParam); } and the last we have to do is one change in our CreateWindowEx function. The last parameter must be ##this## instead of ##nullptr##. If you don't change that the program won't see your messages and, in this case, don't close itself properly (window will disappear but the program will not close). I asume that convertion of this solution to lambda will be no problem :) So far so good. I will use the last version in the next lesson as a code base and add error handling, timming and some configuration managing. Till next time :)