You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何捕获Gtk::DrawingArea的点击信号?gtkmm开发问题

如何在gtkmm中捕获Gtk::DrawingArea的点击事件(结合Glade使用)

问题背景

使用Glade和C++开发分形生成器时,需要捕获Gtk::DrawingArea的点击事件,但遇到两个问题:

  1. 栈分配Gtk::EventBox时,出现错误:Can't create a GtkStyleContext without a display connection
  2. 改为堆分配后编译通过,但运行时报错: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);
}

错误原因解释

  1. 栈分配EventBox报错:Gtk Widget必须在Gtk应用初始化(Gtk::Application::create)之后才能实例化,栈分配的全局EB_CatchClick会在程序启动时就创建,此时还没有建立Display连接,导致错误。
  2. 堆分配后运行报错:DrawingArea已经被Glade添加到GtkGrid容器中,Gtk不允许一个Widget同时属于两个容器,必须先调用remove从原容器移除,再添加到新容器。

内容的提问来源于stack exchange,提问作者AlgebraicsAnonymous

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.22 02:30:54