如何捕获Gtk::DrawingArea的点击信号?gtkmm开发问题
如何在gtkmm中捕获Gtk::DrawingArea的点击事件(结合Glade使用)
问题背景
使用Glade和C++开发分形生成器时,需要捕获Gtk::DrawingArea的点击事件,但遇到两个问题:
- 栈分配
Gtk::EventBox时,出现错误:Can't create a GtkStyleContext without a display connection - 改为堆分配后编译通过,但运行时报错:
Attempting to add a widget with type gtkmm__GtkDrawingArea to a container of type gtkmm__GtkEventBox, but the widget is already inside a container of type gtkmm__GtkWindow, please remove the widget from its existing container first.
疑问:是否必须先将Gtk::DrawingArea从原容器移除,再添加到EventBox后放回窗口?有没有更优方案?
解决方案
核心结论
是的,Gtk的Widget不能同时属于多个容器,必须先将DrawingArea从原父容器(这里是GtkGrid)移除,再添加到EventBox,最后把EventBox添加回原容器。不过更优的方案是直接在Glade中完成EventBox的嵌套配置,避免代码中手动调整容器层级。
方案1:代码中手动调整容器层级(修复运行时错误)
修改main函数,调整容器层级:
int main(int argc, char** argv) { test = false; auto app = Gtk::Application::create(argc, argv, "org.myname.mandelbronatoor"); auto builder = Gtk::Builder::create_from_file("Mandelbronatoor.glade"); builder->get_widget("W_Main", W_Main); builder->get_widget("D_Fractal", D_Fractal); Gtk::Grid* G_Main; builder->get_widget("G_Main", G_Main); // 获取DrawingArea的原父容器 // 1. 先把DrawingArea从Grid中移除 G_Main->remove(*D_Fractal); // 2. 创建EventBox并添加DrawingArea EB_CatchClick = new Gtk::EventBox; EB_CatchClick->add(*D_Fractal); // 3. 将EventBox添加回Grid的原位置 G_Main->attach(*EB_CatchClick, 0, 0, 1, 1); // 连接信号 D_Fractal->signal_draw().connect(sigc::ptr_fun(on_D_Fractal_draw)); EB_CatchClick->signal_button_press_event().connect(sigc::ptr_fun(on_D_Fractal_button_press_event)); app->run(*W_Main); delete EB_CatchClick; }
方案2:在Glade中直接配置EventBox(更简洁)
直接在Glade里把DrawingArea嵌套到EventBox中,无需代码调整容器层级:
修改后的Glade文件
<?xml version="1.0" encoding="UTF-8"?> <!-- Generated with glade 3.40.0 --> <interface> <requires lib="gtk+" version="3.24"/> <object class="GtkWindow" id="W_Main"> <property name="can-focus">False</property> <property name="title" translatable="yes">Mandelbronatoor</property> <property name="default-width">600</property> <property name="default-height">400</property> <child> <!-- n-columns=1 n-rows=2 --> <object class="GtkGrid" id="G_Main"> <property name="visible">True</property> <property name="can-focus">False</property> <property name="row-homogeneous">True</property> <child> <object class="GtkEventBox" id="EB_CatchClick"> <property name="visible">True</property> <property name="can-focus">False</property> <child> <object class="GtkDrawingArea" id="D_Fractal"> <property name="visible">True</property> <property name="can-focus">False</property> <property name="hexpand">True</property> <property name="vexpand">True</property> </object> </child> </object> <packing> <property name="left-attach">0</property> <property name="top-attach">0</property> </packing> </child> <child> <placeholder/> </child> </object> </child> </object> </interface>
对应C++代码
#include <glibmm.h> #include <gtkmm.h> #include <cmath> #include <iostream> Gtk::Window* W_Main; Gtk::DrawingArea* D_Fractal; Gtk::EventBox* EB_CatchClick; // 直接从Glade获取 int AWidth; int AHeight; bool test; gboolean on_D_Fractal_draw(const Cairo::RefPtr<Cairo::Context> cr) { AWidth = D_Fractal->get_width(); AHeight = D_Fractal->get_height(); cr->set_source_rgb(1.0, 1.0, 1.0); if(test) { cr->set_source_rgb(0.0, 0.0, 0.0); } cr->rectangle(10, 10, AWidth-20, AHeight-20); cr->fill(); return 0; } gboolean on_D_Fractal_button_press_event(GdkEventButton *event) { switch ( event->type ) { case GDK_BUTTON_PRESS : test = true; std::cout << "test" << std::endl; D_Fractal->queue_draw(); return TRUE; case GDK_BUTTON_RELEASE : return TRUE; default: return TRUE; } return 0; } int main(int argc, char** argv) { test = false; auto app = Gtk::Application::create(argc, argv, "org.myname.mandelbronatoor"); auto builder = Gtk::Builder::create_from_file("Mandelbronatoor.glade"); builder->get_widget("W_Main", W_Main); builder->get_widget("D_Fractal", D_Fractal); builder->get_widget("EB_CatchClick", EB_CatchClick); // 直接获取EventBox D_Fractal->signal_draw().connect(sigc::ptr_fun(on_D_Fractal_draw)); EB_CatchClick->signal_button_press_event().connect(sigc::ptr_fun(on_D_Fractal_button_press_event)); app->run(*W_Main); }
错误原因解释
- 栈分配EventBox报错:Gtk Widget必须在Gtk应用初始化(
Gtk::Application::create)之后才能实例化,栈分配的全局EB_CatchClick会在程序启动时就创建,此时还没有建立Display连接,导致错误。 - 堆分配后运行报错:
DrawingArea已经被Glade添加到GtkGrid容器中,Gtk不允许一个Widget同时属于两个容器,必须先调用remove从原容器移除,再添加到新容器。
内容的提问来源于stack exchange,提问作者AlgebraicsAnonymous
相关产品推荐
相关产品推荐

