create `wxThread` to call backend function for every `EVT_TREELIST_ITEM_EXPANDED` event

荒凉一梦 提交于 2019-12-14 04:23:12

问题


I have following classes:

 BEGIN_EVENT_TABLE(MyFrame, wxFrame)
 EVT_TREELIST_ITEM_CHECKED(wxID_ANY, MyFrame::OnItemChecked)
 EVT_TREELIST_ITEM_EXPANDED(wxID_ANY, MyFrame::OnItemExpand)              
 END_EVENT_TABLE()

class MyThread: public wxThread
{
    public:
    MyThread(MyFrame *frame, wxTreeListItem &item);
    virtual void *Entry();
    SeleSyncFrame *m_frame;
    wxTreeListItem item;
};

class MyFrame
{
    friend class MyThread;
    private:
       wxTreeListCtrl* m_treelist;
    public:
       void OnItemExpand(wxTreeListEvent& event);
};

I have to update m_treelist on every EVT_TREELIST_ITEM_EXPANDED event. For that I am calling OnItemExpand().

void MyFrame::OnItemExpand(wxTreeListEvent& event)
{
    wxTreeListItem item = event.GetItem();
    MyThread *thread = new MyThread(this, item);
    if (thread->Create() != wxTHREAD_NO_ERROR)
    {
       dbg.Error(__FUNCTION__, "Can't create thread!");
    }
    thread->Run();
}   

constructor of MyThread class:

MyThread::MyThread(MyFrame *frame, wxTreeListItem &item) : wxThread()
{
    m_frame = frame;
    this->item = item;
}

Entry function of MyThread:

 wxThread::ExitCode MyThread::Entry()
{
    wxTreeListItem root = m_frame->m_treelist->GetRootItem();
    m_frame->m_treelist->CheckItem(root, wxCHK_CHECKED);

    //This back-end fun is time consuming
        Calltobackend(string resp);
        // I have to convert this string resp into xml and append all items of xml as children for 'item'.
    (m_frame->m_treelist)->AppendItem(item, "child");

    m_frame->m_treelist->CheckItem(item, wxCHK_CHECKED);
    m_frame->m_treelist->UpdateItemParentStateRecursively(m_frame->m_treelist->GetFirstChild(item));
    return NULL;
}

I want to create thread for every browse request and update corresponding item with its children. Is my approach is not correct? How should I achieve this? I was thinking of one more approach where I will use thread only to send request to backend and I will send response to Main thread using OnWorkerEvent. But I have to update item which is expanded with response returned by backend. How will that OnWorkerEvent will know which item from tree it has to update with children returned by response?


回答1:


As VZ said, updating GUI from a different thread is a can of worms. Don't do it.

For your issue. Let's say you have to update a control (in your case, items of a treelist) with values that come from a long task.
The idea is simple:

  • On your user event handler (like OnItemExpand) just create and run the thread. Don't wait for it, make it "detached".
  • In the thread code, just before it ends, post a message to the main thread by wxQueueEvent(). The value you need may be part of this message. Or you can also write an accesible var, better using wxMutex; and use the message to inform the main thread that that var is updated.
  • Write a new function (e.g. a MyFrame::OnMyThreadEnds) than handles the message and/or var. Here is where you update the GUI.

See http://docs.wxwidgets.org/trunk/classwx_thread.html




回答2:


You can only use GUI objects from one (usually main) thread of your application, so your approach simply can't work. It's also not clear at all why would you go to the trouble of creating a thread just for doing this, it's not like there are any time-consuming operations being done in the thread here.

The standard way to use threads in GUI applications is to perform any long-running tasks in background worker threads and post events to the main thread to perform the GUI updates. You should structure your application like this unless you have really good reasons not to do it.

In more details, the traditional way to do it is for the worker thread to post wxThreadEvents to the main thread, containing the information that the main thread needs to perform the action. Notice that wxThreadEvent has SetPayload() method which allows you to pass any kind of data between threads, so you just need to call it in the worker and then use GetPayload() in the main thread to extract the information and process it.

However since wxWidgets 3.0 you have another way to do it with CallAfter(), which is especially convenient if you use C++11 (and you really should). This allows you to write the code you want to execute in the scope of the thread function, but it will actually get executed in the context of the main thread. So you could do this:

wxThread::ExitCode MyThread::Entry()
{
    wxGetApp().CallAfter([this] {
        wxTreeListItem root = m_frame->m_treelist->GetRootItem();
        m_frame->m_treelist->CheckItem(root, wxCHK_CHECKED);
    });
    ...
}

and it would actually work because the code inside the lambda would be run in the main thread. This is extremely convenient and you should do it like this, but just make sure you actually understand what does this do and that it still uses the same underlying mechanism of posting events to do its magic.



来源:https://stackoverflow.com/questions/47894040/create-wxthread-to-call-backend-function-for-every-evt-treelist-item-expanded

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!