(转)【探索wireshark】 熟悉GTK+

本文介绍了在Windows环境下使用GTK+2.0进行开发的步骤,包括下载安装过程和使用GTK+的示例。重点讨论了GtkTreeView和GtkTextView构件的使用,详细阐述了Model/View/Controller设计,并提供了创建model、添加数据、处理选中项和使用cell renderers的代码示例。此外,文章还提到了文本编辑框架GtkTextBuffer及其相关概念。

// 所有原创文章转载请注明作者及链接

// http://blog.csdn.net/blackboyofsnp/archive/2010/09/29/5913206.aspx
//
blackboycpp(AT)gmail.com
// QQ群: 135202158

1. 背景

ethereal 0.2.0使用了GTK+来实现界面, 然而, 由于GTK+的更新很快, ethereal 0.2.0所使用的GTK+版本(1.0.1)和目前的版本(比如, 我所用的2.16.6)已经很不一样了, 很多构件或函数已遭废弃(如GtkText和GtkTree已经被设计更好的GtkTextView和GtkTreeView所代替), 因此直接编译是会出错的. 我们必须修改源代码, 以使得编译通过.

2. 下载并安装GTK+2.0

可以到http://www.gtk.org/download.html去下载GTK+. 对于Windows用户, 为了简便, 可以下载所谓的”All-in-one bundles”版本, 这种版本除了GTK+, 还包括它所要用到的所有第三方程序包. 一键安装, 岂不快哉?

下载之后解压缩到某个目录,如x/gtk. 然后将x/gtk/bin加入到环境变量PATH. 之后, 打开CMD, 运行”pkg-config --cflags gtk+-2.0”, 如果正常, 应该显示和以下相似的内容:

-mms-bitfields -IX/gtk/include/gtk-2.0 -IX/gtk/lib/gtk-2.0/include -ID :/dev/gtk/include/atk-1.0 -IX/gtk/include/cairo -IX/gtk/include/pango- 1.0 -IX/gtk/include/glib-2.0 -IX/gtk/lib/glib-2.0/include -IX/gtk /include/freetype2 -IX/gtk/include -IX/gtk/include/libpng14


注意: X/gtk/是你将GTK+解压缩到的主目录.

运行”gtk-demo”, 则会打开自带的演示程序. 不光如此, 它还贴心地附带了大量的文档, 位置在X/gtk/share/gtk-doc/html/目录下. 方便了离线查阅.

3. 在Windows下练习GTK+的开发

我推荐用两种IDE来进行Windows下的GTK+开发. 一是Code::Blocks, 二是Dev-C++. 我强烈推荐用强大的Code::Blocks!!! 因为她太好用了, 以至我不需要写说明. 喜欢用Dev-C++的朋友可以参考我的这篇介绍: http://blog.csdn.net/blackboyofsnp/archive/2008/11/20/3343045.aspx 好了, 有了GTK+, 有了IDE, 是时候练习一些东西了. HelloWorld就不说了, 主要学习一下较新的GtkTreeView和GTextView构件.

3.1 GtkTreeView构件

GTK+中的这个构件用来实现LIstView和TreeView.

概述

在GTK+中, 使用GtkTreeModel接口和GtkTreeView构件, 可以创建树形或列表控件. 此构件采用了Model/View/Controller设计, 包含4个主要部分:
(1) Tree view构件(GtkTreeView)
(2) 视图列(GtkTreeViewColumn)
(3) 单元格[cell]呈现器(GtkCellRenderer等)
(4) 模型接口(GtkTreeModel)

视图由前3个对象组成, 最后一个是模型. MVC设计的一个主要优点是多个视图可以基于一个模型创建. 例如, 可以为文件管理器创建一个映射文件系统的模型. 这样, 就可以创建多个视图来显示文件系统的多个部分, 而只需要在内存中保存一份拷贝. 单元格呈现器的目的是为了给构件提供可扩展性, 并以多种方式呈现相同类型的数据. 考虑如何呈现一个布尔变量. 它应该以字符串”True”或”False”, “On”或 ”Off”, 还是以一个checkbox呈现?

创建一个model

GTK+提供两个简单可用的Models: GtkListStore和GtkTreeStore. GtkListStore用于列表构件, 而GtkTreeStore用于树形构件. 开发一个新的model类型也是可能, 但这两个基本已经够用了. 创建model很简单:

  1. GtkListStore *store = gtk_list_store_new (2, G_TYPE_STRING, G_TYPE_BOOLEAN);   

以上代码创建了有两个列的列表, 一个字符串列和一个布尔值列. 不过一般不直接传递2这样的字面参数; 通常使用一个enum来包含不同的列, 它的最后一项是列的总数. 下面的示例代码显示了这一点, 仅仅是用treeview代替了listview:

  1. enum  
  2. {  
  3.    TITLE_COLUMN,  
  4.    AUTHOR_COLUMN,  
  5.    CHECKED_COLUMN,  
  6.    N_COLUMNS  
  7. };  
  8.   
  9. GtkTreeStore *store = gtk_tree_store_new (N_COLUMNS, /* 列总数 */  
  10.                             G_TYPE_STRING,   /* 书名 */  
  11.                             G_TYPE_STRING,   /* 作者 */  
  12.                             G_TYPE_BOOLEAN); /* 是否选中?  */  

要将数据添加到model, 需要使用gtk_tree_store_set()或gtk_list_store_set()函数. 另外还需要取得GtkTreeIter, 此迭代器指向数据将要被添加的位置. 请看以下示例:

  1. GtkTreeIter   iter;  
  2. gtk_tree_store_append (store, &iter, NULL);  /* 取得迭代器 */  
  3. gtk_tree_store_set (store, &iter,  
  4.                     TITLE_COLUMN, "深入浅出HTML",  
  5.                     AUTHOR_COLUMN, "某网友",  
  6.                     CHECKED_COLUMN, FALSE,  
  7.                     -1);  

注意最后一个参数是-1, 这是因为gtk_tree/list_store_set()是一个可变参数的函数, 它需要知道何时停止处理参数. 它可以用于为一个给定行的任意或所有列设定数据.

gtk_tree_store_append()的第3个参数是父迭代器. 它用于向一个GtkTreeStore添加一行, 并做为已存在的行的子行. 这意味着此新行只有在它的父行可见并处于展开状态时. 请考虑以下的示例:

  1. GtkTreeIter iter1;  /* 父 iter */  
  2. GtkTreeIter iter2;  /* 子 iter  */  
  3.   
  4. gtk_tree_store_append (store, &iter1, NULL);  /* 获得最顶层的iterator */  
  5. gtk_tree_store_set (store, &iter1,  
  6.                     TITLE_COLUMN, "The Art of Computer Programming",  
  7.                     AUTHOR_COLUMN, "Donald E. Knuth",  
  8.                     CHECKED_COLUMN, FALSE,  
  9.                     -1);  
  10.   
  11. gtk_tree_store_append (store, &iter2, &iter1);  /* 取得一个子iterator */  
  12. gtk_tree_store_set (store, &iter2,  
  13.                     TITLE_COLUMN, "Volume 1: Fundamental Algorithms",  
  14.                     -1);  
  15.   
  16. gtk_tree_store_append (store, &iter2, &iter1);  
  17. gtk_tree_store_set (store, &iter2,  
  18.                     TITLE_COLUMN, "Volume 2: Seminumerical Algorithms",  
  19.                     -1);  
  20.   
  21. gtk_tree_store_append (store, &iter2, &iter1);  
  22. gtk_tree_store_set (store, &iter2,  
  23.                     TITLE_COLUMN, "Volume 3: Sorting and Searching",  
  24.                     -1);  

尽管可以选择多种不同的models, 但只需要一种view构件. 它可以用于listview或treeview模式. 使用GtkTreeView不难, 它需要GtkTreeModel来了解从哪里取得它的数据:

  1. GtkWidget *tree;  
  2. tree = gtk_tree_view_new_with_model (GTK_TREE_MODEL (store));  

Columns和cell renderers

有了Model的GtkTreeView需要用columns和cell renderers来显示model.

cell renderers以某种方式绘制tree model中的数据. GTK+2.0有很多cell renderers, 包括GtkCellRendererText, GtkCellRendererPixbuf和 GtkCellRendererToggle. 写一个自定义的renderer也是比较简单的.

GtkTreeView用GtkTreeViewColumn对象来组织tree view中的纵向列. 它需要知道列的名字, cell renderer的类型以及向给定行显示哪些数据.

  1. GtkCellRenderer *renderer;  
  2. GtkTreeViewColumn *column;  
  3.   
  4. renderer = gtk_cell_renderer_text_new ();  
  5. column = gtk_tree_view_column_new_with_attributes ("Author"// 表头文字  
  6.                                                    renderer, // cell renderer  
  7.                                                    "text", AUTHOR_COLUMN, // 多个attributes  
  8.                                                    NULL);   // 可变参数, 最后必须以NULL结束  
  9. gtk_tree_view_append_column (GTK_TREE_VIEW (tree), column);  

处理选中的项

大多数应用程序不仅要显示数据, 还接受来自用户的输入事件<如选中某个结点>. 要完成这个功能, 可以简单地取得选中项(selection)对象的引用, 并和”changed”信号连接:

  1. /* Prototype for selection handler callback */  
  2. static void tree_selection_changed_cb (GtkTreeSelection *selection, gpointer data);  
  3.   
  4. /* Setup the selection handler */  
  5. GtkTreeSelection *select;  
  6.   
  7. select = gtk_tree_view_get_selection (GTK_TREE_VIEW (tree));  
  8. gtk_tree_selection_set_mode (select, GTK_SELECTION_SINGLE);  
  9. g_signal_connect (G_OBJECT (select), "changed",  
  10.                   G_CALLBACK (tree_selection_changed_cb),  
  11.                   NULL);  

然后, 在回调函数里取得选择行的数据:

  1. static void  
  2. tree_selection_changed_cb (GtkTreeSelection *selection, gpointer data)  
  3. {  
  4.         GtkTreeIter iter;  
  5.         GtkTreeModel *model;  
  6.         gchar *author;  
  7.   
  8.         if (gtk_tree_selection_get_selected (selection, &model, &iter))  
  9.         {  
  10.                 gtk_tree_model_get (model, &iter, AUTHOR_COLUMN, &author, -1);  
  11.                 g_print ("You selected a book by %s/n", author);  
  12.                 g_free (author);  
  13.         }  
  14. }  

一个完整而简单的示例

  1. /* 
  2. * File:     GtkTreeView演示 
  3. * Author:   blackboy,    blackboycpp@gmail.com 
  4. * D/T:      2010.9.25 
  5. */  
  6.   
  7.   
  8. #include <gtk/gtk.h>  
  9.   
  10. /* 用于list view */  
  11. enum ListCols  
  12. {  
  13.     LIST_NUM,  
  14.     LIST_NAME,  
  15.     LIST_CHECKED,  
  16.     LIST_CNT  
  17. };  
  18.   
  19. /* 用于tree view */  
  20. enum TreeCols  
  21. {  
  22.     TREE_NAME,  
  23.     TREE_CNT  
  24. };  
  25.   
  26. /* tree view选项项改变时调用的回调函数 */  
  27. static void  
  28. tree_select_changed_cb(GtkTreeSelection* select, gpointer data)  
  29. {  
  30.     GtkTreeIter iter;  
  31.     GtkTreeModel* model;  
  32.     gchar* str;  
  33.   
  34.     if(gtk_tree_selection_get_selected(select, &model, &iter))  
  35.     {  
  36.         gtk_tree_model_get(model, &iter, TREE_NAME, &str, -1);  
  37.         gtk_statusbar_push(GTK_STATUSBAR(data),  
  38.                            gtk_statusbar_get_context_id(GTK_STATUSBAR(data),  
  39.                                    str), str);  
  40.         g_free(str);  
  41.     }  
  42. }  
  43.   
  44. int main (int argc, char *argv[])  
  45. {  
  46.     GtkWidget* win;  
  47.     GtkWidget* vbox ;  
  48.     GtkWidget* statusbar ;  
  49.     GtkTreeView* tree;  
  50.     GtkTreeView* list;  
  51.     GtkTreeStore* tree_store;  
  52.     GtkListStore*  list_store;  
  53.     GtkTreeIter iter;  
  54.     GtkTreeIter iter_child;  
  55.     GtkCellRenderer* renderer;  
  56.     GtkTreeViewColumn* column;  
  57.     GtkTreeSelection* select;  
  58.   
  59.     /* 初始化gtk */  
  60.     gtk_init (&argc, &argv);  
  61.     /* 创建窗口并设置其参数 */  
  62.     win = gtk_window_new (GTK_WINDOW_TOPLEVEL);  
  63.     gtk_window_set_title (GTK_WINDOW (win), "My GtkTreeView");  
  64.     gtk_window_set_position (GTK_WINDOW (win), GTK_WIN_POS_CENTER);  
  65.     gtk_widget_set_size_request(win, 480, 480);  
  66.   
  67.     /* 创建垂直布局构件 */  
  68.     vbox = gtk_vbox_new (FALSE, 2);  
  69.     gtk_container_add (GTK_CONTAINER (win), vbox);  
  70.   
  71.     /* 创建TreeView */  
  72.     tree = gtk_tree_view_new();  
  73.     /* 创建TreeView的Model, 添加数据 */  
  74.     tree_store = gtk_tree_store_new(TREE_CNT, G_TYPE_STRING);  
  75.     gtk_tree_store_append(tree_store, &iter, NULL);  
  76.     gtk_tree_store_set(tree_store, &iter,  
  77.                        TREE_NAME, "Windows", -1);  
  78.     gtk_tree_store_append(tree_store, &iter_child, &iter);  
  79.     gtk_tree_store_set(tree_store, &iter_child,  
  80.                        TREE_NAME,  "Windows 98", -1);  
  81.     gtk_tree_store_append(tree_store, &iter_child, &iter);  
  82.     gtk_tree_store_set(tree_store, &iter_child,  
  83.                        TREE_NAME,  "Windows XP", -1);  
  84.     gtk_tree_store_append(tree_store, &iter_child, &iter);  
  85.     gtk_tree_store_set(tree_store, &iter_child,  
  86.                        TREE_NAME,  "Windows 7", -1);  
  87.     gtk_tree_store_append(tree_store, &iter, NULL);  
  88.     gtk_tree_store_set(tree_store, &iter,  
  89.                        TREE_NAME, "Linux", -1);  
  90.     gtk_tree_store_append(tree_store, &iter_child, &iter);  
  91.     gtk_tree_store_set(tree_store, &iter_child,  
  92.                        TREE_NAME,  "Redhat", -1);  
  93.     gtk_tree_store_append(tree_store, &iter_child, &iter);  
  94.     gtk_tree_store_set(tree_store, &iter_child,  
  95.                        TREE_NAME,  "Fedora", -1);  
  96.     gtk_tree_store_append(tree_store, &iter_child, &iter);  
  97.     gtk_tree_store_set(tree_store, &iter_child,  
  98.                        TREE_NAME,  "Suse", -1);  
  99.     gtk_tree_view_set_model(tree, tree_store);  
  100.     g_object_unref(tree_store);  
  101.   
  102.     /* 创建TreeView的View */  
  103.     renderer = gtk_cell_renderer_text_new();  
  104.     column  = gtk_tree_view_column_new_with_attributes(  
  105.                   "操作系统(选择项改变时状态栏会响应)",  
  106.                   renderer, "text", TREE_NAME, NULL);  
  107.     gtk_tree_view_append_column(tree, column);  
  108.     gtk_tree_view_expand_all(tree);  
  109.     gtk_box_pack_start(vbox, tree, TRUE, TRUE, 1);  
  110.   
  111.     /* 创建ListView */  
  112.     list = gtk_tree_view_new();  
  113.     /* 创建ListView的model, 添加数据 */  
  114.     list_store = gtk_list_store_new(LIST_CNT,  
  115.                                     G_TYPE_STRING, G_TYPE_STRING, G_TYPE_BOOLEAN);  
  116.     gtk_list_store_append(list_store, &iter);  
  117.     gtk_list_store_set(list_store, &iter,  
  118.                        LIST_NUM, "1001",  
  119.                        LIST_NAME, "Lucy",  
  120.                        LIST_CHECKED, FALSE, -1);  
  121.     gtk_list_store_append(list_store, &iter);  
  122.     gtk_list_store_set(list_store, &iter,  
  123.                        LIST_NUM, "1001",  
  124.                        LIST_NAME, "韩梅梅",  
  125.                        LIST_CHECKED, TRUE, -1);  
  126.     gtk_list_store_append(list_store, &iter);  
  127.     gtk_list_store_set(list_store, &iter,  
  128.                        LIST_NUM, "1001",  
  129.                        LIST_NAME, "Tom Green",  
  130.                        LIST_CHECKED, TRUE, -1);  
  131.     gtk_list_store_append(list_store, &iter);  
  132.     gtk_list_store_set(list_store, &iter,  
  133.                        LIST_NUM, "1001",  
  134.                        LIST_NAME, "李明",  
  135.                        LIST_CHECKED, FALSE, -1);  
  136.     gtk_tree_view_set_model(list, list_store);  
  137.     g_object_unref(list_store);  
  138.   
  139.     /* 创建ListView的View */  
  140.     renderer = gtk_cell_renderer_text_new();  
  141.     column = gtk_tree_view_column_new_with_attributes("学号", renderer,  
  142.              "text", LIST_NUM, NULL);  
  143.     column = gtk_tree_view_append_column(list, column);  
  144.     renderer = gtk_cell_renderer_text_new();  
  145.     column = gtk_tree_view_column_new_with_attributes("姓名", renderer,  
  146.              "text", LIST_NAME, NULL);  
  147.     column = gtk_tree_view_append_column(list, column);  
  148.     renderer = gtk_cell_renderer_toggle_new();  
  149.     column = gtk_tree_view_column_new_with_attributes("是否优秀?", renderer,  
  150.              "active", LIST_CHECKED, NULL);  
  151.     column = gtk_tree_view_append_column(list, column);  
  152.     gtk_box_pack_start(vbox, list, TRUE, TRUE, 1);  
  153.   
  154.     /* 创建状态栏 */  
  155.     statusbar = gtk_statusbar_new();  
  156.     gtk_box_pack_start(vbox, statusbar, FALSE, TRUE, 1);  
  157.   
  158.     /* 设置tree view选择项改变时的事件处理函数 */  
  159.     select = gtk_tree_view_get_selection(tree);  
  160.     gtk_tree_selection_set_mode(select, GTK_SELECTION_SINGLE);  
  161.     g_signal_connect(G_OBJECT(select), "changed",  
  162.                      G_CALLBACK(tree_select_changed_cb), statusbar);  
  163.     /* 窗口关闭处理 */  
  164.     g_signal_connect (win, "destroy", gtk_main_quit, NULL);  
  165.   
  166.     /* 主循环 */  
  167.     gtk_widget_show_all (win);  
  168.     gtk_main ();  
  169.     return 0;  
  170. }  


3.2 GtkTextView构件

概述

GTK+拥有一个非常强大的多行文本编辑框架. 主要的对象是GtkTextBuffer,它用来表示正在编辑的文本, 还有GtkTextView构件, 它用来显示GtkTextBuffer. 每个buffer可以被多个view显示.

GTK+中的文本都以UTF-8进行编码. 这意味着一个字符可以被编码为多个字节. 字符数通常称为offsets, 而字节数则称为indexes. 如果混淆了两者, 在 ASCII的情况下当然没事, 但只要你的buffer包含了多字节字符, 情况就糟了.

buffer里的文本可以由tags来进行标记. tag是一个可以用于一系列文本的属性. 例如, 一个名为”bold”的tag可以用来加粗一些文本. 然而, tag的概念比这个更宽泛, 它也不只是影响到文本的外观, 还可以影响鼠标或击键的行为, “锁定”文字以使用户不可以编辑, 等等等等. tag由GtkTextTag对象表示. 一个GtkTextTag可以用于多个buffer内的多项文本.

每个tag储存于GtkTextTagTable. 一个tag table定义了一套可以一起使用的tags. 每个buffer关联一个tag table; 只有来自tab table的tags才能被 buffer所使用. 然而, 单个tag table可以在多个buffer间共享.

tag可以有名字(如”bold”), 也可以没有.

大多数文本操作使用GtkTextIter表示的iterators来完成. iterator用来表示text buffer中两个字符之间的位置. GtkTextIter这个struct被设计为在stack上分配内存, 它保证可以被值拷贝, 并绝不包含任何heap上分配的数据. Iterator也不是一直有效的, 每当buffer被修改导致其中的字符数发生变化时, 先前的iterator就会失效. (注意: 删除5个字符, 再重新插入5个字符仍然会使iterator失效, 尽管最终的数目一样, 但状态已经改变了)

因此, 不能使用iterators在buffer修改时保存位置, 而应使用理想的GtkTextMark. 可以把mark看作一个不可见的光标或插入点, 它通过在buffer内浮动来保存位置. 如果包围mark的文本被删除, 此mark依然处于以前所占的位置; 如果在mark那里插入文本, mark会根据gravity来决定位于新文本的左边或右边. 从左向右读的语言的标准文本光标是一个gravity为右的mark, 因为它总是呆在插入文本的右边.

和tag一样, mark也可以有名或匿名. GtkTextBuffer有2个内建的marks: “insert”和”selection_bound”, 分别指插入点和不是插入点的那个选择项的边界. 如果没有选择文本, 那么这两个marks的位置相同.

text buffer通常至少包含一行, 但有可能是空的(即buffer可以包含0个字符). text buffer的最后一行绝不会以行分隔符结束(如newline); 其他行则以一个行分隔符结束. 在计算字符数和字符偏移时会算上行分隔符. 注意一些Unicode行分隔符在UTF-8中以多个字节表示, 2个字符序列”/r/n”也会被看成是1个行分隔符.

一个简单示例

  1. /* 
  2. * File:      GtkTextView演示 
  3. * Author:   blackboy,    blackboycpp@gmail.com 
  4. * D/T:       2010.9.29 
  5. */  
  6.   
  7. #include <gtk/gtk.h>  
  8.   
  9. int main (int argc, char *argv[])  
  10. {  
  11.     GtkWidget* win;  
  12.     GtkWidget* vbox;  
  13.     GtkWidget* text_view;  
  14.     GtkTextBuffer* buffer;  
  15.     GtkTextIter start, end;  
  16.     PangoFontDescription* font_desc;  
  17.     GdkColor color;  
  18.     GtkTextTag* tag;  
  19.   
  20.     gtk_init (&argc, &argv);  
  21.   
  22.     win = gtk_window_new (GTK_WINDOW_TOPLEVEL);  
  23.     gtk_container_set_border_width (GTK_CONTAINER (win),  1);  
  24.     gtk_window_set_title (GTK_WINDOW (win), "GtkTextView Demo");  
  25.     gtk_window_set_position (GTK_WINDOW (win), GTK_WIN_POS_CENTER);  
  26.     gtk_window_set_default_size(GTK_WINDOW(win), 400,200);  
  27.     g_signal_connect (win, "destroy", gtk_main_quit, NULL);  
  28.   
  29.     vbox = gtk_vbox_new(TRUE, 1);  
  30.   
  31.     text_view = gtk_text_view_new();  
  32.     buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text_view));  
  33.     gtk_text_buffer_set_text(buffer, "Hello, this is  some text", -1);  
  34.   
  35.     /* 更改整个TextView的默认字体与颜色 */  
  36.     font_desc = pango_font_description_from_string("Courier New 15");  
  37.     gtk_widget_modify_font(text_view, font_desc);  
  38.     pango_font_description_free(font_desc);  
  39.     gdk_color_parse("red", &color);  
  40.     gtk_widget_modify_text(text_view, GTK_STATE_NORMAL, &color);  
  41.   
  42.     /* 设置文本缩进 */  
  43.     gtk_text_view_set_left_margin(GTK_TEXT_VIEW(text_view), 30);  
  44.     /* 利用tag更改一部分文本的背景色 */  
  45.     tag = gtk_text_buffer_create_tag(buffer, "blue_foreground",  
  46.                                      "background""yellow", NULL);  
  47.     gtk_text_buffer_get_iter_at_offset(buffer, &start, 7);  
  48.     gtk_text_buffer_get_iter_at_offset(buffer, &end, 12);  
  49.     gtk_text_buffer_apply_tag(buffer, tag, &start, &end);  
  50.   
  51.     gtk_box_pack_start(GTK_BOX(vbox), text_view, FALSE, TRUE, 0);  
  52.     gtk_container_add(GTK_CONTAINER(win), vbox);  
  53.   
  54.     gtk_widget_show_all (win);  
  55.     gtk_main ();  
  56.     return 0;  
  57. }  


1. Gtk+自带文档 X/gtk/share/gtk-doc/html/gtk/
2. GTK+初学者教程(中文) – 卢名杨译
http://zetcode.com/tutorials/gtktutorial/chinese/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值