我已经学会了如何使用 C++ 和 gtkmm-3.0 创建一个简单的 GTK 应用程序。
到目前为止,我已经了解了两种可能的方法,我想要第三种方法,即这两种方法的合并。
第一种方法:3 个文件(头文件、构造函数、主文件)。代码中的小部件
在这种方法中,我编写了三个文件:
window.h
MainWindow 类的声明位置#pragma once
#include <gtkmm.h>
class MainWindow : public Gtk::Window
{
public:
MainWindow();
};
window.cpp
类的构造函数写在哪里#include "window.h"
MainWindow::MainWindow()
{
set_title("Simple application");
set_default_size(200, 200);
}
main.cpp
app->run() 启动窗口并运行
“事件循环”#include "window.h"
int main(int argc, char* argv[])
{
auto app = Gtk::Application::create("org.gtkmm.window");
MainWindow w;
return app->run(w);
}
第二种方法:1个代码文件。 XML 文件中的小部件
在这种方法中,我只有一个“代码”文件,
main.cpp
:
#include <gtkmm.h>
#include <iostream>
Gtk::Window* gWindow = nullptr;
int main(int argc, char const *argv[])
{
auto app = Gtk::Application::create("org.gtkmm.window");
auto gladeBuilder = Gtk::Builder::create();
gladeBuilder->add_from_file("builder.ui");
gladeBuilder->get_widget("MainWindow", gWindow);
app->run(*gWindow);
return 0;
}
然后我有一个 XML 文件
builder.ui
,其中定义了小部件:
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<requires lib="gtk+" version="3.22"/>
<object class="GtkWindow" id="MainWindow">
<property name="default_width">400</property>
<property name="default_height">400</property>
</object>
</interface>
第一种方法的优点是将代码分离在不同的文件中,这在编写更复杂的应用程序时非常有用,例如有多个窗户。 第二种方法的优点是将小部件放在单独的文件中,并且可以使用图形应用程序来设计更复杂的 UI。
所以,我想要第三种方法,将这两种方法合并起来,结合所有优点。
我看到的问题是如何为主窗口编写类,因为它既位于
window.cpp
代码文件中,又位于 XML builder.ui
文件中。
我尝试了一个天真的想法,将
window.cpp
文件修改为:
#include "window.h"
MainWindow::MainWindow()
{
auto builder = Gtk::Builder::create();
builder->add_from_file("builder.ui");
builder->get_widget("MainWindow", (*this) );
}
即在代码和 XML 文件中定义相同的
MainWindow
类,但我知道这太天真了,而且确实出现了错误。
我有一些用 Python 编写 GUI 的经验,而我是用 C++ 编写 GUI 的新手,非常感谢任何帮助!
经过一些研究,包括 Stack Overflow 中的一些问题(这里、这里和这里),我为一个简单的应用程序编写了以下代码,其中包含一些小部件和一些连接的信号,这满足了问题:
myWindow.h
#pragma once
#include <gtkmm/window.h>
#include <gtkmm/builder.h>
#include <gtkmm/button.h>
#include <gtkmm/entry.h>
#include <gtkmm/label.h>
class MainWindow : public Gtk::Window
{
public:
MainWindow(BaseObjectType* cobject, const Glib::RefPtr<Gtk::Builder>& p_builder);
protected:
Glib::RefPtr<Gtk::Builder> m_builder;
Glib::RefPtr<Gtk::Button> m_button;
Glib::RefPtr<Gtk::Entry> m_entry;
Glib::RefPtr<Gtk::Label> m_label;
// Signal handler:
void on_button_clicked();
};
myWindow.cpp
#include "myWindow.h"
#include <gtkmm/builder.h>
#include <gtkmm/button.h>
#include <iostream>
MainWindow::MainWindow(BaseObjectType* cobject, const Glib::RefPtr<Gtk::Builder>& p_builder)
: Gtk::Window(cobject), m_builder{p_builder}
{
Gtk::Button* p_button = nullptr; // Create a raw pointer to a button
Gtk::Entry* p_entry = nullptr; // Create a raw pointer to an entry field
Gtk::Label* p_label = nullptr; // Create a raw pointer to a label
m_builder->get_widget("button1", p_button); // Retrieve the button from XML file
m_button = Glib::RefPtr<Gtk::Button>(p_button); // Assign to m_button
m_builder->get_widget("entry1", p_entry); // Retrieve the entry from XML file
m_entry = Glib::RefPtr<Gtk::Entry>(p_entry); // Assign to m_entry
m_builder->get_widget("label1", p_label); // Retrieve the label from XML file
m_label = Glib::RefPtr<Gtk::Label>(p_label); // Assign to m_label
// connect the button click event to the function
m_button->signal_clicked().connect(sigc::mem_fun(*this, &MainWindow::on_button_clicked));
}
void MainWindow::on_button_clicked()
{
m_label->set_label(m_entry->get_text());
}
main.cpp
#include <iostream>
#include <cstring>
#include <gtkmm/application.h>
#include <gtkmm/builder.h>
#include "myWindow.h"
int main(int argc, char** argv)
{
// Create an application object. This is mandatory since it initializes
// the toolkit. If you don't do this, widgets won't show.
auto app = Gtk::Application::create(argc, argv);
// Since you want to use the builder, you need to instantiate one. You
// can do this directly using your UI file.
auto builder = Gtk::Builder::create_from_file("builder.ui");
// At this point, you are ready to create your window. The builder is
// going to create an instance for you, and you get a handle to this
// istance to later use.
// First, you create a MainWindow pointer, which points to nothing. It
// is going to be used by the builder to get you the handle.
MainWindow* window = nullptr;
// Then, using you builder instance. You call get_widget_derived and
// pass the pointer to get the handle.
builder->get_widget_derived("mainWindow", window);
// Check if the window was created successfully
if (window) {
// You then show the window by using you handle (i.e window).
app->run(*window);
} else {
std::cerr << "Failed to create main window." << std::endl;
return 1;
}
return 0;
}
(此代码中的精彩评论来自@BobMorane,写在this问题的答案中)
builder.ui
<?xml version='1.0' encoding='UTF-8'?>
<interface>
<requires lib="gtk+" version="3.20"/>
<object class="GtkWindow" id="mainWindow">
<property name="default-height">300</property>
<property name="default-width">400</property>
<property name="title">Simple copy</property>
<child>
<object class="GtkGrid" id="grid">
<property name="visible">True</property>
<child>
<object class="GtkLabel" id="label1">
<property name="visible">True</property>
<property name="width-request">2</property>
</object>
<packing>
<property name="height">2</property>
<property name="left-attach">0</property>
<property name="top-attach">0</property>
<property name="width">2</property>
</packing>
</child>
<child>
<object class="GtkEntry" id="entry1">
<property name="visible">True</property>
<property name="width-request">2</property>
</object>
<packing>
<property name="left-attach">0</property>
<property name="top-attach">3</property>
<property name="width">2</property>
</packing>
</child>
<child>
<object class="GtkButton" id="button1">
<property name="label">copy</property>
<property name="visible">True</property>
<property name="width-request">2</property>
</object>
<packing>
<property name="left-attach">0</property>
<property name="top-attach">2</property>
</packing>
</child>
</object>
</child>
</object>
</interface>
此代码构建并运行,创建一个带有标签、按钮和输入字段的窗口。 单击按钮会将字段中输入的文本复制到标签。 关键方面之一是用于处理不同小部件的指针的数据类型。