Related
I'm a hobbyist programmer coming back to C++ after many years away from programming and new to winapi so sorry for the "basic" GUI question. I'm trying to find the best practice for implementing the following, very common, behaviour.
From a users perspective this is the behaviour I want to create. I have 1 window with some buttons in it. The user clicks on 1 of the buttons and the window contents appears to change to show different buttons/text fields etc. The user interacts with these controls then finally clicks a "back" button and they are returned to the first screen.
This behaviour is so common I thought it would be easy to find examples and best practices for implementing it but clearly I'm not asking the right questions in google. Not sure if the right way forward is a new window, a child window and how to set up winproc to capture the events in these 2 options, i.e. a winproc for each window or child or 1 massive winproc for everything. Hence the best practice question.
Can anyone help, either be explaining the best way to set this up with the WINAPI or by pointing me to some material online. I've spent days looking, plenty on creating 1 windows with controls. Very happy to follow tutorials and experiment to learn more.
Thanks jwezorek I got very close to your updated code last night but still couldn't get the child events to work. Finally cracked it using your updated code and putting the page switch buttons in the same pane as the other buttons so all clicks were handled on a pane basis in the child winproc. Thank you all for your help. Code below in case it's of interest/use to anyone else.
#include <windows.h>
#define PANE1_ID 101
#define BUTTON11_ID 102
#define BUTTON12_ID 103
#define BUTTON_TO_PAGE_1 104
#define PANE2_ID 201
#define BUTTON21_ID 202
#define BUTTON22_ID 203
#define BUTTON_TO_PAGE_2 204
LRESULT CALLBACK parentWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
LRESULT CALLBACK childWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
HINSTANCE g_hinst;
static HWND g_pane1;
static HWND g_pane2;
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
g_hinst = hInstance;
MSG msg = { 0 };
// Parent window definition
WNDCLASS parentWindow = { 0 };
parentWindow.lpfnWndProc = parentWndProc;
parentWindow.hInstance = hInstance;
parentWindow.hbrBackground = (HBRUSH)(COLOR_BACKGROUND);
parentWindow.lpszClassName = L"MainWindow";
if (!RegisterClass(&parentWindow))
return 1;
// Child window definition
WNDCLASS childWindow = { 0 };
childWindow.lpfnWndProc = childWndProc;
childWindow.hInstance = hInstance;
childWindow.hbrBackground = (HBRUSH)(COLOR_BACKGROUND);
childWindow.lpszClassName = L"Pane";
if (!RegisterClass(&childWindow))
return 1;
// Create main window
if (!CreateWindow(parentWindow.lpszClassName, L"grouped buttons", WS_OVERLAPPEDWINDOW | WS_VISIBLE, 0, 0, 640, 480, 0, 0, hInstance, NULL))
return 2;
while (GetMessage(&msg, NULL, 0, 0) > 0) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return 0;
}
LRESULT CALLBACK parentWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_CREATE:
// create pane 2 with button then hide the pane and the button to switch to pane 1
g_pane2 = CreateWindow(L"Pane", L"", WS_CHILD | WS_VISIBLE, 20, 20, 250, 200, hWnd, (HMENU)PANE2_ID, g_hinst, 0);
CreateWindow(L"BUTTON", L"Button 2.1", WS_CHILD | WS_VISIBLE | BS_DEFPUSHBUTTON, 50, 10, 180, 35, g_pane2, (HMENU)BUTTON21_ID, g_hinst, 0);
CreateWindow(L"BUTTON", L"Button 2.2", WS_CHILD | WS_VISIBLE | BS_DEFPUSHBUTTON, 50, 40, 180, 35, g_pane2, (HMENU)BUTTON22_ID, g_hinst, 0);
CreateWindow(L"BUTTON", L"Back to Page 1", WS_CHILD | WS_VISIBLE | BS_DEFPUSHBUTTON, 50, 130, 180, 35, g_pane2, (HMENU)BUTTON_TO_PAGE_1, g_hinst, 0);
ShowWindow(g_pane2, SW_HIDE);
UpdateWindow(g_pane2);
// create pane 1 with buttons and show it and the button to switch to pane 2
g_pane1 = CreateWindow(L"Pane", L"", WS_CHILD | WS_VISIBLE, 20, 20, 250, 200, hWnd, (HMENU)PANE1_ID, g_hinst, 0);
CreateWindow(L"BUTTON", L"Button 1.1", WS_CHILD | WS_VISIBLE | BS_DEFPUSHBUTTON, 10, 10, 180, 35, g_pane1, (HMENU)BUTTON11_ID, g_hinst, 0);
CreateWindow(L"BUTTON", L"Button 1.2", WS_CHILD | WS_VISIBLE | BS_DEFPUSHBUTTON, 10, 40, 180, 35, g_pane1, (HMENU)BUTTON12_ID, g_hinst, 0);
CreateWindow(L"BUTTON", L"Go to page 2", WS_CHILD | WS_VISIBLE | BS_DEFPUSHBUTTON, 10, 130, 180, 35, g_pane1, (HMENU)BUTTON_TO_PAGE_2, g_hinst, 0);
ShowWindow(g_pane1, SW_SHOW);
UpdateWindow(g_pane1);
break;
case WM_CLOSE:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
LRESULT CALLBACK childWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
if (message == WM_COMMAND) {
switch (wParam) {
case BUTTON_TO_PAGE_2:
{
// MessageBox(hWnd, L"You pressed go to page 2", L"Button Pressed", MB_OK);
ShowWindow(g_pane1, SW_HIDE);
ShowWindow(g_pane2, SW_SHOW);
}
break;
case BUTTON11_ID:
MessageBox(NULL, L"Button 1.1", L"Page 1 Button", 0);
break;
case BUTTON12_ID:
MessageBox(NULL, L"Button 1.2", L"Page 1 Button", 0);
break;
case BUTTON_TO_PAGE_1:
{
// MessageBox(hWnd, L"You pressed go to page 1", L"Button Pressed", MB_OK);
ShowWindow(g_pane2, SW_HIDE);
ShowWindow(g_pane1, SW_SHOW);
}
break;
case BUTTON21_ID:
MessageBox(NULL, L"Button 2.1", L"Page 2 Button", 0);
break;
case BUTTON22_ID:
MessageBox(NULL, L"Button 2.2", L"Page 2 Button", 0);
break;
}
return 0;
}
else {
return DefWindowProc(hWnd, message, wParam, lParam);
}
}
Currently creating an application that just outputs Physics equations + some historical and mathematical context. I've ran into this problem where I'm using WS_EX_CLIENTEDGE to sort out a list of these terms and when I click said term on the screen, expecting a definition to appear, it doesn't output anything until I've clicked outside of the application window(Visual Studio, Debug Terminal, etc)
Cursed gif
And I want to make it clear that I DON'T want to use a button due to GUI aesthetics.
Mundane WINAPI application creation
int main(HINSTANCE hInstance, HINSTANCE hPrevInstance, PWSTR pCmdLine, int nCmdShow)
{
const wchar_t CLASS_NAME[] = L"Application";
WNDCLASS window = { 0 };
MSG msg = { 0 };
window.lpfnWndProc = WindowProc;
window.lpszClassName = CLASS_NAME;
window.hInstance = hInstance;
RegisterClass(&window);
HWND hWnd = CreateWindow(CLASS_NAME, L"PhysicsBox", WS_VISIBLE | WS_OVERLAPPEDWINDOW, 100, 100, 600, 500, NULL, NULL, NULL, NULL);
ShowWindow(hWnd, nCmdShow);
//aMessageBox = CreateWindowW(L"edit", L" ", WS_VISIBLE | WS_CHILD | WS_BORDER, 165, 150, 400, 300, hWnd, NULL, NULL, NULL);
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return 0;
}
The Juicy Window Procedure
LRESULT CALLBACK WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch (uMsg)
{
case WM_CREATE:
aListBox(hWnd);
break;
case WM_COMMAND:
if (SendMessage(hList, LB_GETSEL, FORCE, 0))
{
CreateWindowW(L"static", L"Simple Text", WS_VISIBLE | WS_CHILD | WS_BORDER | SS_CENTER, 200, 100, 100, 100, hWnd, NULL, NULL, NULL);
}
break;
case WM_DESTROY:
PostQuitMessage(0); break;
default:
return DefWindowProc(hWnd, uMsg, wParam, lParam);
}
}
The void function that creates the list
void aListBox(HWND hWnd)
{
hList = CreateWindowEx(WS_EX_CLIENTEDGE, L"ListBox", L" ", WS_CHILD | WS_VISIBLE | WS_VSCROLL | ES_AUTOVSCROLL, 3, 4, 150, 300, hWnd, (HMENU)ID_LISTBOX, 0, 0);
SendMessageW(hList, LB_ADDSTRING, FORCE, (LPARAM)L"Force");
SendMessageW(hList, LB_ADDSTRING, WORK, (LPARAM)L"Work");
SendMessageW(hList, LB_ADDSTRING, POWER, (LPARAM)L"Power");
SendMessageW(hList, LB_ADDSTRING, EFFICENCY, (LPARAM)L"Efficency");
SendMessageW(hList, LB_ADDSTRING, POTENTIALENERGY, (LPARAM)L"Potential Energy");
SendMessageW(hList, LB_ADDSTRING, HOOKESLAW, (LPARAM)L"Hooke's Law");
}
Note: the MSG('s) placed in the "aListBox(HWND hWnd)" SendMessageW parameters are numerically ordered(from 0). So FORCE = 0, WORK = 1, and so on. Also hList is a global variable if that helps :P
The listbox is created without the LBS_NOTIFY style and will therefore not send LBN_SELCHANGE notifications to the parent window. Changing the first line in aListBox to the following fixes this.
hList = CreateWindowEx(WS_EX_CLIENTEDGE, L"ListBox", L" ", WS_CHILD | LBS_NOTIFY | WS_VISIBLE | WS_VSCROLL | ES_AUTOVSCROLL, 3, 4, 150, 300, hWnd, (HMENU)ID_LISTBOX, 0, 0);
Also, the WindowProc does not return a result on all code paths, and does not call DefWindowProc for all messages that it does not handle. The following corrects those issues.
LRESULT CALLBACK WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch (uMsg)
{
case WM_CREATE:
aListBox(hWnd);
return 0;
case WM_COMMAND:
if (SendMessage(hList, LB_GETSEL, FORCE, 0))
{
HWND hText = CreateWindowW(L"static", L"Simple Text", WS_VISIBLE | WS_CHILD | WS_BORDER | SS_CENTER, 200, 100, 100, 100, hWnd, NULL, NULL, NULL);
ShowWindow(hText, SW_SHOW);
return 0;
}
break;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hWnd, uMsg, wParam, lParam);
}
Other problems remain with the code, for example the WindowProc does not check the WM_COMMAND notification code and will happily create new windows over and over, but the assumption was that this was minimal code only meant to showcase the particular issue in the question.
[ EDIT ] The behavior described by the OP when running without the LBS_NOTIFY style bit is mostly accidental, due to WM_COMMAND not checking the WPARAM notification code. What happens is that clicking outside the app window causes the listbox to send a LBN_KILLFOCUS notification (which is being sent regardless of LBS_NOTIFY). That notification is the one which triggers the WM_COMMAND code in WindowProc that creates the static control when the FORCE item is selected.
I working on my new program about asking the user if he agree to the terms.
My program is creating a black window with a checkbox, when the user check the checkbox,
an button called "RUN" created.
My problem is that I can't change the color of the static text next to the checkbox, I must create first the checkbox and then create and change the static text.
Let me explain, my WM_CREATE is using three CreateWindow functions, one is the checkbox and after it comes the "RUN" button, and the last one is the static text, next to the checkbox.
Now, when I create first the button, the button work well and I can check it and uncheck it, but the static text is not working well.
For the static text I removed his background and change his color with WM_CTLCOLORSTATIC, but the background is not removed and the color too.
Now, when I create first the text, the text is working well and the color and the background are changed, and the button isn't working, I can't check it or uncheck it.
Please try to debug my program, it is hard to explain.
You need to try to switch between the two functions in the WM_CREATE, try to create first text and then you can see that the checkbox is not working well.
My whole program:
#include <windows.h>
#include <iostream>
#include <thread>
using namespace std;
// Text
#define IDC_STATIC 1
// Buttons
#define IDC_BUTTON 2
HWND agree, button1, text;
LRESULT CALLBACK WindowProcessMessages(HWND hwnd, UINT msg, WPARAM param, LPARAM lparam);
int WINAPI WinMain(HINSTANCE currentInstance, HINSTANCE previousInstance, PSTR cmdLine, INT cmdCount)
{
// Register the window class
const char* CLASS_NAME = "myWin32WindowClass";
WNDCLASS wc{};
wc.hInstance = currentInstance;
wc.lpszClassName = CLASS_NAME;
wc.hIcon = 0;
wc.hCursor = LoadCursor(nullptr, IDC_ARROW);
wc.hbrBackground = CreateSolidBrush(RGB(20, 20, 20));
wc.lpfnWndProc = WindowProcessMessages;
RegisterClass(&wc);
HWND main = CreateWindow(CLASS_NAME, "WastedBit 1.6.2",
WS_VISIBLE, // Window style
CW_USEDEFAULT, CW_USEDEFAULT, // Window initial position
950, 750, // Window size
nullptr, nullptr, nullptr, nullptr);
// TopMost
SetWindowPos(main, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE);
// Window loop
MSG msg{};
while (GetMessage(&msg, nullptr, 0, 0)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return 0;
}
LRESULT CALLBACK WindowProcessMessages(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)
{
switch (msg)
{
case WM_CREATE: {
button1 = CreateWindow("button", 0, WS_VISIBLE | WS_CHILD | BS_CHECKBOX, 20, 490, 15, 15, hwnd, (HMENU)1, ((LPCREATESTRUCT)lparam)->hInstance, NULL);
agree = CreateWindow("button", "RUN", WS_CHILD, 750, 525, 150, 150, hwnd, 0, 0, 0);
text = CreateWindow("static", "By checking this button, you agree to the terms above", WS_CHILD | WS_VISIBLE, 30, 490, 150, 150, hwnd, (HMENU)IDC_STATIC, 0, 0);
}
break;
case WM_COMMAND: {
BOOL checked = IsDlgButtonChecked(hwnd, 1);
if (checked) {
CheckDlgButton(hwnd, 1, BST_UNCHECKED);
ShowWindow(agree, SW_HIDE);
}
else {
CheckDlgButton(hwnd, 1, BST_CHECKED);
ShowWindow(agree, SW_SHOW);
}
}
break;
case WM_DESTROY: {
PostQuitMessage(0);
}
break;
case WM_CTLCOLORSTATIC: {
if ((HWND)lparam == GetDlgItem(hwnd, IDC_STATIC))
{
SetBkMode((HDC)wparam, TRANSPARENT);
SetTextColor((HDC)wparam, RGB(400, 0, 0));
return (BOOL)GetStockObject(NULL_BRUSH);
}
break;
}
break;
default:
return DefWindowProc(hwnd, msg, wparam, lparam);
}
}
I have tried to use it with valueless functions, but still the same.
Your problem is that you have same child-window identifiers.
Change:
button1 = CreateWindow("button", 0, WS_VISIBLE | WS_CHILD | BS_CHECKBOX, 20, 490, 15, 15, hwnd, (HMENU)1, ((LPCREATESTRUCT)lparam)->hInstance, NULL);
to something:
#define ID_BUTTON_2 101
button1 = CreateWindow("button", 0, WS_VISIBLE | WS_CHILD | BS_CHECKBOX, 20, 490, 15, 15, hwnd, (HMENU)ID_BUTTON_2, ((LPCREATESTRUCT)lparam)->hInstance, NULL);
When you receive WM_COMMAND, you should check WM_COMMAND source.
You created 3 controls, so, they can send you WM_COMMAND.
WM_COMMAND source can be known from control id ( parameter HMENU of CreateWindow ).
Here is quick fix.
case WM_COMMAND:
if( wparam == 1 ) {
BOOL checked = IsDlgButtonChecked(hwnd, 1);
if (checked) {
CheckDlgButton(hwnd, 1, BST_UNCHECKED);
ShowWindow(agree, SW_HIDE);
}
else {
CheckDlgButton(hwnd, 1, BST_CHECKED);
ShowWindow(agree, SW_SHOW);
}
}
}
break;
Hello i am making my first windows application code.
I want to know how can i make a CreateWindow(TEXT("STATIC") child window, inside my main window, in which when the user clicks to add text on it,i want the
CreateWindow(TEXT("STATIC"), TEXT("REPORT") "REPORT" text to dissapear.
(Some of the text is in greek)
Example: like this "Email or username" and "password" texts, that dont interrupt the user
https://www.codecademy.com/login?redirect=about%3A%2F%2Fblank
LRESULT CALLBACK WndProc(HWND hwnd, UINT Message, WPARAM wParam, LPARAM lParam) {
switch(Message) {
case WM_COMMAND:{
break;
}
case WM_CREATE:{
HMENU hMenubar= CreateMenu();
HMENU hFile= CreateMenu();
HMENU hOptions= CreateMenu();
AppendMenu(hMenubar, MF_POPUP, (UINT_PTR)hFile, "File");
AppendMenu(hMenubar, MF_POPUP, NULL, "Edit");
AppendMenu(hMenubar, MF_POPUP, (UINT_PTR)hOptions, "Options");
AppendMenu(hFile, MF_STRING, NULL, "Open");
AppendMenu(hOptions, MF_STRING, NULL, "Correction");
AppendMenu(hOptions, MF_STRING, NULL, "Search");
SetMenu(hwnd,hMenubar);
CreateWindow(TEXT("edit"), TEXT(""),
WS_VISIBLE | WS_CHILD | WS_BORDER | ES_AUTOHSCROLL,
10, 10, 200, 30,
hwnd, (HMENU) ID_NAMEBOX, NULL, NULL
);
CreateWindow(TEXT("STATIC"), TEXT("NAME"),
WS_VISIBLE | WS_CHILD,
10, 10, 200, 30,
hwnd, (HMENU) ID_VNAMEBOX, NULL, NULL
);
CreateWindow(TEXT("edit"), TEXT("CALL1"),
WS_VISIBLE | WS_CHILD | WS_BORDER | ES_AUTOHSCROLL,
220, 10, 200, 30,
hwnd, (HMENU) ID_CALLBOX, NULL, NULL
);
CreateWindow(TEXT("edit"), TEXT("CALL2"),
WS_VISIBLE | WS_CHILD | WS_BORDER | ES_AUTOHSCROLL,
220, 50, 200, 30,
hwnd, (HMENU) ID_CALLBOX, NULL, NULL
);
CreateWindow(TEXT("edit"), TEXT("REPORT"),
WS_VISIBLE | WS_CHILD | WS_BORDER | ES_AUTOHSCROLL,
10, 90, 410, 100,
hwnd, (HMENU) ID_REPORTBOX, NULL, NULL
);
hwnd= CreateWindow(TEXT("button"), TEXT("SUBMIT"),
WS_VISIBLE | WS_CHILD,
55, 50, 100, 30,
hwnd, (HMENU) ID_SUBMITBOX, NULL, NULL
);
break;
}
/* Upon destruction, tell the main thread to stop */
case WM_DESTROY: {
PostQuitMessage(0);
break;
}
/* All other messages (a lot of them) are processed using default procedures */
default:
return DefWindowProc(hwnd, Message, wParam, lParam);
}
return 0;
}
I know it is a very simple question but i currently cant create a parent window...
My code:
LRESULT CALLBACK WndProc( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam )
{
static HWND paste;
static HWND update_list;
/*HWND changeuser = CreateWindow(0, 0,
0,
0, 0, x, y,
0, (HMENU)changeuser2, 0, NULL); */
switch(msg)
{
case WM_CREATE:
meniu(hwnd);
CreateWindow(TEXT("static"), TEXT("\nSuckers online:"),
WS_VISIBLE | WS_CHILD | SS_CENTER,
0, 0, x, 55,
hwnd, (HMENU)delete, NULL, NULL);
connected = CreateWindow(TEXT("edit"), TEXT(""),
WS_VISIBLE | WS_CHILD | WS_VSCROLL| ES_MULTILINE ,
0, 60, x, 340,
hwnd, (HMENU)delete2, NULL, NULL);
CreateWindow(TEXT("static"), TEXT(""),
WS_VISIBLE | WS_CHILD | SS_CENTER|BS_PUSHBUTTON,
0, 405, x, 358,
hwnd, (HMENU) delete3, NULL, NULL);
paste = CreateWindow(TEXT("Edit"), TEXT("Paste the ip here"),
WS_VISIBLE | WS_CHILD | SS_CENTER,
x/2 - 60, 410, 120, 40,
hwnd, (HMENU) ip, NULL, NULL);
CreateWindow(TEXT("Button"), TEXT("Connect!"),
WS_VISIBLE | WS_CHILD | SS_CENTER | BS_PUSHBUTTON,
x/2 - 120, 450, 120, 40,
hwnd, (HMENU) connect2, NULL, NULL);
update_list = CreateWindow(TEXT("Button"), TEXT("Update the list!"),
WS_VISIBLE | WS_CHILD | SS_CENTER | BS_PUSHBUTTON,
x/2, 450, 120, 40,
hwnd, (HMENU) update, NULL, NULL);
_beginthread( lista, 0, (void*)(0) );//begin thread lista
break;
case WM_CTLCOLORSTATIC : {
HBRUSH br = CreateSolidBrush(RGB(80,67,77)); // change background color
SetTextColor((HDC)wParam,RGB(0,102,51)); //the controls text color
return (LRESULT) br;
}
case WM_COMMAND:
switch LOWORD(wParam)
{
case exit:
PostQuitMessage(0);
break;
case ip:
int nr;
nr = GetWindowTextLength(paste);
if (nr >= 17)
SetWindowText(paste, "");
break;//omor textul, ca sa pot sa fac paste
case connect2:
GetWindowText(paste,adresa,16);
_beginthread( start, 0, (void*)(0) ); //as\ici se face conexiunea principala
//DestroyWindow(hwnd);
MessageBox(0,"Connected with the user","Ok",0);
break;
case update:
exit2 = true;
Sleep(100);
SetWindowText(connected,"");
_beginthread( lista, 0, (void*)(0) );//begin thread lista
break;
}
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
}
return DefWindowProc(hwnd, msg, wParam, lParam);
}
I want the other windows created to be the child of the changeuser window...
I just cant make it happen...
Any help will be appreciated!
To change the window of a parent, use SetParent().
But i would think about the structure - if you need to change the parent of one or more windows in a normal dialog setting, it is likely these windows should have a different parent.
In your case there is a problem in your handling of window messages though:
If your message handler receives WM_DESTROY you call PostQuitMessage(), which probably results in your application closing.
There are two ways you can handle that:
use different window processes for your main dialog and child dialogs (preferably)
or use the hwnd parameter to decide wether you call PostQuitMessage()
You can't 'replace' a window. If you need to tear down and replace your main window, delete it and make a new one. Windows only get the parent flag when they have children, not because you tell them to.