65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.85/5 (10投票s)

2008年7月3日

GPL3

8分钟阅读

viewsIcon

26555

一个完整的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.hanatomydemowidgetbase.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文件中的主要关注点是trtrutf8函数,它们有助于实现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函数请求连接到特定信号。

为了实现这一点,我们在类头文件中使用了两个新关键字。它们是signalsslots关键字。我们已经在上面的头文件中看到了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编程的新手能够开始学习,并对正在发生的事情有所了解,以便他们能够继续进行自己的项目,而不是坐在那里挠头猜测到底发生了什么。

© . All rights reserved.