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()は、gdk_cairo_create()は言うまでもなく、フレームを描画した後、DrawingAreaを強制的に更新します。プライベートストレージをグローバル変数として設定しました(ホラー!これは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);
}

イベントハンドラーの間にgdk_cairo_create()とgdk_drawable_get_clip_region()を呼び出した古いコードを、candy_draw_pane_init()の間にそれらを呼び出すこのコードに置き換えると、アプリケーションは描画しなくなりました。デバッガーをステップスルーすると、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への私の最初の本当の進出です。したがって、用語を誤って使用している場合は、失礼します。私の混乱の原因の1つは、Gtkにはインスタンスとクラスの初期化の概念が別々にあることだと思います。C ++は「内部」で同様のことを行う可能性がありますが、そうであれば、コーダーにはそれほど明白ではありません。

これがC++の場合、candy_draw_pane_init()に入るコードのほとんどはクラスコンストラクターにあり、コンストラクターの完了に依存する2次初期化は「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

この記事A Gentle Introduction to GObject Constructionが役に立ちます。あなたのコードとあなたの質問を見て、私が考えたいくつかのヒントを次に示します。

  • ウィジェットの GDK ウィンドウが変更されるたびにポインターpriv->crとポインターを変更する必要がある場合は、そのコードをシグナルのシグナル ハンドラーに移動することもできます。は、オブジェクトのプロパティが変更されるたびに発生するシグナルであり、そのようなシグナルの名前に追加することで、特定のプロパティをリッスンするようにシグナルの放出を絞り込むことができます。priv->regionnotify::windownotify

  • GET_PRIVATEマクロからの戻り値を確認する必要はありません。のソースコードを見るとg_type_instance_get_private()、エラーが発生した場合に戻る可能NULL性がありますが、実際にはそうではなく、端末に警告を出力します。私の感じでは、GET_PRIVATE返された場合NULL、何かが本当にうまくいかず、回復してプログラムを実行し続けることはできません。

  • プライベート ストレージをグローバル変数として設定していません。このグローバル変数をどこで宣言していますか? グローバル レベルでstructand宣言しか表示されません。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 に答える