46、GTK+ 编程入门:从基础窗口到复杂视图

GTK+ 编程入门:从基础窗口到复杂视图

1. GTK+ 窗口基础

在 GTK+ 编程中, GTK_WINDOW_TOPLEVEL 是常用的窗口类型,因为创建对话框有更便捷的方式。创建窗口时,先使用 gtk_window_new 在内存中初始化窗口,之后可以对其进行各种设置,如添加组件、调整大小、更改标题等,最后使用 gtk_widget_show 让窗口显示在屏幕上。示例代码如下:

#include <gtk/gtk.h>

int main (int argc, char *argv[])
{
    GtkWidget *window;
    gtk_init(&argc, &argv);
    window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
    gtk_widget_show(window);
    gtk_main ();
    return 0;
}

gtk_main 函数启动交互过程,将控制权交给 GTK+,直到调用 gtk_main_quit 才会返回。在上述示例中,若不进行额外处理,关闭窗口后应用程序不会结束,可通过在启动程序的 shell 窗口中输入 Ctrl+C 来退出。

2. 事件、信号与回调函数

2.1 基本概念

所有 GUI 库都需要一种机制来响应用户操作。在命令行程序中,可以暂停执行等待输入,然后根据输入使用 switch 语句分支执行。但 GUI 应用程序需要持续响应用户输入,因此现代窗口系统采用事件和事件监听器机制。GTK+ 有自己的事件和事件监听器系统,即信号和回调函数。

GTK+ 信号由 GtkObject 在用户输入等情况下发出,连接到信号的函数称为回调函数。回调函数原型通常如下:

void a_callback_function ( GtkWidget *widget, gpointer user_data);

连接回调函数使用 g_signal_connect 函数:

gulong g_signal_connect(gpointer *object, const gchar *name, GCallback func, gpointer  user_data );

2.2 示例:回调函数的使用

以下是一个在窗口中添加按钮并连接 “clicked” 信号到回调函数的示例:

#include <gtk/gtk.h>
#include <stdio.h>

static int count = 0;

void button_clicked(GtkWidget *button, gpointer data)
{
    printf("%s pressed %d time(s) \n", (char *) data, ++count);
}

int main (int argc, char *argv[])
{
    GtkWidget *window;
    GtkWidget *button;
    gtk_init(&argc, &argv);
    window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
    button = gtk_button_new_with_label("Hello World!");
    gtk_container_add(GTK_CONTAINER(window), button);
    g_signal_connect(GTK_OBJECT (button), "clicked", 
                     GTK_SIGNAL_FUNC (button_clicked), 
                     "Button 1");
    gtk_widget_show(button);
    gtk_widget_show(window);
    gtk_main ();
    return 0;
}

在这个示例中,每次点击按钮,回调函数会打印一条消息。

3. 容器组件:打包框组件

3.1 布局的重要性

GUI 的布局对其可用性至关重要,但由于用户的屏幕分辨率、窗口大小、主题、字体和配色方案不同,布局变得困难。为了创建在所有系统上都统一的 GUI,应避免使用绝对坐标放置组件,而使用更灵活的布局系统,GTK+ 提供了容器组件来实现这一点。

3.2 打包框组件介绍

打包框组件是不可见的组件,用于包含其他组件并控制其布局。GTK+ 有两种主要的打包框子类:
- GtkHBox :单行水平打包框组件。
- GtkVBox :单列垂直打包框组件。

创建打包框时,需要指定两个参数 homogeneous spacing

GtkWidget*  gtk_hbox_new (gboolean homogeneous, gint spacing);
GtkWidget*  gtk_vbox_new (gboolean homogeneous, gint spacing);

homogeneous TRUE 时,强制包含的组件占用相等空间; spacing 设置组件之间的像素间隔。

添加组件到打包框使用 gtk_box_pack_start gtk_box_pack_end 函数:

void gtk_box_pack_start (GtkBox *box, GtkWidget *child, 
                         gboolean expand, gboolean fill, 
                         guint padding);
void gtk_box_pack_end (GtkBox *box, GtkWidget *child, 
                       gboolean expand, gboolean fill, 
                       guint padding);

这两个函数的参数说明如下表:
| 参数 | 描述 |
| ---- | ---- |
| GtkBox *box | 要填充的打包框 |
| GtkWidget *child | 要放置在打包框中的组件 |
| gboolean expand | 若为 TRUE ,该组件将填充与其他设置相同标志的组件共享的所有可用空间 |
| gboolean fill | 若为 TRUE ,该组件将填充分配给它的空间,而不是用作边缘填充,仅在 expand TRUE 时有效 |
| guint padding | 组件周围的像素填充大小 |

3.3 示例:组件容器布局

以下是一个使用 GtkHBox GtkVBox 布局 GtkLabel 组件的示例:

#include <gtk/gtk.h>

void closeApp ( GtkWidget *window, gpointer data)
{
    gtk_main_quit();
}

gboolean delete_event(GtkWidget *widget, GdkEvent *event, gpointer data) 
{
    printf("In delete_event\n");
    return FALSE;
}

int main (int argc, char *argv[])
{
    GtkWidget *window;
    GtkWidget *label1, *label2, *label3;
    GtkWidget *hbox;
    GtkWidget *vbox;
    gtk_init(&argc, &argv);
    window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
    gtk_window_set_title(GTK_WINDOW(window), "The Window Title");
    gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
    gtk_window_set_default_size(GTK_WINDOW(window), 300, 200);
    g_signal_connect ( GTK_OBJECT (window), "destroy", 
                       GTK_SIGNAL_FUNC ( closeApp), NULL);
    g_signal_connect ( GTK_OBJECT (window), "delete_event", 
                       GTK_SIGNAL_FUNC ( delete_event), NULL);
    label1 = gtk_label_new("Label 1");
    label2 = gtk_label_new("Label 2");
    label3 = gtk_label_new("Label 3");
    hbox = gtk_hbox_new ( TRUE, 5 );
    vbox = gtk_vbox_new ( FALSE, 10);
    gtk_box_pack_start(GTK_BOX(vbox), label1, TRUE, FALSE, 5);
    gtk_box_pack_start(GTK_BOX(vbox), label2, TRUE, FALSE, 5);
    gtk_box_pack_start(GTK_BOX(hbox), vbox, FALSE, FALSE, 5);
    gtk_box_pack_start(GTK_BOX(hbox), label3, FALSE, FALSE, 5);
    gtk_container_add(GTK_CONTAINER(window), hbox);
    gtk_widget_show_all(window);
    gtk_main ();
    return 0;
}

该示例创建了两个打包框 hbox vbox ,并将 label1 label2 放入 vbox ,再将 vbox label3 放入 hbox ,最后将 hbox 放入窗口并显示。

4. 常见 GTK+ 组件 API

4.1 GtkWindow

GtkWindow 是所有 GTK+ 应用程序的基本元素,以下是一些重要的函数:
| 函数 | 描述 |
| ---- | ---- |
| GtkWidget* gtk_window_new (GtkWindowType type) | 创建一个新的空窗口 |
| void gtk_window_set_title (GtkWindow *window, const gchar *title) | 设置窗口标题 |
| void gtk_window_set_position (GtkWindow *window, GtkWindowPosition position) | 设置窗口初始位置 |
| void gtk_window_set_default_size (GtkWindow *window, gint width, gint height) | 设置窗口默认大小 |
| void gtk_window_resize (GtkWindow *window, gint width, gint height) | 调整窗口大小 |
| void gtk_window_set_resizable (GtkWindow *window, gboolean resizable) | 设置窗口是否可调整大小 |
| void gtk_window_present (GtkWindow *window) | 确保窗口在屏幕上可见 |
| void gtk_window_maximize (GtkWindow *window) | 最大化窗口 |
| void gtk_window_unmaximize (GtkWindow *window) | 取消窗口最大化 |

4.2 GtkEntry

GtkEntry 是单行文本输入组件,常用于输入简单的文本信息,如电子邮件地址、用户名或主机名。以下是一些常用函数:
| 函数 | 描述 |
| ---- | ---- |
| GtkWidget* gtk_entry_new (void) | 创建一个新的 GtkEntry 组件 |
| GtkWidget* gtk_entry_new_with_max_length (gint max) | 创建一个有最大输入长度限制的 GtkEntry 组件 |
| void gtk_entry_set_max_length (GtkEntry *entry, gint max) | 设置最大允许字符数 |
| G_CONST_RETURN gchar* gtk_entry_get_text (GtkEntry *entry) | 获取输入的文本 |
| void gtk_entry_set_text (GtkEntry *entry, const gchar *text) | 设置输入的文本 |
| void gtk_entry_append_text (GtkEntry *entry, const gchar *text) | 追加文本到输入框 |
| void gtk_entry_prepend_text (GtkEntry *entry, const gchar *text) | 在输入框前添加文本 |
| void gtk_entry_set_visibility (GtkEntry *entry, gboolean visible) | 设置输入文本是否可见 |
| void gtk_entry_set_invisible_char (GtkEntry *entry, gchar invch) | 设置不可见字符 |
| void gtk_entry_set_editable (GtkEntry *entry, gboolean editable) | 设置输入框是否可编辑 |

4.3 示例:用户名和密码输入

以下是一个创建用户名和密码输入窗口并验证密码的示例:

#include <gtk/gtk.h>
#include <stdio.h>
#include <string.h>

const char * password = "secret";

void closeApp ( GtkWidget *window, gpointer data)
{
    gtk_main_quit();
}

void button_clicked (GtkWidget *button, gpointer data)
{
    const char *password_text = gtk_entry_get_text(GTK_ENTRY((GtkWidget *) data));
    if (strcmp(password_text, password) == 0)
        printf("Access granted!\n");
    else
        printf("Access denied!\n");
}

int main (int argc, char *argv[])
{
    GtkWidget *window;
    GtkWidget *username_label, *password_label;
    GtkWidget *username_entry, *password_entry;
    GtkWidget *ok_button;
    GtkWidget *hbox1, *hbox2;
    GtkWidget *vbox;
    gtk_init(&argc, &argv);
    window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
    gtk_window_set_title(GTK_WINDOW(window), "GtkEntryBox");
    gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
    gtk_window_set_default_size(GTK_WINDOW(window), 200, 200);
    g_signal_connect ( GTK_OBJECT (window), "destroy", 
                       GTK_SIGNAL_FUNC ( closeApp), NULL);
    username_label = gtk_label_new("Login:");
    password_label = gtk_label_new("Password:");
    username_entry = gtk_entry_new();
    password_entry = gtk_entry_new();
    gtk_entry_set_visibility(GTK_ENTRY (password_entry), FALSE);
    ok_button = gtk_button_new_with_label("Ok");
    g_signal_connect (GTK_OBJECT (ok_button), "clicked", 
                      GTK_SIGNAL_FUNC(button_clicked), password_entry);
    hbox1 = gtk_hbox_new ( TRUE, 5 );
    hbox2 = gtk_hbox_new ( TRUE, 5 );
    vbox = gtk_vbox_new ( FALSE, 10);
    gtk_box_pack_start(GTK_BOX(hbox1), username_label, TRUE, FALSE, 5);
    gtk_box_pack_start(GTK_BOX(hbox1), username_entry, TRUE, FALSE, 5);
    gtk_box_pack_start(GTK_BOX(hbox2), password_label, TRUE, FALSE, 5);
    gtk_box_pack_start(GTK_BOX(hbox2), password_entry, TRUE, FALSE, 5);
    gtk_box_pack_start(GTK_BOX(vbox), hbox1, FALSE, FALSE, 5);
    gtk_box_pack_start(GTK_BOX(vbox), hbox2, FALSE, FALSE, 5);
    gtk_box_pack_start(GTK_BOX(vbox), ok_button, FALSE, FALSE, 5);
    gtk_container_add(GTK_CONTAINER(window), vbox);
    gtk_widget_show_all(window);
    gtk_main ();
    return 0;
}

该示例创建了两个 GtkEntry 组件用于输入用户名和密码,将密码输入框的可见性设置为 FALSE 以隐藏输入的密码,点击按钮时验证密码并输出相应信息。

4.4 GtkSpinButton

GtkSpinButton 适用于让用户输入数值的场景,如最大速度或设备长度。它限制用户只能输入数字字符,还能设置允许值的范围,并且提供上下箭头方便用户使用鼠标调整数值。以下是常用的 API 调用:
| 函数 | 描述 |
| ---- | ---- |
| GtkWidget* gtk_spin_button_new (GtkAdjustment *adjustment, gdouble climb_rate, guint digits) | 使用 GtkAdjustment 对象创建 GtkSpinButton |
| GtkWidget* gtk_spin_button_new_with_range (gdouble min, gdouble max, gdouble step) | 直接指定范围创建 GtkSpinButton |
| void gtk_spin_button_set_digits (GtkSpinButton *spin_button, guint digits) | 设置显示的小数位数 |
| void gtk_spin_button_set_increments (GtkSpinButton *spin_button, gdouble step, gdouble page) | 设置增量大小 |
| void gtk_spin_button_set_range (GtkSpinButton *spin_button, gdouble min, gdouble max) | 设置允许值的范围 |
| gdouble gtk_spin_button_get_value (GtkSpinButton *spin_button) | 获取当前值 |
| gint gtk_spin_button_get_value_as_int (GtkSpinButton *spin_button) | 获取当前值的整数形式 |
| void gtk_spin_button_set_value (GtkSpinButton *spin_button, gdouble value) | 设置当前值 |

创建 GtkSpinButton 前,需要先创建 GtkAdjustment 对象,示例如下:

#include <gtk/gtk.h>

void closeApp ( GtkWidget *window, gpointer data)
{
    gtk_main_quit();
}

int main (int argc, char *argv[])
{
    GtkWidget *window;
    GtkWidget *spinbutton;
    GtkObject *adjustment;
    gtk_init (&argc, &argv);
    window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
    gtk_window_set_default_size ( GTK_WINDOW(window), 300, 200);
    g_signal_connect ( GTK_OBJECT (window), "destroy",
                      GTK_SIGNAL_FUNC ( closeApp), NULL);
    adjustment = gtk_adjustment_new(100.0, 50.0, 150.0, 0.5, 0.05, 0.05); 
    spinbutton = gtk_spin_button_new(GTK_ADJUSTMENT(adjustment), 0.01, 2);
    gtk_container_add(GTK_CONTAINER(window), spinbutton);
    gtk_widget_show_all(window);
    gtk_main ();
    return 0;
}

这个示例创建了一个范围在 50 到 150 之间的 GtkSpinButton

4.5 不同类型的 GtkButton

4.5.1 GtkToggleButton

GtkToggleButton GtkButton 类似,但它有状态,可以是开启或关闭。用户点击时,它会发出 “clicked” 信号并切换状态。相关 API 如下:
| 函数 | 描述 |
| ---- | ---- |
| GtkWidget* gtk_toggle_button_new (void) | 创建一个新的 GtkToggleButton |
| GtkWidget* gtk_toggle_button_new_with_label (const gchar *label) | 创建一个带标签的 GtkToggleButton |
| gboolean gtk_toggle_button_get_active (GtkToggleButton *toggle_button) | 获取按钮的状态 |
| void gtk_toggle_button_set_active (GtkToggleButton *toggle_button, gboolean is_active) | 设置按钮的状态 |

4.5.2 GtkCheckButton

GtkCheckButton GtkToggleButton 的一种特殊形式,外观为复选框,功能上没有区别。创建函数如下:
| 函数 | 描述 |
| ---- | ---- |
| GtkWidget* gtk_check_button_new (void) | 创建一个新的 GtkCheckButton |
| GtkWidget* gtk_check_button_new_with_label (const gchar *label) | 创建一个带标签的 GtkCheckButton |

4.5.3 GtkRadioButton

GtkRadioButton 可以分组,同一组中只能选择一个选项。相关函数如下:
| 函数 | 描述 |
| ---- | ---- |
| GtkWidget* gtk_radio_button_new (GSList *group) | 创建一个 GtkRadioButton 并指定组 |
| GtkWidget* gtk_radio_button_new_from_widget (GtkRadioButton *group) | 根据现有按钮创建同组的 GtkRadioButton |
| GtkWidget* gtk_radio_button_new_with_label (GSList *group, const gchar *label) | 创建一个带标签的 GtkRadioButton 并指定组 |
| void gtk_radio_button_set_group (GtkRadioButton *radio_button, GSList *group) | 设置按钮所在的组 |
| GSList* gtk_radio_button_get_group (GtkRadioButton *radio_button) | 获取按钮所在的组 |

4.5.4 示例:不同类型的 GtkButton
#include <gtk/gtk.h>
#include <stdio.h>

GtkWidget *togglebutton;
GtkWidget *checkbutton;
GtkWidget *radiobutton1, *radiobutton2;

void closeApp ( GtkWidget *window, gpointer data)
{
    gtk_main_quit();
}

void add_widget_with_label(GtkContainer * box, gchar * caption, GtkWidget * widget)
{
    GtkWidget *label = gtk_label_new (caption);
    GtkWidget *hbox = gtk_hbox_new (TRUE, 4);
    gtk_container_add(GTK_CONTAINER (hbox), label);
    gtk_container_add(GTK_CONTAINER (hbox), widget);
    gtk_container_add(box, hbox);
}

void print_active(char * button_name, GtkToggleButton *button)
{
    gboolean active = gtk_toggle_button_get_active(button);
    printf("%s is %s\n", button_name, active?"active":"not active");
}

void button_clicked(GtkWidget *button, gpointer data)
{
    print_active("Checkbutton", GTK_TOGGLE_BUTTON(checkbutton));
    print_active("Togglebutton", GTK_TOGGLE_BUTTON(togglebutton));
    print_active("Radiobutton1", GTK_TOGGLE_BUTTON(radiobutton1));
    print_active("Radiobutton2", GTK_TOGGLE_BUTTON(radiobutton2)); 
    printf("\n");
}

gint main (gint argc, gchar *argv[])
{
    GtkWidget *window;
    GtkWidget *button;       
    GtkWidget *vbox;
    gtk_init (&argc, &argv);
    window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
    gtk_window_set_default_size(GTK_WINDOW(window), 200, 200);
    g_signal_connect ( GTK_OBJECT (window), "destroy",
                      GTK_SIGNAL_FUNC (closeApp), NULL);
    button = gtk_button_new_with_label("Ok");
    togglebutton = gtk_toggle_button_new_with_label("Toggle");
    checkbutton = gtk_check_button_new(); 
    radiobutton1 = gtk_radio_button_new(NULL);
    radiobutton2 = gtk_radio_button_new_from_widget(GTK_RADIO_BUTTON(radiobutton1));
    vbox = gtk_vbox_new (TRUE, 4); 
    add_widget_with_label (GTK_CONTAINER(vbox), "ToggleButton:", togglebutton);
    add_widget_with_label (GTK_CONTAINER(vbox), "CheckButton:", checkbutton);
    add_widget_with_label (GTK_CONTAINER(vbox), "Radio 1:", radiobutton1);
    add_widget_with_label (GTK_CONTAINER(vbox), "Radio 2:", radiobutton2);
    add_widget_with_label (GTK_CONTAINER(vbox), "Button:", button);
    g_signal_connect(GTK_OBJECT(button), "clicked", 
                     GTK_SIGNAL_FUNC(button_clicked), NULL);
    gtk_container_add(GTK_CONTAINER(window), vbox);
    gtk_widget_show_all(window);
    gtk_main ();
    return 0;
}

这个示例展示了如何使用不同类型的 GtkButton ,并通过点击 “Ok” 按钮查看各个按钮的状态。

4.6 GtkTreeView

GtkTreeView 是 GTK+ 2 中的新型组件,用于创建数据的列表和树视图,如电子表格或文件管理器中的视图。它封装了大量功能,可以创建复杂的数据视图,混合文本和位图图形,甚至使用 GtkEntry 组件输入数据。

GtkTreeView 家族由四个组件组成:
- GtkTreeView :树和列表视图
- GtkTreeViewColumn :表示列表或树的列
- GtkCellRenderer :控制单元格的绘制
- GtkTreeModel :表示树和列表数据

这里采用了 Model/View/Controller(MVC)设计模式,将视图和模型分离,数据可以由不同视图同时渲染,避免不必要的重复。

4.6.1 创建 GtkTreeStore

GtkTreeStore 用于存储多级数据,如目录层次结构。创建时需要指定列数和每列的类型,示例如下:

Gtkwidget *store = gtk_tree_store_new (3, G_TYPE_STRING, G_TYPE_INT, G_TYPE_BOOLEAN);

添加数据时,需要使用 GtkTreeIter 结构指向新行,示例如下:

GtkTreeIter   iter;
gtk_tree_store_append (store, &iter, NULL);   
gtk_tree_store_set (store, &iter,
                    0, "Def Leppard",
                    1, 1987,
                    2, TRUE,
                    -1);
4.6.2 创建 GtkTreeView

创建 GtkTreeView 时,需要传入 GtkTreeStore GtkListStore 模型,示例如下:

GtkWidget *view = gtk_tree_view_new_with_model (GTK_TREE_MODEL (store));

配置视图时,需要为每列定义 GtkCellRenderer 并设置数据源,示例如下:

GtkCellRenderer *renderer = gtk_cell_renderer_text_new ();
gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW(view),
                                             0,
                                             "This is the column title", 
                                             renderer,
                                             "text", 0,
                                             NULL);
4.6.3 示例:GtkTreeView 的使用
#include <gtk/gtk.h>

enum {
    COLUMN_TITLE,
    COLUMN_ARTIST,
    COLUMN_CATALOGUE,
    N_COLUMNS
};

void closeApp ( GtkWidget *window, gpointer data)
{
    gtk_main_quit();
} 

int main (int argc, char *argv[])
{
    GtkWidget *window; 
    GtkTreeStore *store;
    GtkWidget *view;
    GtkTreeIter parent_iter, child_iter;
    GtkCellRenderer *renderer;
    gtk_init(&argc, &argv);
    window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
    gtk_window_set_default_size ( GTK_WINDOW(window), 300, 200);
    g_signal_connect ( GTK_OBJECT (window), "destroy",
                      GTK_SIGNAL_FUNC ( closeApp), NULL);
    store = gtk_tree_store_new (N_COLUMNS, G_TYPE_STRING,  G_TYPE_STRING, G_TYPE_STRING);
    gtk_tree_store_append (store, &parent_iter, NULL); 
    gtk_tree_store_set (store, &parent_iter, COLUMN_TITLE, "Dark Side of the Moon",
                        COLUMN_ARTIST, "Pink Floyd",
                        COLUMN_CATALOGUE, "B000024D4P",
                        -1);
    gtk_tree_store_append (store, &child_iter, &parent_iter);
    gtk_tree_store_set (store, &child_iter, COLUMN_TITLE, "Speak to Me",
                        -1);
    view = gtk_tree_view_new_with_model (GTK_TREE_MODEL (store));
    renderer = gtk_cell_renderer_text_new ();
    gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW(view),
                                                 COLUMN_TITLE,
                                                 "Title", renderer,
                                                 "text", COLUMN_TITLE,
                                                 NULL);
    gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW(view),
                                                 COLUMN_ARTIST,
                                                 "Artist", renderer,
                                                 "text", COLUMN_ARTIST,
                                                 NULL);
    gtk_container_add(GTK_CONTAINER(window), view);
    gtk_widget_show_all(window);
    gtk_main ();
    return 0;
}

这个示例展示了如何使用 GtkTreeView 创建一个简单的树视图,包含父行和子行,并设置列的标题和数据源。

综上所述,掌握这些常见的 GTK+ 组件及其 API,能够帮助开发者创建出功能丰富、布局合理的 GUI 应用程序。在实际开发中,可以根据具体需求选择合适的组件,并结合信号和回调函数实现交互功能。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值