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

我的计划:

  • 创建一个对象列表,其中包含有关每个检测到的端口的数据,最多32个对象指针。示例: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。由于与此问题无关的因素,将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();
        }
    }
    

    最佳答案

    因为动态似乎是这里的关键词,所以我会去进行动态事件处理(实际上,我总是使用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配合使用的原因:
  • 由lambda表达式生成的对象具有唯一的类型。即使您将完全相同的lambda表达式复制粘贴到代码中的另一个位置,第二个lambda也会生成与原始lambda生成的闭包对象类型不同的闭包对象。由于Unbind根据安装的处理程序的类型检查functor参数的类型,因此它将永远不会找到匹配项。
  • 即使我们解决了上面的问题,也存在另一个问题:传递给Unbind的函数对象还需要与传递给相应Bind的函数对象具有相同的地址。将lambda表达式直接传递给Bind时生成的对象是临时对象(通常将其分配在堆栈上),因此,在函数调用之间对其地址进行任何假设都是不正确的。

  • 我们可以解决上述两个问题(将闭包对象分别存储在某个地方,依此类推),但是我认为任何这样的解决方案都非常麻烦,不值得考虑-它会否决基于lambda的解决方案的所有优点。

    这就是为什么它似乎可以在您的代码中起作用的原因:

    如果Unbind找不到要删除的事件处理程序,则仅返回false;所有现有的处理程序都保留在那里。稍后,Bind在事件处理程序列表的开头添加了一个用于相同事件类型和相同条目ID的新处理程序,因此首先调用较新的处理程序。除非处理程序在返回之前调用evt.Skip(),否则该事件将在处理程序返回后视为已处理,并且不会调用任何其他处理程序。

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

    关于c++ - 如何动态地重新创建带有可变数量的项目的wxMenu(子菜单)?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/28183885/

    10-11 23:21
    查看更多