使用GTK创建简易计算器
本文将介绍如何使用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 <gtk/gtk.h>
#include <pango/pango-font.h>
#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 <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>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项目中取得成功!