使用GTK创建简易计算器

使用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 
#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创建简易计算器_第1张图片

总结

通过这个简单的计算器示例,你学习了如何使用GTK创建基本的图形用户界面。GTK提供了丰富的功能和组件,可用于开发各种类型的应用程序。希望这个例子能够帮助你入门GTK开发,并为你构建更复杂的应用程序打下基础。

你可以在GTK官方文档中找到更多关于GTK的详细信息和示例代码。祝你在GTK项目中取得成功!

你可能感兴趣的:(GTK,c,GTK,计算器)