I have some windows application that can change his icon, using win api functions
SendMessage(hwnd, WM_SETICON, ICON_BIG, icon_handle);
SendMessage(hwnd, WM_SETICON, ICON_SMALL, icon_handle);
Shell_NotifyIcon(...);
It changes icon in taskbar and tray (taskbar notification area), but icon in taskmanager still not changed.
How can I change icon in taskmanager? Is it possible?
From this SO answer
It's important to change all icons, including the application, both small and big:
//Change both icons to the same icon handle.
SendMessage(hwnd, WM_SETICON, ICON_SMALL, hIcon);
SendMessage(hwnd, WM_SETICON, ICON_BIG, hIcon);
//This will ensure that the application icon gets changed too.
SendMessage(GetWindow(hwnd, GW_OWNER), WM_SETICON, ICON_SMALL, hIcon);
SendMessage(GetWindow(hwnd, GW_OWNER), WM_SETICON, ICON_BIG, hIcon);
EDIT:
According to the this SO answer, the icon needs to be a .ICO file created by an icon editor; this SO article also mentions that you need to send the message to the top-most window of the application.
I could switch the icon in the task bar, alt-tab and in the task manager by
a) creating an icon using the Visual Studio Resource Editor
b) loading the icon with code like HICON hIcon = LoadIcon(GetModuleHandle(NULL), MAKEINTRESOURCE(IDI_ICON1));
c) sending WM_SETICON, ICON_SMALL to the topmost window -- I was using a MFC SDI application, so I sent the message to the main frame window (AfxGetApp()->m_pMainWnd)
NOTE: a comment in the MSDN Docs for WM_SETICON mentions
The system does not make a copy of the icon. Do not destroy the icon before destroying the window
It is a general Windows bug. Task manager and explorer remember icons associated with files for a very long time. If your icon has lowest ID in exe, it should appear as the application icon in task manager (root node). But if you changed it recently, it may not work. The icon of the window itself is a quite different thing - if it isn't displaying, your code is wrong.
BOOL sendWndIconToTaskbar(HWND hWnd,HICON hIcon)
{
BOOL ret = TRUE;
ASSERT(hWnd);
if(!::IsWindow(hWnd))
return FALSE;
CWnd* pWnd;
pWnd = pWnd->FromHandle(hWnd);
ASSERT(pWnd);
if(!pWnd)
return FALSE;
if(pWnd->GetParent())
{
if(::SetWindowLong(hWnd,GWL_HWNDPARENT,NULL) == 0)
return FALSE;
}
if(!(pWnd->ModifyStyle(NULL,WS_OVERLAPPEDWINDOW)))
ret = FALSE;
pWnd->SetIcon(hIcon,TRUE);
pWnd->SetIcon(hIcon,FALSE);
return ret;
}
HICON hIconSm = (HICON)LoadImage(NULL, "default.ico", IMAGE_ICON, 32, 32, LR_LOADFROMFILE);
sendWndIconToTaskbar(pOcxDlg->m_hWnd,hIconSm);
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 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.
I am maintaining old C++ application running console. I has disabled "close" buttun . I need to disable maximize button as well. The following code disabes the close button
DeleteMenu(GetSystemMenu(GetConsoleWindow(), FALSE), SC_CLOSE, MF_BYCOMMAND);
DrawMenuBar(GetConsoleWindow());
I have added line to disable maximize button:
DeleteMenu(GetSystemMenu(GetConsoleWindow(), FALSE), SC_CLOSE, MF_BYCOMMAND);
DeleteMenu(GetSystemMenu(GetConsoleWindow(), FALSE), SC_MAXIMIZE, MF_BYCOMMAND);
DrawMenuBar(GetConsoleWindow());
It works, the button is disabled but it is not greyed out. (The Close button is greyed out)
What am I missing?
Thank you.
Use SetWindowLong to change the window style, then call SetWindowPos. Example:
HWND hwnd = GetConsoleWindow();
DWORD style = GetWindowLong(hwnd, GWL_STYLE);
style &= ~WS_MAXIMIZEBOX;
SetWindowLong(hwnd, GWL_STYLE, style);
SetWindowPos(hwnd, NULL, 0, 0, 0, 0, SWP_NOSIZE|SWP_NOMOVE|SWP_FRAMECHANGED);
In my application I am handling the WM_HELP message and then creating a tooltip for a control using this method:
Taken from: http://msdn.microsoft.com/en-us/library/bb760252(v=vs.85).aspx
HWND CreateToolTip(int toolID, HWND hDlg, PTSTR pszText)
{
if (!toolID || !hDlg || !pszText)
{
return FALSE;
}
// Get the window of the tool.
HWND hwndTool = GetDlgItem(hDlg, toolID);
// Create the tooltip. g_hInst is the global instance handle.
HWND hwndTip = CreateWindowEx(NULL, TOOLTIPS_CLASS, NULL,
WS_POPUP |TTS_ALWAYSTIP | TTS_BALLOON,
CW_USEDEFAULT, CW_USEDEFAULT,
CW_USEDEFAULT, CW_USEDEFAULT,
hDlg, NULL,
g_hInst, NULL);
if (!hwndTool || !hwndTip)
{
return (HWND)NULL;
}
// Associate the tooltip with the tool.
TOOLINFO toolInfo = { 0 };
toolInfo.cbSize = sizeof(toolInfo);
toolInfo.hwnd = hDlg;
toolInfo.uFlags = TTF_IDISHWND | TTF_SUBCLASS;
toolInfo.uId = (UINT_PTR)hwndTool;
toolInfo.lpszText = pszText;
SendMessage(hwndTip, TTM_ADDTOOL, 0, (LPARAM)&toolInfo);
return hwndTip;
}
The tooltip vanishes as soon as I move my mouse pointer.
My questions are:
Is tooltip is destroyed or is it just hidden ?
If it is hidden then how to destroy it and when?
Thanks.
It's been a while since I've done any WinAPI programming but if my memory serves me...
The call to CreateWindowEx passes the hDlg as the hWndParent parameter meaning the dialog window is now the parent of the tooltip.
From the MSDN documentation on the DestroyWindow function it says:
If the specified window is a parent or owner window, DestroyWindow automatically destroys the associated child or owned windows when it destroys the parent or owner window. The function first destroys child or owned windows, and then it destroys the parent or owner window.
So you can assume your tooltip window will be destroyed eventually. Be careful if you are calling CreateToolTip in response to every WM_HELP message as you will end up with a number of tooltip windows hanging around in memory until your dialog is closed and DestroyWindow is finally called.
As vz0 pointed out you could create the tooltip once, hang on to the window handle, then show the tooltip in response to the help message rather than creating it again.
In your comment to vz0's answer you said:
there are multiple ways in which a tooltip goes awya. example: mouse move, timeout etc.
All of those only result in the window being hidden so the handle to the tooltip is still valid and can be redisplayed using ShowWindow.
For every CreateWindowEx call you need a matching DestroyWindow call.
As an alternative, instead of creating and destroying the window every time you can use the ShowWindow call with SW_SHOW and SW_HIDE to show and hide the popup.
In my experience, I had to DestroyWindow() on the tooltip so an HFONT (font GDI resource) was properly released. There was a parent-child bond of the two windows at one time - but my system changes this at run-time and could be to blame. Probably no harm in doing it if your system generalizes it.
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.