使用GTKMM,我扩展了 DrawingArea 小部件,其思想是外部进程为其提供图像。然后,我的 CameraDrawingArea 将使用 Cairo 以正确的尺寸显示图像。
每次图像到达时,我将其存储并调用invalidate
方法,该方法最终会导致对on_draw
的调用,在此我可以调整大小并显示图像。
我的问题如下:
invalidate
on_draw
。 为了在这里展示它,我简化了代码,以便在类外部没有任何内容,并且没有与其他库的链接。我已经用带有for循环的方法替换了提供图像的过程,并通过在小部件区域的中间打印一个简单的文本来显示图像:
std::thread
以在同一实例中调用doCapture
方法。我还设置了字体描述,以备后用。doCapture
方法是一个愚蠢的CPU吞噬者,只要refreshDrawing
不是keepCapturing
,除了不时调用false
方法外,它什么都不做。 / li>
refreshDrawing
通过调用invalidate
使整个窗口的矩形无效。on_draw
并提供一个 Cairo 上下文来绘制任何内容。就我而言,出于测试目的,我绘制了一个褐色居中的整数。keepCapturing
设置为false来停止线程,并等待以join
终止。 #include "camera-drawing-area.hpp"
#include <iostream>
CameraDrawingArea::CameraDrawingArea():
captureThread(nullptr) {
fontDescription.set_family("Monospace");
fontDescription.set_weight(Pango::WEIGHT_BOLD);
fontDescription.set_size(30 * Pango::SCALE);
keepCapturing = true;
captureThread = new std::thread([this] {
doCapture();
});
}
void CameraDrawingArea::doCapture() {
while (keepCapturing) {
float f = 0.0;
for (int n = 0; n < 1000; n++) {
for (int m = 0; m < 1000; m++) {
for (int o = 0; o < 500; o++) {
f += 1.2;
}
}
}
std::cout << "doCapture - " << f << std::endl;
refreshDrawing();
}
}
void CameraDrawingArea::refreshDrawing() {
auto win = get_window();
if (win) {
win->invalidate(false);
std::cout << "refreshDrawing" << std::endl;
}
}
bool CameraDrawingArea::on_draw(const Cairo::RefPtr<Cairo::Context>& cr) {
std::cout << "on_draw" << std::endl;
static char buffer[50];
static int n = 0;
sprintf(buffer, "-%d-", n++);
Gtk::Allocation allocation = get_allocation();
const int width = allocation.get_width();
const int height = allocation.get_height();
auto layout = create_pango_layout(buffer);
layout->set_font_description(fontDescription);
int textWidth, textHeight;
layout->get_pixel_size(textWidth, textHeight);
cr->set_source_rgb(0.5, 0.2, 0.1);
cr->move_to((width - textWidth) / 2, (height - textHeight) / 2);
layout->show_in_cairo_context(cr);
cr->stroke();
return true;
}
CameraDrawingArea::~CameraDrawingArea() {
keepCapturing = false;
captureThread->join();
free(captureThread);
}
这是我的头文件:
#ifndef CAMERA_DRAWING_AREA_HPP
#define CAMERA_DRAWING_AREA_HPP
#include <gtkmm.h>
#include <thread>
class CameraDrawingArea : public Gtk::DrawingArea {
public:
CameraDrawingArea();
virtual ~CameraDrawingArea();
protected:
bool on_draw(const Cairo::RefPtr<Cairo::Context>& cr) override;
private:
bool keepCapturing;
void doCapture();
void refreshDrawing();
std::thread* captureThread;
Pango::FontDescription fontDescription;
};
#endif
问题表现如下:
cout
,我可以看到调用了refreshDrawing
,请确保也调用了invalidate
,但没有调用on_draw
。此外,如果我在停止刷新之前就停止了该应用程序,则最终效果会很好。但是,如果我在停止刷新后停止应用程序,则会在下面看到此消息(ID值有所不同):
GLib-CRITICAL **: 10:05:04.716: Source ID 25 was not found when attempting to remove it
我很确定我做错了什么,但是对事情一无所知。任何帮助将不胜感激。
我还检查了以下问题,但它们似乎与我的情况无关:
答案 0 :(得分:1)
除启动GTK主循环的线程外,不能从任何其他线程使用GTK方法。可能是win->invalidate()
调用导致此处出现问题。
相反,使用Glib::Dispatcher
与主线程通信,或者使用gdk_threads_add_idle()
获得更C风格的解决方案。
答案 1 :(得分:0)
基于答案形式@ptomato,我重写了示例代码。黄金法则是不要从另一个线程调用GUI函数,但是如果这样做,则先获取一些特定的GDK锁。这就是Glib::Dispatcher的目的:
如果在主GUI线程(因此将成为接收器线程)中构造了Glib :: Dispatcher对象,则任何工作线程都可以在其上发射并让连接的插槽安全地执行gtkmm函数。
基于此,我添加了一个新的私有成员Glib::Dispatcher refreshDrawingDispatcher
,该成员将允许线程安全地invalidate
Windows区域:
#ifndef CAMERA_DRAWING_AREA_HPP
#define CAMERA_DRAWING_AREA_HPP
#include <gtkmm.h>
#include <thread>
class CameraDrawingArea :
public Gtk::DrawingArea {
public:
CameraDrawingArea();
virtual ~CameraDrawingArea();
protected:
bool on_draw(const Cairo::RefPtr<Cairo::Context>& cr) override;
private:
bool keepCapturing;
void doCapture();
void refreshDrawing();
Glib::Dispatcher refreshDrawingDispatcher;
std::thread* captureThread;
Pango::FontDescription fontDescription;
};
#endif
然后,我已将调度程序连接到refreshDrawing
方法。我在类构造函数中执行此操作,该类构造函数在GUI启动期间被调用,因此在主GUI线程中被调用:
CameraDrawingArea::CameraDrawingArea():
refreshDrawingDispatcher(),
captureThread(nullptr) {
fontDescription.set_family("Monospace");
fontDescription.set_weight(Pango::WEIGHT_BOLD);
fontDescription.set_size(30 * Pango::SCALE);
keepCapturing = true;
captureThread = new std::thread([this] {
doCapture();
});
refreshDrawingDispatcher.connect(sigc::mem_fun(*this, &CameraDrawingArea::refreshDrawing));
}
最后,线程必须调用调度程序:
void CameraDrawingArea::doCapture() {
while (keepCapturing) {
float f = 0.0;
for (int n = 0; n < 1000; n++) {
for (int m = 0; m < 1000; m++) {
for (int o = 0; o < 500; o++) {
f += 1.2;
}
}
}
std::cout << "doCapture - " << f << std::endl;
refreshDrawingDispatcher.emit();
}
}
现在,它可以正常工作了。