GTK+helloword详解

文章原地址:https://blog.mydata.top/index.php/gtkplus0002.html

源码

在上一篇文章中,写了一个helloword来验证GTK+的开发环境是否正常。这一篇,我们一起来详细看一下这个程序的细节。代码如下:

#include <gtk/gtk.h>

static void print_hello (GtkWidget *widget, gpointer   data)
{
     g_print ("Hello World\n");
}

static void activate(GtkApplication *app, gpointer user_data)
{
     GtkWidget *window;
     GtkWidget *button;

     window = gtk_application_window_new(app);
     gtk_window_set_title(GTK_WINDOW(window), "Window");
     gtk_window_set_default_size(GTK_WINDOW(window), 480, 360);
     gtk_container_set_border_width(GTK_CONTAINER(window), 28);

     button = gtk_button_new_with_label("Hello World");
     g_signal_connect(button, "clicked", G_CALLBACK(print_hello), NULL);

     gtk_container_add(GTK_CONTAINER(window), button);
     gtk_widget_show_all(window);
}

int main(int argc, char **argv)
{
     GtkApplication *app;
     int status;

     app = gtk_application_new("org.gtk.helloword", G_APPLICATION_FLAGS_NONE);
     g_signal_connect(app, "activate", G_CALLBACK(activate), NULL);
     status = g_application_run(G_APPLICATION(app), argc, argv);
     g_object_unref(app);

     return status;
}

头文件

首当其冲的就是include一下GTK+的头文件。如果你去gtk的头文件目录msys64\mingw64\include\gtk-3.0\gtk中看一下就会发现,gtk有很多很多的头文件。在以后写程序的过程中,我们会经常跟这些头文件打交道。比如你实现一个窗口,就一定会用到gtkwindow.h,实现菜单就会用到gtkmenu.hgtkmenubar.hgtkmenubutton.h…不过,我们不会也不允许直接include这些头文件,而是直接include gtk.h。这个头文件再把其他的头文件include进来。

入口函数

然后,我们定义了三个函数,main就不用多讲了,这是C/C++语言所写应用程序的入口函数。需要注意的是,通过main入口的应用程序其实是控制台界面的。虽然我们写的gtk程序是图形界面的,但是,在Windows下,依旧会出现一个console窗口。这个需要通过用WinMain入口函数解决。代码如下:


#ifdef WIN32

#include <windows.h>

extern "C" int APIENTRY WinMain(HINSTANCE, HINSTANCE, LPSTR /*cmdParamarg*/, int /* cmdShow */) {
    int argc;
    wchar_t **argvW = CommandLineToArgvW(GetCommandLineW(), &argc);
    if (!argvW)
        return -1;
    char **argv = new char *[argc + 1];
    for (int i = 0; i < argc; ++i) {
        auto iSize = WideCharToMultiByte(CP_ACP, 0, argvW[i], -1, nullptr, 0, nullptr, nullptr);
        argv[i] = (char *) malloc(iSize * sizeof(char));
        WideCharToMultiByte(CP_ACP, 0, argvW[i], -1, argv[i], iSize, nullptr, nullptr);
    }
    argv[argc] = nullptr;
    LocalFree(argvW);
    const int exitCode = main(argc, argv);
    for (int i = 0; i < argc && argv[i]; ++i) {
        free(argv[i]);
    }
    delete[] argv;
    return exitCode;
}
#endif

上面这个函数,就是windows下图形程序的入口函数。我们这儿仅仅是用它做入口,然后,再调用控制台入口函数main。所以,整个函数被包在WIN32宏里面,仅仅在Windows下使用。

当然,并不是仅仅定义这个函数就可以了,还需要在编译时做一点点工作。

target_link_libraries(${PROJECT_NAME} -Wl,-subsystem,windows)

很简单,就是告诉编译器,我们使用的是windows子系统,程序启动时会首先调用WinMain函数。如果不指定就是console,程序启动时会首先调用main函数。

main函数中,首先需要创建一个GtkApplication对象。不过,需要留意的是GTK+是C语言库,没有类和对象的语法。所以,GTK提供了gtk_application_new函数来创建GtkApplication对象。后面,调用g_signal_connectg_application_run等函数时,第一个参数就是对象指针。其实,gtk_application_new就是C++里面new+类构造函数,而对象指针就是C++里面的this指针。虽然GTK时C语言写的,没有类和对象,但是,还是使用了类和对象的概念的。关于gtk_application_new的详细用法可以参考:GTK文档。这儿就不再深入讨论了。

信号

接下来我们通过,g_signal_connect将函数activate关联到了GtkApplication对象的activate信号中。换句话说,当发生activate信号时,gtk就会调用咱们定义的函数activate

这儿出现了一个新的概念—信号。信号是图形编程里绕不开的概念。包括Qt、MFC、GTK+在内的绝大多数图形界面库,都是使用的事件驱动。所谓,事件驱动就是,程序中的函数,是通过相应事件的方式来执行的。而事件是否用户、系统或者程序自身发起的。比如用户点击鼠标这个动作。鼠标将电信号转化为数字信号,通过USB、蓝牙或者wifi,传递到操作系统内核驱动中,操作系统内核驱动在将这个信号传递给X11等图形子系统,接着X11处理这个信号,判断需要将这个信号传递给窗口管理器还是某个应用程序窗口。如果是传递给应用程序窗口,就会被GTK+的主循环程序接收到。GTK+的主循环程序再将这个信号传递到该信号的处理函数。于是,咱们写的消息处理函数就被调用执行,完成了事件响应。

GtkApplication对象被激活时就会触发activate信号,于是,就会调用咱们关联的信号处理函数activate了。那么,啥时候会触发activate信号呢。文档中说是在g_application_activate函数中。单独看文档会很困惑,咱们的helloword程序中并没有调用这个函数。其实,这个函数是在GTK+内部被调用的。对我们而言,需要调用的是g_application_run函数。

GtkApplication有很多信号,具体列表可以在文档

主循环

我们可以将g_application_run函数理解GTK程序真正的入口函数。这个函数比较复杂。当前,我们只需要记得,它解析了命令行参数,然后实现了一个事件主循环。关于命令行参数,这儿先不展开讲。咱们先聊一聊,GTK+的事件主循环。

每个图形应用程序都是为了响应用户的操作而设计的。当用户不做任何动作时,应用程序只会做两件事件——后台任务和等待用户操作。如时钟事件、工作线程等等,都是后台任务。界面的程序的主要任务时等待用户的操作,比如点击鼠标、键盘等等。在g_application_run函数中就会有一个类似的代码块:

while(true){
    等待信号;
    获取信号;
    分发并处理信号;
}

这个循环是所有图形程序的核心处理逻辑。在g_application_run中,就会调用GTK+文档中说的g_application_activate函数,这个函数触发了activate信号。再由事件主循环块获取到并处理。

我们一直没有解释事件的概念。我查阅了不少资料,并没有找到事件的详细定义。通常,我们事件理解为信号及信号处理函数组成的整体。换句话说,信号是个名词,事件是个动作,用来描述信号及其处理过程。

信号处理函数

前面我们提到函数activateg_application_runactivate信号处理函数。信号处理函数可以有多个,当信号发生时,GTK+会依次调用这些信号处理函数。一个信号处理函数也关联多次,当信号发生时,GTK+也会多次调用该信号处理函数。

函数print_hello也是一个信号处理函数。不同的是,函数print_hello是button对象的信号clicked处理函数。

在函数activate中,首先使用gtk_application_window_new创建了一个应用级别的窗口。单纯讲创建窗口,一般使用的是gtk_window_new。但是,在GTK+框架中,至少要有一个应用级别的窗口,否则主循环就会自动推出。

然后,通过gtk_window_set_title设置窗口标题,通过gtk_window_set_default_size设置窗口的默认大小,通过gtk_container_set_border_width设置容器内容的边界。注意:这个设置的是容器内边界,而不是窗口边框。gtk_window_set_titlegtk_window_set_default_size都是GtkWindow对象的操作函数。类似的操作函数还有很多,具体可以参考官方文档。另外,通过官方文档我们可以看到它的继承关系为:

GObject
GInitiallyUnowned
GtkWidget
GtkContainer
GtkWindow
AtkImplementorIface
GtkBuildable

所以,我们也可以调用GtkContainerGtkWidget等父类的操作函数。不过,因为是C语言,我们需要通过GTK_CONTAINERGTK_WIDGETGtkWindow指针对应操作函数需要的指针。

接下来是通过gtk_button_new_with_label创建一个带标签的按钮,然后,将函数print_hello关联到按钮的clicked信号上面。这样当用户点击按钮时就会调用函数print_hello

不过,按钮只是创建了,还需要通过函数gtk_container_add将其添加到窗口中,否则,按钮不会被显示出来。但是,仅仅添加到窗口中还不够,因为,GTK+中所有的控件默认都是不可见的,我们还需要通过调用函数gtk_widget_show_all将窗口中所有的子控件显示出来。在我们这个例子中函数gtk_widget_show_all可以用下面的代码代替:

    gtk_widget_show(GTK_WIDGET(button));
    gtk_widget_show(GTK_WIDGET(window));

如果你并不想把窗口中所有的子控件都显示出来,就可以通过这种方法。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值