本文将介绍如何使用GTK(GIMP Toolkit)创建一个简单的计算器应用程序。通过这个例子,你将学习如何构建基本的图形用户界面,并了解GTK的一些常用组件和回调函数的使用。
首先,确保你已经安装了GTK和Pango库。你可以通过在终端中运行以下命令来安装它们(适用于Ubuntu和Debian系统):
sudo apt-get install libgtk-3-dev libpango1.0-dev
我们的项目将包含以下文件:
main.c
:包含主函数和回调函数的源代码文件。expression_parser.c
:一个包含用于计算表达式的函数的源代码文件。下面是一个简单的计算器应用程序的实现代码。你可以将代码保存到一个名为main.c
的文件中。
#include
#include
#include "expression_parser.c"
// 回调函数:当按下数字或操作符按钮时调用
void button_clicked(GtkWidget *button, gpointer data);
// 回调函数:当按下等号按钮时调用
void equal_button_clicked(GtkWidget *button, gpointer data);
// 设置窗口在屏幕上居中显示
void set_window_center(GtkWidget *window);
// 创建数字和操作符按钮
GtkWidget* create_button(const gchar *label, GtkWidget *entry);
int main(int argc, char *argv[]) {
gtk_init(&argc, &argv);
// 创建主窗口
GtkWidget *window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
g_signal_connect(window, "destroy", G_CALLBACK(gtk_main_quit), NULL);
gtk_container_set_border_width(GTK_CONTAINER(window), 30);
gtk_window_set_default_size(GTK_WINDOW(window), 800, 800);
set_window_center(window);
// 创建垂直布局容器
GtkWidget *vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 15);
gtk_container_add(GTK_CONTAINER(window), vbox);
// 创建结果显示文本框
GtkWidget *entry = gtk_entry_new();
gtk_entry_set_alignment(GTK_ENTRY(entry), 1);
PangoFontDescription *font_desc = pango_font_description_from_string("Sans Bold 60");
gtk_widget_override_font(entry, font_desc);
gtk_box_pack_start(GTK_BOX(vbox), entry, TRUE, TRUE, 0);
// 创建网格布局容器
GtkWidget *grid = gtk_grid_new();
gtk_grid_set_row_spacing(GTK_GRID(grid), 5);
gtk_grid_set_column_spacing(GTK_GRID(grid), 5);
gtk_box_pack_start(GTK_BOX(vbox), grid, TRUE, TRUE, 0);
// 数字和操作符按钮的标签数组
const gchar *buttons[] = {
"7", "8", "9", "/",
"4", "5", "6", "*",
"1", "2", "3", "-",
"0", ".", "%", "+"
};
int button_index = 0;
for (int row = 0; row < 4; row++) {
for (int col = 0; col < 4; col++) {
// 创建按钮并连接回调函数
GtkWidget *button = create_button(buttons[button_index], entry);
gtk_grid_attach(GTK_GRID(grid), button, col, row, 1, 1);
button_index++;
}
}
// 创建等号按钮并连接回调函数
GtkWidget *equal_button = create_button("=", entry);
gtk_grid_attach(GTK_GRID(grid), equal_button, 0, 4, 4, 1);
g_signal_connect(equal_button, "clicked", G_CALLBACK(equal_button_clicked), entry);
// 设置容器的homogeneous属性为TRUE,使子组件均匀分布并随窗口大小进行缩放
gtk_box_set_homogeneous(GTK_BOX(vbox), TRUE);
// 显示窗口及其所有子组件
gtk_widget_show_all(window);
// 进入GTK主循环
gtk_main();
return 0;
}
// 按钮点击事件的回调函数
void button_clicked(GtkWidget *button, gpointer data) {
const gchar *label = gtk_button_get_label(GTK_BUTTON(button));
GtkWidget *entry = GTK_WIDGET(data);
const gchar *text = gtk_entry_get_text(GTK_ENTRY(entry));
gchar *new_text = g_strdup_printf("%s%s", text, label);
gtk_entry_set_text(GTK_ENTRY(entry), new_text);
g_free(new_text);
}
// 等号按钮点击事件的回调函数
void equal_button_clicked(GtkWidget *button, gpointer data) {
GtkWidget *entry = GTK_WIDGET(data);
const gchar *text = gtk_entry_get_text(GTK_ENTRY(entry));
// 检查按钮文本是否为等号
const gchar *equal_sign = "=";
if (strcmp(gtk_button_get_label(GTK_BUTTON(button)), equal_sign) != 0) {
// 不是等号按钮,直接将按钮文本追加到文本框中
gchar *new_text = g_strdup_printf("%s%s", text, equal_sign);
gtk_entry_set_text(GTK_ENTRY(entry), new_text);
g_free(new_text);
return;
}
// 执行计算操作
double result = eval_expression(text);
if (!isnan(result) && !isinf(result)) {
gchar *result_str = g_strdup_printf("%g", result);
gtk_entry_set_text(GTK_ENTRY(entry), result_str);
g_free(result_str);
} else {
gtk_entry_set_text(GTK_ENTRY(entry), "Error");
}
}
// 设置窗口在屏幕上居中显示
void set_window_center(GtkWidget *window) {
GdkDisplay *display = gdk_display_get_default();
GdkMonitor *monitor = gdk_display_get_primary_monitor(display);
GdkRectangle monitor_rect;
gdk_monitor_get_geometry(monitor, &monitor_rect);
gint screen_width = monitor_rect.width;
gint screen_height = monitor_rect.height;
gint window_width, window_height;
gtk_window_get_size(GTK_WINDOW(window), &window_width, &window_height);
gint x = (screen_width - window_width) / 2;
gint y = (screen_height - window_height) / 2;
gtk_window_move(GTK_WINDOW(window), x, y);
}
// 创建按钮并连接回调函数
GtkWidget* create_button(const gchar *label, GtkWidget *entry) {
GtkWidget *button = gtk_button_new_with_label(label);
g_signal_connect(button, "clicked", G_CALLBACK(button_clicked), entry);
gtk_widget_set_hexpand(button, TRUE);
gtk_widget_set_vexpand(button, TRUE);
return button;
}
在上面的代码中,我们使用了GTK提供的一些函数和宏来创建窗口、布局容器、文本框和按钮等组件。其中,button_clicked
函数用于处理数字和操作符按钮的点击事件,equal_button_clicked
函数用于处理等号按钮的点击事件。
下面是一个简单的表达式解析器的实现代码,可以计算包含四则运算的数学表达式,你可以将代码保存到一个名为expression_parser.c
的文件中。
首先,遍历表达式字符串的每个字符,根据字符的类型进行相应的处理。如果是空格,则忽略;如果是数字,则将连续的数字字符解析为一个操作数,并将其入栈;如果是运算符,则根据其优先级与栈顶的运算符进行比较,如果栈顶的运算符优先级较高或相等,则从栈中弹出运算符和操作数进行计算,并将计算结果入栈。
最后,当所有字符处理完毕后,还可能剩余一些运算符和操作数没有处理,此时需要对它们进行计算。循环从栈中取出运算符和操作数,执行相应的计算,并将结果入栈,直到栈中只剩下一个元素,即为最终的计算结果。
#include
#include
#include
#include
double eval_expression(const char* expression);
// 获取运算符的优先级
int get_operator_priority(char op) {
switch (op) {
case '+':
case '-':
return 1;
case '*':
case '/':
case '%':
return 2;
default:
return 0;
}
}
// 执行二元运算
double perform_operation(double left_operand, char op, double right_operand) {
switch (op) {
case '+':
return left_operand + right_operand;
case '-':
return left_operand - right_operand;
case '*':
return left_operand * right_operand;
case '/':
return left_operand / right_operand;
case '%':
return fmod(left_operand, right_operand);
default:
return 0.0;
}
}
// 检查字符是否为数字
bool is_digit(char ch) {
return (ch >= '0' && ch <= '9');
}
// 计算表达式的结果
double eval_expression(const char* expression) {
int length = strlen(expression);
double operands[length]; // 操作数栈
char operators[length]; // 运算符栈
int operand_top = -1; // 操作数栈顶指针
int operator_top = -1; // 运算符栈顶指针
for (int i = 0; i < length; i++) {
char current = expression[i];
if (current == ' ') {
continue; // 忽略空格
} else if (is_digit(current)) {
// 解析数字
double operand = 0.0;
int decimal_places = 0;
while (is_digit(expression[i]) || expression[i] == '.') {
if (expression[i] == '.') {
decimal_places = 1;
} else {
if (decimal_places > 0) {
operand += (expression[i] - '0') / (10.0 * decimal_places);
decimal_places *= 10;
} else {
operand = operand * 10 + (expression[i] - '0');
}
}
i++;
}
operands[++operand_top] = operand;
i--; // 回退一个字符,因为 for 循环会再自增
} else {
// 解析运算符
while (operator_top >= 0 && get_operator_priority(operators[operator_top]) >= get_operator_priority(current)) {
double right_operand = operands[operand_top--];
double left_operand = operands[operand_top--];
char op = operators[operator_top--];
double result = perform_operation(left_operand, op, right_operand);
operands[++operand_top] = result;
}
operators[++operator_top] = current;
}
}
// 处理剩余的运算符和操作数
while (operator_top >= 0) {
double right_operand = operands[operand_top--];
double left_operand = operands[operand_top--];
char op = operators[operator_top--];
double result = perform_operation(left_operand, op, right_operand);
operands[++operand_top] = result;
}
return operands[0];
}
代码中的eval_expression
函数接受一个字符串参数expression
,该字符串表示一个数学表达式。函数通过遍历字符串的每个字符,将表达式中的操作数和运算符进行解析和计算,最终返回表达式的结果。
代码中的get_operator_priority
函数用于获取运算符的优先级,根据运算符的不同,返回不同的优先级值。优先级用于确定运算符的计算顺序。
perform_operation
函数用于执行两个操作数之间的二元运算,根据运算符的不同,执行相应的加法、减法、乘法或除法运算,并返回运算结果。
is_digit
函数用于检查一个字符是否是数字,通过判断字符的ASCII码值是否在数字字符的范围内来确定。
在eval_expression
函数中,代码使用两个栈来解析和计算表达式。operands
数组作为操作数栈,存储解析得到的操作数;operators
数组作为运算符栈,存储解析得到的运算符。
要编译该程序,你可以使用以下命令:
gcc main.c -o calculator `pkg-config --cflags --libs gtk+-3.0` -lm
然后,在终端中运行可执行文件:
./calculator
这将启动计算器应用程序并显示一个简单的界面,你可以通过点击按钮进行数字输入和运算。
通过这个简单的计算器示例,你学习了如何使用GTK创建基本的图形用户界面。GTK提供了丰富的功能和组件,可用于开发各种类型的应用程序。希望这个例子能够帮助你入门GTK开发,并为你构建更复杂的应用程序打下基础。
你可以在GTK官方文档中找到更多关于GTK的详细信息和示例代码。祝你在GTK项目中取得成功!