Related
I have a custom subclassed button created in the WM_CREATE message of my WindowProc callback. Here are the creation and subclassing instructions, along with a structure used to control the button state:
static button_state btnstateBtnInstall;
hBtnInstall = CreateWindow(WC_BUTTON, L"Button", WS_CHILD | WS_VISIBLE, (window_width / 2) - (btn_install_width / 2), window_height - (window_height / 6) - (btn_install_height / 2), btn_install_width, btn_install_height, hwnd, (HMENU)HMENU_btn_install, NULL, NULL);
SetWindowSubclass(hBtnInstall, BtnInstallProc, 0, (DWORD_PTR)&btnstateBtnInstall);
The struct is defined as follows:
struct button_state
{
bool pushed;
button_state() { pushed = false; }
};
The subclassed procedure is coded as follows:
LRESULT CALLBACK BtnInstallProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubClass, DWORD_PTR dwRefData)
{
button_state* state = (button_state*)dwRefData;
// Omitted part where I create brushes and font to be used for painting
switch (msg)
{
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hwnd, &ps);
RECT rc = ps.rcPaint;
POINT pt;
GetCursorPos(&pt);
ScreenToClient(hwnd, &pt);
BOOL hover = PtInRect(&rc, pt);
if (state->pushed)
{
// Pushed
FillRect(hdc, &rc, hBrPushed);
}
else if (hover)
{
// Mouse over
FillRect(hdc, &rc, hBrHover);
}
else
{
// Normal
FillRect(hdc, &rc, hBrNormal);
}
SetBkMode(hdc, TRANSPARENT);
SetTextColor(hdc, RGB(255, 255, 255));
SelectFont(hdc, SegoeUI);
static LPCWSTR InstallBtnTxt = L"Install";
static int InstallBtnTxtLen = static_cast<int>(wcslen(InstallBtnTxt)); // Should be a safe cast, for small arrays like this one
DrawText(hdc, InstallBtnTxt, InstallBtnTxtLen, &rc, DT_SINGLELINE | DT_CENTER | DT_VCENTER);
EndPaint(hwnd, &ps);
return 0;
}
case WM_LBUTTONDOWN:
{
state->pushed = true;
break;
}
case WM_LBUTTONUP:
{
state->pushed = false;
break;
}
// Omitted part where I handle WM_DESTROY to do cleanup
}
return DefSubclassProc(hwnd, msg, wParam, lParam);
}
To compare the behaviour of my button with a standard one, I created another button without subclassing and using only the standard BUTTON class and WS_VISIBLE | WS_CHILD attributes.
As you can see from the attached gif, when I repeatedly click on a standard class button, every click produces the animation while when hitting multiple times the button I created with this code it seems like it isn't catching every click but (as you can see) something like 50% of them.
What am I missing? Why isn't my button as responsive as the one that comes with the standard class?
TL;DR: when clicking slowly on my subclassed button, the painting is correct. As you can see from my attached image, the difference between mine and a standard button is big when repeating clicks on them. How can I make my control be as responsive as the standard one?
When you are clicking fast some WM_LBUTTONDOWN messages are substituted by the WM_LBUTTONDBLCLK (default double click handling). You need to handle it just like you are handling the WM_LBUTTONDOWN message.
case WM_LBUTTONDOWN:
case WM_LBUTTONDBLCLK: // <-- Added
{
state->pushed = true;
break;
}
I'm having trouble detecting a hover over a static Win32 control.
This is not a duplicate issue because this looks for multiple static controls instead of just looking for a single known handle to a static control at compile time.
While this can be done in seconds in another language, I'm growing a little frustrated after trying things out for some hours. I hope to get an answer here.
First, I created a class called Label. I create a static control window within it. I'll refer to static as label for now on.
// Create the label's handle.
m_handle = CreateWindowEx(NULL, "static", m_text.c_str(),
WS_CHILD | SS_LEFT | SS_NOTIFY, m_x, m_y, m_width, m_height,
m_parentWindow, (HMENU)(UINT_PTR)m_id, m_hInstance, NULL);
if (m_handle == NULL)
return false;
When the mouse hovers over this label, the following method should be called:
void Label::invokeOnMouseHover()
{
if (m_onMouseOver)
m_onMouseOver();
}
This will call my method:
void lblName_onMouseOver()
{
MessageBox::show("Hovering!", "My Console",
MessageBoxButtons::Ok, MessageBoxIcon::Information);
}
This is how I create it from the top level:
Label lblName("This is a label.", 0, 0);
lblName.setVisible(true);
lblName.OnMouseOver(lblName_onMouseOver);
frm.add(lblName);
Admit it, this thin layer is beautiful.
While my callbacks are working fine for my Button and Checkbox controls, I noticed that statics are a little different.
So, let's go down a few levels:
This is in the main window's procedure:
case WM_MOUSEMOVE:
{
int xPos = LOWORD(lParam);
int yPos = HIWORD(lParam);
frm.setMousePos(xPos, yPos);
// Get the static's id
int id = // ?? Which static control id is it out of several?
// Obtain the control associated with the id.
X3D::Windows::Control *ctrl = frm.getControls().find(id)->second;
if (ctrl == NULL)
return 0;
// Check if this is a X3D Label control.
if (typeid(*ctrl) == typeid(X3D::Windows::Label))
{
Label *lbl = dynamic_cast<X3D::Windows::Label*>(ctrl);
int xPos = GET_X_LPARAM(lParam);
int yPos = GET_Y_LPARAM(lParam);
if (xPos >= lbl->getX() &&
yPos >= lbl->getY() &&
(xPos < (lbl->getX() + lbl->getWidth())) &&
(yPos < (lbl->getY() + lbl->getHeight())))
{
if (lbl != NULL)
lbl->invokeOnMouseHover();
}
}
}
break;
What I'm trying to do here is detect what label id was detected, and then call Label::invokeOnMouseOver().
Even though I Understand I need to use TRACKMOUSEEVENT at some point, its field member 'HWND' requires the label's handle. But I can't easily say which handle it will be because the collection may contain one or several labels.
Overall, I'm looking for suggestions on how to either restructure this or see if there's an easy solution here. My guess is I'm overthinking this.
Thanks.
Update:
Here is a code update after reading the first answer with the first solution. While this solves the hover issue, I don't see the label's text on execution.
case WM_MOUSEMOVE:
{
int xPos = LOWORD(lParam);
int yPos = HIWORD(lParam);
// Get the mouse position
frm.setMousePos(xPos, yPos);
// Check for labels
X3D::Windows::Control *ctrl = (X3D::Windows::Control*)GetWindowLongPtr(hWnd, GWLP_USERDATA);
if (ctrl)
{
// Check if this is a X3D Label control.
Label *lbl = dynamic_cast<X3D::Windows::Label*>(ctrl);
if (lbl)
lbl->invokeOnMouseHover();
return CallWindowProc(lbl->getOldProc(), hWnd, msg, wParam, lParam);
}
}
break;
And the control creation:
// Create the label's handle.
m_handle = CreateWindowEx(NULL, TEXT("static"), m_text.c_str(),
WS_CHILD | SS_LEFT | SS_NOTIFY, m_x, m_y, m_width, m_height,
m_parentWindow, (HMENU)(UINT_PTR)m_id, m_hInstance, NULL);
if (!m_handle)
return false;
SetWindowLongPtr(m_handle, GWLP_USERDATA, (LONG_PTR)(X3D::Windows::Control*)this);
m_oldProc = (WNDPROC)SetWindowLongPtr(m_handle, GWLP_WNDPROC, (LONG_PTR)&wndProc);
If it's a paint issue, this is what I have in WndProc.
case WM_PAINT:
{
hDC = BeginPaint(hWnd, &ps);
EndPaint(hWnd, &ps);
return 0;
}
break;
Update #2:
The alternative solution fixed the issue.
If anyone has trouble with unresolved external symbols with SetWindowSubclass() in the future, remember to add the following to your project or just #pragma it:
#pragma comment(lib, "comctl32.lib")
The only identifying info that WM_MOUSEMOVE gives you is the HWND that the mouse is moving over (or the HWND that has captured the mouse). The reported X/Y coordinates are relative to that HWND. That is the HWND that you are looking for, so you don't need to hunt for it, the message gives it to you.
If you need access to an HWND's control ID, you can use GetDlgCtrlID() for that. But note that every HWND has its own window procedure, so control IDs are typically only useful for notification messages, like WM_COMMAND and WM_NOTIFY, which are sent to a control's parent window (and even then, such notifications also carry the child's HWND as well).
When the mouse moves over a particular HWND, WM_MOUSEMOVE is posted to the message procedure of that HWND only (or to the HWND that has captured the mouse). It sounds like you are expecting it to be posted to the control's parent window instead, and that is simply not the case. That is why your WM_MOUSEMOVE handler is not being called. You are handling the message at the wrong level. You need to be prepared to handle messages on a per-control basis instead, using the control's own message procedure.
It would be more efficient to store your Control object's this pointer inside its associated HWND itself, via SetWindowLongPtr(GWLP_USERDATA), SetWindowSubClass(), or SetProp(), and then your message handlers can access the Control* pointer of the message's reported HWND when needed, for example:
// Create the label's handle.
m_handle = CreateWindowEx(NULL, TEXT("static"), m_text.c_str(),
WS_CHILD | SS_LEFT | SS_NOTIFY, m_x, m_y, m_width, m_height,
m_parentWindow, (HMENU)(UINT_PTR)m_id, m_hInstance, NULL);
if (!m_handle)
return false;
SetWindowLongPtr(m_handle, GWLP_USERDATA, (LONG_PTR)(X3D::Windows::Control*)this);
m_oldproc = (WNDPROC) SetWindowLongPtr(m_handle, GWL_WNDPROC, (LONG_PTR)&MyWndProc);
...
LRESULT CALLBACK MyWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch (uMsg)
{
case WM_MOUSEMOVE:
{
...
X3D::Windows::Control *ctrl = (X3D::Windows::Control*) GetWindowLongPtr(hWnd, GWLP_USERDATA);
if (ctrl)
{
// Check if this is a X3D Label control.
Label *lbl = dynamic_cast<X3D::Windows::Label*>(ctrl);
if (lbl)
lbl->invokeOnMouseHover();
}
break;
}
...
}
return CallWindowProc(m_oldproc, hWnd, uMsg, wParam, lParam);
}
Alternatively:
// Create the label's handle.
m_handle = CreateWindowEx(NULL, TEXT("static"), m_text.c_str(),
WS_CHILD | SS_LEFT | SS_NOTIFY, m_x, m_y, m_width, m_height,
m_parentWindow, (HMENU)(UINT_PTR)m_id, m_hInstance, NULL);
if (!m_handle)
return false;
SetWindowSubclass(m_handle, &MySubclassProc, 1, (DWORD_PTR)(X3D::Windows::Control*)this);
...
LRESULT CALLBACK MySubclassProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData)
{
switch (uMsg)
{
case WM_NCDESTROY:
RemoveWindowSubclass(hWnd, &MySubclassProc, uIdSubclass);
break;
case WM_MOUSEMOVE:
{
X3D::Windows::Control *ctrl = (X3D::Windows::Control*) dwRefData;
// Check if this is a X3D Label control.
Label *lbl = dynamic_cast<X3D::Windows::Label*>(ctrl);
if (lbl)
lbl->invokeOnMouseHover();
break;
}
...
}
return DefSubclassProc(hWnd, uMsg, wParam, lParam);
}
This post doesn't answer the question you asked but you should read it. It is important, and it is posted in a spirit of helpfulness after my rather acid comment above. I hope you find it so.
This class library is going to run into trouble. Code like this (using a dynamic_cast):
case WM_MOUSEMOVE:
{
X3D::Windows::Control *ctrl = (X3D::Windows::Control*) dwRefData;
// Check if this is a X3D Label control.
Label *lbl = dynamic_cast<X3D::Windows::Label*>(ctrl);
if (lbl)
lbl->invokeOnMouseHover();
break;
}
Is almost always wrong.
Why? Well, suppose you want to hover over some other kind of control? What are you going to do now? And these guys are just going to multiply like rabbits, so don't do that.
Instead, for a message that the library understands (such as this one), declare a corresponding virtual method in the base class that derived classes can override if they're interested in processing that message. Then you have the basis of a solid design (and this is pretty basic stuff).
So, in this case you would have:
class Control // Base class
{
...
virtual void onMouseHover (...) { }
...
};
And then:
class Label : public Control // Derived class
{
...
virtual void onMouseHover (...) override { ... }
...
};
Now for my second point: you are going to find that, particularly for dialogs, your application is going to want to handle a lot of messages that the base class doesn't understand (or care about).
How are you going to do this? Are you going to add code to the base class for each new message that your application (or for that matter specific types of control implemented in the class library) becomes interested in? That is not a very attractive prospect.
Now MFC handles this with something it calls a message map, which is essentially a table of message ID's and their corresponding command handlers which you can associate with (in your case) any object ultimately derived from Control and I recommend you do something similar.
But thanks to the magic of the STL, you can do better. I have something like this in my class library (my base class is actually called Window, as I would suggest yours should be):
typedef INT_PTR (Window::*MESSAGE_HANDLER)
(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
// Register a message handler
void Window::RegisterMessageHandler (UINT uMsg, MESSAGE_HANDLER handler);
And what RegisterMessageHandler() actually does is to add handler to a std::unordered_map associated with the Window object using uMsg as a key. Then, when that message subsequently comes in, it can be dispatched to the right handler without the base class knowing anything about the meaning of the message whatsoever, and that's what you're going to need.
So, you might declare the following in class Control (code untested, written in Notepad):
class Control // Base class
{
...
std::unordered_map <UINT, MESSAGE_HANDLER> m_message_map;
...
};
And then RegisterMessageHandler() might look like this:
void Window::RegisterMessageHandler (UINT uMsg, MESSAGE_HANDLER handler)
{
m_message_map.emplace (uMsg, handler);
}
And MySubclassProc() might look like this:
LRESULT CALLBACK MySubclassProc (HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData)
{
X3D::Windows::Control *ctrl = (X3D::Windows::Control*) dwRefData;
auto handler ctrl->m_message_map.find (uMsg);
if (handler != ctrl->m_message_map.end ())
return handler.second (hWnd, uMsg, wParam, lParam);
...
}
My own class library is actually more sophisticated than this (I started with something simple but then embellished it over time) but that's the basic idea. You might have to master a few C++ skills to pull this off but trust me, if you implement something like this you'll be very glad you did, down the line.
I've just created multiple edit boxes (11x11 controls) based on this article:
https://msdn.microsoft.com/en-us/library/windows/desktop/hh298433%28v=vs.85%29.aspx
Well, not exactly same, but I used the code in case WM_CREATE: block to create huge number of controls.
I use this dialog process on the parent window:
INT_PTR CALLBACK StartupDialogProc(HWND dialog, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch (msg){
case WM_INITDIALOG:
Init_Startup(dialog);
return 1;
/*
case EN_CHANGE:
case WM_CTLCOLOREDIT:
{
HDC hdC = (HDC)wParam;
COLORREF crColorBackground = RGB(255,0,0);
if (crColorBackground)
SetBkColor(hdC, crColorBackground);
SetTextColor( hdC, RGB(12,112,212) );
SetBkMode( hdC, TRANSPARENT );
RECT rect;
GetClientRect( (HWND)lParam, &rect );
HBRUSH hBrush = CreateSolidBrush( RGB(209,209,209) );
//FrameRect( hdC, &rect, hBrush );
Rectangle( hdC, (int)rect.left, (int)rect.top, (int)rect.right, (int)rect.bottom );
DeleteObject( hBrush );
LOGBRUSH lb;
lb.lbStyle = BS_SOLID;
lb.lbColor = RGB(249,249,249);
lb.lbHatch = 0;
CreateBrushIndirect(&lb); // LRESULT
// GetStockObject(NULL_BRUSH);
return 1;
}
break;
*/
case WM_DESTROY:
setts.options.page = GetDlgItemInt(dialog, IDC_O_STARTUP_PAGE, NULL, FALSE);
setts.options.recent = GetDlgItemInt(dialog, IDC_O_STARTUP_RECENT, NULL, FALSE);
break;
case WM_CLOSE:
EndDialog(dialog, FALSE);
break;
case WM_COMMAND:
if (wParam == IDOK) {
EndDialog(dialog, TRUE);
return 0;
}
}
return 0;
}
There is few things unclear to me:
1) if I would like to change color of border for all edit controls from id 5001 to id 5121, how to do that? To me, the commented code does not work (when would it be uncommented). It looks like I have this in incorrect place.
2) how correctly create the dialog processes to all the controls? Because there is big number and could be yet few times higher, should I just call a loop from 5001 to id 5121 and call the function:
INT_PTR CALLBACK EditDlgProc(HWND dialog, UINT msg, WPARAM wParam, LPARAM lParam) - that won't work, because every function would need to have different name.
To change the border color of edit control, you have to subclass the edit control and override WM_NCPAINT. That's a little advanced, and you don't really need it. You can just use WS_EX_CLIENTEDGE flag:
CreateWindowEx(WS_EX_CLIENTEDGE, L"EDIT" ...
Also make sure project manifest is setup so you get modern window's look.
This would be an error if it had not been commented out:
case EN_CHANGE:
case WM_CTLCOLOREDIT:
Each case should end in break; or return 0;
Moreover, WM_CTLCOLOREDIT should return a brush which was created on heap. It should not return 1. See documentation :
There are also other errors in that section, you should just get rid of that. See this example for painting.
I added a notification icon to my dialog based application, and it received WM_LBUTTONDBLCLK when the icon is double clicked on, but it is not receiving WM_CONTEXTMENU when the icon is right clicked or when the icon is highlighted with the keyboard and the context menu key is pressed. I based my usage of the notification icon on the example in the Windows 7.1 SDK Samples. So, I have no idea where I'm going wrong or why this isn't working.
Note: If I change WM_CONTEXTMENU to WM_RBUTTONUP, it receives the event, but the cursor coordinates are wrong.
/******************************************************************************/
/* Menu Resource */
/******************************************************************************/
LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL
IDR_TRAYMENU MENU
{
POPUP ""
{
MENUITEM "&Show Status Window", IDM__SHOW_STATUS_WINDOW
MENUITEM "&About", IDM__ABOUT
MENUITEM SEPARATOR
MENUITEM "&Exit", IDM__EXIT
}
}
/******************************************************************************/
/* WinMain() */
/******************************************************************************/
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow )
{
// ... code unrelated to icon
// Enable Visual Styles
InitCommonControls();
// create the main dialog
if( NULL == (hWnd=CreateDialog(hInstance,MAKEINTRESOURCE(IDD_MAINDLG),NULL,(DLGPROC)WndProc)) )
{
MessageBox( NULL, "Error creating the main dialog!", NULL, MB_OK | MB_ICONERROR );
return -1;
}
// ... code unrelated to icon
MSG msg;
while( GetMessage(&msg,NULL,0,0) && IsWindow(hWnd) )
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return 0;
}
/******************************************************************************/
/* WndProc() */
/******************************************************************************/
BOOL CALLBACK WndProc(HWND hWndDlg, UINT Message, WPARAM wParam, LPARAM lParam)
{
switch(Message)
{
case WM_INITDIALOG:
{
// ... code unrelated to icon
hIcon = (HICON)LoadImage( GetModuleHandle(NULL), MAKEINTRESOURCE(IDI_DDCMP), IMAGE_ICON, 16, 16, LR_DEFAULTSIZE );
// Setup the system tray icon
memset( &nid, 0, sizeof(NOTIFYICONDATA) );
nid.cbSize = sizeof(NOTIFYICONDATA);
nid.hWnd = hWndDlg;
nid.uID = 0xDDC;
nid.uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP | NIF_SHOWTIP;
nid.uCallbackMessage = WM_APP + 0xDDC;
nid.hIcon = hIcon;
strcpy( nid.szTip, "DDCMP Driver" );
Shell_NotifyIcon( NIM_ADD, &nid );
// ... code unrelated to icon
return true;
} break;
case WM_APP + 0xDDC:
{
switch( LOWORD(lParam) )
{
case WM_CONTEXTMENU:
{
MessageBox( hWndDlg, "This message box never shows up.", NULL, MB_OK | MB_SYSTEMMODAL );
HMENU hMenu = LoadMenu(GetModuleHandle(NULL),MAKEINTRESOURCE(IDR_TRAYMENU));
if( hMenu )
{
HMENU hSubMenu = GetSubMenu(hMenu,0);
if( hSubMenu )
{
SetForegroundWindow( hWndDlg );
POINT pt = { LOWORD(wParam), HIWORD(wParam) };
UINT uFlags = TPM_RIGHTBUTTON;
if( 0 != GetSystemMetrics(SM_MENUDROPALIGNMENT) )
uFlags |= TPM_RIGHTALIGN;
else
uFlags |= TPM_LEFTALIGN;
TrackPopupMenuEx( hSubMenu, uFlags, pt.x, pt.y, hWndDlg, NULL );
}
DestroyMenu( hMenu );
}
} break;
case WM_LBUTTONDBLCLK:
if( IsWindowVisible(hWndDlg) )
ShowWindow( hWnd, SW_HIDE );
else
ShowWindow( hWnd, SW_SHOW );
break;
}
return true;
} break;
case WM_CLOSE:
ShowWindow( hWndDlg, SW_HIDE );
break;
case WM_DESTROY:
case WM_QUIT:
{
Shell_NotifyIcon( NIM_DELETE, &nid );
// ... code unrelated to icon
return true;
} break;
}
return false;
}
This is the WndProc from the Windows 7.1 SDK Sample
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
static HWND s_hwndFlyout = NULL;
static BOOL s_fCanShowFlyout = TRUE;
switch (message)
{
case WM_CREATE:
// add the notification icon
if (!AddNotificationIcon(hwnd))
{
MessageBox(hwnd,
L"Please read the ReadMe.txt file for troubleshooting",
L"Error adding icon", MB_OK);
return -1;
}
break;
case WM_COMMAND:
{
int const wmId = LOWORD(wParam);
// Parse the menu selections:
switch (wmId)
{
case IDM_LOWINK:
ShowLowInkBalloon();
break;
case IDM_NOINK:
ShowNoInkBalloon();
break;
case IDM_PRINTJOB:
ShowPrintJobBalloon();
break;
case IDM_OPTIONS:
// placeholder for an options dialog
MessageBox(hwnd, L"Display the options dialog here.", L"Options", MB_OK);
break;
case IDM_EXIT:
DestroyWindow(hwnd);
break;
case IDM_FLYOUT:
s_hwndFlyout = ShowFlyout(hwnd);
break;
default:
return DefWindowProc(hwnd, message, wParam, lParam);
}
}
break;
case WMAPP_NOTIFYCALLBACK:
switch (LOWORD(lParam))
{
case NIN_SELECT:
// for NOTIFYICON_VERSION_4 clients, NIN_SELECT is prerable to listening to mouse clicks and key presses
// directly.
if (IsWindowVisible(s_hwndFlyout))
{
HideFlyout(hwnd, s_hwndFlyout);
s_hwndFlyout = NULL;
s_fCanShowFlyout = FALSE;
}
else if (s_fCanShowFlyout)
{
s_hwndFlyout = ShowFlyout(hwnd);
}
break;
case NIN_BALLOONTIMEOUT:
RestoreTooltip();
break;
case NIN_BALLOONUSERCLICK:
RestoreTooltip();
// placeholder for the user clicking on the balloon.
MessageBox(hwnd, L"The user clicked on the balloon.", L"User click", MB_OK);
break;
//
//
// As you can very plainly see, the Windows SDK Sample ONLY used WM_CONTEXTMNEU
//
//
case WM_CONTEXTMENU:
{
POINT const pt = { LOWORD(wParam), HIWORD(wParam) };
ShowContextMenu(hwnd, pt);
}
break;
}
break;
case WMAPP_HIDEFLYOUT:
HideFlyout(hwnd, s_hwndFlyout);
s_hwndFlyout = NULL;
s_fCanShowFlyout = FALSE;
break;
case WM_TIMER:
if (wParam == HIDEFLYOUT_TIMER_ID)
{
// please see the comment in HideFlyout() for an explanation of this code.
KillTimer(hwnd, HIDEFLYOUT_TIMER_ID);
s_fCanShowFlyout = TRUE;
}
break;
case WM_DESTROY:
DeleteNotificationIcon();
PostQuitMessage(0);
break;
default:
return DefWindowProc(hwnd, message, wParam, lParam);
}
return 0;
}
I think you should try changing the uVersion member of the NOTIFYICONDATA structure to NOTIFYICON_VERSION_4, the documentation states that this members value will tell how the uCallbackMessage parameters will be interpreted when passed on to your callback function.
You can also have a look at this: Basic use of Shell_NotifyIcon in Win32
I did a bit of research and the following should work for you:
memset(&nid, 0, sizeof(NOTIFYICONDATA));
nid.cbSize = sizeof(NOTIFYICONDATA);
nid.hWnd = hWndDlg;
nid.uID = 0xDDC;
nid.uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP | NIF_SHOWTIP;
nid.uCallbackMessage = WM_APP + 0xDDC;
nid.hIcon = hIcon;
nid.uVersion = NOTIFYICON_VERSION_4;
strcpy(nid.szTip, "DDCMP Driver");
Shell_NotifyIcon(NIM_ADD, &nid);
Shell_NotifyIcon(NIM_SETVERSION, &nid);
NIM_SETVERSION (MSDN):
Shell32.dll version 5.0 and later only. Instructs the notification
area to behave according to the version number specified in the
uVersion member of the structure pointed to by lpdata. The version
number specifies which members are recognized.
The notify icon has changed behaviour over the years. For reasons of compatibility with pre-existing code, you must opt-in to the new behaviour. If you don't opt-in then you don't get sent WM_CONTEXTMENU messages. Instead you have to respond to WM_RBUTTONUP. Even if you invoke the context menu from the keyboard, the system still sends WM_RBUTTONUP. You have to obtain the cursor position, in order to know where to show the menu, by calling GetCursorPos.
You can opt in to the new behaviour (and WM_CONTEXTMENU) as described in the documentation, by calling Shell_NotifyIcon passing NIM_SETVERSION after the NIM_ADD call. Presumably the SDK sample you are looking at does this somewhere. My guess is that is what is missing from your code.
The key extract from the documentation is in the remarks section:
As of Windows 2000 (Shell32.dll version 5.0), Shell_NotifyIcon mouse and keyboard events are handled differently than in earlier Shell versions found on Microsoft Windows NT 4.0, Windows 95, and Windows 98. The differences include the following:
If a user selects a notify icon's shortcut menu with the keyboard, the Shell now sends the associated application a WM_CONTEXTMENU message. Earlier versions send WM_RBUTTONDOWN and WM_RBUTTONUP messages.
If a user selects a notify icon with the keyboard and activates it with the SPACEBAR or ENTER key, the version 5.0 Shell sends the associated application an NIN_KEYSELECT notification. Earlier versions send WM_RBUTTONDOWN and WM_RBUTTONUP messages.
If a user selects a notify icon with the mouse and activates it with the ENTER key, the Shell now sends the associated application an NIN_SELECT notification. Earlier versions send WM_RBUTTONDOWN and WM_RBUTTONUP messages.
After creating the initial dialog box that works perfectly fine, I create another dialog box when the Join Game button is pressed. The dialog box is created and show successfully, however I am unable to type in the edit box or even press or exit the dialog. Does anyone understand how to fix this or why it happens? I made sure the dialog box itself was not the problem by creating and displaying it from the main loop in the application. It worked fine when I created it that way. So why does it error when being created from another dialog?
My code is below.
This code is for the DLGPROC function that each dialog uses.
#define WIN32_LEAN_AND_MEAN
#include "Windows.h"
#include ".\Controllers\Menu\MenuSystem.h"
#include ".\Controllers\Game Controller\GameManager.h"
#include ".\Controllers\Network\Network.h"
#include "resource.h"
#include "main.h"
using namespace std;
extern GameManager g;
extern bool men;
NET_Socket server;
extern HWND d;
HWND joinDlg;
char ip[64];
void JoinMenu(){
joinDlg = CreateDialog(g_hInstance, MAKEINTRESOURCE(IDD_GETADDRESSINFO), NULL, (DLGPROC)GameJoinDialogPrompt);
SetFocus(joinDlg);
// ShowWindow(joinDlg, SW_SHOW);
ShowWindow(d, SW_HIDE);
}
LRESULT CALLBACK GameJoinDialogPrompt(HWND Dialogwindow, UINT Message, WPARAM wParam, LPARAM lParam){
switch(Message){
case WM_COMMAND:{
switch(LOWORD(wParam)){
case IDCONNECT:{
GetDlgItemText(joinDlg, IDC_IP, ip, 63);
if(server.ConnectToServer(ip, 7890, NET_UDP) == NET_INVALID_SOCKET){
LogString("Failed to connect to server! IP: %s", ip);
MessageBox(NULL, "Failed to connect!", "Error", MB_OK);
ShowWindow(joinDlg, SW_SHOW);
break;
}
}
LogString("Connected!");
break;
case IDCANCEL:
ShowWindow(d, SW_SHOW);
ShowWindow(joinDlg, SW_HIDE);
break;
}
break;
}
case WM_CLOSE:
PostQuitMessage(0);
break;
}
return 0;
}
LRESULT CALLBACK GameMainDialogPrompt(HWND Dialogwindow, UINT Message, WPARAM wParam, LPARAM lParam){
switch(Message){
case WM_PAINT:{
PAINTSTRUCT ps;
RECT rect;
HDC hdc = GetDC(Dialogwindow);
hdc = BeginPaint(Dialogwindow, &ps);
GetClientRect (Dialogwindow, &rect);
FillRect(hdc, &rect, CreateSolidBrush(RGB(0, 0, 0)));
EndPaint(Dialogwindow, &ps);
break;
}
case WM_COMMAND:{
switch(LOWORD(wParam)){
case IDC_HOST:
if(!NET_Initialize()){
break;
}
if(server.CreateServer(7890, NET_UDP) != 0){
MessageBox(NULL, "Failed to create server.", "Error!", MB_OK);
PostQuitMessage(0);
return -1;
}
ShowWindow(d, SW_HIDE);
break;
case IDC_JOIN:{
JoinMenu();
}
break;
case IDC_EXIT:
PostQuitMessage(0);
break;
default:
break;
}
break;
}
return 0;
}
}
I call the first dialog using the below code
void EnterMenu(){
// joinDlg = CreateDialog(g_hInstance, MAKEINTRESOURCE(IDD_GETADDRESSINFO), g_hWnd, (DLGPROC)GameJoinDialogPrompt);//
d = CreateDialog(g_hInstance, MAKEINTRESOURCE(IDD_SELECTMENU), g_hWnd, (DLGPROC)GameMainDialogPrompt);
}
The dialog boxes are not DISABLED by default, and they are visible by default. Everything is set to be active on creation and no code deactivates the items on the dialog or the dialog itself.
First, make sure you write the correct signature for the dialog procedures:
INT_PTR CALLBACK DialogProc(HWND hwndDlg, UINT uMsg,
WPARAM wParam, LPARAM lParam);
(See http://msdn.microsoft.com/en-us/library/ms645469(v=VS.85).aspx)
Your dialog procedures should therefore look like this:
INT_PTR CALLBACK GameJoinDialogPrompt(HWND Dialogwindow, UINT Message,
WPARAM wParam, LPARAM lParam)
{ /* ... */ }
INT_PTR CALLBACK GameMainDialogPrompt(HWND Dialogwindow, UINT Message,
WPARAM wParam, LPARAM lParam)
{ /* ... */ }
Then you should be able to do this without warnings or errors:
void EnterMenu()
{
d = CreateDialog(g_hInstance, MAKEINTRESOURCE(IDD_SELECTMENU),
g_hWnd, &GameMainDialogPrompt);
// Note the ampersand. Also note that no cast is needed. You should
// not need to use a cast to pass in the address of the function.
}
See http://blogs.msdn.com/oldnewthing/archive/2004/01/15/58973.aspx for why it's extremely important to get the function signature right.
That being said, your joinDlg should be a modal dialog box, since it is requesting information from the user:
void JoinMenu()
{
// DialogBox() creates a modal dialog box. It "blocks" its owner until
// it closes. On the other hand, CreateDialog() creates a non-modal
// dialog box.
joinDlg = DialogBox(g_hInstance, MAKEINTRESOURCE(IDD_GETADDRESSINFO),
d, &GameJoinDialogPrompt);
// Again, note the ampersand and the lack of a cast when passing in
// the address of the dialog procedure. Also, the main dialog box is
// serving as the owner of this dialog box.
}
Also note that dialog box procedures are different from windows procedures in that they return either TRUE or FALSE: TRUE if your dialog procedure processed the message, FALSE otherwise. (There are some "weird" messages that violate this rule, but you're not handling for those messages)
So your dialog procedures should look something like this:
INT_PTR CALLBACK GameMainDialogPrompt(HWND Dialogwindow, UINT Message,
WPARAM wParam, LPARAM lParam)
{
switch(Message)
{
case WM_PAINT:
/* Do painting */
return TRUE; // We handled the paint message
case WM_COMMAND:
switch(LOWORD(wParam))
{
case IDC_HOST:
/* Do command */
return TRUE; // We handled this particular command.
case IDC_JOIN:
/* Do command */
return TRUE; // We handled this particular command.
case IDC_EXIT:
/* Do command */
return TRUE; // We handled this particular command.
}
return FALSE; // The command wasn't handled.
}
return FALSE; // The message wasn't handled.
}
Dialog procedures do not call DefWindowProc() nor return 0!
In addiiton to the other excellent post you are also doing silly things like:
if(server.CreateServer(7890, NET_UDP) != 0){
MessageBox(NULL, "Failed to create server.", "Error!", MB_OK);
PostQuitMessage(0);
in a WM_COMMAND handler. This is a horrible piece of code as it stalls the dialogs modal loop without disabling it, or popping up the message box.
If you call a modal window from a different window (or dialogs) message proc you MUST disable the stalled window. Practically speaking, pass the windows HWND to the MessageBox call.
If all else fails, start fresh with this:
In resource.h:
#define IDD_DIALOG1 101
#define IDD_DIALOG2 102
#define ID_OPEN 1001
#define ID_MESSAGE 1002
In a resource file:
#include <winres.h>
#include "resource.h"
IDD_DIALOG1 DIALOGEX 0, 0, 300, 200
STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | DS_CENTER |
WS_POPUP | WS_CAPTION | WS_SYSMENU
CAPTION "Main Dialog"
FONT 8, "MS Shell Dlg", 400, 0, 0x1
BEGIN
DEFPUSHBUTTON "Open Secondary Dialog", ID_OPEN, 73 ,49, 133, 64
END
IDD_DIALOG2 DIALOGEX 0, 0, 200, 150
STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | DS_CENTER |
WS_POPUP | WS_CAPTION | WS_SYSMENU
CAPTION "Secondary Dialog"
FONT 8, "MS Shell Dlg", 400, 0, 0x1
BEGIN
DEFPUSHBUTTON "Message Box", ID_MESSAGE, 50, 49, 88, 50
END
In a source file:
#include <windows.h>
#include "resource.h"
INT_PTR CALLBACK SecondaryDialogProc(HWND hwnd, UINT msg,
WPARAM wParam, LPARAM lParam)
{
switch(msg)
{
case WM_INITDIALOG:
return TRUE;
case WM_COMMAND:
switch(LOWORD(wParam))
{
case ID_MESSAGE:
// Show a message box. Note that we're passing in our own HWND into
// the function, so we "block" this dialog box until the user
// dismisses this message box.
::MessageBox(hwnd, "Hello World!", "Greetings", MB_OK);
return TRUE;
}
return FALSE;
case WM_CLOSE:
// Because this is a modal dialog box (we used ::DialogBox()), we
// use ::EndDialog() instead of ::DestroyWindow() to destroy this
// dialog box.
::EndDialog(hwnd, 0);
return TRUE;
}
return FALSE;
}
INT_PTR CALLBACK MainDialogProc(HWND hwnd, UINT msg,
WPARAM wParam, LPARAM lParam)
{
switch(msg)
{
case WM_INITDIALOG:
return TRUE;
case WM_COMMAND:
switch(LOWORD(wParam))
{
case ID_OPEN:
// Open a modal dialog box. This will block the main dialog box
// until the secondary dialog box is closed.
::DialogBox(::GetModuleHandle(NULL),
MAKEINTRESOURCE(IDD_DIALOG2), hwnd, &SecondaryDialogProc);
return TRUE;
}
return FALSE;
case WM_CLOSE:
// We close this dialog box with ::DestroyWindow(). This causes the
// WM_DESTROY message to be sent.
::DestroyWindow(hwnd);
return TRUE;
case WM_DESTROY:
// Since the main dialog box is being destroyed, we quit
// the application.
::PostQuitMessage(0);
return TRUE;
}
return FALSE;
}
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nShowCmd)
{
// Open a non-modal dialog box using ::CreateDialog().
HWND mainDlg = ::CreateDialog(::GetModuleHandle(NULL),
MAKEINTRESOURCE(IDD_DIALOG1), NULL, &MainDialogProc);
// The first ::ShowWindow() call should use nShowCmd.
::ShowWindow(mainDlg, nShowCmd);
MSG msg;
while (::GetMessage(&msg, NULL, 0, 0) > 0)
{
// So our main dialog behaves properly.
if(!::IsDialogMessage(mainDlg, &msg))
{
::TranslateMessage( & msg );
::DispatchMessage( & msg );
}
}
return msg.wParam;
}
Here, this is just the bare bones code to open up a main dialog, with a button to open another dialog. See if this works first, then add your business logic.