I create a notification icon with:
notifyIcon.cbSize = sizeof(NOTIFYICONDATA);
notifyIcon.hWnd = mainWnd;
notifyIcon.uID = 100;
notifyIcon.uFlags = NIF_ICON | NIF_TIP | NIF_MESSAGE;
notifyIcon.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_LOGO));
notifyIcon.dwState = NIS_SHAREDICON;
notifyIcon.uVersion = NOTIFYICON_VERSION;
notifyIcon.uTimeout = 15000;
notifyIcon.uCallbackMessage = APP_MSG_TRAY;
wcscpy_s(notifyIcon.szTip, 127, WTXT_APP_TRAY_TOOLTIP);
Shell_NotifyIcon(NIM_ADD, ¬ifyIcon);
Shell_NotifyIcon(NIM_SETVERSION, ¬ifyIcon);
And have a context menu popup on WM_RBUTTONDOWN and WM_CONTEXTMENU like this:
MENUITEMINFO separatorBtn = {0};
separatorBtn.cbSize = sizeof(MENUITEMINFO);
separatorBtn.fMask = MIIM_FTYPE;
separatorBtn.fType = MFT_SEPARATOR;
HMENU hMenu = CreatePopupMenu();
if(hMenu) {
InsertMenu(hMenu, -1, MF_BYPOSITION, APP_OPEN_OPTIONS, WTXT_OPTIONS);
InsertMenuItem(hMenu, -1, FALSE, &separatorBtn);
InsertMenu(hMenu, -1, MF_BYPOSITION, APP_MSG_EXIT, WTXT_EXIT);
POINT pt;
GetCursorPos(&pt);
SetForegroundWindow(mainWnd);
TrackPopupMenu(hMenu, TPM_RIGHTBUTTON, pt.x, pt.y, 0, mainWnd, NULL);
PostMessage(mainWnd, WM_NULL, 0, 0);
DestroyMenu(hMenu);
}
It works fine, but the context menu doesn't disappear always. Sometimes (often) if you have ie. winamp and my app icons in system tray, if you right click my app and winamp afterwards, bot menus will appear, and my menu won't disappear automatically until you click an item.
Any ideas?
thanks...
To display a context menu for a notification icon, the current window must be the foreground window before the application calls TrackPopupMenu or TrackPopupMenuEx. Otherwise, the menu will not disappear when the user clicks outside of the menu or the window that created the menu (if it is visible).
SetForegroundWindow(hDlg);
TrackPopupMenu( hSubMenu,
TPM_RIGHTBUTTON,
pt.x,
pt.y,
0,
hDlg,
NULL);
Do not catch WM_RBUTTONDOWN but WM_RBUTTONUP. And of course do not handle both WM_RBUTTONUP and WM_CONTEXTMENU, since they will both get handled and you'd show the context menu twice for every right-click.
Showing the menu twice would have the effect you describe: the menu shows up, but doesn't seem to disappear (because it shows up again right away a second time).
There are apps to try to hack around the restrictions of the notification area (tray) API. They'll hook the Explorer window and listen for Windows messages. That lets them do stuff that isn't otherwise possible but it inevitably destabilizes other apps. Getting two context menus is a sure sign of this kind of trouble.
You've got a good lead on what kind of program may do this, it's got an icon. Kill them one by one until you find the evil-doer. Not much you can do about it probably, other than not running it or complaining to the vendor.
You seem to already have both of the documented bugfixes (SetForegroundWindow & WM_NULL) I'd say anything beyond this is a bug in windows.
If you really want to do hacky things, you could probably get the menu window handle in WM_INITMENU* (And I don't mean the HMENU, but the HWND for the menu) and hide that window.
Related
I've been wanting to incorporate systray functionality into my Flutter app so I went to modify the native C++ code that initiates the window etc to see if I could hook into it.
Despite not having much prior experience in C++ I have been able to create an icon for my app in the systray with a menu that allows the window to be shown again when hidden (using ShowWindow(hwnd, SW_HIDE);) and to quit entirely.
However when an option in my systray menu is selected to show the window again using ShowWindow(hwnd, SW_NORMAL); after being hidden, the app stays blank like this:
Then, when the window is finally interacted with, the contents of the window show again:
Here is the code that I have added so far to my win32_window.cpp (from a default Flutter application). I haven't included the entire functions because I thought it would make things less clear, but I will also attach the full win32_window.cpp at the end of this post.
Win32Window::CreateAndShow():
//Systray:
HICON hMainIcon;
hMainIcon = LoadIcon(hInst, MAKEINTRESOURCE(IDI_APP_ICON));
nidApp.cbSize = sizeof(NOTIFYICONDATA); // sizeof the struct in bytes
nidApp.hWnd = (HWND) window; //handle of the window which will process this app. messages
nidApp.uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP; //ORing of all the flags
nidApp.hIcon = hMainIcon; // handle of the Icon to be displayed, obtained from LoadIcon
nidApp.uCallbackMessage = WM_USER_SHELLICON;
StringCchCopy(nidApp.szTip, ARRAYSIZE(nidApp.szTip), L"All Platforms Test");
Shell_NotifyIcon(NIM_ADD, &nidApp);
return OnCreate();
Win32Window::WndProc():
if (message == WM_NCCREATE) { ... }
else if (message == WM_USER_SHELLICON) { //interacting with systray icon
if (LOWORD(lparam) == WM_RBUTTONDOWN) { //right clicked
POINT lpClickPoint;
GetCursorPos(&lpClickPoint);
hPopMenu = CreatePopupMenu();
InsertMenu(hPopMenu,0xFFFFFFFF,MF_BYPOSITION|MF_STRING,IDM_SHOW,_T("Show"));
InsertMenu(hPopMenu,0xFFFFFFFF,MF_BYPOSITION|MF_STRING,IDM_EXIT,_T("Quit"));
SetForegroundWindow(window);
TrackPopupMenu(hPopMenu,TPM_LEFTALIGN|TPM_LEFTBUTTON|TPM_BOTTOMALIGN,lpClickPoint.x, lpClickPoint.y,0,window,NULL);
}
else if (LOWORD(lparam) == WM_LBUTTONDOWN) { //left clicked
ShowWindow(window, SW_NORMAL);
//LOOK: works but shows blank screen until is interacted with (mouse enters or key is pressed etc)
}
}
else if (message == WM_COMMAND) { //if message is a command event such as a click on the exit menu option
int wmId;
wmId = LOWORD(wparam);
if (wmId == IDM_EXIT) { //if quit has been pressed
Shell_NotifyIcon(NIM_DELETE,&nidApp);
DestroyWindow(window);
}
else if (wmId == IDM_SHOW) {
ShowWindow(window, SW_NORMAL);
//LOOK: works but shows blank screen until is interacted with (mouse enters or key is pressed etc)
}
Win32Window::MessageHandler():
switch (message) {
...
case WM_CLOSE: //stop window from closing normally, can only be closed when DestroyWindow() is run from systray
//Hide window and continue running in background.
ShowWindow(hwnd, SW_HIDE);
return 0;
}
Link to full win32_window.cpp here.
What's going on here? I thought using UpdateWindow() would help but then I realise the app is painted upon ShowWindow() anyway. My guess is that this has something to do with Flutter's run loop being blocked but I can't figure out where to go next, especially considering I usually don't dabble in C++ but just wanted to add an extra feature to my app when running on Windows.
Any help would be greatly appreciated, thanks.
Ok so I've worked out why it wasn't working. When closing the window, I couldn't just use SW_HIDE, but SW_MINIMIZE too. Otherwise attempting to redraw the window wouldn't work correctly:
ShowWindow(hwnd, SW_MINIMIZE);
ShowWindow(hwnd, SW_HIDE);
After that, when showing the window it got drawn but wasn't the active window, but adding SetForegroundWindow() fixed that:
ShowWindow(window, SW_NORMAL);
SetForegroundWindow(window);
Thanks for everyone's help :)
I have an installation dialog (made with nsis) that has two buttons (install and cancel). I'm trying to write automated tests for the install process using low level win32 api. To click on the button(s) I use the following code:
char windowName[] = "Desktop Application Setup";
char cancelButtonText[] = "Cancel";
HWND hWndMainWindow = NULL;
HWND hButton = NULL;
hWndMainWindow = FindWindow(NULL, windowName);
if (hWndMainWindow)
{
hButton = FindWindowEx(hWndMainWindow, NULL, NULL, cancelButtonText);
if (hButton)
{
SendMessage(hButton, BM_CLICK, 0, 0);
}
}
On Windows 7, this works perfectly. On Windows 10, it simply does nothing. It finds the button, it sends the message, but the click just doesn't happen.
Is this some security thing introduced in Windows 10? Is it a known issue?
it is better to send WM_COMMAND with the ID of the button, but the way you are doing works also if Lang is always in English. but the problem with your case is that buttons on dialog usually have an "&" to indicate the keyboard short cut, and usually hidden by system unless you press alt key. (like menus).
so: text of the button is most likely to be "&cancel"
I've seen a lot of "unofficial" workarounds to bypass this visual bug, but none of them seem to work correctly for me. Let me explain.
Say, I display my own system tray icon in the Windows notification area (by the clock.)
My program also supports displaying a context menu when a user right-clicks my tray icon. I implement it by trapping the WM_RBUTTONDOWN notification from the message handler specified in the uCallbackMessage parameter of the NOTIFYICONDATA. I then processing it as such:
//Show menu (ask to return results here)
int nRes = TrackPopupMenu(hMenu, TPM_RIGHTALIGN | TPM_TOPALIGN |
TPM_LEFTBUTTON | TPM_VERPOSANIMATION | TPM_HORNEGANIMATION | TPM_RETURNCMD,
pnt.x, pnt.y, 0, this->GetSafeHwnd(), NULL);
Unfortunately the code above, all by itself, does not allow the menu to be dismissed after a left-click somewhere outside of it.
To resolve this, the solution I found was this:
//Need this to bypass a glitch in Windows to remove popup menu from
//System Tray after mouse click outside of it
::SetForegroundWindow(this->GetSafeHwnd());
//Show menu (ask to return results here)
int nRes = TrackPopupMenu(hMenu, TPM_RIGHTALIGN | TPM_TOPALIGN |
TPM_LEFTBUTTON | TPM_VERPOSANIMATION | TPM_HORNEGANIMATION | TPM_RETURNCMD,
pnt.x, pnt.y, 0, this->GetSafeHwnd(), NULL);
PostMessage(WM_NULL, 0, 0);
But the "fix" above introduces an occasional additional bug.
For instance, here's a screenshot from Windows 8. Sometimes, I think it depends on what window had focus prior to this, when I right-click on my tray icon my context menu is displayed on top of the Windows default context menu, that looks like this:
Is there any way to resolve this visual bug of overlapping context menus?
I'm trying to place my application that is mainly running in the background in a 'tray-like' area on the Windows Mobile 6.5.
I do it the obvious way by with Shell_NotifyIcon
BOOL ShowTrayIcon(HWND hWnd, HINSTANCE hIns, BOOL bShowIcon)
{
BOOL bRet = FALSE;
g_structNotifyIconData.cbSize = sizeof(NOTIFYICONDATA);
g_structNotifyIconData.hIcon = LoadIcon(hIns, MAKEINTRESOURCE(IDI_GPSCOMPASS));
g_structNotifyIconData.hWnd = hWnd;
g_structNotifyIconData.uCallbackMessage = WM_SYSTRAY_MSG;
g_structNotifyIconData.uFlags = NIF_MESSAGE | NIF_ICON;
g_structNotifyIconData.szTip[0] = 'Bzz';
g_structNotifyIconData.uID = ID_TRAY;
if (bShowIcon)
bRet = Shell_NotifyIcon(NIM_ADD, &g_structNotifyIconData);
else
bRet = Shell_NotifyIcon(NIM_DELETE, &g_structNotifyIconData);
return bRet;
}
This is where i am trying to place the icon :
Tray icon within the 'today' area http://www.fotoszok.pl/upload/666d99dc.jpg
The Shell_NotifyIcon does the thing but the Icon is not shown on the Today screen, i can see it's in the tray from any place except the Today/Home screen.
Now I've read somewhere that this is because the Tray area in Today screen is reserved for system notifications and 'we' can't place any icons in there - well if that's true, can somebody please confirm that?
Indeed, Shell_NotifyIcon doesn't support adding the icon in the Today screen. This is even one of the first things mentioned in the function documentation:
This function sends a message to the system to add, modify, or delete an application-specific icon from the taskbar status area. It does not affect icons appearing on the home screen.
You can try using SHNotificationAdd.
I create a notification icon with:
notifyIcon.cbSize = sizeof(NOTIFYICONDATA);
notifyIcon.hWnd = mainWnd;
notifyIcon.uID = 100;
notifyIcon.uFlags = NIF_ICON | NIF_TIP | NIF_MESSAGE;
notifyIcon.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_LOGO));
notifyIcon.dwState = NIS_SHAREDICON;
notifyIcon.uVersion = NOTIFYICON_VERSION;
notifyIcon.uTimeout = 15000;
notifyIcon.uCallbackMessage = APP_MSG_TRAY;
wcscpy_s(notifyIcon.szTip, 127, WTXT_APP_TRAY_TOOLTIP);
Shell_NotifyIcon(NIM_ADD, ¬ifyIcon);
Shell_NotifyIcon(NIM_SETVERSION, ¬ifyIcon);
And have a context menu popup on WM_RBUTTONDOWN and WM_CONTEXTMENU like this:
MENUITEMINFO separatorBtn = {0};
separatorBtn.cbSize = sizeof(MENUITEMINFO);
separatorBtn.fMask = MIIM_FTYPE;
separatorBtn.fType = MFT_SEPARATOR;
HMENU hMenu = CreatePopupMenu();
if(hMenu) {
InsertMenu(hMenu, -1, MF_BYPOSITION, APP_OPEN_OPTIONS, WTXT_OPTIONS);
InsertMenuItem(hMenu, -1, FALSE, &separatorBtn);
InsertMenu(hMenu, -1, MF_BYPOSITION, APP_MSG_EXIT, WTXT_EXIT);
POINT pt;
GetCursorPos(&pt);
SetForegroundWindow(mainWnd);
TrackPopupMenu(hMenu, TPM_RIGHTBUTTON, pt.x, pt.y, 0, mainWnd, NULL);
PostMessage(mainWnd, WM_NULL, 0, 0);
DestroyMenu(hMenu);
}
It works fine, but the context menu doesn't disappear always. Sometimes (often) if you have ie. winamp and my app icons in system tray, if you right click my app and winamp afterwards, bot menus will appear, and my menu won't disappear automatically until you click an item.
Any ideas?
thanks...
To display a context menu for a notification icon, the current window must be the foreground window before the application calls TrackPopupMenu or TrackPopupMenuEx. Otherwise, the menu will not disappear when the user clicks outside of the menu or the window that created the menu (if it is visible).
SetForegroundWindow(hDlg);
TrackPopupMenu( hSubMenu,
TPM_RIGHTBUTTON,
pt.x,
pt.y,
0,
hDlg,
NULL);
Do not catch WM_RBUTTONDOWN but WM_RBUTTONUP. And of course do not handle both WM_RBUTTONUP and WM_CONTEXTMENU, since they will both get handled and you'd show the context menu twice for every right-click.
Showing the menu twice would have the effect you describe: the menu shows up, but doesn't seem to disappear (because it shows up again right away a second time).
There are apps to try to hack around the restrictions of the notification area (tray) API. They'll hook the Explorer window and listen for Windows messages. That lets them do stuff that isn't otherwise possible but it inevitably destabilizes other apps. Getting two context menus is a sure sign of this kind of trouble.
You've got a good lead on what kind of program may do this, it's got an icon. Kill them one by one until you find the evil-doer. Not much you can do about it probably, other than not running it or complaining to the vendor.
You seem to already have both of the documented bugfixes (SetForegroundWindow & WM_NULL) I'd say anything beyond this is a bug in windows.
If you really want to do hacky things, you could probably get the menu window handle in WM_INITMENU* (And I don't mean the HMENU, but the HWND for the menu) and hide that window.