简单的 KDE 设计器应用程序剖析






4.85/5 (10投票s)
一个完整的KDE应用程序初学者指南。
引言
在本文中,我们将探讨许多人将在KDevelop中进行KDE编程的第一次尝试时使用的“简单设计器KDE应用程序”模板。与任何其他编程环境一样,了解你所处理的应用程序的结构有助于你编写自己的程序。因此,考虑到这一点,本文不仅会解释当你第一次创建项目时项目向导生成的文件的内容,还会解释当你编译项目时生成的文件以及它们如何协同工作。当然,我们将只关注与你的项目编程直接相关的文件,而不会列出配置文件等的内容。
创建项目
打开KDevelop,然后转到“项目/新项目/C++/KDE”,然后选择“简单设计器KDE应用程序”。
然后设置应用程序名称和位置,然后单击“下一步”按钮。你将看到“选项”对话框,允许你设置用户偏好设置,例如你的姓名和电子邮件地址。“下一步”对话框将用于源代码管理,我们将不在此项目中使用它。最后,你将看到文件头模板供你查看。只需一直点击“下一步”,直到出现“完成”按钮。
当你单击“完成”按钮时,向导将消失,KDevelop将打开你的项目。主窗口将包含项目主文件:在这种情况下,是AnatomyDemo.cpp文件。屏幕右侧将显示项目信息,如下所示:
正如你所看到的,Automake Manager只是项目/解决方案窗口的另一种名称。
文档视图架构
“简单设计器KDE应用程序”具有文档视图架构,其中有一个派生自KMainWindow
的类,该类包含一个视图类;在这种情况下,是一个控件或派生自控件的类。
项目中包含两个头文件;它们在Automake Manager中列在(header in noinst)标题下。第一个是AnatomyDemo.h文件,内容如下:
class AnatomyDemo : public KMainWindow { Q_OBJECT public: /** * Default Constructor */ AnatomyDemo(); /** * Default Destructor */ virtual ~AnatomyDemo(); };
我们这里只有构造函数和析构函数,但正是这个类控制着应用程序的布局以及应用程序的外围设备,如状态栏、菜单栏和工具栏。当我们查看CPP文件时,这一点会更加明显。
AnatomyDemo::AnatomyDemo() : KMainWindow( 0, "AnatomyDemo" ) { setCentralWidget( new AnatomyDemoWidget( this ) ); } AnatomyDemo::~AnatomyDemo() { }
尽管我们只有构造函数和析构函数,但你可以清楚地看到构造函数中唯一的函数调用是对setCentralWidget
的调用,它使用一个名为AnatomyDemoWidget
的类。this
参数设置了父控件。
在AnatomyDemoWidget.h文件中,我们看到:
class AnatomyDemoWidget : public AnatomyDemoWidgetBase { Q_OBJECT public: AnatomyDemoWidget(QWidget* parent = 0, const char* name = 0, WFlags fl = 0 ); ~AnatomyDemoWidget(); /*$PUBLIC_FUNCTIONS$*/ public slots: /*$PUBLIC_SLOTS$*/ virtual void button_clicked(); protected: /*$PROTECTED_FUNCTIONS$*/ protected slots: /*$PROTECTED_SLOTS$*/ };
与上面一样,此类目前除了构造函数和析构函数外,还有一个函数(此处称为槽,但我们稍后会讲到,并且从这个类的角度来看,它是一个标准函数),即button_clicked()
函数。这是程序的hello world部分,我们可以通过查看AnatomyDemoWidget.cpp文件来看到它。
AnatomyDemoWidget::AnatomyDemoWidget(QWidget* parent, const char* name, WFlags fl) : AnatomyDemoWidgetBase(parent,name,fl) {} AnatomyDemoWidget::~AnatomyDemoWidget() {} /*$SPECIALIZATION$*/ void AnatomyDemoWidget::button_clicked() { if ( label->text().isEmpty() ) { label->setText( "Hello World!" ); } else { label->clear(); } }
除了构造函数和析构函数,我们还可以看到button_clicked
函数的实现。如果标签中没有文本,它会设置为“Hello World”,如果标签中有任何文本,它会清除它。
但在看到它的实际效果之前,还有几个文件我们还没有看过。main.cpp文件,它如下所示:
static const char description[] = I18N_NOOP("A KDE KPart Application"); static const char version[] = "0.1"; static KCmdLineOptions options[] = { // { "+[URL]", I18N_NOOP( "Document to open" ), 0 }, KCmdLineLastOption }; int main(int argc, char **argv) { KAboutData about("anatomydemo", I18N_NOOP("AnatomyDemo"), version, description, KAboutData::License_GPL, "(C) 2008 pseudonym67", 0, 0, "pseudonym67@hotmail.com"); about.addAuthor( "pseudonym67", 0, "pseudonym67@hotmail.com" ); KCmdLineArgs::init(argc, argv, &about); KCmdLineArgs::addCmdLineOptions( options ); KApplication app; AnatomyDemo *mainWin = 0; if (app.isRestored()) { RESTORE(AnatomyDemo); } else { // no session.. just start up normally KCmdLineArgs *args = KCmdLineArgs::parsedArgs(); /// @todo do something with the command line args here mainWin = new AnatomyDemo(); app.setMainWidget( mainWin ); mainWin->show(); args->clear(); } // mainWin has WDestructiveClose flag by default, so it will delete itself. return app.exec(); }
正如你所期望的,main
是程序的入口点,它处理关于对话框和任何命令行选项。它的主要功能是实例化KApplication
对象,并将KMainWindow
设置为应用程序的主控件。
最后一个文件是anatomydemowidgetbase.ui文件,这是一个用于窗体/控件设计器的XML文件。如果我们打开它,我们会看到:
这是你标准的拖放式窗体/控件设计器界面。如果你想查看XML,请在你的系统上安装KXML Editor(如果还没有安装),然后用它打开.ui文件。
构建应用程序
要构建应用程序,请转到“构建”菜单并选择“构建项目”选项。然后你会看到一个对话框,告诉你没有make文件或配置文件。这些是构建所需的文件,由KDevelop自动生成。除非你对Linux命令行编程有经验,否则让KDevelop为你处理它们。运行它们并等待。
然后,在“调试”菜单中选择“启动”,你就会看到:
如果你点击“Click Me!”按钮,对话框上就会显示“Hello World”文本。那么,刚才发生了什么?你可能已经注意到,虽然我们有一个名为anatomydemowidgetbase.ui的文件,但我们没有一个同名的类实现,但我们在anatomydemowidget.h文件中确实有:
class AnatomyDemoWidget : public AnatomyDemoWidgetBase { Q_OBJECT
在我们的anatomydemowidget.h文件中。另外,什么是槽,而“Click Me!”按钮是如何工作的?这些问题的答案是Qt库在后台完成的事情,所以为了回答这些问题,我们需要了解一些称为元对象编译器(Meta Object Compiler)和用户界面编译器(User Interface Compiler)的东西。首先,我们来看看用户界面编译器:
用户界面编译器
用户界面编译器非常直接,它接收.ui文件,在这种情况下是anatomydemowidgetbase.ui文件,并生成一个C++类,该类精确地实现了你在GUI中定义的窗体。因此,在debug/src目录中,我们现在将拥有anatomydemowidgetbase.h和anatomydemowidgetbase.cpp文件。头文件内容如下:
#ifndef ANATOMYDEMOWIDGETBASE_H #define ANATOMYDEMOWIDGETBASE_H #include <qvariant.h> #include <qwidget.h> class QVBoxLayout; class QHBoxLayout; class QGridLayout; class QSpacerItem; class QPushButton; class QLabel; class AnatomyDemoWidgetBase : public QWidget { Q_OBJECT public: AnatomyDemoWidgetBase( QWidget* parent = 0, const char* name = 0, WFlags fl = 0 ); ~AnatomyDemoWidgetBase(); QPushButton* button; QLabel* label; public slots: virtual void button_clicked(); protected: QGridLayout* anatomydemowidgetbaseLayout; protected slots: virtual void languageChange(); }; #endif // ANATOMYDEMOWIDGETBASE_H
而这个类就是我们继承AnatomyDemoWidget
类所派生的。
而头文件内容如下:
#include "anatomydemowidgetbase.h" #include <qvariant.h> #include <qpushbutton.h> #include <qlabel.h> #include <qlayout.h> #include <qtooltip.h> #include <qwhatsthis.h> /* * Constructs a AnatomyDemoWidgetBase as a child of 'parent', with the * name 'name' and widget flags set to 'f'. */ AnatomyDemoWidgetBase::AnatomyDemoWidgetBase( QWidget* parent, const char* name, WFlags fl ) : QWidget( parent, name, fl ) { if ( !name ) setName( "anatomydemowidgetbase" ); anatomydemowidgetbaseLayout = new QGridLayout( this, 1, 1, 11, 6, "anatomydemowidgetbaseLayout"); button = new QPushButton( this, "button" ); anatomydemowidgetbaseLayout->addWidget( button, 1, 0 ); label = new QLabel( this, "label" ); anatomydemowidgetbaseLayout->addWidget( label, 0, 0 ); languageChange(); resize( QSize(220, 133).expandedTo(minimumSizeHint()) ); clearWState( WState_Polished ); // signals and slots connections connect( button, SIGNAL( clicked() ), this, SLOT( button_clicked() ) ); } /* * Destroys the object and frees any allocated resources */ AnatomyDemoWidgetBase::~AnatomyDemoWidgetBase() { // no need to delete child widgets, Qt does it all for us } /* * Sets the strings of the subwidgets using the current * language. */ void AnatomyDemoWidgetBase::languageChange() { setCaption( QString::null ); button->setText( tr2i18n( "Click Me!" ) ); label->setText( QString::null ); } void AnatomyDemoWidgetBase::button_clicked() { qWarning( "AnatomyDemoWidgetBase::button_clicked(): Not implemented yet" ); } #include "anatomydemowidgetbase.moc"
正如你所看到的,这基本上是你期望从一个实现窗体的文件中看到的内容。值得注意的是构造函数中的connect
函数和文件末尾的行;这两者将在下面解释。
元对象编译器
元对象编译器是Qt类型管理系统,为开发人员提供了几个好处。它的主要功能是在编译C++源文件之前读取它们,如果类声明包含Q_OBJECT
宏,它将生成一个名为“filename.moc”的文件,在上面的情况下,这就是anatomydemowidget.moc文件,内容如下:
const char *AnatomyDemoWidget::className() const { return "AnatomyDemoWidget"; } QMetaObject *AnatomyDemoWidget::metaObj = 0; static QMetaObjectCleanUp cleanUp_AnatomyDemoWidget( "AnatomyDemoWidget", &AnatomyDemoWidget::staticMetaObject ); #ifndef QT_NO_TRANSLATION QString AnatomyDemoWidget::tr( const char *s, const char *c ) { if ( qApp ) return qApp->translate( "AnatomyDemoWidget", s, c, QApplication::DefaultCodec ); else return QString::fromLatin1( s ); } #ifndef QT_NO_TRANSLATION_UTF8 QString AnatomyDemoWidget::trUtf8( const char *s, const char *c ) { if ( qApp ) return qApp->translate( "AnatomyDemoWidget", s, c, QApplication::UnicodeUTF8 ); else return QString::fromUtf8( s ); } #endif // QT_NO_TRANSLATION_UTF8 #endif // QT_NO_TRANSLATION QMetaObject* AnatomyDemoWidget::staticMetaObject() { if ( metaObj ) return metaObj; QMetaObject* parentObject = AnatomyDemoWidgetBase::staticMetaObject(); static const QUMethod slot_0 = {"button_clicked", 0, 0 }; static const QMetaData slot_tbl[] = { { "button_clicked()", &slot_0, QMetaData::Public } }; metaObj = QMetaObject::new_metaobject( "AnatomyDemoWidget", parentObject, slot_tbl, 1, 0, 0, #ifndef QT_NO_PROPERTIES 0, 0, 0, 0, #endif // QT_NO_PROPERTIES 0, 0 ); cleanUp_AnatomyDemoWidget.setMetaObject( metaObj ); return metaObj; } void* AnatomyDemoWidget::qt_cast( const char* clname ) { if ( !qstrcmp( clname, "AnatomyDemoWidget" ) ) return this; return AnatomyDemoWidgetBase::qt_cast( clname ); } bool AnatomyDemoWidget::qt_invoke( int _id, QUObject* _o ) { switch ( _id - staticMetaObject()->slotOffset() ) { case 0: button_clicked(); break; default: return AnatomyDemoWidgetBase::qt_invoke( _id, _o ); } return TRUE; } bool AnatomyDemoWidget::qt_emit( int _id, QUObject* _o ) { return AnatomyDemoWidgetBase::qt_emit(_id,_o); } #ifndef QT_NO_PROPERTIES bool AnatomyDemoWidget::qt_property( int id, int f, QVariant* v) { return AnatomyDemoWidgetBase::qt_property( id, f, v); } bool AnatomyDemoWidget::qt_static_property( QObject* , int , int , QVariant* ){ return FALSE; } #endif // QT_NO_PROPERTIES
一旦在类头文件中声明了Q_OBJECT
,你就会为你的程序生成一个.moc文件,并且你的类将至少继承其基类QObject
。这就是为什么几乎所有Qt和KDE的CPP文件最后都有一行:
#include "filename".moc
.moc文件中的主要关注点是tr
和trutf8
函数,它们有助于实现Qt的多语言支持。
qt_property
函数允许你在类中使用QT_PROPERTY
宏。在QT_PROPERTY
宏内部使用的任何属性都可以在窗体/控件编辑器属性部分进行编辑/设置。
以QButton
头文件为例,我们得到:
Q_OBJECT Q_ENUMS( ToggleType ToggleState ) Q_PROPERTY( QString text READ text WRITE setText ) Q_PROPERTY( QPixmap pixmap READ pixmap WRITE setPixmap ) Q_PROPERTY( QKeySequence accel READ accel WRITE setAccel ) Q_PROPERTY( bool toggleButton READ isToggleButton ) Q_PROPERTY( ToggleType toggleType READ toggleType ) Q_PROPERTY( bool down READ isDown WRITE setDown DESIGNABLE false ) Q_PROPERTY( bool on READ isOn ) Q_PROPERTY( ToggleState toggleState READ state ) Q_PROPERTY( bool autoResize READ autoResize WRITE setAutoResize DESIGNABLE false ) Q_PROPERTY( bool autoRepeat READ autoRepeat WRITE setAutoRepeat ) Q_PROPERTY( bool exclusiveToggle READ isExclusiveToggle )
你可以在这里看到,也可以显式地从设计器中排除某些项目。
.moc文件还设置了你的信号和槽。
信号和槽
信号和槽是Qt类之间通信的方式。信号是一种类型安全的方法,可以从一个类向任何感兴趣的类发送信号/消息。没有类会自动接收来自另一个类的信号。每个类都必须使用connect
函数请求连接到特定信号。
为了实现这一点,我们在类头文件中使用了两个新关键字。它们是signals
和slots
关键字。我们已经在上面的头文件中看到了slots
关键字:
public slots: /*$PUBLIC_SLOTS$*/ virtual void button_clicked();
这仅仅是一个占位符,它在类级别声明了一个标准函数。槽的实现是在CPP文件中:
void AnatomyDemoWidget::button_clicked() { if ( label->text().isEmpty() ) { label->setText( "Hello World!" ); } else { label->clear(); } }
如果我们暂时看一下UI,特别是如果我们点击按钮并查看属性,我们会看到一个名为“信号处理器”(Signal Handlers)的部分:
本节将显示窗体上任何对象的可用信号,在这里我们可以设置一个响应槽/函数。
这些在QButton.h中定义为:
signals: void pressed(); void released(); void clicked(); void toggled( bool ); void stateChanged( int );
然后,如果我们回到上述函数的构造函数,我们看到connect
函数调用,其内容如下:
connect( button, SIGNAL( clicked() ), this, SLOT( button_clicked() ) );
connect
函数有几种变体,但大多数遵循此模式:connect(发送信号的对象, SIGNAL(要发送的信号), 接收信号的对象, SLOT(信号触发时调用的函数) )
。
摘要
这是一个简单的KDE应用程序剖析的快速指南,重点介绍了简单项目模板生成的内容,以及构建时创建的文件。因此,没有可以下载的源代码,因为所有代码都是由KDevelop和相关的Qt工具自动生成的。本文的目的是使Qt和KDevelop编程的新手能够开始学习,并对正在发生的事情有所了解,以便他们能够继续进行自己的项目,而不是坐在那里挠头猜测到底发生了什么。