GTK+控件简介

与大多数图形界面开发工具一样,GTK+的控件也是以对象的形式出现的。GTK+控件的基础对象GtkObject继承自GObject,所以具有GObject的所有特征,完全可以用创建GObject对象的方法来创建GtkObject对象或新的GTK+控件,同时GTK+还提供了一套新的自定义控件的方式。

直接继承自GtkObject对象的控件主要是GtkWidget,它几乎是所有可视控件的共同的祖先,大多数控件共有的属性都包括在其中。与其它GUI开发工具不同的是,我们不用直接创建GtkObject或GtkWidget对象,而是用定义对象的实例结构和类结构的方式来定义对象,然后再通过类型注册来实现对象。

下图简单说明了一个GOBJECT对象的创建过程,也就是GTK+控件创建的过程:
image001.jpg

在做GTK+自定义控件之前,应先了解两个问题,其一是GTK+中的GDK库完成了对X Window的核心Xlib库的封装,使之简化易用;其二是GTK+本身完成了绝大部分常用控件的封装,使之可在编程中灵活运用。所以读懂GTK+控件的源代码就应会写简单的自定义控件,透彻掌握GDK则会做出复杂的GTK+控件来。

一个简单的组合型控件的实现

我们的目的是创建一个控件,它由两部分构成,左面是一个文字标签,右面是一个单行文本输入控件,两者一同显示出来,在向文本控件输入信息,点击回车键后,会提示相应信息。基于上面的考虑,此控件应该继承自横向盒状容器。

1、实例结构与类结构

与GObject对象相同,GTK+控件对象也分为实例结构和类构,不同的是实例结构和类结构中的变量类型大多是在GTK+中已经定义过的了,我们可以直接应用。在实例结构中定义parent变量和在类结构中定义parent_class变量来实现读者朋友们关注的控件的继承关系。

下面是ouritem.h的代码:

None.gif #ifndef __OUR_ITEM_H__
None.gif
#define  __OUR_ITEM_H__
None.gif
None.gif#include 
< gtk / gtk.h >
None.gif
// 定义类型宏和转换宏
None.gif
#define  TYPE_OUR_ITEM    (our_item_get_type())
None.gif
#define  OUR_ITEM(obj)    (GTK_CHECK_CAST(obj,TYPE_OUR_ITEM,OurItem))
None.gif
// 定义实例结构和类结构
None.gif
typedef  struct  _OurItem OurItem;
None.giftypedef 
struct  _OurItemClass OurItemClass;
None.gif
ExpandedBlockStart.gifContractedBlock.gif
struct  _OurItem  dot.gif {
InBlock.gif    GtkHBox parent;    
//父控件为横向盒状容器
InBlock.gif    
//
InBlock.gif
    GtkWidget *label;    //标签
InBlock.gif
    GtkWidget *entry;    //单行文本录入
ExpandedBlockEnd.gif
}
;
None.gif
ExpandedBlockStart.gifContractedBlock.gif
struct  _OurItemClass  dot.gif {
InBlock.gif    GtkHBoxClass parent_class;
InBlock.gif    
//下面定义函数指针,为所有OurItem实例所使用,即所有的OurItem控件在输入信息点击回车键后都执行此函数
InBlock.gif
    void (*enter_ok)(void);
ExpandedBlockEnd.gif}
;
None.gif
None.gifGtkType our_item_get_type(
void );
None.gifGtkWidget
*  our_item_new( void );
None.gif
void  our_item_set_label(GtkWidget *  item, gchar *  label);
None.gifGtkWidget
*  our_item_new_with_label(gchar *  label);
None.gif
None.gif
#endif   // __OUR_ITEM_H__
None.gif

在上面的代码中我们定义了控件的类结构_OurItemClass和实例结构_OurItem,其中实例结构_OurItem中包含三个成员变量,一个是表示控件实例的父对象parent,另两个分别是控件前面显示的标签label和标签后面的单行输入控件entry,它们相当于控件的两个属性,我们定义的函数our_item_set_label就是用来改变标签属性的方法。这里未定义控件的函数,有兴趣的朋友们可以试一试,为控件加上函数。需要说明的是在实例结构中定义的属性或函数,每个控件的实例都有自己的属性和函数,它们可以是不同的值,也可以是相同的值,实例与实例之间并不影响。而在类结构中定义的属性或函数指针是唯一的,也就是说所有实例共有的,一旦某一实例改变了这一属性,其它实例得到的这一属性的值就是前一实例改变后的值,这也是为什么信号定义在类结构中的原因,因为控件的所有实例都有相同的信号。

控件的类结构_OurItemClass中只有两个成员,一个是表示控件类的父对象类parent_class,另一个是函数指针enter_ok,它用来在发射我们自定义的信号时执行,以测试我们自定义的信号是否发射成功,由于这个函数指针是定义在类结构中的,所以此控件的所有实例都可以调用此函数指针执行。

2、GtkTypeInfo结构

GTK+中的GtkTypeInfo结构可取代GObject中的GTypeInfo结构,包含以下内容:

1、 控件的名称,字符串;

2、 控件的类结构的长度,整型,一般用sizeof来取得

3、 控件的实例结构的长度,整型,同上

4、 控件的类结构的初始化函数,需要转换为GtkClassInitFunc型函数指针

5、 控件的实例结构的初始化函数,需要转换为GtkObjectInitFunc型函数指针

6、 最后两个值是未定义的,预留给以后扩展自定义控件功能时用

详细的定义见下面的代码。这样的结构定义较之GObject中的GTypeInfo结构的定义简化了许多,也更加清晰易懂。在定义完GtkTypeInfo结构后,可以用gtk_type_unique函数来注册自定义的控件,这个函数有两个参数,第一个参数是自定义控件的父类型,如本例中将自定义控件封装在一个横向盒状容器中了,所以用GTK_TYPE_HBOX,读者可以根据自己需要的控件类型来定义;第二个参数是上面定义的GtkTypeInfo结构的地址或指针。如此,就完成了控件的定义和注册。同样也可以参考GObject对象的创建方法,来创建自定义的GTK+控件。

3、信号的定义、发射与连接

这里自定义的信号是当按下回车键后,自动输出一行信息,说明输入已经结束。需要说明的是信号的定义不是在实例结构和类结构的定义(ouritem.h)中定义,而是在实例结构和类结构的实现(ouritem.c)中定义和实现的。

首先为信号定义标记,它是以枚举类型来实现的(见代码),为我们定义的信号命名为OURITEM_OK_SIGNAL,也是第一个信号名;最后一个信号名为LAST_SIGNAL,这样按照C语言中枚举类型进行定义,如果定义多个信号的话,可以自行添加。

然后,再定义一个整型数组ouritem_signals,其长度为LAST_SIGNAL,来保存信号创建后返回的值,这个值很关键,当发射信号的时候用到,如果多个信号的话,每个数组元素对应一个信号,而LAST_SIGNAL是没有具体做用的,只用来标识数组的长度。

用函数g_signal_new来创建一个新的信号,它的第一个参数是信号名,它是以字母开始,其后可以是字母、数字、下划线或减号,这里命名为"ouritem_ok";第二个参数是此对象的类型,用宏G_TYPE_FROM_CLASS来取得;第三个参数是信号运行时的标记,我们取值为G_SIGNAL_RUN_FIRST,还有许多其它值可取(详见GOBJECT的API参考);第四个参数是函数指针在此对象的类结构中的偏移,一般用于调用对象的方法,这里我们调用函数指针enter_ok;第五个参数和第六个参数分别是此信号的类聚(accumulator)和类聚的数据,可以为空;第七个参数是用一标明此信号回调函数的返回类型和参数类型的closure_marshal, 我们取值为g_cclosure_marshal_VOID__VOID;第八个参数是信号的返回值的类型,如果没有返回值,则为G_TYPE_NONE;第九个参数标识我们自己加的参数的个数,我们不加自定义参数,所以设为0,如果有自定义参数可以加在这里,最后一个参数必需是NULL。

对于一个有众多参数的函数,我们在应用时一定要多加小心,仔细理解,如果一个函数的参数超过三个的话,出错的情况就会大增加,何况有这么多呢:->

信号的创建一般在类初始化(our_item_class_init)中进行,这里我们用函数g_signal_emit来发射信号,它也有多个参数,它的第一个参数是对象的实例,用G_OBJECT来转换;第二个参数是信号的标记,即我们上面定义的ouritem_signals数组中的一个值,这里取ouritem_signals[OURITEM_OK_SIGNAL];第三个参数是详细内容,可以设为0不做处理。还可以用g_signal_emitv函数来发射信号,它的用法参考GOBJECT的API手册。

当我们在控件的类结构中定义并实现了信号的发射后,我们就可以在应用此控件时为控件的信号连接回调函数,即用g_signal_connect宏就可以实现最常用的连接,如要连接我们上面定义的ouritem_ok信号,代码可以写成:

None.gif g_signal_connect(G_OBJECT(ouritem), " ouritem_ok " ,
None.gifG_CALLBACK(our_callback), NULL);
None.gif

内容:
GTK+控件简介
一个简单的组合型控件的实现
1、实例结构与类结构
2、GtkTypeInfo结构
3、信号的定义、发射与连接
4、其它函数的实现
5、编译与测试
参考资料
关于作者
在 Linux 专区还有:
教程
工具与产品
代码与组件
文章

宋国伟 (gwsong52@sohu.com)
2003 年 12 月

很多在WINDOWS下用DELPHI做开发的朋友都会做自定义控件,本文讲述在用LINUX下的GTK+做图形界面开发时如何轻松地做出自定义控件来。

在做GTK+自定义控件之前,应先了解两个问题,其一是GTK+中的GDK库完成了对X Window的核心Xlib库的封装,使之简化易用;其二是GTK+本身完成了绝大部分常用控件的封装,使之可在编程中灵活运用。所以读懂GTK+控件的源代码就应会写简单的自定义控件,透彻掌握GDK则会做出复杂的GTK+控件来。

GTK+控件简介

与大多数图形界面开发工具一样,GTK+的控件也是以对象的形式出现的。GTK+控件的基础对象GtkObject继承自GObject,所以具有GObject的所有特征,完全可以用创建GObject对象的方法来创建GtkObject对象或新的GTK+控件,同时GTK+还提供了一套新的自定义控件的方式。

直接继承自GtkObject对象的控件主要是GtkWidget,它几乎是所有可视控件的共同的祖先,大多数控件共有的属性都包括在其中。与其它GUI开发工具不同的是,我们不用直接创建GtkObject或GtkWidget对象,而是用定义对象的实例结构和类结构的方式来定义对象,然后再通过类型注册来实现对象。

下图简单说明了一个GOBJECT对象的创建过程,也就是GTK+控件创建的过程:


一个简单的组合型控件的实现

我们的目的是创建一个控件,它由两部分构成,左面是一个文字标签,右面是一个单行文本输入控件,两者一同显示出来,在向文本控件输入信息,点击回车键后,会提示相应信息。基于上面的考虑,此控件应该继承自横向盒状容器。

1、实例结构与类结构

与GObject对象相同,GTK+控件对象也分为实例结构和类构,不同的是实例结构和类结构中的变量类型大多是在GTK+中已经定义过的了,我们可以直接应用。在实例结构中定义parent变量和在类结构中定义parent_class变量来实现读者朋友们关注的控件的继承关系。

下面是ouritem.h的代码:


#ifndef __OUR_ITEM_H__
#define __OUR_ITEM_H__

#include <gtk/gtk.h>
//定义类型宏和转换宏
#define TYPE_OUR_ITEM	(our_item_get_type())
#define OUR_ITEM(obj)	(GTK_CHECK_CAST(obj,TYPE_OUR_ITEM,OurItem))
//定义实例结构和类结构
typedef struct _OurItem OurItem;
typedef struct _OurItemClass OurItemClass;

struct _OurItem {
	GtkHBox parent;	//父控件为横向盒状容器
	//
	GtkWidget *label;	//标签
	GtkWidget *entry;	//单行文本录入
};

struct _OurItemClass {
	GtkHBoxClass parent_class;
	//下面定义函数指针,为所有OurItem实例所使用,即所有的OurItem控件在输入信息点击回车键后都执行此函数
	void (*enter_ok)(void);
};

GtkType our_item_get_type(void);
GtkWidget* our_item_new(void);
void our_item_set_label(GtkWidget* item, gchar* label);
GtkWidget* our_item_new_with_label(gchar* label);

#endif //__OUR_ITEM_H__

在上面的代码中我们定义了控件的类结构_OurItemClass和实例结构_OurItem,其中实例结构_OurItem中包含三个成员变量,一个是表示控件实例的父对象parent,另两个分别是控件前面显示的标签label和标签后面的单行输入控件entry,它们相当于控件的两个属性,我们定义的函数our_item_set_label就是用来改变标签属性的方法。这里未定义控件的函数,有兴趣的朋友们可以试一试,为控件加上函数。需要说明的是在实例结构中定义的属性或函数,每个控件的实例都有自己的属性和函数,它们可以是不同的值,也可以是相同的值,实例与实例之间并不影响。而在类结构中定义的属性或函数指针是唯一的,也就是说所有实例共有的,一旦某一实例改变了这一属性,其它实例得到的这一属性的值就是前一实例改变后的值,这也是为什么信号定义在类结构中的原因,因为控件的所有实例都有相同的信号。

控件的类结构_OurItemClass中只有两个成员,一个是表示控件类的父对象类parent_class,另一个是函数指针enter_ok,它用来在发射我们自定义的信号时执行,以测试我们自定义的信号是否发射成功,由于这个函数指针是定义在类结构中的,所以此控件的所有实例都可以调用此函数指针执行。

2、GtkTypeInfo结构

GTK+中的GtkTypeInfo结构可取代GObject中的GTypeInfo结构,包含以下内容:

1、 控件的名称,字符串;

2、 控件的类结构的长度,整型,一般用sizeof来取得

3、 控件的实例结构的长度,整型,同上

4、 控件的类结构的初始化函数,需要转换为GtkClassInitFunc型函数指针

5、 控件的实例结构的初始化函数,需要转换为GtkObjectInitFunc型函数指针

6、 最后两个值是未定义的,预留给以后扩展自定义控件功能时用

详细的定义见下面的代码。这样的结构定义较之GObject中的GTypeInfo结构的定义简化了许多,也更加清晰易懂。在定义完GtkTypeInfo结构后,可以用gtk_type_unique函数来注册自定义的控件,这个函数有两个参数,第一个参数是自定义控件的父类型,如本例中将自定义控件封装在一个横向盒状容器中了,所以用GTK_TYPE_HBOX,读者可以根据自己需要的控件类型来定义;第二个参数是上面定义的GtkTypeInfo结构的地址或指针。如此,就完成了控件的定义和注册。同样也可以参考GObject对象的创建方法,来创建自定义的GTK+控件。

3、信号的定义、发射与连接

这里自定义的信号是当按下回车键后,自动输出一行信息,说明输入已经结束。需要说明的是信号的定义不是在实例结构和类结构的定义(ouritem.h)中定义,而是在实例结构和类结构的实现(ouritem.c)中定义和实现的。

首先为信号定义标记,它是以枚举类型来实现的(见代码),为我们定义的信号命名为OURITEM_OK_SIGNAL,也是第一个信号名;最后一个信号名为LAST_SIGNAL,这样按照C语言中枚举类型进行定义,如果定义多个信号的话,可以自行添加。

然后,再定义一个整型数组ouritem_signals,其长度为LAST_SIGNAL,来保存信号创建后返回的值,这个值很关键,当发射信号的时候用到,如果多个信号的话,每个数组元素对应一个信号,而LAST_SIGNAL是没有具体做用的,只用来标识数组的长度。

用函数g_signal_new来创建一个新的信号,它的第一个参数是信号名,它是以字母开始,其后可以是字母、数字、下划线或减号,这里命名为"ouritem_ok";第二个参数是此对象的类型,用宏G_TYPE_FROM_CLASS来取得;第三个参数是信号运行时的标记,我们取值为G_SIGNAL_RUN_FIRST,还有许多其它值可取(详见GOBJECT的API参考);第四个参数是函数指针在此对象的类结构中的偏移,一般用于调用对象的方法,这里我们调用函数指针enter_ok;第五个参数和第六个参数分别是此信号的类聚(accumulator)和类聚的数据,可以为空;第七个参数是用一标明此信号回调函数的返回类型和参数类型的closure_marshal, 我们取值为g_cclosure_marshal_VOID__VOID;第八个参数是信号的返回值的类型,如果没有返回值,则为G_TYPE_NONE;第九个参数标识我们自己加的参数的个数,我们不加自定义参数,所以设为0,如果有自定义参数可以加在这里,最后一个参数必需是NULL。

对于一个有众多参数的函数,我们在应用时一定要多加小心,仔细理解,如果一个函数的参数超过三个的话,出错的情况就会大增加,何况有这么多呢:->

信号的创建一般在类初始化(our_item_class_init)中进行,这里我们用函数g_signal_emit来发射信号,它也有多个参数,它的第一个参数是对象的实例,用G_OBJECT来转换;第二个参数是信号的标记,即我们上面定义的ouritem_signals数组中的一个值,这里取ouritem_signals[OURITEM_OK_SIGNAL];第三个参数是详细内容,可以设为0不做处理。还可以用g_signal_emitv函数来发射信号,它的用法参考GOBJECT的API手册。

当我们在控件的类结构中定义并实现了信号的发射后,我们就可以在应用此控件时为控件的信号连接回调函数,即用g_signal_connect宏就可以实现最常用的连接,如要连接我们上面定义的ouritem_ok信号,代码可以写成:


g_signal_connect(G_OBJECT(ouritem),"ouritem_ok",
G_CALLBACK(our_callback), NULL);

这和常见的为按钮的clicked信号加回调函数是一样的,详细用法见下面的测试代码。

4、其它函数的实现

这里除了信号的定义等函数外,还有一些函数需要定义,以进一步完善控件的功能。首先是信号发射的时机,我们这里设定当用户按下回车键后即发射此信号,所以要为单行文本录入控件的key_release_event信号加回调函数,来判断键入的信息,如果是回车键则发射我们定义的信号。

在发射信号后,我们需要显示一个简短的信息以证明信号已经发射,就是下面代码中定义的enter_ok函数,它的功能就是显示信息,表明信号成功发射。至于实例初始函数our_item_init和类初始化函数our_item_class_init,他们的实现和Gobject中的对象的实现是相同的,可以参考《Gobject对象系统》一文。

此外创建控件的方法our_item_new和our_item_new_with_label都很简单,改变标签文字内容的方法our_item_set_label也只有几行代码,相信读者定会一目了然的。

以下为ouritem.c的完整代码:

None.gif #include  < gtk / gtk.h >
None.gif#include 
< gdk / gdkkeysyms.h >
None.gif#include 
" ouritem.h "
None.gif
None.gif
// 定义枚举类型,说明信号的名称和次序
ExpandedBlockStart.gifContractedBlock.gif
enum   dot.gif {
InBlock.gif    OURITEM_OK_SIGNAL,
InBlock.gif    LAST_SIGNAL
ExpandedBlockEnd.gif}
;
None.gif
ExpandedBlockStart.gifContractedBlock.gif
static  gint ouritem_signals[LAST_SIGNAL]  =   dot.gif 0 } ;
None.gif
None.gif
static   void  our_item_init(OurItem  * ouritem);
None.gif
static   void  our_item_class_init(OurItemClass  * ouritemclass);
None.gif
None.gif
static   void  enter_ok( void );
None.gif
None.gif
void     on_key_release(GtkWidget  * entry, GdkEventKey  * event , gpointer data);
None.gif
None.gif
// 注册自定义控件
None.gif
GtkType    our_item_get_type( void )
ExpandedBlockStart.gifContractedBlock.gif
dot.gif {
InBlock.gif    
static GtkType our_item_type = 0;
InBlock.gif    
if(!our_item_type)
ExpandedSubBlockStart.gifContractedSubBlock.gif    
dot.gif{
ExpandedSubBlockStart.gifContractedSubBlock.gif        GtkTypeInfo our_item_info 
= dot.gif{
InBlock.gif            
"OurItem",    //控件名
InBlock.gif
            sizeof(OurItem),    //控件实例的尺寸
InBlock.gif
            sizeof(OurItemClass),    //控件类的尺寸
InBlock.gif
            (GtkClassInitFunc)our_item_class_init,    //控件类初始化函数
InBlock.gif
            (cour_item_init,    //控件实例初始化函数
InBlock.gif
            NULL, //
InBlock.gif
            NULL //
ExpandedSubBlockEnd.gif
        }
;
InBlock.gif        our_item_type 
= gtk_type_unique(GTK_TYPE_HBOX, &our_item_info);//注册此控件
ExpandedSubBlockEnd.gif
    }

InBlock.gif    
return our_item_type;
ExpandedBlockEnd.gif}

None.gif
None.gif
// 初始化实例结构
None.gif
static   void  our_item_init(OurItem  * ouritem)
ExpandedBlockStart.gifContractedBlock.gif
dot.gif {
InBlock.gif    ouritem
->label = gtk_label_new(NULL);
InBlock.gif    gtk_box_pack_start(GTK_BOX(ouritem),ouritem
->label,FALSE,FALSE,2);
InBlock.gif    ouritem
->entry = gtk_entry_new();
InBlock.gif    g_signal_connect(G_OBJECT(ouritem
->entry),"key_release_event",
InBlock.gif            G_CALLBACK(on_key_release),ouritem);
InBlock.gif    gtk_box_pack_start(GTK_BOX(ouritem),ouritem
->entry,TRUE,TRUE,2);
ExpandedBlockEnd.gif}

None.gif
None.gif
// 初始化类结构
None.gif
static   void  our_item_class_init(OurItemClass  * ouritemclass)
ExpandedBlockStart.gifContractedBlock.gif
dot.gif {
InBlock.gif    GtkObjectClass 
*object_class;
InBlock.gif    object_class 
= (GtkObjectClass*)ouritemclass;
InBlock.gif    
//下面函数创建一个新的信号
InBlock.gif
    ouritem_signals[OURITEM_OK_SIGNAL] = g_signal_new("ouritem_ok",
InBlock.gif                    G_TYPE_FROM_CLASS(object_class),
InBlock.gif                    G_SIGNAL_RUN_FIRST,
InBlock.gif                    G_STRUCT_OFFSET(OurItemClass, enter_ok),
InBlock.gif                    NULL,NULL,
InBlock.gif                    g_cclosure_marshal_VOID__VOID,
InBlock.gif                    G_TYPE_NONE, 
0, NULL);
InBlock.gif    ouritemclass
->enter_ok = enter_ok;//此函数在下面定义
ExpandedBlockEnd.gif
}

None.gif
// 创建新的自定义控件
None.gif
GtkWidget *  our_item_new( void )
ExpandedBlockStart.gifContractedBlock.gif
dot.gif {
InBlock.gif    
return GTK_WIDGET(g_object_new(TYPE_OUR_ITEM,0));
ExpandedBlockEnd.gif}

None.gif
None.gif
// 设定自定义控件前面的静态文本
None.gif
void  our_item_set_label(GtkWidget *  item, gchar *  label)
ExpandedBlockStart.gifContractedBlock.gif
dot.gif {
InBlock.gif    gtk_label_set_text(GTK_LABEL(OUR_ITEM(item)
->label),label);
ExpandedBlockEnd.gif}

None.gif
None.gif
// 带参数创建自定义控件
None.gif
GtkWidget *  our_item_new_with_label(gchar *  label)
ExpandedBlockStart.gifContractedBlock.gif
dot.gif {
InBlock.gif    GtkWidget
* item;
InBlock.gif    item 
= our_item_new();
InBlock.gif    our_item_set_label(item,label);
InBlock.gif    
return item;
ExpandedBlockEnd.gif}

None.gif
None.gif
// 此函数只是简单的在终端上提示你已经按了一次回车键
None.gif
static   void  enter_ok( void )
ExpandedBlockStart.gifContractedBlock.gif
dot.gif {
InBlock.gif    g_print(
"OK! Enter key was clicked!  ");
ExpandedBlockEnd.gif}

None.gif
None.gif
// 以下函数捕获键盘输入消息
None.gif
void     on_key_release(GtkWidget  * entry, GdkEventKey  * event , gpointer data)
ExpandedBlockStart.gifContractedBlock.gif
dot.gif {
InBlock.gif    
if(event->keyval == GDK_Return)    //当按下回车键后发射自定义的信号
ExpandedSubBlockStart.gifContractedSubBlock.gif
    dot.gif{
InBlock.gif        g_signal_emit(G_OBJECT(data),ouritem_signals[OURITEM_OK_SIGNAL],
0);
ExpandedSubBlockEnd.gif    }

ExpandedBlockEnd.gif}

None.gif

转载于:https://www.cnblogs.com/minjunjie/archive/2004/07/13/23814.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值