PopupMenu parent and events

If you are using the main C++ distribution of wxWidgets, Feel free to ask any question related to wxWidgets development here. This means questions regarding to C++ and wxWidgets, not compile problems.
Natulux
Filthy Rich wx Solver
Filthy Rich wx Solver
Posts: 242
Joined: Thu Aug 03, 2017 12:20 pm

PopupMenu parent and events

Post by Natulux »

Hey guys

I used to have a context menu attached to a wxTaskBarIcon, working as one would expect a context menu to work. Now I wanted to call a context menu on demand. My App does not always show an active GUI, btw.

If I use

Code: Select all

PopupMenu(m_taskBarMenu);
in just any class, nothing happens.

If I use it in a class which creates a visible GUI (frames etc.) and show that GUI, I can create that context menu, but it won't disappear when clicking somewhere else (focus lost) or hitting esc.

I then used wxPopupTransientWindow. It does react to ESC and closes on focus change automatically.
If I create an instance of this class and call the PopupMenu afterwards, it would work. But kinda weird.
Closes on click and esc alright. Menu Events go the class, which calls the code, but the popup needs a parent window and it just takes the window created before? Thats confusing.

Code: Select all

wxPopupTransientWindow *ptw = new wxPopupTransientWindow(this);
ptw->Show();
PopupMenu(m_taskBarMenu);
//ptw->PopupMenu(m_taskBarMenu); // Or like this, doesn't matter
In short: Why does the context menu work, if I create an instance of a GUI just before it, even if that GUI itself has no functionallity and is not connected to the context menu or the menu itself in any other way? (Except that it has the same parent - the calling class - I guess)

Cheers Natu
User avatar
doublemax
Moderator
Moderator
Posts: 19164
Joined: Fri Apr 21, 2006 8:03 pm
Location: $FCE2

Re: PopupMenu parent and events

Post by doublemax »

Now I wanted to call a context menu on demand. My App does not always show an active GUI,
What does "on demand" mean here? If you have no gui, how is showing of the popup initiated? And if it's in response to a click on the taskbar icon, how is it different from the "working as one would expect a context menu to work."?
PopupMenu(m_taskBarMenu);
I assume the context here is a wxTaskBarIcon ?

I don't know if it makes any difference (it shouldn't), but i create all popup menus on the stack when i need them. I don't create them once and re-use them.
Use the source, Luke!
Natulux
Filthy Rich wx Solver
Filthy Rich wx Solver
Posts: 242
Joined: Thu Aug 03, 2017 12:20 pm

Re: PopupMenu parent and events

Post by Natulux »

doublemax wrote: Thu Feb 28, 2019 9:59 am What does "on demand" mean here? If you have no gui, how is showing of the popup initiated? And if it's in response to a click on the taskbar icon, how is it different from the "working as one would expect a context menu to work."?
I created a second program to watch over the main process. If the main process crashes, it is restarted by its new "parent process". And this parent process is telling its child, that there has been an event to trigger the PopupMenu (via socket connection). I gave the taskBarIcon the parent, because I want to be able to control this process (eg end it) even if there is no child process or no response (due to an error or something).
That is why the icon and the PopupMenu are devided.

So, "on demand" means: When an appropriate event is called.
doublemax wrote: Thu Feb 28, 2019 9:59 amI assume the context here is a wxTaskBarIcon ?
It was before, yes.
doublemax wrote: Thu Feb 28, 2019 9:59 amI don't know if it makes any difference (it shouldn't), but i create all popup menus on the stack when i need them. I don't create them once and re-use them.
I first thought I could reuse them. But I am actually creating them on the heap and delete them after one time use. I could as well create them on the stack as well.
But it would surprise me if that would make a difference to calling the PopupMenu. ;-)

Cheers Natu
User avatar
doublemax
Moderator
Moderator
Posts: 19164
Joined: Fri Apr 21, 2006 8:03 pm
Location: $FCE2

Re: PopupMenu parent and events

Post by doublemax »

I think instead of showing a popup, you're looking for wxNotificationMessage
https://docs.wxwidgets.org/trunk/classw ... ssage.html
Under Windows this will show a ballon tooltip over the taskbar icon.

Check the dialogs sample -> dialog -> user notifications to see them in action
Use the source, Luke!
Natulux
Filthy Rich wx Solver
Filthy Rich wx Solver
Posts: 242
Joined: Thu Aug 03, 2017 12:20 pm

Re: PopupMenu parent and events

Post by Natulux »

Oh no, I was really talking about a context popup menu.
Parent process with a taskbar icon - gets click event - sends request to child process - child process opens a context menu, smiliar to a wxTaskBarIcon-menu, which holds options to configure the app and start actions.

Well, regardless, I think that a context menu really needs an active/shown parent object, for which the context menu is suited - the popup context menu was thought to be for a task icon only, because it's the only case where you click something else than a visible object and still get a menu, suited for the whole app.
I still can raise a popup menu with an event or a hotkey, but it needs a frame it can hold on to (else it has no "context" ;-) ).

Technically, I dont understand why that is, but I can accept it.

Looks like this now:

Code: Select all

wxMenu contextMenu;
//add menu stuff
wxPopupTransientWindow ptw(this);
ptw.Show();
PopupMenu(&contextMenu);
Cheers Natu
Natulux
Filthy Rich wx Solver
Filthy Rich wx Solver
Posts: 242
Joined: Thu Aug 03, 2017 12:20 pm

Re: PopupMenu parent and events

Post by Natulux »

Hey again,

when I created this article I thought it worked - now I discovered, that every few attempts it would fail.
I derived a minimal class from wxPopupTransientWindow to log the events:

Code: Select all

wxBEGIN_EVENT_TABLE(TransientPopupWindow, wxPopupTransientWindow)
EVT_SET_FOCUS(TransientPopupWindow::OnSetFocus)
EVT_KILL_FOCUS(TransientPopupWindow::OnKillFocus)
wxEND_EVENT_TABLE()

TransientPopupWindow::TransientPopupWindow(wxWindow *parent)
	:wxPopupTransientWindow(parent)
{
	m_panel = new wxPanel(this, wxID_ANY);
}

TransientPopupWindow::~TransientPopupWindow()
{
}

void TransientPopupWindow::Popup(wxWindow* WXUNUSED(focus))
{
	Log::DoLog("TransientPopupWindow::Popup");
	wxPopupTransientWindow::Popup();
}

bool TransientPopupWindow::Show(bool show)
{
	Log::DoLog("TransientPopupWindow::Show");
	return wxPopupTransientWindow::Show(show);
}

void TransientPopupWindow::OnSetFocus(wxFocusEvent &event)
{
	Log::DoLog("TransientPopupWindow::OnSetFocus");
	event.Skip();
}

void TransientPopupWindow::OnKillFocus(wxFocusEvent &event)
{
	Log::DoLog("TransientPopupWindow::OnKillFocus");
	event.Skip();
}
I call it locally on the stack:

Code: Select all

TransientPopupWindow tpw(this);
if (!tpw.Show()) Log::DoLog("show returned false");
if (!tpw.PopupMenu(&contextMenu)) Log::DoLog("PopupMenu returned false");
tpw.SetFocus();
tpw.SetFocusFromKbd();
CanBeFocused() and AcceptsFocus() are always true, if thats means anything.

And this is my log output:
2019-03-04T13:23:29 - TransientPopupWindow::Show
2019-03-04T13:23:29 - TransientPopupWindow::OnSetFocus
2019-03-04T13:23:30 - TransientPopupWindow::Show
2019-03-04T13:23:41 - TransientPopupWindow::Show
2019-03-04T13:23:41 - TransientPopupWindow::OnSetFocus
2019-03-04T13:23:43 - TransientPopupWindow::Show
2019-03-04T13:23:43 - TransientPopupWindow::OnSetFocus
2019-03-04T13:23:45 - TransientPopupWindow::Show
2019-03-04T13:23:45 - TransientPopupWindow::OnSetFocus
2019-03-04T13:23:46 - TransientPopupWindow::Show
2019-03-04T13:26:13 - TransientPopupWindow::Show
2019-03-04T13:26:14 - TransientPopupWindow::OnSetFocus
2019-03-04T13:26:16 - TransientPopupWindow::Show
2019-03-04T13:28:42 - TransientPopupWindow::Show
2019-03-04T13:28:42 - TransientPopupWindow::OnSetFocus
2019-03-04T13:28:43 - TransientPopupWindow::Show
2019-03-04T13:30:12 - TransientPopupWindow::Show
2019-03-04T13:30:12 - TransientPopupWindow::OnSetFocus
2019-03-04T13:30:13 - TransientPopupWindow::Show
2019-03-04T13:30:25 - TransientPopupWindow::Show
2019-03-04T13:30:25 - TransientPopupWindow::OnSetFocus
2019-03-04T13:30:26 - TransientPopupWindow::Show
2019-03-04T13:30:26 - TransientPopupWindow::OnSetFocus
2019-03-04T13:30:27 - TransientPopupWindow::Show
2019-03-04T13:30:27 - TransientPopupWindow::OnSetFocus
You can see, that sometimes it gets no focus, but is shown. When it gets no focus, it cant be destroyed by changing the focus (click onto something else).

Any idea why it might not get the focus? I gave the window focus and keyboard focus manually, but that doesn't change anything.

Cheers Natu
ONEEYEMAN
Part Of The Furniture
Part Of The Furniture
Posts: 7481
Joined: Sat Apr 16, 2005 7:22 am
Location: USA, Ukraine

Re: PopupMenu parent and events

Post by ONEEYEMAN »

Hi,
Is there any controls on this window? Maybe the first control receives the focus?
Do you handle any focus events in your program?

Thank you.
Natulux
Filthy Rich wx Solver
Filthy Rich wx Solver
Posts: 242
Joined: Thu Aug 03, 2017 12:20 pm

Re: PopupMenu parent and events

Post by Natulux »

No, the parent class is derived from a wxFrame, just to be able to give a parent pointer to the TransientPopupWindow. I don't even show it.
The TransientPopupWindow is exactly as I posted it, it contains a wxPanel (I kept it from the sample, because I know it can capture events that a bare frame can not). I took it out for a test and the behaviour is the same. It works a few times and then skips one.

Cheers Natu

EDIT:

I tried a different thing now:

Code: Select all

TransientPopupWindow tpw(this);
tpw.Show();
do
{
	tpw.SetFocus();
	Sleep(10);
} while (!tpw.HasFocus());
tpw.PopupMenu(&contextMenu);
This proves to be an infinite loop in the error case. Strange, it just isn't able to accept the focus at all.

EDIT2:

Ugliest workarround:

Code: Select all

TransientPopupWindow tpw(this);
tpw.Show();
while (!tpw.HasFocus())
{
	tpw.Show(false);
	tpw.Show();
}
tpw.PopupMenu(&contextMenu);
Total overkill, but it works, even though a little sluggish. Do I really have to do this? :-/
2019-03-05T08:36:33 - TransientPopupWindow::Show
2019-03-05T08:36:33 - TransientPopupWindow::OnSetFocus
2019-03-05T08:36:33 - TransientPopupWindow::Show
2019-03-05T08:36:33 - TransientPopupWindow::OnSetFocus
2019-03-05T08:36:34 - TransientPopupWindow::Show
2019-03-05T08:36:34 - TransientPopupWindow::OnSetFocus
2019-03-05T08:36:35 - TransientPopupWindow::Show
2019-03-05T08:36:35 - TransientPopupWindow::OnSetFocus
2019-03-05T08:36:36 - TransientPopupWindow::Show
2019-03-05T08:36:36 - TransientPopupWindow::OnSetFocus
2019-03-05T08:36:36 - TransientPopupWindow::Show
2019-03-05T08:36:36 - TransientPopupWindow::OnSetFocus
2019-03-05T08:36:37 - TransientPopupWindow::Show
2019-03-05T08:36:37 - TransientPopupWindow::OnSetFocus
2019-03-05T08:36:38 - TransientPopupWindow::Show
2019-03-05T08:36:38 - TransientPopupWindow::Show
2019-03-05T08:36:38 - TransientPopupWindow::Show
2019-03-05T08:36:38 - TransientPopupWindow::Show
2019-03-05T08:36:38 - TransientPopupWindow::Show
2019-03-05T08:36:38 - TransientPopupWindow::OnSetFocus
2019-03-05T08:36:41 - TransientPopupWindow::Show
2019-03-05T08:36:41 - TransientPopupWindow::OnSetFocus
2019-03-05T08:36:41 - TransientPopupWindow::Show
2019-03-05T08:36:41 - TransientPopupWindow::Show
2019-03-05T08:36:41 - TransientPopupWindow::Show
2019-03-05T08:36:41 - TransientPopupWindow::Show
2019-03-05T08:36:41 - TransientPopupWindow::Show
2019-03-05T08:36:41 - TransientPopupWindow::Show
2019-03-05T08:36:41 - TransientPopupWindow::Show
2019-03-05T08:36:41 - TransientPopupWindow::Show
2019-03-05T08:36:41 - TransientPopupWindow::Show
2019-03-05T08:36:41 - TransientPopupWindow::Show
2019-03-05T08:36:41 - TransientPopupWindow::Show
2019-03-05T08:36:41 - TransientPopupWindow::OnSetFocus
2019-03-05T08:36:42 - TransientPopupWindow::Show
2019-03-05T08:36:42 - TransientPopupWindow::Show
2019-03-05T08:36:42 - TransientPopupWindow::Show
2019-03-05T08:36:42 - TransientPopupWindow::Show
2019-03-05T08:36:42 - TransientPopupWindow::Show
2019-03-05T08:36:42 - TransientPopupWindow::Show
2019-03-05T08:36:42 - TransientPopupWindow::Show
2019-03-05T08:36:42 - TransientPopupWindow::Show
2019-03-05T08:36:42 - TransientPopupWindow::Show
2019-03-05T08:36:42 - TransientPopupWindow::Show
2019-03-05T08:36:42 - TransientPopupWindow::Show
2019-03-05T08:36:42 - TransientPopupWindow::Show
2019-03-05T08:36:42 - TransientPopupWindow::Show
2019-03-05T08:36:42 - TransientPopupWindow::Show
2019-03-05T08:36:42 - TransientPopupWindow::Show
2019-03-05T08:36:42 - TransientPopupWindow::Show
2019-03-05T08:36:42 - TransientPopupWindow::Show
2019-03-05T08:36:42 - TransientPopupWindow::Show
2019-03-05T08:36:42 - TransientPopupWindow::Show
2019-03-05T08:36:42 - TransientPopupWindow::Show
2019-03-05T08:36:42 - TransientPopupWindow::Show
2019-03-05T08:36:42 - TransientPopupWindow::Show
2019-03-05T08:36:42 - TransientPopupWindow::Show
2019-03-05T08:36:42 - TransientPopupWindow::Show
2019-03-05T08:36:42 - TransientPopupWindow::Show
2019-03-05T08:36:42 - TransientPopupWindow::Show
2019-03-05T08:36:42 - TransientPopupWindow::Show
2019-03-05T08:36:42 - TransientPopupWindow::OnSetFocus
User avatar
doublemax
Moderator
Moderator
Posts: 19164
Joined: Fri Apr 21, 2006 8:03 pm
Location: $FCE2

Re: PopupMenu parent and events

Post by doublemax »

I'd be more interested in the initial case (without any workarounds). But i won't have time to take a closer look into this before the end of the week.
Use the source, Luke!
Natulux
Filthy Rich wx Solver
Filthy Rich wx Solver
Posts: 242
Joined: Thu Aug 03, 2017 12:20 pm

Re: PopupMenu parent and events

Post by Natulux »

doublemax wrote: Tue Mar 05, 2019 9:08 am I'd be more interested in the initial case (without any workarounds). But i won't have time to take a closer look into this before the end of the week.
I was about to state that this should be reproducible with the code I provided and using a wxKeyEvent, but when I tested this, I actually couldn't reproduce the problem myself.

//This actually works

Code: Select all

void MyFrame::OnCharHook(wxKeyEvent& event)
{
	if (event.GetKeyCode() == WXK_F5)
	{
		m_taskEventHandler.ShowContextMenu();
	}
}
//This gets stuck every third time approximately, when using a click event (focus change) to exit

Code: Select all

void TaskEventHandler::OnRightButtonUp(wxCommandEvent&)
{	
	if (BusyMessage::IsNotBusy())
	{
		ShowContextMenu();		
	}
}
It is not as easy to describe the differences though. The charhook is exactly what you see, but the right click event gets a little messy:

Using a:

Code: Select all

wxDEFINE_EVENT(HttpEvent, wxCommandEvent);
The event comes from

Code: Select all

wxBEGIN_EVENT_TABLE(TaskEventHandler, wxFrame)
EVT_COMMAND(ID_TASK_RIGHT_UP, HttpEvent, TaskEventHandler::OnRightButtonUp)
wxEND_EVENT_TABLE()
and is generated by:

Code: Select all

void TaskEventHandler::GenerateHttpEvent(int nEventID)
{
	wxCommandEvent * evt = new wxCommandEvent(HttpEvent, nEventID);
	::wxQueueEvent(this, evt);
}
which is called when receiving an appropriate Http request:

Code: Select all

void MyFrame::OnSocketEvent(wxSocketEvent& event)
{
	wxSocketBase *sock = event.GetSocket();	
	switch (event.GetSocketEvent())
	{
	case wxSOCKET_INPUT:
	{
		unsigned char c;
		sock->Read(&c, 1);

		//[...]
		
		if (resource.Upper().StartsWith("ICONRIGHT"))
		{
			HttpRespond(sock, 200);
			m_taskEventHandler.GenerateHttpEvent(ID_TASK_RIGHT_UP);
		}
		//[...]
	}
}
Sorry, this looks like trouble :-/

Any further hints are welcome though :-)
Cheers Natu
User avatar
doublemax
Moderator
Moderator
Posts: 19164
Joined: Fri Apr 21, 2006 8:03 pm
Location: $FCE2

Re: PopupMenu parent and events

Post by doublemax »

It's a wild guess, but i would try to use CallAfter() to call ShowContextMenu() instead of from inside another event handler.

https://docs.wxwidgets.org/trunk/classw ... 50b3719519
Use the source, Luke!
Natulux
Filthy Rich wx Solver
Filthy Rich wx Solver
Posts: 242
Joined: Thu Aug 03, 2017 12:20 pm

Re: PopupMenu parent and events

Post by Natulux »

doublemax wrote: Tue Mar 05, 2019 3:05 pm It's a wild guess, but i would try to use CallAfter() to call ShowContextMenu() instead of from inside another event handler.

https://docs.wxwidgets.org/trunk/classw ... 50b3719519
I used CallAfter in Http-event-handler

Code: Select all

if (resource.Upper().StartsWith("ICONRIGHT"))
{
	HttpRespond(sock, 200);
	m_taskEventHandler.CallAfter(&TaskEventHandler::GenerateHttpEvent,ID_TASK_RIGHT_UP);
}
and again in the generated event:

Code: Select all

void TaskEventHandler::OnRightButtonUp(wxCommandEvent&)
{	
	if (BusyMessage::IsNotBusy())
	{
		CallAfter(&TaskEventHandler::ShowContextMenu);
	}
}
No visible changes though.
Thanks for wild guessing anyway ;-)

Cheers Natu