Archive:Development/Tutorials/Using KConfig XT (zh CN)

From KDE TechBase

Template:TutorialBrowser (zh CN)

使用KConfig XT

原作者: Zack Rusin <[email protected]>

本教程介绍了Kconfig XT配置框架的主要概念,并演示了如何在应用程序里快速使用它。本文假设读者已经开发过KDE应用程序,并且熟悉KConfig。同时也要求了解XML及XML Schema的概念。

KConfig XT框架背后的主要想法是,更方便地管理庞大的KDE安装,让应用程序开发者生活得更惬意。

这个新框架有四个基本部分:

  • KConfigSkeleton:libkdecore里的一个类。提供了对配置项的便捷操作
  • 一个XML文件:包含了配置项的信息(.kcfg文件)
  • 一个ini格式的文件:配置生产代码时的细节(.kcfgc文件)
  • kconfig_compiler:从.kcfg.kcfgc文件中生成C++代码。自动生成的类继承于KConfigSkeleton,并且为应用程序提供了API来访问它的配置项。
Note
本教程中里KConfig XT框架的高级可选特性及其描述将标记为斜体。如果您首次阅读本教程时跳过了它们,请在必要时回顾这些内容。


.kcfg 结构

.kcfg文件的结构由它的XML Schema描述(kcfg.xsd - 可从这里或者kdecore库里下载)。请在继续阅读本教程之前,略读一遍这个小文件。

首先让我们来创建一个简单的.kcfg文件,请参看下面这段示例,来学习各个步骤。

<?xml version="1.0" encoding="UTF-8"?>
<kcfg xmlns="http://www.kde.org/standards/kcfg/1.0"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://www.kde.org/standards/kcfg/1.0
                          http://www.kde.org/standards/kcfg/1.0/kcfg.xsd" >
  <kcfgfile name="kjotsrc"/>
  <include>kglobalsettings.h</include>
  <group name="kjots">
    <entry name="SplitterSizes" type="IntList">
      <label>How the main window is divided.</label>
    </entry>
    <entry name="Width" type="Int">
      <label>Width of the main window.</label>
      <default>600</default>
    </entry>
    <entry name="Height" type="Int">
      <label>Height of the main window.</label>
      <default>400</default>
    </entry>
    <entry name="OpenBooks" type="StringList">
      <label>All books that are opened.</label>
    </entry>
    <entry name="CurrentBook" type="String">
      <label>The book currently opened.</label>
    </entry>
    <entry name="Font" type="Font">
      <label>The font used to display the contents of books.</label>
      <default code="true">KGlobalSettings::generalFont()</default>
    </entry>
  </group>
</kcfg>
  • 使用您最爱的文本编辑器打开 your_application_name.kcfg 文件(记得将your_application_name要替换成实际的应用程序名哦~)。
  • 首先给该文件添加<kcfgfile>标签,该标签决定了配置项数据存储于哪个KConfig文件中。这里有三种可能:
    1. 如果<kcfgfile>标签没有任何属性,那么自动生成的代码将使用程序默认的KConfig文件(通常是$HOME/.kde/config/<appname>rc)。
    2. 使用“name”属性可以手动指定一个文件名。如果“name”的值不是绝对路径,那么文件将在KDE默认配置文件夹中存放(一般是在$HOME/.kde/config/)。
    3. 如果您希望在程序运行时决定配置文件的路径,那么请使用<kcfgfile arg="true">。这个会让生成的代码里的构造函数使用KSharedConfig::Ptr作为参数,这就允许您能够创建多个实例,指向不同的文件。
  • 添加可选的<include>标签,以便代码编译阶段计算默认值时,能够包含指定的C++头文件,
  • XML文件中的其他<entry>由<group>标签来分组,描述了配置文件中的对应分组。
    1. 每个独立的<entry>至少得有一个name属性或者key属性。key属性用作配置文件中的键名。name属性则用来作为代码中的变量名、标识符等。如果不提供key属性,那么name属性将作为配置文件中的键名;如果提供了key属性,但是不提供name属性,那么会自动将key属性的内容去掉空白符后作为name属性值。
    2. 务必添加<label>、<tooltip>和<whatsthis>标签来描述您的应用程序里的配置项。<label>标签是简短的表述,而<tooltip>标签和<whatsthis>标签则是详细的描述与说明。这些标签很重要,例如系统管理员使用KConfigEditor工具在网络上配置机器时就需要这些描述信息。请注意,如果不在您的.kcfgc文件中配置SetUserTexts=true,那这些选项会被忽略(请查阅下面章节)。
    3. 每个<entry>必须有个类型(type属性)。目前可用的类型有:String, Url, StringList, Font, Rect, Size, Color, Point, Int, UInt, Bool, Double, DataTime, Int64, UInt64,Password。除了这些基本类型以外,以下特殊类型也是支持的:
      • Path - 其实还是一个字符串,但是会特别作为文件路径处理而已。对于那些在家目录下的路径,存储到配置文件里时会使用$HOME作为前缀。
      • Enum - 枚举类型。枚举类型的值要在<choices>标签中提供。应用程序以整数型来访问这些枚举值,但以字符串形式存储到配置文件中。即使以后添加更多的枚举值也不会破坏兼容性。
      • IntList - 整数列表。这将使得提供给应用程序的数据格式是QList<int>。在存储QSplitter的位置时很方便。
      • PathList - 路径列表。没啥好说的。
    4. min和max属性能够用来限制整数型选项的取值范围。
    5. 每个<entry>还可以有个<default>标签来指定默认值。当任何配置文件中都没有指定该选项的值时就使用这个默认值。默认值将被解释为字面常量。如果某默认值需要被计算,或者它是某个函数调用的返回值,那<default>标签应该包含一个code="true"属性,这样<default>标签里的内容就会当作C++表达式来处理。
    6. 若还需要代码来计算默认值,那可在<code>标签中提供。该标签里的内容将被原样插入到代码中。典型的用途是,计算一个通用的默认值,给接下来的多处<entry>使用。
    7. 还可以,给<entry>标签添加hidden属性。可选的值是true和false。

.kcfgc文件

创建完一个.kcfg文件后,还需要创建一个.kcfgc文件来描述C++代码的生成细节。.kcfgc文件是一个简单的ini格式文件,使用"entry=value"格式。可以按照以下的步骤来创建一个简单的.kcfgc文件:

  1. 使用您喜欢的文本编辑器打开一个新文件。
  2. 首先添加一行"File=your_application_name.kcfg",指定您的应用程序的配置选项的位置。
  3. 添加一行"ClassName=YourConfigClassName",指明由.kcfg文件生成的代码的类名。请记住,生成的类将继承于KConfigSkeleton类。也请确保这个YourConfigClassName类名未在您的应用程序里使用。将此文件保存为yourconfigclassname.kcfgc。这将确保yourconfigclassname.{h,cpp}的生成,您的配置类也在其中。
  4. 添加其他可选项。也许您的应用程序会用到。有以下几种:
    • NameSpace - 指定配置类所在的名字空间
    • Inherits - 继承您的自定义类
    • Singleton - 指定您的配置类是否为单例模式
    • MemberVariables - 指出成员变量的访问级别。默认是private。
    • ItemAccessors - 与上面那选项有关。如果成员变量是public,那么生成成员变量的get方法就没啥意义了。默认,生成成员变量的get方法。
    • Mutators - 类似于上面那个,但是变成了是否生成成员变量的set方法。
    • GlobalEnums - specifies whether enums should be class wide of whether they should be always explicitly prefixed with their type name(指明枚举类型是否声明为类,并且明确地以类型名为前缀?)
    • UseEnumTypes - 指明在set方法、get方法、信号函数中,枚举值是以int型来传递,还是以enum xxx枚举型传递。
    • SetUserTexts - 指明是否处理<label>、<tooltip>、<whatsthis>等标签。这个选项能让KConfigDialog之类的工具利用这几个标签。默认,这仨标签会被忽略。

更详细的kconfig_compiler说明: [1]

调整 CMakeLists.txt 文件

创建完.kcfg文件和.kcfgc文件后,下一步是调整编译参数,让kconfig_compiler在编译时生成必要的类。对于在源码目录里的编译,调整很少,只需要添加下面这两行代码到CMakeLists.txt中(这里假设您的配置文件叫做settings.kcfg和settings.kcfgc):

kde4_add_kcfg_files(<project name>_SRCS settings.kcfgc)
install(FILES settings.kcfg DESTINATION ${KCFG_INSTALL_DIR})

另外,如果在编译生成的源代码之前要生成.moc文件,使用下面这两行代码:

kde4_add_kcfg_files(<project name>_SRCS GENERATE_MOC settings.kcfgc)
install(FILES settings.kcfg DESTINATION ${KCFG_INSTALL_DIR})

以上代码确保了配置类自动生成(kde4_add_kcfg_files),并且.kcfg文件正确地安装以便被KConfigEditor等工具使用(install)。

在源码目录外编译

对于在源码目录外编译的情况,需要多一个步骤。

问题:对于在源码目录外的编译,kconfig_compiler所生成的代码被保存在了编译目录中。此时,如何在源代码中简单地使用#include "settings.h"来包含kconfig_compiler所生成的头文件呢?

方案:也许您的CMakeLists.txt里有一行下面这样的代码:

include_directories(${QT_INCLUDE} ${KDE4_INCLUDES})

给它加上一个变量CMAKE_CURRENT_BINARY_DIR,结果如下:

include_directories(${QT_INCLUDE} ${KDE4_INCLUDES} ${CMAKE_CURRENT_BINARY_DIR})

需要注意的是,在您的工程中应当只调用一次include_directories。(如果您在源码根目录里调用了一次include_directories,但是又在其他的子目录(由add_subdirectory添加的目录)中调用了一次,那么第二次include_directories调用将会被忽略。)

使用对话框

做完以上的步骤之后,您已经为使用KConfig XT框架准备完毕了。kconfig_compiler将生成与.kcfg文件同名的代码文件(后缀替换成".h")。在您需要访问配置项的源代码中#include该头文件即可。

只有在.kcfgc文件中添加了Singleton=true,您才能使用本特性。

KConfig XT的最强之处在于,它与Qt设计器生成的对话框能无缝集成。您可是通过使用KConfigDialog来实现。步骤如下:

  1. 创建KConfigDialog,然后将您的配置数据对象作为参数传递给它。构造函数的调用类似下面的代码:
KConfigDialog* dialog = new KConfigDialog(
         this, "settings", YourAppSettings::self() );

这里假设YourAppSettings是kcfgc中ClassName变量的值。并且该配置类是单例模式。

  1. 在Qt设计器中创建widget来配置您的程序选项。为了让这些widget与kcfg集成,您需要按照如下的规则来命名widget里的变量:
    1. 控制某选项的widget的名字必须以"kcfg_"开头
    2. 紧接着是name属性的值。这个name属性是该widget对应的kcfg中的选项的name属性。
  2. 将该Qt设计器生成的widget添加到KConfigDialog中。
  3. 搞定后,显示该对话框。

示例

此处使用KConfig XT框架的例子。应用程序的名字叫做Example。

它的.kcfg文件如下:

<?xml version="1.0" encoding="UTF-8"?>
<kcfg xmlns="http://www.kde.org/standards/kcfg/1.0"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://www.kde.org/standards/kcfg/1.0
                          http://www.kde.org/standards/kcfg/1.0/kcfg.xsd" >
  <kcfgfile name="examplerc"/>
  <group name="network">
    <entry name="ServerName" type="String">
      <label>Defines the sample server.</label>
    </entry>
    <entry name="Port" type="Int">
      <label>Defines the server port</label>
      <default>21</default>
      <min>20</min>
      <max>990</max>
    </entry>
  </group>
</kcfg>

然后配置生成的配置类的细节。.kcfgc文件如下:

File=example.kcfg
ClassName=ExampleSettings
Singleton=true
Mutators=true

程序源代码的头文件不需要变动,只需要在cpp文件中包含如下的代码来访问和修改配置项数据:

...
#include <ExampleSettings.h>
...
void ExampleClass::readConfig() {
        m_server  = ExampleSettings::serverName();
        m_port    = ExampleSettings::port();
}
void ExampleClass::saveSettings() {
        ExampleSettings::setServerName( m_server );
        ExampleSettings::setPort( m_port );
        ExampleSettings::self()->writeConfig();
}

self()返回的是对象的实例。所有的writeConfig()都得这样调用,因为它是一个虚函数方法。(其他的读取和设置选项的方法是静态方法。)

要想增加一个配置对话框,您的Qt设计器生成的widget的名字需要与它们对应的选项的名字相同,并且加上"kcfg_"前缀。例如:

然后您就可以照着下面的代码来使用该配置对话框:

// 对话框的实例可能已经创建过并缓存着,
// 因此可以直接显示该缓存的对话框,而无须又创建一个
if ( KConfigDialog::showDialog( "settings" ) )
  return;

// KConfigDialog并没有找到对话框的缓存,所有创建新的吧:
KConfigDialog* dialog = new KConfigDialog(this, "settings",
                                          ExampleSettings::self());
ExampleDesignerWidget* confWdg =
                  new ExampleDesignerWidget( 0, "Example" );

dialog->addPage( confWdg, i18n("Example"), "example" );

// 用户更改对话框里的配置项,然后您更新本地的配置
connect( dialog, SIGNAL(settingsChanged()),
         this, SLOT(updateConfiguration()) );

dialog->show();

这就是全部的内容。您可以查看一下kdegames模块里面的KReversiKTron的源代码,看看KConfig XT的真实的例子!

常见的陷阱和技巧

  • 别忘了给.kcfg文件里的<entry>标签添加type属性。
  • 给所有的<entry>增加<label>、<whatsthis>、<tooltip>等标签。
  • 在.kcfgc文件中添加MemberVariables=public通常是脑残的做法。碰到变量的访问竞争时,您就可以去领杯具了。
  • 如果您的应用程序中没有一个中心对象(在其他对象生成之前生成,其他对象销毁后才销毁),那么请务必给.kcfgs文件添加上Singleton=true

其他参考