📄 gtk_tut-22.html
字号:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN"><HTML><HEAD> <META NAME="GENERATOR" CONTENT="SGML-Tools 1.0.9"> <TITLE>GTK v1.2 Tutorial: Writing Your Own Widgets </TITLE> <LINK HREF="gtk_tut-23.html" REL=next> <LINK HREF="gtk_tut-21.html" REL=previous> <LINK HREF="gtk_tut.html#toc22" REL=contents></HEAD><BODY BGCOLOR="#FFFFFF"><A HREF="gtk_tut-23.html">Next</A><A HREF="gtk_tut-21.html">Previous</A><A HREF="gtk_tut.html#toc22">Contents</A><HR NOSHADE><H2><A NAME="s22">22. Writing Your Own Widgets </A></H2><H2><A NAME="ss22.1">22.1 Overview</A></H2><P>Although the GTK distribution comes with many types of widgets thatshould cover most basic needs, there may come a time when you need tocreate your own new widget type. Since GTK uses widget inheritanceextensively, and there is already a widget that is close to what you want,it is often possible to make a useful new widget type injust a few lines of code. But before starting work on a new widget, checkaround first to make sure that someone has not already writtenit. This will prevent duplication of effort and keep the number ofGTK widgets out there to a minimum, which will help keep both the codeand the interface of different applications consistent. As a flip sideto this, once you finish your widget, announce it to the world soother people can benefit. The best place to do this is probably the<CODE>gtk-list</CODE>.<P><P><H2><A NAME="ss22.2">22.2 The Anatomy Of A Widget</A></H2><P>In order to create a new widget, it is important to have anunderstanding of how GTK objects work. This section is just meant as abrief overview. See the reference documentation for the details. <P>GTK widgets are implemented in an object oriented fashion. However,they are implemented in standard C. This greatly improves portabilityand stability over using current generation C++ compilers; however,it does mean that the widget writer has to pay attention to some ofthe implementation details. The information common to all instances ofone class of widgets (e.g., to all Button widgets) is stored in the <EM>class structure</EM>. There is only one copy of this inwhich is stored information about the class's signals(which act like virtual functions in C). To support inheritance, thefirst field in the class structure must be a copy of the parent'sclass structure. The declaration of the class structure of GtkButttonlooks like:<P><BLOCKQUOTE><CODE><PRE>struct _GtkButtonClass{ GtkContainerClass parent_class; void (* pressed) (GtkButton *button); void (* released) (GtkButton *button); void (* clicked) (GtkButton *button); void (* enter) (GtkButton *button); void (* leave) (GtkButton *button);};</PRE></CODE></BLOCKQUOTE><P>When a button is treated as a container (for instance, when it isresized), its class structure can be cast to GtkContainerClass, andthe relevant fields used to handle the signals.<P>There is also a structure for each widget that is created on aper-instance basis. This structure has fields to store information thatis different for each instance of the widget. We'll call thisstructure the <EM>object structure</EM>. For the Button class, it lookslike:<P><BLOCKQUOTE><CODE><PRE>struct _GtkButton{ GtkContainer container; GtkWidget *child; guint in_button : 1; guint button_down : 1;};</PRE></CODE></BLOCKQUOTE><P>Note that, similar to the class structure, the first field is theobject structure of the parent class, so that this structure can becast to the parent class's object structure as needed.<P><H2><A NAME="ss22.3">22.3 Creating a Composite widget</A></H2><H3>Introduction</H3><P>One type of widget that you may be interested in creating is awidget that is merely an aggregate of other GTK widgets. This type ofwidget does nothing that couldn't be done without creating newwidgets, but provides a convenient way of packaging user interfaceelements for reuse. The FileSelection and ColorSelection widgets inthe standard distribution are examples of this type of widget.<P>The example widget that we'll create in this section is the Tictactoewidget, a 3x3 array of toggle buttons which triggers a signal when allthree buttons in a row, column, or on one of the diagonals aredepressed. <P><H3>Choosing a parent class</H3><P>The parent class for a composite widget is typically the containerclass that holds all of the elements of the composite widget. Forexample, the parent class of the FileSelection widget is theDialog class. Since our buttons will be arranged in a table, itmight seem natural to make our parent class the GtkTableclass. Unfortunately, this turns out not to work. The creation of awidget is divided among two functions - a <CODE>WIDGETNAME_new()</CODE>function that the user calls, and a <CODE>WIDGETNAME_init()</CODE> functionwhich does the basic work of initializing the widget which isindependent of the arguments passed to the <CODE>_new()</CODE>function. Descendent widgets only call the <CODE>_init</CODE> function oftheir parent widget. But this division of labor doesn't work well fortables, which when created, need to know the number of rows andcolumns in the table. Unless we want to duplicate most of thefunctionality of <CODE>gtk_table_new()</CODE> in our Tictactoe widget, we hadbest avoid deriving it from GtkTable. For that reason, we derive itfrom GtkVBox instead, and stick our table inside the VBox.<P><H3>The header file</H3><P>Each widget class has a header file which declares the object andclass structures for that widget, along with public functions. A couple of features are worth pointing out. To prevent duplicatedefinitions, we wrap the entire header file in:<P><BLOCKQUOTE><CODE><PRE>#ifndef __TICTACTOE_H__#define __TICTACTOE_H__...#endif /* __TICTACTOE_H__ */</PRE></CODE></BLOCKQUOTE><P>And to keep C++ programs that include the header file happy, in:<P><BLOCKQUOTE><CODE><PRE>#ifdef __cplusplusextern "C" {#endif /* __cplusplus */...#ifdef __cplusplus}#endif /* __cplusplus */</PRE></CODE></BLOCKQUOTE><P>Along with the functions and structures, we declare three standardmacros in our header file, <CODE>TICTACTOE(obj)</CODE>,<CODE>TICTACTOE_CLASS(klass)</CODE>, and <CODE>IS_TICTACTOE(obj)</CODE>, which cast apointer into a pointer to the object or class structure, and checkif an object is a Tictactoe widget respectively.<P>Here is the complete header file:<P><BLOCKQUOTE><CODE><PRE>/* tictactoe.h */#ifndef __TICTACTOE_H__#define __TICTACTOE_H__#include <gdk/gdk.h>#include <gtk/gtkvbox.h>#ifdef __cplusplusextern "C" {#endif /* __cplusplus */#define TICTACTOE(obj) GTK_CHECK_CAST (obj, tictactoe_get_type (), Tictactoe)#define TICTACTOE_CLASS(klass) GTK_CHECK_CLASS_CAST (klass, tictactoe_get_type (), TictactoeClass)#define IS_TICTACTOE(obj) GTK_CHECK_TYPE (obj, tictactoe_get_type ())typedef struct _Tictactoe Tictactoe;typedef struct _TictactoeClass TictactoeClass;struct _Tictactoe{ GtkVBox vbox; GtkWidget *buttons[3][3];};struct _TictactoeClass{ GtkVBoxClass parent_class; void (* tictactoe) (Tictactoe *ttt);};guint tictactoe_get_type (void);GtkWidget* tictactoe_new (void);void tictactoe_clear (Tictactoe *ttt);#ifdef __cplusplus}#endif /* __cplusplus */#endif /* __TICTACTOE_H__ */</PRE></CODE></BLOCKQUOTE><P><H3>The <CODE>_get_type()</CODE> function.</H3><P>We now continue on to the implementation of our widget. A corefunction for every widget is the function<CODE>WIDGETNAME_get_type()</CODE>. This function, when first called, tellsGTK about the widget class, and gets an ID that uniquely identifiesthe widget class. Upon subsequent calls, it just returns the ID.<P><BLOCKQUOTE><CODE><PRE>guinttictactoe_get_type (){ static guint ttt_type = 0; if (!ttt_type) { GtkTypeInfo ttt_info = { "Tictactoe", sizeof (Tictactoe), sizeof (TictactoeClass), (GtkClassInitFunc) tictactoe_class_init, (GtkObjectInitFunc) tictactoe_init, (GtkArgSetFunc) NULL, (GtkArgGetFunc) NULL }; ttt_type = gtk_type_unique (gtk_vbox_get_type (), &ttt_info); } return ttt_type;}</PRE></CODE></BLOCKQUOTE><P>The GtkTypeInfo structure has the following definition:<P><BLOCKQUOTE><CODE><PRE>struct _GtkTypeInfo{ gchar *type_name; guint object_size; guint class_size; GtkClassInitFunc class_init_func; GtkObjectInitFunc object_init_func; GtkArgSetFunc arg_set_func; GtkArgGetFunc arg_get_func;};</PRE></CODE></BLOCKQUOTE><P>The fields of this structure are pretty self-explanatory. We'll ignorethe <CODE>arg_set_func</CODE> and <CODE>arg_get_func</CODE> fields here: they have an important, but as yet largelyunimplemented, role in allowing widget options to be conveniently setfrom interpreted languages. Once GTK has a correctly filled in copy ofthis structure, it knows how to create objects of a particular widgettype. <P><H3>The <CODE>_class_init()</CODE> function</H3><P>The <CODE>WIDGETNAME_class_init()</CODE> function initializes the fields ofthe widget's class structure, and sets up any signals for theclass. For our Tictactoe widget it looks like:<P><BLOCKQUOTE><CODE><PRE>enum { TICTACTOE_SIGNAL, LAST_SIGNAL};static gint tictactoe_signals[LAST_SIGNAL] = { 0 };static voidtictactoe_class_init (TictactoeClass *class){ GtkObjectClass *object_class; object_class = (GtkObjectClass*) class; tictactoe_signals[TICTACTOE_SIGNAL] = gtk_signal_new ("tictactoe", GTK_RUN_FIRST, object_class->type, GTK_SIGNAL_OFFSET (TictactoeClass, tictactoe), gtk_signal_default_marshaller, GTK_TYPE_NONE, 0); gtk_object_class_add_signals (object_class, tictactoe_signals, LAST_SIGNAL); class->tictactoe = NULL;}</PRE></CODE></BLOCKQUOTE><P>Our widget has just one signal, the <CODE>tictactoe</CODE> signal that isinvoked when a row, column, or diagonal is completely filled in. Notevery composite widget needs signals, so if you are reading this forthe first time, you may want to skip to the next section now, asthings are going to get a bit complicated.<P>The function:<P><BLOCKQUOTE><CODE><PRE>gint gtk_signal_new( const gchar *name, GtkSignalRunType run_type, GtkType object_type, gint function_offset, GtkSignalMarshaller marshaller, GtkType return_val, guint nparams, ...);</PRE></CODE></BLOCKQUOTE><P>Creates a new signal. The parameters are:<P><UL><LI> <CODE>name</CODE>: The name of the signal.</LI><LI> <CODE>run_type</CODE>: Whether the default handler runs before or afteruser handlers. Usually this will be <CODE>GTK_RUN_FIRST</CODE>, or <CODE>GTK_RUN_LAST</CODE>,although there are other possibilities.</LI><LI> <CODE>object_type</CODE>: The ID of the object that this signal appliesto. (It will also apply to that objects descendents)</LI><LI> <CODE>function_offset</CODE>: The offset within the class structure ofa pointer to the default handler.</LI><LI> <CODE>marshaller</CODE>: A function that is used to invoke the signalhandler. For signal handlers that have no arguments other than theobject that emitted the signal and user data, we can use thepre-supplied marshaller function <CODE>gtk_signal_default_marshaller</CODE>.</LI><LI> <CODE>return_val</CODE>: The type of the return val.</LI><LI> <CODE>nparams</CODE>: The number of parameters of the signal handler(other than the two default ones mentioned above)</LI><LI> <CODE>...</CODE>: The types of the parameters.</LI></UL><P>When specifying types, the <CODE>GtkType</CODE> enumeration is used:<P><BLOCKQUOTE><CODE><PRE>typedef enum{ GTK_TYPE_INVALID, GTK_TYPE_NONE, GTK_TYPE_CHAR, GTK_TYPE_BOOL, GTK_TYPE_INT, GTK_TYPE_UINT, GTK_TYPE_LONG, GTK_TYPE_ULONG, GTK_TYPE_FLOAT, GTK_TYPE_DOUBLE, GTK_TYPE_STRING, GTK_TYPE_ENUM, GTK_TYPE_FLAGS, GTK_TYPE_BOXED, GTK_TYPE_FOREIGN, GTK_TYPE_CALLBACK, GTK_TYPE_ARGS, GTK_TYPE_POINTER, /* it'd be great if the next two could be removed eventually */ GTK_TYPE_SIGNAL, GTK_TYPE_C_CALLBACK, GTK_TYPE_OBJECT} GtkFundamentalType;</PRE></CODE></BLOCKQUOTE><P><CODE>gtk_signal_new()</CODE> returns a unique integer identifier for thesignal, that we store in the <CODE>tictactoe_signals</CODE> array, which weindex using an enumeration. (Conventionally, the enumeration elementsare the signal name, uppercased, but here there would be a conflictwith the <CODE>TICTACTOE()</CODE> macro, so we called it <CODE>TICTACTOE_SIGNAL</CODE>instead.<P>After creating our signals, we need to tell GTK to associate oursignals with the Tictactoe class. We do that by calling<CODE>gtk_object_class_add_signals()</CODE>. We then set the pointer whichpoints to the default handler for the `tictactoe' signal to NULL,indicating that there is no default action.<P><H3>The <CODE>_init()</CODE> function.</H3><P>Each widget class also needs a function to initialize the objectstructure. Usually, this function has the fairly limited role ofsetting the fields of the structure to default values. For compositewidgets, however, this function also creates the component widgets.<P><BLOCKQUOTE><CODE><PRE>static voidtictactoe_init (Tictactoe *ttt){ GtkWidget *table; gint i,j; table = gtk_table_new (3, 3, TRUE); gtk_container_add (GTK_CONTAINER(ttt), table); gtk_widget_show (table); for (i=0;i<3; i++) for (j=0;j<3; j++) { ttt->buttons[i][j] = gtk_toggle_button_new (); gtk_table_attach_defaults (GTK_TABLE(table), ttt->buttons[i][j], i, i+1, j, j+1); gtk_signal_connect (GTK_OBJECT (ttt->buttons[i][j]), "toggled", GTK_SIGNAL_FUNC (tictactoe_toggle), ttt); gtk_widget_set_usize (ttt->buttons[i][j], 20, 20); gtk_widget_show (ttt->buttons[i][j]); }}</PRE></CODE></BLOCKQUOTE><P><H3>And the rest...</H3><P>There is one more function that every widget (except for base widgettypes like GtkBin that cannot be instantiated) needs to have - thefunction that the user calls to create an object of that type. This isconventionally called <CODE>WIDGETNAME_new()</CODE>. In somewidgets, though not for the Tictactoe widgets, this function takesarguments, and does some setup based on the arguments. The other twofunctions are specific to the Tictactoe widget. <P><CODE>tictactoe_clear()</CODE> is a public function that resets all thebuttons in the widget to the up position. Note the use of<CODE>gtk_signal_handler_block_by_data()</CODE> to keep our signal handler forbutton toggles from being triggered unnecessarily.<P><CODE>tictactoe_toggle()</CODE> is the signal handler that is invoked when theuser clicks on a button. It checks to see if there are any winningcombinations that involve the toggled button, and if so, emitsthe "tictactoe" signal.<P><BLOCKQUOTE><CODE><PRE> GtkWidget*tictactoe_new (){ return GTK_WIDGET ( gtk_type_new (tictactoe_get_type ()));}void tictactoe_clear (Tictactoe *ttt){ int i,j; for (i=0;i<3;i++) for (j=0;j<3;j++) { gtk_signal_handler_block_by_data (GTK_OBJECT(ttt->buttons[i][j]), ttt); gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (ttt->buttons[i][j]), FALSE); gtk_signal_handler_unblock_by_data (GTK_OBJECT(ttt->buttons[i][j]), ttt); }}static voidtictactoe_toggle (GtkWidget *widget, Tictactoe *ttt){ int i,k; static int rwins[8][3] = { { 0, 0, 0 }, { 1, 1, 1 }, { 2, 2, 2 }, { 0, 1, 2 }, { 0, 1, 2 }, { 0, 1, 2 }, { 0, 1, 2 }, { 0, 1, 2 } }; static int cwins[8][3] = { { 0, 1, 2 }, { 0, 1, 2 }, { 0, 1, 2 },
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -