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 :)
User contributions licensed under CC BY-SA 3.0