IceSandwich

Gtk自定义控件(一)GObject系统

略解GObject,清楚工作原理

字数统计: 1.8k阅读时长: 7 min
2020/03/23 Share

GObject是Gtk里非常重要的概念,几乎所有的Gtk代码都涉及到GObject,那到底这是何方神圣以致它在Gtk里应用如此广泛?

对GObject的初次了解

总的来说,GObject做的事情是:在c里实现面向对象

众所周知,c是面向过程语言,可是可以通过一些手段实现面向对象。

从面向对象了解

来看一下这段C++代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include <iostream>

class CircleShape {
constexpr static double PI = 3.1415926; //静态和常量
int radius; //局部变量
private:
void setRadius(int r) { //私有函数
radius = r;
}
public:
CircleShape(int r) { //构造函数
setRadius(r);
}
double getArea() { //公开函数
return PI*radius*radius;
}
static double getPI() { //静态函数
return PI;
}
};

int main(int argc, char *argv[]) {
CircleShape cs(2);
std::cout <<
CircleShape::getPI() << "*2=" << cs.getArea()
<< std::endl;
return 0;
}

重点关注CircleShape这个类,里面有静态变量、局部变量和常量,有私有函数、公开函数、静态函数。

面向对象有三个特性:继承、封装、多态

结合GObject用C实现这个类

为了实现面向对象的三个特征,我们可以将其拆成三个部分。

如图,蓝色部分是类的定义,描述类的信息;红色部分是类内变量;绿色部分是各种函数定义。

按照这样拆分并且按照一定规则翻译成C即可,没有什么难度,是吗?完整代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
/* 文件: CircleShape.hpp */

#ifndef __CIRCLESHAPE_HPP__
#define __CIRCLESHAPE_HPP__
#include <gtk-3.0/gtk/gtk.h>
G_BEGIN_DECLS

#define CIRCLESHAPE_TYPE (circleshape_get_type())
#define CIRCLESHAPE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), CIRCLESHAPE_TYPE, CircleShape))
#define CIRCLESHAPE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), CIRCLESHAPE_TYPE, CircleShapeClass))
#define IS_CIRCLESHAPE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), CIRCLESHAPE_TYPE))
#define IS_CIRCLESHAPE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), CIRCLESHAPE_TYPE))
#define CIRCLESHAPE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), CIRCLESHAPE_TYPE, CircleShapeClass))

const static double PI = 3.1415926; //这里是静态和常量

typedef struct _CircleShape {
GObject parent; //指明父类,继承于GObject
} CircleShape;

typedef struct _CircleShapeClass {
GObjectClass parent_class; //指明类的父类,继承于GObjectClass
} CircleShapeClass;

GDK_AVAILABLE_IN_ALL
CircleShape* circleshape_new(int r); //构造函数
GDK_AVAILABLE_IN_ALL
double circleshape_getArea(CircleShape *cs); //公开函数
GDK_AVAILABLE_IN_ALL
double circleshape_getPI(); //静态函数

G_END_DECLS
#endif
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
/* 文件:CircleShape.cpp */

#include "CircleShape.hpp"
#include <gtk-3.0/gtk/gtk.h>

typedef struct _CircleShapePrivate { //私有成员
int radius;
} CircleShapePrivate;

//自动生成:gtk_gadget_get_type,gtk_gadget_class_intern_init,gtk_gadget_parent_class
//With Private:自动生成gtk_gadget_get_instance_private
//需要完成:gtk_gadget_init,gtk_gadget_class_init
G_DEFINE_TYPE_WITH_PRIVATE(CircleShape, circleshape, G_TYPE_OBJECT);

//就目前来说,这个类这两个函数无需干任何事情
static void circleshape_init(CircleShape *cs) {} //对象初始化
static void circleshape_class_init(CircleShapeClass *klass) {} //类初始化

void setRadius(CircleShape* cs, int r) {
CircleShapePrivate *priv = (CircleShapePrivate*)circleshape_get_instance_private(cs);
priv->radius = r;
}

double circleshape_getArea(CircleShape *cs) {
CircleShapePrivate *priv = (CircleShapePrivate*)circleshape_get_instance_private(cs);
double radius = priv->radius;
return PI*radius*radius;
}

double circleshape_getPI() { return PI; }

CircleShape* circleshape_new(int r) {
CircleShape *cs = (CircleShape*)g_object_new(CIRCLESHAPE_TYPE, 0);
setRadius(cs, r);
return cs;
}

哇,代码那么多!别怕,下面一一讲解。

头文件

G_BEGIN_DECLSG_END_DECLS分别是extern "C" {},主要是用于兼容C++,这里跟GObject无关。

那么,这个是什么?

1
2
3
4
5
6
#define CIRCLESHAPE_TYPE (circleshape_get_type())
#define CIRCLESHAPE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), CIRCLESHAPE_TYPE, CircleShape))
#define CIRCLESHAPE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), CIRCLESHAPE_TYPE, CircleShapeClass))
#define IS_CIRCLESHAPE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), CIRCLESHAPE_TYPE))
#define IS_CIRCLESHAPE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), CIRCLESHAPE_TYPE))
#define CIRCLESHAPE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), CIRCLESHAPE_TYPE, CircleShapeClass))

类名字_TYPE:就是类名字_get_type的调用。
类名字(obj):调用G_TYPE_CHECK_INSTANCE_CAST对obj进行检查和转换。
类名字_CLASS(klass):调用G_TYPE_CHECK_CLASS_CAST对obj进行检查和转换。
IS_类名字(klass):就是检查是不是这个类,不转换。
类名字_GET_CLASS(obj):顾名思义,得到这个类的class。

聪明的你已经发现了,类名字_get_type()这个实现在哪里呢?其实在后面G_DEFINE_TYPE_WITH_PRIVATE有定义,我们后面再讲。

1
2
3
4
5
6
7
8
9
const static double PI = 3.1415926; //这里是静态和常量

typedef struct _CircleShape {
GObject parent; //指明父类,继承于GObject
} CircleShape;

typedef struct _CircleShapeClass {
GObjectClass parent_class; //指明类的父类,继承于GObjectClass
} CircleShapeClass;

将类写成两个结构休,要有类的描述CircleShapeClass,这个是类的实现原理。

这里每个结构体都要有一个父类,目的是?体现面向对象的继承性

1
2
3
4
5
6
GDK_AVAILABLE_IN_ALL
CircleShape* circleshape_new(int r); //构造函数
GDK_AVAILABLE_IN_ALL
double circleshape_getArea(CircleShape *cs); //公开函数
GDK_AVAILABLE_IN_ALL
double circleshape_getPI(); //静态函数

这些就是你熟悉的各种函数了,除了GDK_AVAILABLE_IN_ALL,这个就是extern,呃,感觉这个宏变复杂了,不过有这个宏不是更容易看吗?

源文件

有意思的是,我们把私有成员放在源文件里,为了不让懵懵懂懂的程序员随便修改我们的私有成员,达到隐藏数据结构的目的。

1
2
3
typedef struct _CircleShapePrivate { //私有成员
int radius;
} CircleShapePrivate;

!最重要的地方在这里G_DEFINE_TYPE有很多形式,但是原理上是有互相调用的:

  • G_DEFINE_TYPE
  • G_DEFINE_TYPE_EXTEND
  • G_DEFINE_TYPE_WITH_CODE
  • G_DEFINE_TYPE_WITH_PRIVATE

这里使用的是G_DEFINE_TYPE_WITH_PRIVATE,它实质上是调用G_DEFINE_TYPE_EXTENDED实现的。

1
2
3
4
//自动生成:gtk_gadget_get_type,gtk_gadget_class_intern_init,gtk_gadget_parent_class 
//With Private:自动生成gtk_gadget_get_instance_private
//需要完成:gtk_gadget_init,gtk_gadget_class_init
G_DEFINE_TYPE_WITH_PRIVATE(CircleShape, circleshape, G_TYPE_OBJECT);

它可以算是一个语法糖,用一个宏生成一堆代码,不用自己造轮子。

这个宏帮我们生成的函数有:

gtk_gadget_get_type,gtk_gadget_class_intern_init,gtk_gadget_get_instance_private

但是它生成的一些函数需要我们自定义的有:gtk_gadget_init,gtk_gadget_class_init

嗯,一个类的构造函数,一个是类描述的初始化,这些只能是根据你类的功能做出相应的改变,gtk没办法帮你生成。所以我们接着把这两个函数实现了吧:

1
2
3
//就目前来说,这个类这两个函数无需干任何事情
static void circleshape_init(CircleShape *cs) {} //对象初始化
static void circleshape_class_init(CircleShapeClass *klass) {} //类初始化

注意:

每次g_object_new都会调用gtk_gadget_init,因为这个是类的构造函数

但是gtk_gadget_class_init只有第一次才调用,其他时候都不会调用,因为这是类描述的初始化,相当于固定下来了,不需要进行改动。

然后就是我们的自定义函数啦:

1
2
3
4
5
6
7
8
9
10
11
12
void setRadius(CircleShape* cs, int r) {
CircleShapePrivate *priv = (CircleShapePrivate*)circleshape_get_instance_private(cs);
priv->radius = r;
}

double circleshape_getArea(CircleShape *cs) {
CircleShapePrivate *priv = (CircleShapePrivate*)circleshape_get_instance_private(cs);
double radius = priv->radius;
return PI*radius*radius;
}

double circleshape_getPI() { return PI; }

这些没什么好说的,注意两点:

  • 函数命名:(命名空间_)类名_类中函数名

  • 第一个函数参数:指向类的指针,在python里面这个叫self,在C++、JAVA里面这个叫this

还有别忘了这个哦 :

1
2
3
4
5
CircleShape* circleshape_new(int r) {
CircleShape *cs = (CircleShape*)g_object_new(CIRCLESHAPE_TYPE, 0);
setRadius(cs, r);
return cs;
}

这个嘛,相当于自定义JAVA里面的new操作符,对象总得new一个嘛,对不对?就是靠这个new出来的,这个实际上是调用g_object_new实现的,其实这里是调用了我们的circleshape_init哦!对象初始化嘛。如果是第一次new呢?当然还要调用circleshape_class_init啦,这样GObject才能知道这个类的存在嘛。

主程序

通过改写class,那我们怎么使用这个类啊?

1
2
3
4
5
6
7
8
9
10
#include <iostream>
#include "CircleShape.hpp"

int main(int argc, char *argv[]) {
CircleShape* cs = circleshape_new(2);
std::cout <<
circleshape_getPI() << "*2=" << circleshape_getArea(cs)
<< std::endl;
return 0;
}

这里就跟GTK的调用一样了,这么方便调用,我们都不用知道它内部怎么工作的了。

这体现了什么?面向对象的封装性!

前记

什么?前记?怎么才是前记?因为前面都是基础啊。GObject还有很多内容呢

你说它没有体现多态?那确实。但是GObject是把多态设计进去的了,我们以后将会遇到。

其实最近也没在搞GTK了

先鸽一下吧,到搞GTK的时候再更新…

CATALOG
  1. 1. 对GObject的初次了解
  2. 2. 从面向对象了解
  3. 3. 结合GObject用C实现这个类
  4. 4. 头文件
  5. 5. 源文件
  6. 6. 主程序
  7. 7. 前记
  8. 8. 其实最近也没在搞GTK了