I'm trying to write a simple reusable class to encapsulte the functionality for a basic tray icon. This is my first "project" with C++, and I'm having some problems: the message-only window created for the tray icon does not receive any message. The icon is visible in the tray and has the correct tooltip, but clicking on it does not send any message to my invisible window.
To test if the function WindowProcedure was called, i added a printf statement, and the output is that the function is called 4 times upon creation, but no more, even if I click on the notify icon.
Why my window does not receive any message from the tray icon?
This is what I've managed to write:
TrayIcon.h:
class TrayIcon {
public:
TrayIcon();
~TrayIcon();
void Show();
/*...*/
static void Initialize();
private:
static LRESULT CALLBACK WindowProcedure (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam);
/*...*/
};
TrayIcon.cpp:
// Static initializer
int TrayIcon::_idCounter = 1;
void TrayIcon::Initialize() // This method is called just once
{
// Registers the class for the tray windows
WNDCLASSEX wx = {};
wx.lpfnWndProc = TrayIcon::WindowProcedure;
wx.lpszClassName = TRAYICON_WINDOW_CLASSNAME;
wx.cbSize = sizeof(WNDCLASSEX);
RegisterClassEx(&wx);
}
// Constructor
TrayIcon::TrayIcon()
{
// Creates an hidden message-only window
HWND hWnd = CreateWindowEx(0, TRAYICON_WINDOW_CLASSNAME, "trayiconwindow", 0, 0, 0, 0, 0, HWND_MESSAGE, NULL, NULL, NULL);
SetWindowLongPtr(hWnd, GWLP_USERDATA, (LONG)this);
// Creates the notify icon
NOTIFYICONDATA icon;
memset( &icon, 0, sizeof( NOTIFYICONDATA ) ) ;
icon.cbSize = sizeof(NOTIFYICONDATA);
icon.hWnd = hWnd;
icon.uID = TrayIcon::_idCounter++;
icon.uCallbackMessage = WM_TRAYICON; //Set up our invented Windows Message
icon.uFlags = NIF_MESSAGE;
this->_iconData = icon;
}
// Shows the tray icon
void TrayIcon::Show()
{
// ...
Shell_NotifyIcon(NIM_ADD, &this->_iconData);
}
// Processes the messages received by the hidden window for each icon
LRESULT CALLBACK TrayIcon::WindowProcedure (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
TrayIcon* icon = (TrayIcon*)GetWindowLongPtr(hwnd, GWLP_USERDATA);
printf("Window Procedure called.\n\r");
return DefWindowProc(hwnd, message, wParam, lParam);
}
And this is how I use the class:
int main()
{
// Creates a new Tray icon
TrayIcon::Initialize();
TrayIcon* icon = new TrayIcon();
icon->SetTooltip("Foo Tooltip");
icon->SetIcon(...);
icon->Show();
// Waits for user input
void* tmp;
cin >> tmp;
return 0;
}
Thank you for your time.
You need some form of message loop, not a blocking call to input something. Try the canonical message loop:
MSG msg;
while (GetMessage(&msg, nullptr, 0, 0)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
Related
Running my program it runs and due to me having a menu with EXIT to Destroy the window it runs and immediately exits the window. Unsure how to fix my issue here on compiling the program to have it not run the WindowProcedure function and passing the argument EXITMENU resulting in the Window being destroyed.
*.CPP
#include <windows.h>
#define HELPMENU 1
#define HIGHSCROREMENU 2
#define EXITMENU 3
int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hPrevInst, LPSTR args, int ncmdshow) {
WNDCLASS wc = { 0 }; // WNDCLASSW is a structure
LPCWSTR title = L"Window"; // Long Pointer Constant Wide (UTF-16) String
wc.hbrBackground = (HBRUSH)COLOR_WINDOW; // Background
wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
wc.hCursor = LoadCursor(NULL, IDC_HAND); // Sets Cursor
wc.hInstance = hInst; // Instance of window
wc.lpszClassName = L"windowClass"; // Class name
wc.lpfnWndProc = WindowProcedure; // Pointer to the function // Controller of window handle
if (!RegisterClassW(&wc)) { // Registers the window class
return -1;
}
// | binary combination value, posX, posY, Width, Height
// Creates the window
CreateWindow(wc.lpszClassName, title, WS_OVERLAPPEDWINDOW | WS_VISIBLE | WS_BORDER, 100, 100, 800, 600, NULL, NULL, NULL, NULL);
MSG msg = { 0 };
while (GetMessage(&msg, NULL, NULL, NULL) > 0) { // Keeps the window running
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return 0;
}
/* Event Paths */
LRESULT CALLBACK WindowProcedure(HWND hWnd, UINT msg, WPARAM wp, LPARAM lp) {
switch (msg) {
case WM_CREATE: // On window creation
AddControls(hWnd);
AddMenu(hWnd);
break;
case WM_LBUTTONDOWN: // Left Mouse button
break;
case WM_DESTROY: // Makes GetMessage Function return false, closing the window
PostQuitMessage(0);
return 0;
case EXITMENU:
DestroyWindow(hWnd); // This part of the code shouldn't run on creation
break;
default:
return DefWindowProc(hWnd, msg, wp, lp);
}
}
/* Creates menu */
void AddMenu(HWND hWnd) {
hMenu = CreateMenu(); // Creates menu object
// AppendMenu(Menu Instance, Usage Type, Argument, String info);
AppendMenu(hMenu, MF_STRING, HELPMENU, L"Help - F1");
AppendMenu(hMenu, MF_STRING, HIGHSCROREMENU, L"Highscores - F2"); // Menu Created
AppendMenu(hMenu, MF_STRING, EXITMENU, L"Exit - ESC");
// SetMenu(Window Handle , Menu Instance);
SetMenu(hWnd, hMenu); // Sets menu for window //
}
You are not handling the menu commands correctly in your WindowProcedure().
You have defined EXITMENU as 3, which is the same value as the WM_MOVE message. So, in your switch, you are destroying your window as soon as it receives a WM_MOVE message during window creation.
You need to instead handle the menu commands via the WM_COMMAND message, per the documentation:
About Menus: Messages Used With Menus
When the user chooses a command item from a menu, the system sends a WM_COMMAND message to the window procedure. The low-order word of the WM_COMMAND message's wParam parameter contains the identifier of the chosen item. The window procedure should examine the identifier and process the message accordingly.
Try this instead:
LRESULT CALLBACK WindowProcedure(HWND hWnd, UINT msg, WPARAM wp, LPARAM lp) {
switch (msg) {
...
case WM_COMMAND:
switch (wp) {
case HELPMENU: {
...
return 0;
}
case HIGHSCROREMENU: {
...
return 0;
}
case EXITMENU: {
DestroyWindow(hWnd);
return 0;
}
}
break;
}
...
}
return DefWindowProc(hWnd, msg, wp, lp);
}
UPDATE: That being said, consider having your EXITMENU handler use SendMessage(WM_CLOSE) instead of DestroyWindow(). If your app maintains data that should be saved when the app is closed by the user, you can add a WM_CLOSE handler to perform that action regardless of how the window is being closed (your exit menu, X close button, Alt-F4, etc). DefWindowProc() destroys a window when processing WM_CLOSE.
LRESULT CALLBACK WindowProcedure(HWND hWnd, UINT msg, WPARAM wp, LPARAM lp) {
switch (msg) {
...
case WM_CLOSE: {
if (data has been modified) {
prompt user to save data...
if (cancelled) {
return 0;
}
if (should save) {
save data ...
}
}
break;
}
case WM_COMMAND:
switch (wp) {
...
case EXITMENU: {
SendMessage(hWnd, WM_CLOSE, 0, 0);
return 0;
}
}
break;
}
...
}
return DefWindowProc(hWnd, msg, wp, lp);
}
This is my attempt using a hook function to get the dialog window handle. Both SetWindowPos() and GetLastError() return correct values, but the dialog window is unaffected and shown at position 0,0.
#include <windows.h>
#include <iostream>
static UINT_PTR CALLBACK OFNHookProc (HWND hdlg, UINT uiMsg, WPARAM wParam, LPARAM lParam) {
using namespace std;
switch (uiMsg) {
case WM_INITDIALOG: {
// SetWindowPos returns 1
cout << SetWindowPos(hdlg, HWND_TOPMOST, 200, 200, 0, 0, SWP_NOSIZE ) << endl;
// GetLastError returns 0
cout << GetLastError() << endl;
break;
}
}
return 0;
}
int main() {
OPENFILENAMEW ofn;
ZeroMemory(&ofn, sizeof(ofn));
ofn.lStructSize = sizeof(OPENFILENAMEW);
ofn.nMaxFile = MAX_PATH;
ofn.Flags = OFN_EXPLORER | OFN_FILEMUSTEXIST | OFN_ENABLEHOOK;
ofn.lpfnHook = OFNHookProc;
GetOpenFileNameW(&ofn);
return 0;
}
When using OFN_EXPLORER, you have to move hdlg's parent window, as the HWND passed to your callback is not the actual dialog window. This is clearly stated in the documentation:
OFNHookProc callback function
hdlg [in]
A handle to the child dialog box of the Open or Save As dialog box. Use the GetParent function to get the handle to the Open or Save As dialog box.
Also, you should wait for the callback to receive the CDN_INITDONE notification, instead of the WM_INITDIALOG message.
Try this:
static UINT_PTR CALLBACK OFNHookProc (HWND hdlg, UINT uiMsg, WPARAM wParam, LPARAM lParam)
{
if ((uiMsg == WM_NOTIFY) &&
(reinterpret_cast<OFNOTIFY*>(lParam)->hdr.code == CDN_INITDONE))
{
SetWindowPos(GetParent(hdlg), HWND_TOPMOST, 200, 200, 0, 0, SWP_NOSIZE);
}
return 0;
}
I'm realize the console win32 app does not quit cleanly so I'm trying to switch to message only windows instead. I'm starting the app from another process and trying to kill it cleanly.
This is the win32 app, it spawns a calc.exe on startup and on clean shutdown, it should kill the calc.exe
LRESULT CALLBACK WindowProc (HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_USER: PostQuitMessage (0); break;
default: return DefWindowProc (hWnd, message, wParam, lParam);break;
}
return 0;
}
int CALLBACK WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int show)
{
WNDCLASSEX wc = { 0 };
wc.cbSize = sizeof (WNDCLASSEX);
wc.lpfnWndProc = WindowProc;
wc.hInstance = hInstance;
wc.lpszClassName = L"WindowClass1";
RegisterClassEx (&wc);
HWND hWnd = CreateWindowEx (NULL,
L"WindowClass1", // name of the window class
L"Our First Windowed Program", // title of the window
0,
//WS_OVERLAPPEDWINDOW, // window style
300,300,500,400,
HWND_MESSAGE,
//NULL, // parent window, NULL
NULL, hInstance, NULL);
//ShowWindow (hWnd, SW_HIDE);
PROCESS_INFORMATION pi;
CreateWindowProcess (L"calc.exe", pi); // helper class to createprocess
MSG msg = { 0 };
while (true)
{
if (PeekMessage (&msg, 0, 0, 0, PM_REMOVE))
{
if (WM_QUIT == msg.message)
break;
TranslateMessage (&msg);
DispatchMessage (&msg);
}
}
// terminate the spawn process, this is not called cleanly
TerminateProcess (pi.hProcess, 0);
return (int)msg.wParam;
}
I made a c# program to start/kill the app cleanly (the calc.exe gets destroyed) by sending a WM_QUIT/WM_CLOSE/WM_USER message . The Win32 App does not receive messages unless the window is visible (WS_OVERLAPPED and ShowWindow true). PostMessage WM_QUIT is received but the calc.exe does not get destroyed, meaning it is not a clean exit.
How should I kill it cleanly from C# app?
class Program
{
[DllImport ("user32.dll")]
public static extern bool PostMessage (IntPtr hwnd, uint msg, int wparam, int lparam);
[DllImport ("User32.dll")]
public static extern int SendMessage (IntPtr hWnd, uint uMsg, int wParam, int lParam);
static void Main (string [] args)
{
try
{
Process myProcess;
myProcess = Process.Start ("My.exe");
// Display physical memory usage 5 times at intervals of 2 seconds.
for (int i = 0; i < 3; i++)
{
if (myProcess.HasExited) break;
else
{
// Discard cached information about the process.
myProcess.Refresh ();
Thread.Sleep (4000);
Console.WriteLine ("Sending Message");
const int WM_USER = 0x0400;
const int WM_CLOSE = 0xF060; // Command code for close window
const int WM_QUIT = 0x0012;
// Received only when windows is visible
//int result = SendMessage (myProcess.MainWindowHandle, WM_USER, 0, 0);
// not clean exit
PostMessage (myProcess.MainWindowHandle, WM_QUIT, 0, 0);
// doesn't receive
SendMessage (myProcess.MainWindowHandle, WM_QUIT, 0, 0);
}
}
}
}
}
Processes on Windows do not really have a "MainWindow". Process.MainWindowHandle is C# making a guess, and it guesses by looking to see if the process in question has a window with focus - which will only find visible windows. Use FindWindowEx to find the window handle you want to close.
Next, when a window closes, it does not automatically try to exit the current threads message loop. You need to handle WM_DESTROY to call PostQuitMessage.
In your message loop use GetMessage rather than PeekMessage if you are not doing any other work as PeekMessage returns immediately if there are no messages meaning the application thread will never have an opportunity to sleep.
With these changes in place you should be fine to simply post a WM_CLOSE to the valid window handle as it will be destroyed, post itself a WM-QUIT message to exit the message loop, and terminate the calc process properly.
I have an MFC application and I want all pop-up generated by this application as dialog box or using AfxMessageBox should be positioned to some location given in config file.
Is there a way in MFC to set the default position for any pop-up window ?
Thanks in advance
Easily done with a window hook procedure.
Consult this SO post: Hooking window creation in an MFC program
Sample code:
static HHOOK g_myHook = NULL;
LRESULT CALLBACK MyCbtHook(int nCode, WPARAM wParam, LPARAM lParam)
{
switch (nCode)
{
case HCBT_ACTIVATE:
{
CWnd* wnd = CWnd::FromHandle((HWND)wParam);
WINDOWINFO wi;
wi.cbSize = sizeof(wi);
wnd->GetWindowInfo(&wi);
if ((wi.dwStyle & WS_POPUPWINDOW) == WS_POPUPWINDOW)
{
wnd->SetWindowPos(NULL, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOZORDER);
}
break;
}
}
return CallNextHookEx(0, nCode, wParam, lParam);
}
static void InstallHook()
{
g_myHook = SetWindowsHookEx(WH_CBT, MyCbtHook, 0, GetCurrentThreadId());
}
static void UninstallHook()
{
if (g_myHook)
{
UnhookWindowsHookEx(g_myHook);
g_myHook = NULL;
}
}
Call InstallHook in the InitInstance, then UninstallHook in the ExitInstance (not required really).
This sample hook procedure moves all popup window to the top left corner.
I have been experimenting with the WINAPI trying to learn it but the window I have created closes instantly. As you see when the W key is pressed or the left button is pressed it will close the program but when running it with no buttons being pressed it still closes.
#include <windows.h>
#include <windowsx.h>
// the WindowProc function prototype
LRESULT CALLBACK WindowProc(HWND hWnd,
UINT message,
WPARAM wParam,
LPARAM lParam);
// the entry point for any Windows program
int WINAPI WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow)
{
// the handle for the window, filled by a function
HWND hWnd;
// this struct holds information for the window class
WNDCLASSEX wc;
// clear out the window class for use
ZeroMemory(&wc, sizeof(WNDCLASSEX));
// fill in the struct with the needed information
wc.cbSize = sizeof(WNDCLASSEX);
wc.style = CS_HREDRAW | CS_VREDRAW;
wc.lpfnWndProc = WindowProc;
wc.hInstance = hInstance;
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH)COLOR_WINDOW;
wc.lpszClassName = L"WindowClass1";
// register the window class
RegisterClassEx(&wc);
// create the window and use the result as the handle
hWnd = CreateWindowEx(NULL,
L"WindowClass1", // name of the window class
L"Game", // title of the window
WS_OVERLAPPEDWINDOW, // window style
1, // x-position of the window
1, // y-position of the window
1800, // width of the window
1000, // height of the window
NULL, // we have no parent window, NULL
NULL, // we aren't using menus, NULL
hInstance, // application handle
NULL); // used with multiple windows, NULL
// display the window on the screen
ShowWindow(hWnd, nCmdShow);
// enter the main loop:
// this struct holds Windows event messages
MSG msg;
// wait for the next message in the queue, store the result in 'msg'
while (GetMessage(&msg, NULL, 0, 0))
{
// translate keystroke messages into the right format
TranslateMessage(&msg);
// send the message to the WindowProc function
DispatchMessage(&msg);
}
// return this part of the WM_QUIT message to Windows
return msg.wParam;
}
// this is the main message handler for the program
LRESULT CALLBACK WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
// sort through and find what code to run for the message given
switch (message)
{
// this message is read when the window is closed
case WM_MOUSEMOVE:
{
// Retrieve mouse screen position
int x = (short)LOWORD(lParam);
int y = (short)HIWORD(lParam);
// Check to see if the left button is held down:
bool leftButtonDown = wParam & MK_LBUTTON;
// Check if right button down:
bool rightButtonDown = wParam & MK_RBUTTON;
if (leftButtonDown == true)
{
//left click
//example lets close the program when press w
PostQuitMessage(0);
return 0;
}
}
case WM_KEYDOWN:
{
switch (wParam)
{
case 'W':
//w pressed
//example lets close the program when press w
PostQuitMessage(0);
return 0;
}
}
case WM_DESTROY:
{
// close the application entirely
PostQuitMessage(0);
return 0;
}
default:
break;
}
// Handle any messages the switch statement didn't
return DefWindowProc(hWnd, message, wParam, lParam);
}
You're missing some break statements in your switch, so for example, if you get the WM_MOUSEMOVE message and the leftButtonDown != true, execution will fall through to WM_KEYDOWN, etc.
Eventually you get to case WM_DESTROY:, which will Post you a lovely QuitMessage.
As an aside, this would be very easy to spot by stepping through, statement-by-statement, in a debugger.
There is no break in your switch statement.
You end up exetuting
PostQuitMessage(0);
You could do something like this:
case WM_FOO:
{
if ( bar ) {
return 0;
}
break;
}
Don't detect clicks via the WM_MOUSEMOVE message, use the WM_MOUSEDOWN instead.
The problem is that your code is probably launched by you clicking on something, so when your window gets its first WM_MOUSEMOVE message, the button is still actually pressed. Code runs much faster than fingers..