我想在子菜单中创建COM端口列表,该菜单在每次查看子菜单时都会更新。
我的计划:
comDetected *COMsFound[MAX_COM_DETECT];
(工作中)Delete()
旧菜单项(有效)EVT_MENU_OPEN()
在AppendRadioItem()
上创建一个新菜单(工作)EVT_MENU()
对每个COM端口选择运行相同的功能如何在事件处理功能(从
wxCommandEvent?
)中确定哪个菜单选项导致了该事件?没有这些信息,我将需要32个单独的函数。有没有一种更动态的方式来创建对象和事件,以避免我创建的32个任意限制?
编辑-这是我现在用于菜单重新创建的功能,似乎可以正常工作:
重新编辑-效果不佳,如bogdan所述
void FiltgenFrame::OnMenuOpen(wxMenuEvent& event)
{
//fill in COM port menu when opened
if(event.GetMenu() == COMSubMenu)
{
int i;
wxString comhelp;
//re-scan ports
comport->getPorts();
if(comport->COMdetectChanged == 1)
{
comport->currentCOMselection = 0; //when menu is regenerated, selection returns to 0
//get rid of old menu entries
for(i = 0; i < comport->oldnumCOMsFound; i++)
{
COMSubMenu->Delete(FILTGEN_COM1 + i);
COMSubMenu->Unbind(wxEVT_MENU, [i](wxCommandEvent&)
{
logMsg(DBG_MENUS, ACT_NORMAL, "menu COM select index: %d\n", i);
}, FILTGEN_COM1 + i);
}
//add new menu entries
for(i = 0; i < comport->numCOMsFound; i++)
{
comhelp.Printf("Use %s", comport->COMsFound[i]->name);
COMSubMenu->AppendRadioItem(FILTGEN_COM1 + i, comport->COMsFound[i]->name, comhelp);
COMSubMenu->Bind(wxEVT_MENU, [i](wxCommandEvent&)
{
comport->currentCOMselection = i;
logMsg(DBG_MENUS, ACT_NORMAL, "menu COM select index: %d\n", i);
}, FILTGEN_COM1 + i);
}
}
}
}
编辑-重新编写代码1-29-15。由于与此问题无关的因素,将
OnMenuOpen
和recreateCOMmenu
分开。由于建议,添加了COMselectionHandler
。void FiltgenFrame::COMselectionHandler(wxCommandEvent& event)
{
comport->currentCOMselection = event.GetId() - FILTGEN_COM1;
logMsg(DBG_MENUS, ACT_NORMAL, "COM menu select index: %d\n", comport->currentCOMselection);
}
void FiltgenFrame::recreateCOMmenu()
{
logMsg(DBG_MENUS, ACT_NORMAL, "FiltgenFrame::recreateCOMmenu():\n");
int i;
wxString comhelp;
//re-scan ports
comport->getPorts();
if(comport->COMdetectChanged == 1)
{
comport->currentCOMselection = 0; //when menu is regenerated, selection returns to 0
//get rid of old menu entries
for(i = 0; i < comport->oldnumCOMsFound; i++)
{
COMSubMenu->Delete(FILTGEN_COM1 + i);
COMSubMenu->Unbind(wxEVT_MENU, &FiltgenFrame::COMselectionHandler, this, FILTGEN_COM1 + i);
}
//add new menu entries
for(i = 0; i < comport->numCOMsFound; i++)
{
comhelp.Printf("Use %s", comport->COMsFound[i]->name);
COMSubMenu->AppendRadioItem(FILTGEN_COM1 + i, comport->COMsFound[i]->name, comhelp);
COMSubMenu->Bind(wxEVT_MENU, &FiltgenFrame::COMselectionHandler, this, FILTGEN_COM1 + i);
}
}
}
void FiltgenFrame::OnMenuOpen(wxMenuEvent& event)
{
//fill in COM port menu when opened
if(event.GetMenu() == COMSubMenu)
{
recreateCOMmenu();
}
}
最佳答案
因为动态似乎是这里的关键词,所以我会去进行动态事件处理(实际上,我总是使用Bind
来进行动态事件处理,它比其他方法要好得多):
auto pm = new wxMenu(); //I suppose you're adding this to an existing menu.
std::wstring port_str = L"COM";
int id_base = 77; //However you want to set up the IDs of the menu entries.
for(int port_num = 1; port_num <= 32; ++port_num)
{
int id = id_base + port_num;
pm->AppendRadioItem(id, port_str + std::to_wstring(port_num));
pm->Bind(wxEVT_MENU, [port_num](wxCommandEvent&)
{
//Do something with the current port_num; for example:
wxMessageBox(std::to_wstring(port_num));
//You can also capture id if you prefer, of course.
}, id);
}
在lambda表达式中,我们按值捕获端口号,因此,对于每次迭代,将捕获当前的
port_num
。这恰好实现了您所要求的:与每个菜单项关联的相同函数(lambda的闭包类型的operator())。该函数知道被调用的条目,因为它可以访问捕获的port_num
值,该值存储在lambda的闭包对象中-一个小对象,在这种情况下,很可能是一个int
的大小。为了避免对对象数量进行固定限制,您只需将它们存储在
std::vector
中。如果您希望 vector 拥有对象(在销毁 vector 时将其自动销毁),则可以将它们直接存储在std::vector<comDetected>
中。如果其他人拥有这些对象并负责分别销毁它们,则可以使用std::vector<comDetected*>
。更新:编写第一个解决方案时,我没有意识到您会想要
Unbind
并重新绑定(bind)这些事件处理程序;很明显,事后看来,但是,无论如何,我的错,对不起。这就是问题所在:据我所知,没有一个简单的方法来
Unbind
一个lambda函数对象,该对象直接传递给Bind
,如我在示例中所做的那样。在更新的代码中执行操作时,仅调用Unbind
是行不通的,因为Unbind
会尝试查找事件处理程序,该事件处理程序是通过使用完全相同的参数对Bind
进行相应调用而安装的。不会因为下一节中说明的原因而发生这种情况(还有它为何起作用的解释),但是您可能对解决方案更感兴趣,所以我将从这些开始。解决方案1 (您情况下最好的):放弃使用lambda;只需使用自由函数或成员函数指针即可。在这种情况下,您需要从
evt.GetId()
获取菜单条目ID,并从中获取端口索引;像这样的东西:void handler_func(wxCommandEvent& evt)
{
int i = evt.GetId() - FILTGEN_COM1;
comport->currentCOMselection = i;
logMsg(DBG_MENUS, ACT_NORMAL, "menu COM select index: %d\n", i);
}
然后,您的代码将如下所示:
void FiltgenFrame::OnMenuOpen(wxMenuEvent& event)
{
/* ... */
COMSubMenu->Unbind(wxEVT_MENU, handler_func, FILTGEN_COM1 + i);
/* ... */
COMSubMenu->Bind(wxEVT_MENU, handler_func, FILTGEN_COM1 + i);
/* ... */
}
上面的示例使用了免费功能。您还可以使用成员函数-more info here。
解决方案2 :如果您可以在
EVT_MENU_OPEN()
之外的其他时间重建菜单,则可以销毁整个wxMenu
并重建并将其插入到正确位置的父菜单中。销毁旧的菜单对象将处理与之绑定(bind)的所有动态事件处理程序,因此您不需要Unbind
它们。但是,在显示菜单之前销毁菜单听起来不是一个好主意-我还没有尝试过,但是据我所知,它是行不通的,或者是以高度依赖平台的方式运行的。这是
Unbind
无法直接与lambdas配合使用的原因:Unbind
根据安装的处理程序的类型检查functor参数的类型,因此它将永远不会找到匹配项。 Unbind
的函数对象还需要与传递给相应Bind
的函数对象具有相同的地址。将lambda表达式直接传递给Bind
时生成的对象是临时对象(通常将其分配在堆栈上),因此,在函数调用之间对其地址进行任何假设都是不正确的。 我们可以解决上述两个问题(将闭包对象分别存储在某个地方,依此类推),但是我认为任何这样的解决方案都非常麻烦,不值得考虑-它会否决基于lambda的解决方案的所有优点。
这就是为什么它似乎可以在您的代码中起作用的原因:
如果
Unbind
找不到要删除的事件处理程序,则仅返回false
;所有现有的处理程序都保留在那里。稍后,Bind
在事件处理程序列表的开头添加了一个用于相同事件类型和相同条目ID的新处理程序,因此首先调用较新的处理程序。除非处理程序在返回之前调用evt.Skip()
,否则该事件将在处理程序返回后视为已处理,并且不会调用任何其他处理程序。即使它可以按您期望的那样工作,但每次重新构建菜单时,让所有那些旧的未使用的处理程序累积在列表中并不是一个好主意。
关于c++ - 如何动态地重新创建带有可变数量的项目的wxMenu(子菜单)?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/28183885/