Search code examples
c++multithreadingwxwidgets

GUI updates from another thread using CallAfter in wxWidgets


I am working on a small Desktop Application in C++ using wxWidgets, it is about visualizing different types of sorting. In order to keep GUI responsive while the sorting is taking place and being drawn on the screen I am trying to do some work on a different thread, but I know that GUI updates must take place from the main thread - so I found a tutorial in which CallAfter was used so that GUI was updated from the main thread. However, the result I am getting is that the table is being sorted almost immediately (by worker thread) and gets displayed on screen but the GUI freezes for some time and gets updated a lot (nothing changes on the screen as the table gest sorted at the beginning).

Some of the code Im talking about:

#include "GUIMyFrame1.h"

GUIMyFrame1::GUIMyFrame1(wxWindow* parent)
    :
    MyFrame1(parent), _maxElemValue{ 25 }
{
    UpdateTabSize();
    Draw();
}

void GUIMyFrame1::drawPanelOnSize(wxSizeEvent& event)
{
    Draw();

}

void GUIMyFrame1::m_slider_Num_of_ElemOnScroll(wxScrollEvent& event)
{
    _maxElemValue = _tab.size();
    UpdateTabSize();
    Draw();

}

void GUIMyFrame1::m_button_SortOnButtonClick(wxCommandEvent& event)
{
    auto f = [this]() {
        for (int i = 0; i < _tab.size() - 1; i++)
            for (int j = 0; j < _tab.size() - i - 1; j++) {

                wxGetApp().CallAfter([this, j] {
                    Draw();
                    });

                if (_tab[j + 1] < _tab[j])
                    std::swap(_tab[j + 1], _tab[j]);
            }
        wxGetApp().CallAfter([this] {
            Draw();
        });
    };

    std::thread worker(f);

    worker.detach();
}

void GUIMyFrame1::m_button_ShuffleOnButtonClick(wxCommandEvent& event)
{
    UpdateTabSize();
    Draw();
}

void GUIMyFrame1::Draw()
{
    wxClientDC dc1(drawPanel);
    wxBufferedDC dc(&dc1);

    dc.Clear();

    int w, h;

    drawPanel->GetSize(&w, &h);

    int tempWidth = w / _tab.size();
    double unitHeight = (double)h / _maxElemValue;

    int shift = GetShift(w);

    for (int i = 0; i < _tab.size(); i++) {
        wxBrush tempBrush(_tab[i]._color);
        wxPen tempPen(_tab[i]._color, 0);

        dc.SetBrush(tempBrush);
        dc.SetPen(tempPen);

        dc.DrawRectangle(
            wxRect(
                shift + i * tempWidth, h - (int)(unitHeight * _tab[i]._value) + 2,
                tempWidth, (int)(unitHeight * _tab[i]._value) - 4
            ));
    }
}

void GUIMyFrame1::UpdateTabSize()
{
    _tab.clear();
    for (int i = 0; i < m_slider_Num_of_Elem->GetValue(); i++)
        _tab.push_back(getNewRandomElement(_maxElemValue));
}

int GUIMyFrame1::GetShift(int w) {
    int AllElemWidth = _tab.size() * int(w / _tab.size());
    return (w - AllElemWidth) / 2;
}

My question is how do I fix that? I guess the whole drawing process is taking much longer than every comparison in sorting but why don't the GUI updates stop as soon as the table gest sorted? I did try std::mutex as I thought the synchronization might help, because _tab might be changed and read at the same time while sorting and drawing to screen but it didn't help.


Solution

  • I think your program would work just fine if you simply replace calls to Draw() inside CallAfter() with the calls to Refresh() -- and only actually draw your window from your wxEVT_PAINT handler which is the only really supported and portable way to do it.