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).
我正在用C++开发一个使用wxWidget的小型桌面应用程序,它是关于可视化不同类型的排序的。为了在排序和绘制屏幕时保持图形用户界面的响应性,我试图在不同的线程上做一些工作,但我知道图形用户界面更新必须从主线程进行-所以我找到了一个教程,其中使用了CallAfter,以便从主线程更新图形用户界面。然而,我得到的结果是,表几乎立即被排序(通过工作线程),并显示在屏幕上,但图形用户界面冻结了一段时间,并进行了大量更新(屏幕上没有任何变化,因为表最早是在开始排序的)。
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.
我的问题是,我该如何解决这个问题?我猜整个绘制过程在排序上比每次比较花费的时间要长得多,但是为什么不在表最短的排序之后立即停止图形用户界面更新?我确实尝试了std::mutex,因为我认为同步可能会有帮助,因为_tag可能会在排序和绘制到屏幕时同时被更改和读取,但它没有帮助。
更多回答
优秀答案推荐
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.
我认为,如果您只需将CallAfter()中对Draw()的调用替换为Refresh()调用,并且只从wxEVT_PAINT处理程序实际绘制窗口,这是唯一真正受支持和可移植的方法,那么您的程序将工作得很好。
to make it look appealing you can use a condition_variable where the worker will wait
on it after every callafter
and the main thread will finish drawing then notify_one
on it so the worker will then continue sorting.
为了让它看起来更吸引人,你可以使用一个condition_variable,其中worker将在每个callafter之后等待它,并且主线程将完成绘制,然后通知它,以便worker将继续排序。
You will get exactly one frame for each modification, feel free to add an extra sleep in the worker till the result is appealing enough.
每一次修改你都会得到一帧,你可以自由地在工人身上增加一个额外的睡眠,直到结果足够吸引人为止。
更多回答
So I connected wxEVT_PAINT
to drawPanelOnPaint()
method (drawPanel->Connect( wxEVT_PAINT, wxPaintEventHandler( MyFrame1::drawPanelOnPaint ), NULL, this );
) and moved code to it from Draw()
method. At first, the result I got was that the GUI didn't freeze anymore, however, the drawPanel
became all black without any elements being painted on it. I tried Refresh(false)
(If true, the background will be erased.) and it worked, I'm not sure why but I'm glad it did.
因此,我将wxEVT_PAINT连接到DrawPanelOnPaint()方法(DrawPanel->Connect(wxEVT_Paint,wxPaintEventHandler(MyFrame1::DrawPanelOnPaint),NULL,This);),并将代码从DRAW()方法移到它。起初,我得到的结果是,图形用户界面不再冻结,然而,DrawPanel变得全黑,没有任何元素在上面绘制。我尝试了刷新(False)(如果为True,则背景将被擦除。)它奏效了,我不知道为什么,但我很高兴它奏效了。
One more thing - when there is no delay (std::this_thread::sleep_for
) between comparisons in bubble sort or it is smaller than 200ns the Refresh()
gets called only once resulting in immediately going from unsorted to sorted table. But that might be just because drawing the frame on the screen takes more than 200ns, right? Anyway, thank you a lot for helping me find the solution!
还有一件事-当冒泡排序中的比较之间没有延迟(STD::THITH_THREAD::SLEEP_FOR)或延迟小于200 ns时,只调用一次刷新(),导致立即从未排序的表转到已排序的表。但这可能只是因为在屏幕上绘制边框需要超过200 ns,对吗?无论如何,非常感谢你帮我找到了解决方案!
我是一名优秀的程序员,十分优秀!