我正在做一个宠物项目,只是为了学习一些 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 运行时,所以我更喜欢手写样板。