<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
<HTML
><HEAD
><TITLE
>从头创建构件</TITLE
><META
NAME="GENERATOR"
CONTENT="Modular DocBook HTML Stylesheet Version 1.76b+
"><LINK
REL="HOME"
TITLE="GTK+ 2.0 教程"
HREF="book1.html"><LINK
REL="UP"
TITLE="编写你自己的构件"
HREF="c2132.html"><LINK
REL="PREVIOUS"
TITLE="创建一个复合构件"
HREF="x2152.html"><LINK
REL="NEXT"
TITLE="深入的学习"
HREF="x2367.html"></HEAD
><BODY
CLASS="SECT1"
BGCOLOR="#FFFFFF"
TEXT="#000000"
LINK="#0000FF"
VLINK="#840084"
ALINK="#0000FF"
><DIV
CLASS="NAVHEADER"
><TABLE
SUMMARY="Header navigation table"
WIDTH="100%"
BORDER="0"
CELLPADDING="0"
CELLSPACING="0"
><TR
><TH
COLSPAN="3"
ALIGN="center"
>GTK+ 2.0 教程</TH
></TR
><TR
><TD
WIDTH="10%"
ALIGN="left"
VALIGN="bottom"
><A
HREF="x2152.html"
ACCESSKEY="P"
><<< Previous</A
></TD
><TD
WIDTH="80%"
ALIGN="center"
VALIGN="bottom"
>编写你自己的构件</TD
><TD
WIDTH="10%"
ALIGN="right"
VALIGN="bottom"
><A
HREF="x2367.html"
ACCESSKEY="N"
>Next >>></A
></TD
></TR
></TABLE
><HR
ALIGN="LEFT"
WIDTH="100%"></DIV
><DIV
CLASS="SECT1"
><H1
CLASS="SECT1"
><A
NAME="SEC-CREATINGAWIDGETFROMSCRATCH">从头创建构件</H1
><DIV
CLASS="SECT2"
><H2
CLASS="SECT2"
><A
NAME="AEN2261">介绍</H2
><P
>在这一节,我们将学习如何让构件把自己显示在屏幕上,以及如何让构件与事件交互。期间,我们将做一个表盘构件,用户可以拖动表盘上的指针来设定值。</P
><P
><SPAN
CLASS="INLINEMEDIAOBJECT"
><IMG
SRC="images/gtkdial.png"></SPAN
></P
></DIV
><DIV
CLASS="SECT2"
><H2
CLASS="SECT2"
><A
NAME="AEN2268">在屏幕上显示构件</H2
><P
>在屏幕上显示需要几个相关步骤。在调用 <TT
CLASS="LITERAL"
>WIDGETNAME_new()</TT
> 创建构件之后,如下几个函数需要用到:</P
><P
></P
><UL
><LI
><P
> <TT
CLASS="LITERAL"
>WIDGETNAME_realize()</TT
> 如果构件有 X 窗口,该函数负责为构件创建 X 窗口。</P
></LI
><LI
><P
> <TT
CLASS="LITERAL"
>WIDGETNAME_map()</TT
> 在用户调用 <TT
CLASS="LITERAL"
>gtk_widget_show()</TT
> 之后会调用该函数。它负责确保构件绘制在屏幕上。对于容器类,该函数必须调用每个子构件的 <TT
CLASS="LITERAL"
>map()</TT
> 函数。</P
></LI
><LI
><P
> <TT
CLASS="LITERAL"
>WIDGETNAME_draw()</TT
> 当为构件或它的一个祖先调用 <TT
CLASS="LITERAL"
>gtk_widget_draw()</TT
> 时该函数被调用。它实际上是调用绘制函数在屏幕上绘制构件。对于容器构件,该函数必须为它的子构件调用 <TT
CLASS="LITERAL"
>gtk_widget_draw()</TT
>。</P
></LI
><LI
><P
> <TT
CLASS="LITERAL"
>WIDGETNAME_expose()</TT
> 是构件的暴露事件处理函数。它调用绘制函数把暴露的部分绘制在屏幕上。对于容器构件,该函数必须为无窗口子构件产生暴露事件。(如果它们有自己的窗口,X 会产生必需的暴露事件。)</P
></LI
></UL
><P
>你可能注意到后面的两个函数十分相似,都是负责在屏幕上绘制构件。实际上许多构件并不真正关心它们之间的不同。构件类里的默认 <TT
CLASS="LITERAL"
>draw()</TT
> 函数只是简单的为重绘区域产生一个暴露事件。然而,一些构件通过区分这两个函数可以减少操作。例如,如果一个构件有多个 X 窗口,因为暴露事件标识了暴露的窗口,它可以只重绘受影响的窗口,调用 <TT
CLASS="LITERAL"
>draw()</TT
> 是不可能这样的。</P
><P
>容器构件,即使它们自身并不关心这个差别,也不能简单的使用默认 <TT
CLASS="LITERAL"
>draw()</TT
> 函数,因为它的子构件可能需要注意这个差别。然而,在两个函数里重复绘制代码是一种浪费的。按惯例,构件有一个名为 <TT
CLASS="LITERAL"
>WIDGETNAME_paint()</TT
> 的函数做实际的绘制构件的工作,<TT
CLASS="LITERAL"
>draw()</TT
> 和 <TT
CLASS="LITERAL"
>expose()</TT
> 函数再调用它。</P
><P
>在我们的示例里,因为表盘构件不是一个容器构件,并且只有一个窗口,我们采用最简便的方法,用默认的 <TT
CLASS="LITERAL"
>draw()</TT
> 函数,并且仅仅实现一个 <TT
CLASS="LITERAL"
>expose()</TT
> 函数。</P
></DIV
><DIV
CLASS="SECT2"
><H2
CLASS="SECT2"
><A
NAME="AEN2300">表盘构件的原形</H2
><P
>正像陆上动物是从泥里爬出的两栖动物的变体,GTK 构件是其它的、以前写的构件的变体。因此,虽然这个章节命名为“从头创建构件”,但表盘构件实际上是从范围构件的源码上开始的。以它为起点是因为如果我们的表盘构件能与比例构件有相同的接口会好一些,比例构件是范围构件的继承。所以,虽然源代码在下面以完整的形式出现,它不能说是<I
CLASS="EMPHASIS"
>从头</I
>写出来的。如果你还不熟悉比例构件如何以应用程序作者的观点来运作,最好先看一下前面的章节。</P
></DIV
><DIV
CLASS="SECT2"
><H2
CLASS="SECT2"
><A
NAME="AEN2304">主体</H2
><P
>我们的构件中的相当多的一部分看起来与井字游戏构件十分相似。首先,我们有一个头文件:</P
><TABLE
BORDER="0"
BGCOLOR="#E0E0E0"
WIDTH="100%"
><TR
><TD
><PRE
CLASS="PROGRAMLISTING"
> /* GTK - GIMP工具包
* 版权 (C) 1995-1997 Peter Mattis, Spencer Kimball 和 Josh MacDonald 所有
*
* 本程序是自由软件。你可以在自由软件基金发布的 GNU GPL 的条款下重新分发
* 或修改它。GPL 可以使用版本 2 或(由你选择)任何随后的版本。
*
* 本程序分发的目的是它可能对其他人有用,但不提供任何的担保,包括隐含的
* 和适合特定用途的保证。请查阅GNU通用公共许可证获得详细的信息。
*
* 你应该已经随该软件一起收到一份GNU通用公共许可。如果还没有,请写信给
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
#ifndef __GTK_DIAL_H__
#define __GTK_DIAL_H__
#include <gdk/gdk.h>
#include <gtk/gtkadjustment.h>
#include <gtk/gtkwidget.h>
#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */
#define GTK_DIAL(obj) GTK_CHECK_CAST (obj, gtk_dial_get_type (), GtkDial)
#define GTK_DIAL_CLASS(klass) GTK_CHECK_CLASS_CAST (klass, gtk_dial_get_type (), GtkDialClass)
#define GTK_IS_DIAL(obj) GTK_CHECK_TYPE (obj, gtk_dial_get_type ())
typedef struct _GtkDial GtkDial;
typedef struct _GtkDialClass GtkDialClass;
struct _GtkDial
{
GtkWidget widget;
/* 更新方式 (GTK_UPDATE_[CONTINUOUS/DELAYED/DISCONTINUOUS]) */
guint policy : 2;
/* 当前按下的按钮,如果没有该值是 0 */
guint8 button;
/* 表盘指针的大小 */
gint radius;
gint pointer_width;
/* 更新计时器的ID , 如果没有该值是 0 */
guint32 timer;
/* 当前角度 */
gfloat angle;
/* 将从调整对象中得到的旧值保存起来,这样在改变时我们就会知道 */
gfloat old_value;
gfloat old_lower;
gfloat old_upper;
/* 为这个表盘构件存储数据的调整对象 */
GtkAdjustment *adjustment;
};
struct _GtkDialClass
{
GtkWidgetClass parent_class;
};
GtkWidget* gtk_dial_new (GtkAdjustment *adjustment);
GtkType gtk_dial_get_type (void);
GtkAdjustment* gtk_dial_get_adjustment (GtkDial *dial);
void gtk_dial_set_update_policy (GtkDial *dial,
GtkUpdateType policy);
void gtk_dial_set_adjustment (GtkDial *dial,
GtkAdjustment *adjustment);
#ifdef __cplusplus
}
#endif /* __cplusplus */
#endif /* __GTK_DIAL_H__ */</PRE
></TD
></TR
></TABLE
><P
>因为相对于上一个构件,这个构件我们要做的工作更多,所以在数据结构里有更多的域,但是其它地方一样。</P
><P
>接下来,在包含了头文件和声明了几个常量之后,我们有几个提供构件信息的函数和初始化构件的函数:</P
><TABLE
BORDER="0"
BGCOLOR="#E0E0E0"
WIDTH="100%"
><TR
><TD
><PRE
CLASS="PROGRAMLISTING"
>#include <math.h>
#include <stdio.h>
#include <gtk/gtkmain.h>
#include <gtk/gtksignal.h>
#include "gtkdial.h"
#define SCROLL_DELAY_LENGTH 300
#define DIAL_DEFAULT_SIZE 100
/* 声明 */
[ 省略以节省空间 ]
/* 局部数据 */
static GtkWidgetClass *parent_class = NULL;
GtkType
gtk_dial_get_type ()
{
static GtkType dial_type = 0;
if (!dial_type)
{
static const GtkTypeInfo dial_info =
{
"GtkDial",
sizeof (GtkDial),
sizeof (GtkDialClass),
(GtkClassInitFunc) gtk_dial_class_init,
(GtkObjectInitFunc) gtk_dial_init,
/* reserved_1 */ NULL,
/* reserved_1 */ NULL,
(GtkClassInitFunc) NULL
};
dial_type = gtk_type_unique (GTK_TYPE_WIDGET, &dial_info);
}
return dial_type;
}
static void
gtk_dial_class_init (GtkDialClass *class)
{
GtkObjectClass *object_class;
GtkWidgetClass *widget_class;
object_class = (GtkObjectClass*) class;
widget_class = (GtkWidgetClass*) class;
parent_class = gtk_type_class (gtk_widget_get_type ());
object_class->destroy = gtk_dial_destroy;
widget_class->realize = gtk_dial_realize;
widget_class->expose_event = gtk_dial_expose;
widget_class->siz