如何使用可变数量的项目动态重新创建wxMenu(子菜单)?

时间:2015-01-28 03:01:04

标签: c++ wxwidgets

我想在子菜单中创建一个COM端口列表,每次查看子菜单时都会更新。

我的计划:

  1. 创建一个对象列表,其中包含有关每个检测到的端口的数据,最多32个对象指针。示例:comDetected *COMsFound[MAX_COM_DETECT];(正在工作)
  2. Delete()旧菜单条目(正常工作)
  3. 使用EVT_MENU_OPEN()(正常工作)
  4. AppendRadioItem()上创建新菜单
  5. 使用EVT_MENU()为每个COM端口选择运行相同的功能
  6. 如何在事件处理函数中确定(来自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。由于与此问题无关的因素,请将OnMenuOpenrecreateCOMmenu分开。由于建议而添加了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();      
        }
    }
    

1 个答案:

答案 0 :(得分:3)

由于 dynamic 似乎是关键词,我会选择动态事件处理(实际上,我总是使用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())。该函数知道调用它的条目,因为它可以访问存储在lambda的闭包对象中的捕获的port_num值 - 一个小对象,很可能是一个int的大小在这种情况下。


为了避免对象数量的固定限制,您只需将它们存储在std::vector中即可。如果您希望向量拥有对象(在向量被销毁时自动销毁它们),则可以将它们直接存储在std::vector<comDetected>中。如果其他东西拥有对象并负责单独销毁它们,则可以使用std::vector<comDetected*>


更新:在编写我的第一个解决方案时,我没有意识到你想要Unbind并重新绑定那些事件处理程序;事后很明显,真的,但......无论如何,我的错误,抱歉。

问题在于:就我所知,Unbind没有直接传递给Bind的lambda函数对象的方法,正如我所做的那样我的例子。只需在您更新的代码中调用Unbind即可开始工作,因为Unbind将尝试查找通过对{的相应调用安装的事件处理程序{1}}与完全相同的参数。由于下一节中解释的原因而不会发生这种情况(这也解释了为什么它似乎有效),但你可能对解决方案更感兴趣,所以我将从那些开始

解决方案1 ​​(在您的情况下最好的一个):放弃使用lambdas;只需使用自由函数或成员函数指针。在这种情况下,您需要从Bind获取菜单条目ID并从中获取端口索引;像这样的东西:

evt.GetId()

然后,您的代码将如下所示:

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); 
}

上面的例子使用的是免费功能。您还可以使用成员函数 - 更多信息here

解决方案2 :如果您可以在void FiltgenFrame::OnMenuOpen(wxMenuEvent& event) { /* ... */ COMSubMenu->Unbind(wxEVT_MENU, handler_func, FILTGEN_COM1 + i); /* ... */ COMSubMenu->Bind(wxEVT_MENU, handler_func, FILTGEN_COM1 + i); /* ... */ } 以外的其他时间重建该菜单,则可以破坏整个EVT_MENU_OPEN()并重建并将其插入其中父菜单在正确的地方。销毁旧的菜单对象将处理绑定到它的所有动态事件处理程序,因此您不需要wxMenu它们。然而,在它显示之前销毁菜单听起来并不是一个好主意 - 我还没有尝试过,但据我所知,它不会起作用,或者表现得很好以高度依赖平台的方式。


以下是Unbind直接与lambdas合作的原因:

  1. lambda表达式生成的对象具有唯一类型。即使将完全相同的lambda表达式复制粘贴到代码中的另一个位置,第二个lambda也会生成一个与原始lambda生成的类型不同的闭包对象。由于Unbind根据已安装的处理程序的类型检查functor参数的类型,因此它永远不会找到匹配项。
  2. 即使我们解决了上述问题,还有另一个问题:传递给Unbind的函数对象还需要与传递给它的那个相同的地址相应的Unbind。将lambda表达式直接传递给Bind时生成的对象是临时的(通常会在堆栈上分配),因此在函数调用中对其地址进行任何假设都是不正确的。
  3. 我们可以解决上面的两个问题(在某处单独存储闭包对象等等),但我认为任何这样的解决方案都太麻烦而不值得考虑 - 它将否定基于lambda的解决方案的所有优点


    这就是为什么它似乎适用于您的代码:

    如果Bind找不到要删除的事件处理程序,则只返回Unbind;所有现有的处理程序都留在那里。稍后,false在事件处理程序列表的前面为相同的事件类型和相同的条目ID添加新的处理程序,因此首先调用更新的处理程序。除非处理程序在返回之前调用Bind,否则在处理程序返回并且没有其他处理程序被调用之后,事件被视为已处理。

    即使它按预期工作,但每次重建菜单时让所有那些旧的未使用的处理程序在列表中累积并不是一个好主意。