2

我正在做一个宠物项目,只是为了学习一些 API。它不是为了具有实用价值,而是作为相对简单的练习,让我在将它们用于任何严肃的事情之前让我对 libpcap、gtk+ 和 cairo 感到舒服。这是一个图形程序,用 C 语言实现,使用 Gtk+ 2.x。它最终会用 pcap 读取帧(目前我只有一个硬编码的测试帧),然后使用 cairo 使用从原始数据包生成的颜色值生成漂亮的图片(在这个阶段,我只是使用 cairo_show_text 打印文本表示帧或数据包)。然后将图片绘制到继承自 GtkDrawingArea 的自定义小部件。

当然,我的第一步是正确掌握 Gtk+ 运行时环境,这样我就可以实现我的小部件。我已经设法使用 cairo 向我的自定义小部件呈现和绘制文本。现在,我认为小部件确实需要私有存储来存储诸如 cairo_t 上下文指针和 GdkRegion 指针之类的东西(我没有计划直接使用 Gdk,但我的研究表明它可能是必要的,以便调用gdk_window_invalidate_region() 强制我的 DrawingArea 在我绘制框架后刷新,更不用说 gdk_cairo_create())。我已经将私有存储设置为全局变量(太可怕了!显然这对于​​ Gtk+ 来说是常规的。我仍然不确定如果我有多个小部件实例这将如何工作,所以也许我没有这样做部分正确。

/* private data */
typedef struct _CandyDrawPanePrivate CandyDrawPanePrivate;
struct _CandyDrawPanePrivate {
        cairo_t *cr;
        GdkRegion *region;
};


#define CANDY_DRAW_PANE_GET_PRIVATE(obj)\
        (G_TYPE_INSTANCE_GET_PRIVATE((obj), CANDY_DRAW_PANE_TYPE, CandyDrawPanePrivate))

这是我的问题:初始化我的私有数据结构中的指针取决于从父级 GtkWidget 继承的成员:

/* instance initializer */
static void candy_draw_pane_init(CandyDrawPane *pane) {
        GdkWindow *win = NULL;
        /*win = gtk_widget_get_window((GtkWidget *)pane);*/
        win = ((GtkWidget*)pane)->window;
        if (!win)
            return;

        /* TODO: I should probably also check this return value */
        CandyDrawPanePrivate *priv = CANDY_DRAW_PANE_GET_PRIVATE(((CandyDrawPane*)pane));
        priv->cr = gdk_cairo_create(win);
        priv->region = gdk_drawable_get_clip_region(win);

        candy_draw_pane_update(pane);
        g_timeout_add(1000, candy_draw_pane_update, pane);
}

当我用这个在 candy_draw_pane_init() 期间调用它们的代码替换我的旧代码(在我的事件处理程序期间调用 gdk_cairo_create() 和 gdk_drawable_get_clip_region() 时,应用程序将不再绘制。使用调试器单步执行,我可以看到当我们在 candy_draw_pane_init() 中时,pane->window 和 pane->parent 都是 NULL 指针。这些指针稍后在 Gtk 事件处理循环中有效。这让我相信当我的派生类的“_init()”方法被调用时,继承的成员还没有被初始化。我确信这只是 Gtk+ 运行时环境的本质。

那么这种事情通常是如何处理的呢?我可以向我的事件处理程序添加逻辑来检查 priv->cr 和 priv->region 是否为 NULL,如果它们仍然为 NULL,则调用 gdk_cairo_create() 和 gdk_drawable_get_clip_region()。或者我可以在我的 CandyDrawPane 小部件中添加一个“post-init”方法,并在我调用 candy_draw_pane_new() 之后显式调用它。我敢肯定很多其他人都遇到过这种情况,那么有没有一种干净且传统的方法来处理它?

这是我第一次真正涉足面向对象的 C,所以如果我使用了任何不正确的术语,请见谅。我认为我困惑的一个原因是 Gtk 具有独立的实例和类初始化概念。C++ 可能会在“幕后”做一些类似的事情,但如果是这样,对编码人员来说就不是那么明显了。

我有一种感觉,如果这是 C++,进入 candy_draw_pane_init() 的大部分代码将在类构造函数中,并且任何依赖于已完成构造函数的二次初始化都将进入“Init()”方法(这当然不是语言的特性,而只是常用的约定)。Gtk+ 有类似的约定吗?或者,当这些小部件被实例化时,也许有人可以很好地概述控制流。我对 Gnome 官方文档的质量印象不深。其中大部分内容要么太高级,要么代码中包含错误和拼写错误,要么链接断开或缺少示例。当然,大量使用宏使得即使是我自己的代码也更难遵循(在这方面它让我想起了 Win32 GUI 开发)。简而言之,

为了完整起见,这是我设置自定义小部件的标题:

#ifndef __GTKCAIRO_H__
#define __GTKCAIRO_H__ 1

#include <gtk/gtk.h>

/* Following tutorial; see gtkcairo.c */
/* Not sure about naming convention; may need revisiting */
G_BEGIN_DECLS
#define CANDY_DRAW_PANE_TYPE    (candy_draw_pane_get_type())
#define CANDY_DRAW_PANE(obj)    (G_TYPE_CHECK_INSTANCE_CAST ((obj), CANDY_DRAW_PANE_TYPE, CandyDrawPane))
#define CANDY_DRAW_PANE_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass)CANDY_DRAW_PANE_TYPE, CandyDrawPaneClass))
#define IS_CANDY_DRAW_PANE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), CANDY_DRAW_PANE_TYPE))
#define IS_CANDY_DRAW_PANE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), CANDY_DRAW_PANE_TYPE))
// official gtk tutorial, which seems to be of higher quality, does not use this.

// #define CANDY_DRAW_PANE_GET_CLASS(obj)        (G_TYPE_INSTANCE_GET_CLASS ((obj), CANDY_DRAW_PANE_TYPE, CandyDrawPaneClass))

typedef struct {
        GtkDrawingArea parent;

        /* private */
} CandyDrawPane;

typedef struct {
        GtkDrawingAreaClass parent_class;
} CandyDrawPaneClass;

/* method prototypes */
GtkWidget*      candy_draw_pane_new(void);
GType   candy_draw_pane_get_type(void);
void    candy_draw_pane_clear(CandyDrawPane *cdp);

G_END_DECLS
#endif 

非常感谢任何见解。我确实意识到我可以使用代码生成 IDE 并更快地完成某些事情,并且可能避免不得不处理其中的一些东西,但是这个练习的重点是很好地掌握 Gtk 运行时,所以我更喜欢手写样板。

4

2 回答 2

2

这篇文章,GObject 构造的简单介绍,可能会对您有所帮助。以下是我在查看您的代码和问题时想到的一些提示:

  • 如果您的priv->crpriv->region指针必须在小部件的 GDK 窗口更改时更改,那么您还可以将该代码移动到信号的信号处理程序中notify::windownotify是一个在对象属性更改时触发的信号,您可以通过将其附加到信号名称来缩小信号发射范围以监听特定属性。

  • 您不需要检查GET_PRIVATE宏的返回值。查看源代码g_type_instance_get_private(),它可以在出错的情况下返回NULL,但它确实不太可能,并且会向终端打印警告。我的感觉是,如果GET_PRIVATE返回,NULL那么确实出了点问题,无论如何您将无法恢复并继续执行该程序。

  • 您没有将私有存储设置为全局变量。你在哪里声明这个全局变量?我只在全球范围内看到一个struct和声明。typedef您最有可能做的以及通常的做法是调用g_type_class_add_private()函数class_init。这会在每个对象中为您的私有结构保留空间。然后当你需要使用它时,g_type_instance_get_private()给你一个指向这个空间的指针。

  • init方法等效于 C++ 中的构造函数。该class_init方法没有等价物,因为那里完成的所有工作都是在 C++ 的幕后完成的。例如,在一个class_init函数中,您可以指定哪些函数覆盖父类的虚函数。在 C++ 中,您只需在类中定义一个与您要覆盖的虚拟方法同名的方法即可。

于 2012-05-03T06:23:10.923 回答
1

据我所知,您的代码的唯一问题是 GtkWidget ( widget->window) 的 GdkWindow 仅在实现小部件时设置,这通常在gtk_widget_show调用时发生。您可以通过调用来告诉它提前实现gtk_widget_realize,但文档建议改为连接到绘图实现信号。

于 2012-05-02T10:12:41.837 回答