gtk / gtkmm学习记录

news/2024/7/5 5:11:09

参考资料

https://github.com/ToshioCP/Gtk4-tutorial/blob/main/gfm

第一个程序

1. 创建一个程序

#include <gtk/gtk.h>

int main (int argc, char **argv)
{
	GtkApplication *app = gtk_application_new ("com.hello", G_APPLICATION_DEFAULT_FLAGS);
	int stat = g_application_run (app, argc, argv);
	g_object_unref (app);
	return stat;
}

这样就是最简程序了:创建一个程序并运行它。

其中"com.hello"是app的id,规则就是域名倒过来,和java的包名规则一样。

然而,编译运行后你会看到这样的警告:

(main:34508): GLib-GIO-WARNING **: 17:21:46.809: Your application does not implement g_application_activate() and has no handlers connected to the 'activate' signal.  It should do one of these.

不用急,程序至少已经正常运行了。
这个警告告诉我们:

  1. You didn’t implement g_application_activate()
  2. And no handlers connected to the activate signal
  3. You should do one of these.

这两个错误原因都与信号有关。因此,先解释一下。
发生某些事情时会发出信号,比如创建/销毁一个窗口。 当程序被activated时(被激活),会发出activate信号。(激活并不等于启动,但是可以暂且认为相同)

如果信号连接到一个函数,这个函数被称为信号处理程序。

流程是这样的:

  1. 有事情发生。
  2. 如果它与某个信号有关,那么该信号就会被发射。
  3. 如果信号已经预先连接到处理程序,则调用处理程序。
    信号在对象中定义。例如,activate信号属于 GApplication 对象,它是 GtkApplication 对象的父对象。

GApplication 对象是 GObject 对象的子对象。GObject 是所有对象层次结构中的顶级对象。

GObject -- GApplication -- GtkApplication
<---parent                      --->child

子对象从其父对象继承信号、函数、属性等。所以,GtkApplication 也有activate信号。

2. 处理activate信号

#include <gtk/gtk.h>


static void
app_activate (GApplication *app, gpointer *user_date)
{
	g_print ("hello world\n");
}
int main (int argc, char **argv)
{
	GtkApplication *app =
	    gtk_application_new ("com.hello", G_APPLICATION_DEFAULT_FLAGS);
	g_signal_connect (app, "activate", G_CALLBACK (app_activate), NULL);
	int stat = g_application_run (G_APPLICATION (app), argc, argv);
	g_object_unref (app);
	return stat;
}

g_signal_connect()有4个参数:

  1. 信号所属的实例。
  2. 信号的名称。
  3. 处理函数(也称为回调),需要用G_CALLBACK来强制转换.
  4. 要传递给处理程序的数据。如果不需要数据,则应给出 NULL。

3. GtkWindow

前面还是没有图形话界面。
所以这里加上:

#include <gtk/gtk.h>


static void
on_app_activate (GApplication *app, gpointer user_data)
{
	GtkWidget *win = gtk_window_new();
	gtk_window_set_application (GTK_WINDOW (win), GTK_APPLICATION (app));
	gtk_window_present (GTK_WINDOW (win));
}

int main (int argc, char **argv)
{
	GtkApplication *app =
	    gtk_application_new ("com.hello", G_APPLICATION_DEFAULT_FLAGS);
	g_signal_connect (G_APPLICATION (app), "activate", G_CALLBACK (on_app_activate), NULL);
	int stat = g_application_run (G_APPLICATION (app), argc, argv);
	g_object_unref (app);
	return stat;
}

Widget 是一个抽象的概念,包括所有的GUI 界面,如窗口、对话框、按钮、多行文本、容器等。GtkWidget 是一个基础对象,所有 GUI 对象都从中派生。

parent <-----> child
GtkWidget -- GtkWindow

GtkWindow 在其对象的顶部包含 GtkWidget

在这里插入图片描述

该函数gtk_window_new定义如下。

GtkWidget *
gtk_window_new (void);

根据这个定义,它返回一个指向 GtkWidget 的指针,而不是 GtkWindow。它实际上创建了一个新的 GtkWindow 实例(不是 GtkWidget),但返回一个指向 GtkWidget 的指针。但是,该指针指向 GtkWidget,同时它也指向其中包含 GtkWidgetGtkWindow

如果要用作win指向 GtkWindow 类型实例的指针,则需要对其进行强制转换。

(GtkWindow *) win

它有效,但通常不使用。相反,使用宏GTK_WINDOW

GTK_WINDOW (win)

建议使用宏,因为它不仅会转换指针,还会检查类型。

您可以使用gtk_widget_set_visible (win, true)而不是gtk_window_present。但是这两者的行为是不同的。假设屏幕上有win1和win2两个窗口,win1在win2的后面。两个窗口都可见。该函数gtk_widget_set_visible (win1, true)不执行任何操作,因为 win1 已经可见。因此,win1 仍然落后于 win2。另一个函数gtk_window_present (win1)将 win1 移动到窗口堆栈的顶部。因此,如果你想呈现窗口,你应该使用gtk_window_present.

两个函数gtk_widget_showgtk_widget_hide自 GTK 4.10 起已弃用。你应该改用gtk_widget_set_visible

在这里插入图片描述

4. GtkApplicationWindow

GtkApplicationWindowGtkWindow 的子对象。它有一些额外的功能可以更好地与 GtkApplication 集成。建议将其用作应用程序的顶层窗口,而不是 GtkWindow

1 static void
2 app_activate (GApplication *app, gpointer user_data) {
3   GtkWidget *win;
4 
5   win = gtk_application_window_new (GTK_APPLICATION (app));
6   gtk_window_set_title (GTK_WINDOW (win), "pr4");
7   gtk_window_set_default_size (GTK_WINDOW (win), 400, 300);
8   gtk_window_present (GTK_WINDOW (win));
9 }

当创建 GtkApplicationWindow 时,需要将 GtkApplication 实例作为参数提供。然后它会自动连接这两个实例。所以不需要再调用gtk_window_set_application

在这里插入图片描述

GtkLabel, GtkButton, GtkBox

1.GtkLabel

显示一个标签

#include <gtk/gtk.h>


static void
app_activate (GApplication *app)
{
	GtkWindow *win = GTK_WINDOW (gtk_application_window_new (GTK_APPLICATION (app)));
	gtk_window_set_title (win, "hello window");
	gtk_window_set_default_size (win, 400, 300);
	
	GtkLabel *label = GTK_LABEL (gtk_label_new ("hello label"));
	gtk_window_set_child (win, GTK_WIDGET (label));
	gtk_window_present (win);
}

int main (int argc, char **argv)
{
	GtkApplication *app =
	    gtk_application_new ("top.hello", G_APPLICATION_DEFAULT_FLAGS);
	g_signal_connect (app, "activate", G_CALLBACK (app_activate), NULL);
	int stat = g_application_run (G_APPLICATION (app), argc, argv);
	g_object_unref (app);
	return stat;
}

在这里插入图片描述

2. GtkButton

点击按钮关闭窗口。

#include <gtk/gtk.h>

static void
click_btn (GtkButton *btn, GtkWindow *win)
{
	gtk_window_destroy (win);
}

static void
app_activate (GtkApplication *app)
{
	GtkWindow *win = GTK_WINDOW (gtk_application_window_new (app));
	gtk_window_set_title (win, "hello window");
	gtk_window_set_default_size (win, 400, 300);
	
	GtkWidget *btn = gtk_button_new();
	gtk_button_set_label (GTK_BUTTON (btn), "button");
	gtk_window_set_child (win, btn);
	
	g_signal_connect (btn, "clicked", G_CALLBACK (click_btn), win);
	
	gtk_window_present (win);
}

int main (int argc, char  **argv)
{
	GtkApplication *app =
	    gtk_application_new ("top.hello", G_APPLICATION_DEFAULT_FLAGS);
	g_signal_connect (app, "activate", G_CALLBACK (app_activate), NULL);
	int stat = g_application_run (G_APPLICATION (app), argc, argv);
	g_object_unref (app);
	return stat;
}

在这里插入图片描述

3. GtkBox

点击hello button会变为hello button!!!,点击close会关闭。

这里需要明白的是,一个window只能有一个孩子,如果要添加多个组件,就要用布局容器。

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

static void
btn_1_clicked (GtkButton *btn)
{
	const char *text = gtk_button_get_label (btn);
	if (strcmp (text, "hello button") == 0)
		gtk_button_set_label (btn, "hello button !!!");
	else
		gtk_button_set_label (btn, "hello button");
}

static void
btn_2_clicked (GtkButton *btn, GtkWindow *win)
{
	gtk_window_destroy (win);
}

static void
app_activate (GtkApplication *app)
{
	GtkWidget *win = gtk_application_window_new (app);
	gtk_window_set_title (GTK_WINDOW (win), "hello window");
	gtk_window_set_default_size (GTK_WINDOW (win), 400, 300);
	
	GtkWidget *btn_1,
	          *btn_2,
	          *box;
	btn_1 = gtk_button_new_with_label ("hello button");
	btn_2 = gtk_button_new_with_label ("close");
	box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 5);
	gtk_box_set_homogeneous (GTK_BOX (box), TRUE);
	gtk_box_append (GTK_BOX (box), btn_1);
	gtk_box_append (GTK_BOX (box), btn_2);
	
	gtk_window_set_child (GTK_WINDOW (win), box);
	
	g_signal_connect (GTK_BUTTON (btn_1), "clicked", G_CALLBACK (btn_1_clicked), NULL);
	g_signal_connect (GTK_BUTTON (btn_2), "clicked", G_CALLBACK (btn_2_clicked), win);
	
	gtk_window_present (GTK_WINDOW (win));
}

int main (int argc, char **argv)
{
	GtkApplication *app =
	    gtk_application_new ("com.hello", G_APPLICATION_DEFAULT_FLAGS);
	g_signal_connect (app, "activate", G_CALLBACK (app_activate), NULL);
	int stat = g_application_run (G_APPLICATION (app), argc, argv);
	g_object_unref (app);
	return stat;
}

在这里插入图片描述



gtkmm

学到这里,已经感觉出来不对劲了,使用成本太高了,要写一堆强制转换宏真的很烦。

虽然不加宏实际上也没什么,但是编译器会警告,会淹没重要信息。

弃了,后面学gtkmm了,用C++。

gtkmm参考资料:
https://gnome.pages.gitlab.gnome.org/gtkmm-documentation/chapter-basics.html

基础知识

1. hello world

#include <gtkmm.h>

class MyWindow : public Gtk::Window {
public:
	MyWindow() {
		set_title ("Hello world window");
		set_default_size (200, 200);
	}
};

int main (int argc, char **argv)
{
	auto app = Gtk::Application::create ("com.hello");
	return app->make_window_and_run<MyWindow> (argc, argv);
}
g++ simple.cc -o simple `pkg-config --cflags --libs gtkmm-4.0` -std=c++17

运行就显示一个空白窗口,标题是hello world window

需要注意的是,直接用#include <gtkmm.h>会引入gtkmm的所有套件,这个头文件有1MB。

更好的方案是引入需要的部分,这里为了学习,我们直接全部包含就行了。

多妙啊,比tmd C语言看起来舒服多了。

2. 组件(控件)

除了可见的组件,还有一些不可见的组件用于布局。例如Gtk::Box, Gtk::Frame
下面是一个添加Gtk::ButtonGtk::Box的例子:

#include <gtkmm/application.h>
#include <gtkmm/box.h>
#include <gtkmm/window.h>
#include <gtkmm/frame.h>
#include <gtkmm/button.h>


class MyWindow : public Gtk::Window {
public:
	MyWindow() {
		set_title ("Hello world window");
		set_default_size (200, 200);
		set_child (m_box);
		m_box.append (m_button_1);
		m_box.append (m_button_2);
		m_box.set_orientation (Gtk::Orientation::VERTICAL);
	}
private:
	Gtk::Button m_button_1;
	Gtk::Button m_button_2;
	Gtk::Box m_box;
};

int main (int argc, char **argv)
{
	auto app = Gtk::Application::create ("com.hello");
	return app->make_window_and_run<MyWindow> (argc, argv);
	
}

在这里插入图片描述

3.信号

与大多数 GUI 工具包一样,gtkmm是事件驱动的。

  1. 当事件发生时,例如按下鼠标按钮,相应的信号将由被按下的 组件发出。
  2. 每个组件都有一组不同的信号可以发出。
  3. 为了使按钮点击产生动作,我们需要设置一个信号处理程序来捕捉按钮的“clicked”信号。

gtkmm使用 libsigc++ 库来实现信号。下面是连接 Gtk::Button"clicked"信号和名为“on_button_clicked”的信号处理程序的示例代码行

m_button1.signal_clicked().connect( sigc::mem_fun(*this,
  &HelloWorld::on_button_clicked) );

信号的更多内容见附录:https://gnome.pages.gitlab.gnome.org/gtkmm-documentation/chapter-signals.html

4. Glib::ustring

您可能会惊讶地发现gtkmm并未std::string在其界面中使用。相反,它使用Glib::ustring,它是如此相似且不引人注目,以至于您实际上可以假设每个Glib::ustring都是 std::string并忽略本节的其余部分。但如果您想在申请中使用英语以外的语言,请继续阅读。

std::string 每个字符使用 8 位,但 8 位不足以对阿拉伯语、中文和日语等语言进行编码。尽管这些语言的编码已由Unicode Consortium指定,但 C 和 C++ 语言尚未为 UTF-8 编码提供任何标准化的 Unicode 支持。GTK 和 GNOME 选择使用 UTF-8 实现 Unicode,这就是 Glib::ustring 所包装的内容。它提供与 std::string 几乎完全相同的接口,以及与 std::string 的自动转换。

UTF-8 的好处之一是除非您愿意,否则您不需要使用它,因此您不需要一次改造所有代码。std::string仍然适用于 7 位 ASCII 字符串。但是,当您尝试将您的应用程序本地化为中文等语言时,您将开始看到奇怪的错误,并可能出现崩溃。然后你需要做的就是开始使用Glib::ustring

请注意,UTF-8 与 8 位编码(如 ISO-8859-1)不兼容。例如,德语变音符号不在 ASCII 范围内,在 UTF-8 编码中需要超过 1 个字节。如果您的代码包含 8 位字符串文字,则必须将它们转换为 UTF-8(例如,巴伐利亚问候语“Grüß Gott”将是“Gr\xC3\xBC\xC3\x9F Gott”)。

您应该避免使用 C 风格的指针算法和 strlen() 等函数。在 UTF-8 中,每个字符可能需要 1 到 6 个字节,因此不能假设下一个字节是另一个字符。Glib::ustring为您担心这方面的细节,因此您可以使用诸如 Glib::ustring::substr() 之类的方法,同时仍然根据字符而不是字节来思考。

与 Windows UCS-2 Unicode 解决方案不同,这不需要任何特殊的编译器选项来处理字符串文字,并且不会导致与 ASCII 不兼容的 Unicode 可执行文件和库。


http://lihuaxi.xjx100.cn/news/1001959.html

相关文章

idea查看java源码

idea查看java源码idea查看源码时总是出现 .class而不是 .java源码方法一&#xff1a;Ctrl&#xff0b;Alt&#xff0b;B查阅源码方法二&#xff1a;Ctrl&#xff0b;鼠标点击设置查看源码查看工具idea查看源码时总是出现 .class而不是 .java源码 方法一&#xff1a; Ctrl&…

排序算法之希尔排序

&#x1f4dd;个人主页&#xff1a;爱吃炫迈 &#x1f48c;系列专栏&#xff1a;数据结构与算法 &#x1f9d1;‍&#x1f4bb;座右铭&#xff1a;快给我点赞赞&#x1f497; 文章目录1. 希尔排序2. 算法思路3. 算法实现4. 算法性能分析&#x1f49e;总结&#x1f49e;1. 希尔排…

O2O、C2C、B2B、B2C是什么意思

一.O2O、C2C、B2B、B2C的区别在哪里&#xff1f; o2o 是 online to offline 分为四种运营模式 1.online to offline 是线上交易到线下消费体验 2.offline to online 是线下营销到线上交易 3.offline to online to offline 是线下营销到线上交易再到线下消费体验 4.online …

xinput1_3.dll缺失了如何去修复?xinput1_3.dll解决方法分享

缺失了xinput1_3.dll文件&#xff0c;对应用程序或游戏的正常运行造成了严重的影响。这个动态链接库文件&#xff08;DLL&#xff09;是由Microsoft Corporation开发的&#xff0c;它是一个重要的Windows系统文件&#xff0c;提供了针对Xbox 360控制器的输入支持。如果这个文件…

PHP面向对象编程基础(一)

PHP面向对象编程基础 在PHP中&#xff0c;面向对象编程是一种非常常见的编程范式。它允许我们将代码组织成可重用和可扩展的类和对象&#xff0c;并提供了许多强大的功能&#xff0c;例如封装、继承和多态性。 类和对象 在PHP中&#xff0c;类是一种定义对象的蓝图或模板。它…

配置提取到配置类中,并且注入到spring容器

Data ConfigurationProperties(prefix "solr.config") EnableConfigurationProperties({CustomSolrConfigPathProperties.class}) Component public class CustomSolrConfigPathProperties {/*** 上传的配置文件的路径*/private String path;/*** 删除文件的父路径*…

不要图片?CSS实现大屏常见不规则边框(系列二)

前言 &#x1f44f;不要图片&#xff1f;CSS实现大屏常见不规则边框&#xff08;系列二&#xff09;&#xff0c;速速来Get吧~ &#x1f947;文末分享源代码。记得点赞关注收藏&#xff01; 1.实现效果 2.实现原理 系列一已经陈述了相关原理&#xff0c;感兴趣的小伙伴直接参…

NLP | SentenceTransformer将句子进行编码并计算句子语义相似度

环境设置&#xff1a; SentenceTransformertransformers SentenceTransformers Documentation — Sentence-Transformers documentation (sbert.net) Sentence Transformer是一个Python框架&#xff0c;用于句子、文本和图像嵌入Embedding。 这个框架计算超过100种语言的句子…