本章重点介绍了GObject子类的实现,例如创建自定义继承类,或者实现GTK +窗口类的子类。
在整个章节中,使用文件查看器程序的作为示例,其具有用于表示单个文件查看的ViewerFile类,以及具有特殊功能的不同类型的文件(例如音频文件)的各种派生类。 示例应用程序还支持通过使用ViewerEditable接口编辑文件(例如,调整正在查看的照片)。
1、头文件实现
编写GObject代码之前的第一步是编写类型的头文件,其中包含所需的类型,函数和宏定义。 这些元素中的每一个都只是GObject所有用户遵循的约定,并且在多年的开发基于GObject的代码的经验中进行了改进。 如果你正在写一个库,那么遵守这些约定是尤为重要的; 您的用户将会假定你已经遵循这些约定。 即使你不是在写库,遵守这些约定也会帮助其他想要在你的项目上工作的人。
为您的标题和源代码选择一个约定的名称,并坚持下去:
(1)使用破折号将前缀与typename分开:viewer-file.h和viewer-file.c(这是Nautilus和大多数GNOME库使用的约定)。
(2)使用下划线将前缀与typename分开:viewer_file.h和viewer_file.c。
(3)不要将前缀与typename:viewerfile.h和viewerfile.c分开。 (这是GTK +使用的惯例)
有些人喜欢前两种方法:它使阅读文件名更为容易。
任何GType的基本约定在“约定”一节中描述。
如果要在命名空间'viewer'中声明一个名为'file'的类型,请命名实例类型为ViewerFile,类类型命名为ViewerFileClass(名称区分大小写)。 约定的方法是根据类型是基类还是派生类型的不同而不同。
Final types类型不能进一步子类化,并且应该是新类型的默认选择 - 将Final types更改为可继承的类型始终是与现有代码使用兼容的更改,但相反的往往会导致问题。 Final types使用G_DECLARE_FINAL_TYPE声明,并且要求在源代码(而不是头文件)中声明的实例结构体。
/*
* Copyright/Licensing information.
*/
/* inclusion guard */
#ifndef __VIEWER_FILE_H__
#define __VIEWER_FILE_H__
#include <glib-object.h>
/*
* Potentially, include other headers on which this header depends.
*/
G_BEGIN_DECLS
/*
* Type declaration.
*/
#define VIEWER_TYPE_FILE viewer_file_get_type ()
G_DECLARE_FINAL_TYPE (ViewerFile, viewer_file, VIEWER, FILE, GObject)
/*
* Method definitions.
*/
ViewerFile *viewer_file_new (void);
G_END_DECLS
#endif /* __VIEWER_FILE_H__ */
继承类可以被子类化,其类和实例结构构成公共API的一部分,如果API稳定性关注,则不能更改。 它们使用G_DECLARE_DERIVABLE_TYPE声明:
/*
* Copyright/Licensing information.
*/
/* inclusion guard */
#ifndef __VIEWER_FILE_H__
#define __VIEWER_FILE_H__
#include <glib-object.h>
/*
* Potentially, include other headers on which this header depends.
*/
G_BEGIN_DECLS
/*
* Type declaration.
*/
#define VIEWER_TYPE_FILE viewer_file_get_type ()
G_DECLARE_DERIVABLE_TYPE (ViewerFile, viewer_file, VIEWER, FILE, GObject)
struct _ViewerFileClass
{
GObjectClass parent_class;
/* Class virtual function fields. */
void (* open) (ViewerFile *file,
GError **error);
/* Padding to allow adding up to 12 new virtual functions without
* breaking ABI. */
gpointer padding[12];
};
/*
* Method definitions.
*/
ViewerFile *viewer_file_new (void);
G_END_DECLS
#endif /* __VIEWER_FILE_H__ */
头文件的约定是将最少的头文件#include到需要编译该头文件的顶部。 这允许客户端代码简单地#include“viewer-file.h”,而不需要知道viewer-file.h的先决条件。2、源代码实现
在源码里面第一步应该先#include需要的头文件
/*
* Copyright information
*/
#include "viewer-file.h"
/* Private structure definition. */
typedef struct {
gchar *filename;
/* stuff */
} ViewerFilePrivate;
/*
* forward definitions
*/
如果这个类使用G_DECLARE_FINAL_TYPE声明为不可继承的final types,那么他的实例结构体应当定义在.c文件中。
struct _ViewerFile
{
GObject parent_instance;
/* Other members, including private data. */
}
调用G_DEFINE_TYPE宏(或G_DEFINE_TYPE_WITH_PRIVATE如果您的类需要私有数据 - final types不需要私有数据),使用类型名称,函数前缀和父类GType来减少所需的代码数量。这个宏将做如下操作:
(1)实现viewer_file_get_type函数
(2)定义可以从整个.c文件访问的父类指针
(3)将私有实例数据添加到该类型(如果使用G_DEFINE_TYPE_WITH_PRIVATE)
如果使用G_DECLARE_FINAL_TYPE(参见“Boilerplate头代码”一节)将类声明为final,那么私有数据应该放在实例结构中,应该使用ViewerFile和G_DEFINE_TYPE替代G_DEFINE_TYPE_WITH_PRIVATE。final类型的实例结构不会公开暴露,也不会嵌入到任何派生类的实例结构中(因为该类是final);所以它的大小可以变化,而不会导致使用该类的代码不兼容。相反,可继承类的私有数据必须包含在私有结构中,必须使用G_DEFINE_TYPE_WITH_PRIVATE。
G_DEFINE_TYPE (ViewerFile, viewer_file, G_TYPE_OBJECT)
或者
G_DEFINE_TYPE_WITH_PRIVATE (ViewerFile, viewer_file, G_TYPE_OBJECT)
也可以使用G_DEFINE_TYPE_WITH_CODE宏来控制get_type函数实现-例如,添加一个调用G_IMPLEMENT_INTERFACE宏来实现一个接口。