Profil de Libin绿色家园PhotosBlogListes Outils Aide

Blog


22 juin

DesignPatterns--Describing Design Patterns

How do we describe design patterns? Graphical notations, while important and useful, aren't sufficient. They simply capture the end product of the design process as relationships between classes and objects. To reuse the design, we must also record the decisions, alternatives, and trade-offs that led to it. Concrete examples are important too, because they help you see the design in action.

We describe design patterns using a consistent format. Each pattern is divided into sections according to the following template. The template lends a uniform structure to the information, making design patterns easier to learn, compare, and use.

Pattern Name and Classification
The pattern's name conveys the essence of the pattern succinctly. A good name is vital, because it will become part of your design vocabulary. The pattern's classification reflects the scheme we introduce in Section 1.5.

Intent
A short statement that answers the following questions: What does the design pattern do? What is its rationale and intent? What particular design issue or problem does it address?

Also Known As
Other well-known names for the pattern, if any.

Motivation
A scenario that illustrates a design problem and how the class and object structures in the pattern solve the problem. The scenario will help you understand the more abstract description of the pattern that follows.

Applicability
What are the situations in which the design pattern can be applied? What are examples of poor designs that the pattern can address? How can you recognize these situations?

Structure
A graphical representation of the classes in the pattern using a notation based on the Object Modeling Technique (OMT) [ RBP+91]. We also use interaction diagrams [JCJO92, Boo94] to illustrate sequences of requests and collaborations between objects. Appendix B describes these notations in detail.

Participants
The classes and/or objects participating in the design pattern and their responsibilities.

Collaborations
How the participants collaborate to carry out their responsibilities.

Consequences
How does the pattern support its objectives? What are the trade-offs and results of using the pattern? What aspect of system structure does it let you vary independently?

Implementation
What pitfalls, hints, or techniques should you be aware of when implementing the pattern? Are there language-specific issues?

Sample Code
Code fragments that illustrate how you might implement the pattern in C++ or Smalltalk.

Known Uses
Examples of the pattern found in real systems. We include at least two examples from different domains.

Related Patterns
What design patterns are closely related to this one? What are the important differences? With which other patterns should this one be used?

DesignPatterns--What is a Design Pattern?

Christopher Alexander says, "Each pattern describes a problem which occurs over and over again in our environment, and then describes the core of the solution to that problem, in such a way that you can use this solution a million times over, without ever doing it the same way twice" [ AIS+77, page x]. Even though Alexander was talking about patterns in buildings and towns, what he says is true about object-oriented design patterns. Our solutions are expressed in terms of objects and interfaces instead of walls and doors, but at the core of both kinds of patterns is a solution to a problem in a context.

In general, a pattern has four essential elements:

  1. The pattern name is a handle we can use to describe a design problem, its solutions, and consequences in a word or two. Naming a pattern immediately increases our design vocabulary. It lets us design at a higher level of abstraction. Having a vocabulary for patterns lets us talk about them with our colleagues, in our documentation, and even to ourselves. It makes it easier to think about designs and to communicate them and their trade-offs to others. Finding good names has been one of the hardest parts of developing our catalog.

  2. The problem describes when to apply the pattern. It explains the problem and its context. It might describe specific design problems such as how to represent algorithms as objects. It might describe class or object structures that are symptomatic of an inflexible design. Sometimes the problem will include a list of conditions that must be met before it makes sense to apply the pattern.

  3. The solution describes the elements that make up the design, their relationships, responsibilities, and collaborations. The solution doesn't describe a particular concrete design or implementation, because a pattern is like a template that can be applied in many different situations. Instead, the pattern provides an abstract description of a design problem and how a general arrangement of elements (classes and objects in our case) solves it.

  4. The consequences are the results and trade-offs of applying the pattern. Though consequences are often unvoiced when we describe design decisions, they are critical for evaluating design alternatives and for understanding the costs and benefits of applying the pattern. The consequences for software often concern space and time trade-offs. They may address language and implementation issues as well. Since reuse is often a factor in object-oriented design, the consequences of a pattern include its impact on a system's flexibility, extensibility, or portability. Listing these consequences explicitly helps you understand and evaluate them.

Point of view affects one's interpretation of what is and isn't a pattern. One person's pattern can be another person's primitive building block. For this book we have concentrated on patterns at a certain level of abstraction. Design patterns are not about designs such as linked lists and hash tables that can be encoded in classes and reused as is. Nor are they complex, domain-specific designs for an entire application or subsystem. The design patterns in this book are descriptions of communicating objects and classes that are customized to solve a general design problem in a particular context.

A design pattern names, abstracts, and identifies the key aspects of a common design structure that make it useful for creating a reusable object-oriented design. The design pattern identifies the participating classes and instances, their roles and collaborations, and the distribution of responsibilities. Each design pattern focuses on a particular object-oriented design problem or issue. It describes when it applies, whether it can be applied in view of other design constraints, and the consequences and trade-offs of its use. Since we must eventually implement our designs, a design pattern also provides sample C++ and (sometimes) Smalltalk code to illustrate an implementation.

Although design patterns describe object-oriented designs, they are based on practical solutions that have been implemented in mainstream object-oriented programming languages like Smalltalk and C++ rather than procedural languages (Pascal, C, Ada) or more dynamic object-oriented languages (CLOS, Dylan, Self). We chose Smalltalk and C++ for pragmatic reasons: Our day-to-day experience has been in these languages, and they are increasingly popular.

The choice of programming language is important because it influences one's point of view. Our patterns assume Smalltalk/C++-level language features, and that choice determines what can and cannot be implemented easily. If we assumed procedural languages, we might have included design patterns called "Inheritance," "Encapsulation," and "Polymorphism." Similarly, some of our patterns are supported directly by the less common object-oriented languages. CLOS has multi-methods, for example, which lessen the need for a pattern such as Visitor (page 331). In fact, there are enough differences between Smalltalk and C++ to mean that some patterns can be expressed more easily in one language than the other. (See Iterator (257) for an example.)

DesignPatterns--Introduction


Designing object-oriented software is hard, and designing reusable object-oriented software is even harder. You must find pertinent objects, factor them into classes at the right granularity, define class interfaces and inheritance hierarchies, and establish key relationships among them. Your design should be specific to the problem at hand but also general enough to address future problems and requirements. You also want to avoid redesign, or at least minimize it. Experienced object-oriented designers will tell you that a reusable and flexible design is difficult if not impossible to get "right" the first time. Before a design is finished, they usually try to reuse it several times, modifying it each time.

Yet experienced object-oriented designers do make good designs. Meanwhile new designers are overwhelmed by the options available and tend to fall back on non-object-oriented techniques they've used before. It takes a long time for novices to learn what good object-oriented design is all about. Experienced designers evidently know something inexperienced ones don't. What is it?

One thing expert designers know not to do is solve every problem from first principles. Rather, they reuse solutions that have worked for them in the past. When they find a good solution, they use it again and again. Such experience is part of what makes them experts. Consequently, you'll find recurring patterns of classes and communicating objects in many object-oriented systems. These patterns solve specific design problems and make object-oriented designs more flexible, elegant, and ultimately reusable. They help designers reuse successful designs by basing new designs on prior experience. A designer who is familiar with such patterns can apply them immediately to design problems without having to rediscover them.

An analogy will help illustrate the point. Novelists and playwrights rarely design their plots from scratch. Instead, they follow patterns like "Tragically Flawed Hero" (Macbeth, Hamlet, etc.) or "The Romantic Novel" (countless romance novels). In the same way, object-oriented designers follow patterns like "represent states with objects" and "decorate objects so you can easily add/remove features." Once you know the pattern, a lot of design decisions follow automatically.

We all know the value of design experience. How many times have you had design déjà-vu—that feeling that you've solved a problem before but not knowing exactly where or how? If you could remember the details of the previous problem and how you solved it, then you could reuse the experience instead of rediscovering it. However, we don't do a good job of recording experience in software design for others to use.

The purpose of this book is to record experience in designing object-oriented software as design patterns . Each design pattern systematically names, explains, and evaluates an important and recurring design in object-oriented systems. Our goal is to capture design experience in a form that people can use effectively. To this end we have documented some of the most important design patterns and present them as a catalog.

Design patterns make it easier to reuse successful designs and architectures. Expressing proven techniques as design patterns makes them more accessible to developers of new systems. Design patterns help you choose design alternatives that make a system reusable and avoid alternatives that compromise reusability. Design patterns can even improve the documentation and maintenance of existing systems by furnishing an explicit specification of class and object interactions and their underlying intent. Put simply, design patterns help a designer get a design "right" faster.

None of the design patterns in this book describes new or unproven designs. We have included only designs that have been applied more than once in different systems. Most of these designs have never been documented before. They are either part of the folklore of the object-oriented community or are elements of some successful object-oriented systems—neither of which is easy for novice designers to learn from. So although these designs aren't new, we capture them in a new and accessible way: as a catalog of design patterns having a consistent format.

Despite the book's size, the design patterns in it capture only a fraction of what an expert might know. It doesn't have any patterns dealing with concurrency or distributed programming or real-time programming. It doesn't have any application domain-specific patterns. It doesn't tell you how to build user interfaces, how to write device drivers, or how to use an object-oriented database. Each of these areas has its own patterns, and it would be worthwhile for someone to catalog those too.

便利的开发工具 CppUnit 快速使用指南

2003 年 8 月 03 日

本文从开发人员的角度,介绍 CppUnit 框架,希望能够使开发人员用最少的代价尽快掌握这种技术。下面从基本原理,CppUnit 原理,手动使用步骤,通常使用步骤,其他实际问题等方面进行讨论。以下讨论基于 CppUnit1.8.0。

背景

CppUnit 是个基于 LGPL 的开源项目,最初版本移植自 JUnit,是一个非常优秀的开源测试框架。CppUnit 和 JUnit 一样主要思想来源于极限编程(XProgramming)。主要功能就是对单元测试进行管理,并可进行自动化测试。这样描述可能没有让您体会到测试框架的强大威力,那您在开发过程中遇到下列问题吗?如果答案是肯定的,就应该学习使用这种技术:

  • 测试代码没有很好地维护而废弃,再次需要测试时还需要重写;
  • 投入太多的精力,找 bug,而新的代码仍然会出现类似 bug;
  • 写完代码,心里没底,是否有大量 bug 等待自己;
  • 新修改的代码不知道是否影响其他部分代码;
  • 由于牵扯太多,导致不敢进行修改代码;
    ...

这些问题下文都会涉及。这个功能强大的测试框架在国内的 C++ 语言开发人员中使用的不是很多。本文从开发人员的角度,介绍这个框架,希望能够使开发人员用最少的代价尽快掌握这种技术。下面从基本原理,CppUnit 原理,手动使用步骤,通常使用步骤,其他实际问题等方面进行讨论。以下讨论基于 CppUnit1.8.0。


1. 基本原理

对于上面的问题仅仅说明 CppUnit 的使用是没有效果的,下面先从测试的目的,测试原则等方面简要说明,然后介绍 CppUnit 的具体使用。

首先要明确我们写测试代码的目的,就是验证代码的正确性或者调试 bug。这样写测试代码时就有了针对性,对那些容易出错的,易变的编写测试代码;而不用对每个细节,每个功能编写测试代码,当然除非有过量精力或者可靠性要求。

编码和测试的关系是密不可分的,推荐的开发过程并不要等编写完所有或者很多的代码后再进行测试,而是在完成一部分代码,比如一个函数,之后立刻编写测试代码进行验证。然后再写一些代码,再写测试。每次测试对所有以前的测试都进行一遍。这样做的优点就是,写完代码,也基本测试完一遍,心里对代码有信心。而且在写新代码时不断地测试老代码,对其他部分代码的影响能够迅速发现、定位。不断编码测试的过程也就是对测试代码维护的过程,以便测试代码一直是有效的。有了各个部分测试代码的保证,有了自动测试的机制,更改以前的代码没有什么顾虑了。在极限编程(一种软件开发思想)中,甚至强调先写测试代码,然后编写符合测试代码的代码,进而完成整个软件。

根据上面说的目的、思想,下面总结一下平时开发过程中单元测试的原则:

  • 先写测试代码,然后编写符合测试的代码。至少做到完成部分代码后,完成对应的测试代码;
  • 测试代码不需要覆盖所有的细节,但应该对所有主要的功能和可能出错的地方有相应的测试用例;
  • 发现 bug,首先编写对应的测试用例,然后进行调试;
  • 不断总结出现 bug 的原因,对其他代码编写相应测试用例;
  • 每次编写完成代码,运行所有以前的测试用例,验证对以前代码影响,把这种影响尽早消除;
  • 不断维护测试代码,保证代码变动后通过所有测试;

有上面的理论做指导,测试行为就可以有规可循。那么 CppUnit 如何实现这种测试框架,帮助我们管理测试代码,完成自动测试的?下面就看看 CppUnit 的原理。


2. CppUnit 的原理

在 CppUnit 中,一个或一组测试用例的测试对象被称为 Fixture(设施,下文为方便理解尽量使用英文名称)。Fixture 就是被测试的目标,可能是一个对象或者一组相关的对象,甚至一个函数。

有了被测试的 fixture,就可以对这个 fixture 的某个功能、某个可能出错的流程编写测试代码,这样对某个方面完整的测试被称为TestCase(测试用例)。通常写一个 TestCase 的步骤包括:

  1. 对 fixture 进行初始化,及其他初始化操作,比如:生成一组被测试的对象,初始化值;
  2. 按照要测试的某个功能或者某个流程对 fixture 进行操作;
  3. 验证结果是否正确;
  4. 对 fixture 的及其他的资源释放等清理工作。

对 fixture 的多个测试用例,通常(1)(4)部分代码都是相似的,CppUnit 在很多地方引入了 setUp 和 tearDown 虚函数。可以在 setUp 函数里完成(1)初始化代码,而在 tearDown 函数中完成(4)代码。具体测试用例函数中只需要完成(2)(3)部分代码即可,运行时 CppUnit 会自动为每个测试用例函数运行 setUp,之后运行 tearDown,这样测试用例之间就没有交叉影响。

对 fixture 的所有测试用例可以被封装在一个 CppUnit::TestFixture 的子类(命名惯例是[ClassName]Test)中。然后定义这个fixture 的 setUp 和 tearDown 函数,为每个测试用例定义一个测试函数(命名惯例是 testXXX)。下面是个简单的例子:


 class MathTest : public CppUnit::TestFixture {
 protected:
   int m_value1, m_value2;

 public:
   MathTest() {}

	// 初始化函数
   void setUp () {
     m_value1 = 2;
     m_value2 = 3;
   }
   // 测试加法的测试函数
   void testAdd () {
   		// 步骤(2),对 fixture 进行操作
     int result = m_value1 + m_value2;
     	// 步骤(3),验证结果是否争取
     CPPUNIT_ASSERT( result == 5 );
   }
   // 没有什么清理工作没有定义 tearDown. 
 }
 

在测试函数中对执行结果的验证成功或者失败直接反应这个测试用例的成功和失败。CppUnit 提供了多种验证成功失败的方式:


		CPPUNIT_ASSERT(condition)					// 确信condition为真
	CPPUNIT_ASSERT_MESSAGE(message, condition)	// 当condition为假时失败, 并打印message
	CPPUNIT_FAIL(message)						// 当前测试失败, 并打印message
	CPPUNIT_ASSERT_EQUAL(expected, actual)		// 确信两者相等
	CPPUNIT_ASSERT_EQUAL_MESSAGE(message, expected, actual)	// 失败的同时打印message
	CPPUNIT_ASSERT_DOUBLES_EQUAL(expected, actual, delta)	// 当expected和actual之间差大于delta时失败
	

要把对 fixture 的一个测试函数转变成一个测试用例,需要生成一个 CppUnit::TestCaller 对象。而最终运行整个应用程序的测试代码的时候,可能需要同时运行对一个 fixture 的多个测试函数,甚至多个 fixture 的测试用例。CppUnit 中把这种同时运行的测试案例的集合称为 TestSuite。而 TestRunner 则运行测试用例或者 TestSuite,具体管理所有测试用例的生命周期。目前提供了 3 类TestRunner,包括:


		CppUnit::TextUi::TestRunner 	// 文本方式的TestRunner
	CppUnit::QtUi::TestRunner		// QT方式的TestRunner
	CppUnit::MfcUi::TestRunner		// MFC方式的TestRunner
	

下面是个文本方式 TestRunner 的例子:


		CppUnit::TextUi::TestRunner runner;
	CppUnit::TestSuite *suite= new CppUnit::TestSuite();
	
	// 添加一个测试用例
	suite->addTest(new CppUnit::TestCaller<MathTest> (
	              "testAdd", testAdd));
	
	// 指定运行TestSuite 
	runner.addTest( suite );
	// 开始运行, 自动显示测试进度和测试结果
	runner.run( "", true );    // Run all tests and wait
	

对测试结果的管理、显示等功能涉及到另一类对象,主要用于内部对测试结果、进度的管理,以及进度和结果的显示。这里不做介绍。

下面我们整理一下思路,结合一个简单的例子,把上面说的思路串在一起。


3. 手动使用步骤

首先要明确测试的对象 fixture,然后根据其功能、流程,以及以前的经验,确定测试用例。这个步骤非常重要,直接关系到测试的最终效果。当然增加测试用例的过程是个阶段性的工作,开始完成代码后,先完成对功能的测试用例,保证其完成功能;然后对可能出错的部分,结合以前的经验(比如边界值测试、路径覆盖测试等)编写测试用例;最后在发现相关 bug 时,根据 bug 完成测试用例。

比如对整数加法进行测试,首先定义一个新的 TestFixture 子类,MathTest,编写测试用例的测试代码。后期需要添加新的测试用例时只需要添加新的测试函数,根据需要修改 setUp 和 tearDown 即可。如果需要对新的 fixture 进行测试,定义新的 TestFixture 子类即可。注:下面代码仅用来表示原理,不能编译。


/// MathTest.h
// A TestFixture subclass.
// Announce: use as your owner risk.
// Author  : liqun (liqun@nsfocus.com)
// Data    : 2003-7-5

#include "cppunit/TestFixture.h"


class MathTest : public CppUnit::TestFixture {
protected:
	int m_value1, m_value2;
	
public:
	MathTest() {}
	
	// 初始化函数
	void setUp ();
	// 清理函数
	void tearDown();
	
	// 测试加法的测试函数
	void testAdd ();
	// 可以添加新的测试函数
};

/// MathTest.cpp
// A TestFixture subclass.
// Announce: use as your owner risk.
// Author  : liqun (liqun@nsfocus.com)
// Data    : 2003-7-5

#include "MathTest.h"
#include "cppunit/TestAssert.h"

void MathTest::setUp()
{
     m_value1 = 2;
     m_value2 = 3;
}

void MathTest::tearDown()
{

}

void MathTest::testAdd()
{
     int result = m_value1 + m_value2;
     CPPUNIT_ASSERT( result == 5 );
}

然后编写 main 函数,把需要测试的测试用例组织到 TestSuite 中,然后通过 TestRuner 运行。这部分代码后期添加新的测试用例时需要改动的不多。只需要把新的测试用例添加到 TestSuite 中即可。


/// main.cpp
// Main file for cppunit test.
// Announce: use as your owner risk.
// Author  : liqun (liqun@nsfocus.com)
// Data    : 2003-7-5
// Note	   : Cannot compile, only for study.	
#include "MathTest.h"
#include "cppunit/ui/text/TestRunner.h"
#include "cppunit/TestCaller.h"
#include "cppunit/TestSuite.h"

int main()
{
	CppUnit::TextUi::TestRunner runner;
	CppUnit::TestSuite *suite= new CppUnit::TestSuite();
	
	// 添加一个测试用例
	suite->addTest(new CppUnit::TestCaller<MathTest> (
	              "testAdd", testAdd));
	
	// 指定运行TestSuite 
	runner.addTest( suite );
	// 开始运行, 自动显示测试进度和测试结果
	runner.run( "", true );    // Run all tests and wait
}


4. 常用使用方式

按照上面的方式,如果要添加新的测试用例,需要把每个测试用例添加到 TestSuite 中,而且添加新的 TestFixture 需要把所有头文件添加到 main.cpp 中,比较麻烦。为此 CppUnit 提供了 CppUnit::TestSuiteBuilder,CppUnit::TestFactoryRegistry 和一堆宏,用来方便地把 TestFixture 和测试用例注册到 TestSuite 中。下面就是通常的使用方式:


/// MathTest.h
// A TestFixture subclass.
// Announce: use as your owner risk.
// Author  : liqun (liqun@nsfocus.com)
// Data    : 2003-7-5

#include "cppunit/extensions/HelperMacros.h"


class MathTest : public CppUnit::TestFixture {
	// 声明一个TestSuite
	CPPUNIT_TEST_SUITE( MathTest );
	// 添加测试用例到TestSuite, 定义新的测试用例需要在这儿声明一下
	CPPUNIT_TEST( testAdd );
	// TestSuite声明完成
	CPPUNIT_TEST_SUITE_END();
	// 其余不变

protected:
	int m_value1, m_value2;
	
public:
	MathTest() {}
	
	// 初始化函数
	void setUp ();
	// 清理函数
	void tearDown();
	
	// 测试加法的测试函数
	void testAdd ();
	// 可以添加新的测试函数
};

/// MathTest.cpp
// A TestFixture subclass.
// Announce: use as your owner risk.
// Author  : liqun (liqun@nsfocus.com)
// Data    : 2003-7-5

#include "MathTest.h"

// 把这个TestSuite注册到名字为"alltest"的TestSuite中, 如果没有定义会自动定义
// 也可以CPPUNIT_TEST_SUITE_REGISTRATION( MathTest );注册到全局的一个未命名的TestSuite中.
CPPUNIT_TEST_SUITE_NAMED_REGISTRATION( MathTest, "alltest" );

// 下面不变

void MathTest::setUp()
{
     m_value1 = 2;
     m_value2 = 3;
}

void MathTest::tearDown()
{

}

void MathTest::testAdd()
{
     int result = m_value1 + m_value2;
     CPPUNIT_ASSERT( result == 5 );
}


/// main.cpp
// Main file for cppunit test.
// Announce: use as your owner risk.
// Compile : g++ -lcppunit MathTest.cpp main.cpp
// Run     : ./a.out
// Test    : RedHat 8.0 CppUnit1.8.0
// Author  : liqun ( a litthle modification. liqun@nsfocus.com)
// Data    : 2003-7-5

// 不用再包含所有TestFixture子类的头文件
#include <cppunit/extensions/TestFactoryRegistry.h>
#include <cppunit/ui/text/TestRunner.h>

// 如果不更改TestSuite, 本文件后期不需要更改. 

int main()
{
	CppUnit::TextUi::TestRunner runner;
	
	// 从注册的TestSuite中获取特定的TestSuite, 没有参数获取未命名的TestSuite.
	CppUnit::TestFactoryRegistry &registry = CppUnit::TestFactoryRegistry::getRegistry("alltest");
	// 添加这个TestSuite到TestRunner中
	runner.addTest( registry.makeTest() );
	// 运行测试
	runner.run();
}

这样添加新的测试用例只需要在类定义的开始声明一下即可。

5. 其他实际问题

通常包含测试用例代码和被测试对象是在不同的项目中。应该在另一个项目(最好在不同的目录)中编写 TestFixture,然后把被测试的对象包含在测试项目中。

对某个类或者某个函数进行测试的时候,这个 TestFixture 可能引用了别的类或者别的函数,为了隔离其他部分代码的影响,应该在源文件中临时定义一些桩程序,模拟这些类或者函数。这些代码可以通过宏定义在测试项目中有效,而在被测试的项目中无效。



参考资料

关于作者

李群,当前关注于网络安全产品的开发、研究;软件开发过程等方面。您可以通过 liqun@nsfocus.com和他联系。

转自 http://www-128.ibm.com/developerworks/cn/linux/l-cppunit/

利用开源 GIS 确定方位

针对 UNIX 和 Linux 用户的地理信息系统工具
 

级别: 初级

Frank Pohlmann, 技术编辑

2005 年 6 月 16 日

地理信息系统(Geographic Information Systems,GIS)软件依赖于覆盖整个地球的数据集。为处理大量的 GIS 数据及其格式,编程人员创建了若干开源库和 GIS 套件。本文简要介绍可用于 UNIX® 和 Linux® 的 GIS 工具和库的范围和深度。

GIS 软件以前仅限于地理学者和地质工作者使用,自从网上提供全球地图服务和移动电话公司开始提供全球定位系统(Global Positioning System,GPS)服务以来,GIS 软件已变得非常普及了。GIS 系统可获得并处理描述地球表面的空间数据。一般情况下,在空间处理软件对地理数据进行格式化、处理和显示后,这些数据就转换为地理信息。声纳、雷达、照相机和其他观测平台便可以获得这些数据。GIS 软件将这些数据存储在三维数据库中,并将其格式化,然后传输它们。有时甚至可以通过所谓的 四维(三维 + 时间)转换来显示数据随时间的发展情况。

当然,我们能够对所有 GIS 数据进行编辑,而且必须经常编辑这些数据,训练有素的 GIS 专家可以处理接收到的大多数数据集格式。许多(若非全部)数据格式都遵守开放标准,GIS 空间中的所有操作都可以使用运行于 Linux、主要的 Berkeley Software Design (BSD) 和一些 UNIX 变体上的开源应用程序来执行。最重要的是,GIS 软件套件还可以在 Mac OS X 上运行。

GIS 软件具有广泛的应用范围。自然资源管理的正确见解依赖于地图的可用性和使用代表地貌和水文过程等的数据层来覆盖拓扑数据的能力。考古学家使用 GIS 重构古代贸易网络。市政规划人员使用 GIS 模拟基础设施。环境科学家需要 GIS 软件来模拟沿海区域和山谷的侵蚀。通过 GIS 数据查看器来观测数据,全球温室效应变得更为明显。制图人员依靠 GIS 软件集成数据,从而制作数据丰富的地图。启用 GPS 的移动电话用户可以使用 GPS 和 GIS 软件来定位与之交谈的人,有时甚至可以确定他们自己的位置。

Quantum GIS:开源 GIS 数据查看器

近年来,Linux 上运行的 GIS 应用程序迅速增加。20 世纪 80 年代,编程人员开发了地理资源分析支持系统(Geographic Resources Analysis Support System,GRASS)。在 20 世纪 90 年代后期经过修改后,任何具有 GIS 知识和一些 Linux 专业知识的人员都可以利用 GRASS 从 Linux 命令行或图形用户界面 (GUI) 运行完整的 GIS 系统。遗憾的是,GRASS 极其复杂,为在 Linux 上运行 GIS 的初学者造成了一些不便。GUI 显示大量的功能和命令行标识,其目标用户是 GIS 专家,而非尝试创建网络地图的新手。

2002 年 5 月,开发人员开发了 GPLed Quantum GIS (QGIS),该项目面向需要访问、显示和可能编辑 GIS 数据集的初学者和中间用户。GIS 用户可以将 QGIS 部署为单独的 GIS 数据查看器和编辑器,或者部署为 GIS 工具链的一部分。GIS 工具链可包括 QGIS、GRASS 软件套件、一个三维 PostGIS 数据库和一个向通过 Internet 访问地图网站的用户提供数据集和地图的地图服务器。

QGIS 开发人员决定使用基于 C++ 的 Qt 工具包来构建 QGIS 界面,这是不同于以前实践的主要方案(编程人员主要使用 Tcl/Tk 创建了 GRASS,Tcl/Tk 建立于 20 世纪 80 年代后期)。尽管 QGIS 是在 GPLed Qt 工具包的基础上开发的,但它可以在多数 Linux 和 UNIX 变体、Microsoft® Windows® 和 Mac OS X 上运行。

栅格数据

应用程序使用两种不同的数据结构存储 GIS 数据:栅格数据矢量数据。您可以添加三维数据格式的数据库存储,该格式针对 PostgreSQL 处理而优化 —— 即 PostGIS 数据格式。我们将 PostGIS 数据归类为矢量数据,其原因将在下文介绍。

QGIS 处理所有三种数据 —— 栅格数据、矢量数据和数据库,这也是编写 GIS 数据编程库的程序员的重要努力方向。通过想像正方形网格或六边形单元,可以方便地可视化数据结构(实践中,应用程序在多数情况下采用正方形网格)。这些网格覆盖一个像矩阵一样的地理区域,并在一个名为 map algebra 的字段中使用数学表示形式。GIS 专家可以向每个网格中添加数据,如降水量值或经济数据,但描述复杂的不规则地理形状较为困难。该软件通常必须依赖于相似性和网格中值的位置来对特征分类(如街道或海岸),而非基于封装在元数据中的特征描述。另一种可能的解释依赖于使用与个别网格相关联的颜色值将栅格网格组归类为某些特征。

网格按行或按列排列,它反映硬盘存储数据的方式。许多基于栅格的格式都有基本的图像格式:常见的基于栅格的图像格式是位图 (.bmp)。带标记的图像文件格式 (.tiff) 是另一种常用的基于栅格的图像格式,GIS 专家为适应地理学者的需要将其重命名为 GeoTIFF。基于栅格的数据格式更类似于图像。其准确性依赖于描述尽可能少的特征的网格数量。

在栅格数据模型中,地图的准确性还依赖于地图的比例。因此,地图的分辨率和准确性依赖于每个网格代表的实际区域。该数据模型可比较的简单性有助于对 GPS 设备和卫星成像捕获的数据进行建模。有些数据格式可以很好地适用于栅格模型。例如,数字高程模型(Digital Elevation Model,DEM)数据点在网格图形中均匀排列。DEM 格式编码高程数据来创建高清晰地形。前几年,美国地质调查局 (USGS) 向公共领域发布了一个非常受欢迎的全球 DEM 数据集。

矢量数据

基于矢量的数据格式的出现改变了 GIS 专家的生活。新数据查看器和编辑器,如 Thuban 和 QGIS 不必再费力地表示复杂的基于矢量的格式,因为它一开始就内置了编辑和添加基于矢量的数据层的能力。GRASS 已有 20 多年的历史,但最近才获得此能力。

简言之,矢量数据利用最简单的拓扑实体 — 点、线和多边形,并在二维笛卡尔坐标系统中固定它们来描述地理特征。连接线称为,笛卡尔坐标系统中的点称为节点。数据结构与图形关联,并以图形理论做为其数学基础。所谓的弧节点列表 包含弧和节点。列表定义多边形,并可以相互层叠,来表示方向完全相反但描述相同地理区域的数据集,从而形成数据详细的地图。

QGIS 和其他较为简单的数据查看器用于探测各种数据格式和覆盖此行星系统的所有数据集。与某些商业产品、甚至 GRASS 不同,您可以容易地安装这些查看器,并且几乎可以在所有主要操作系统上使用。里程可能稍有偏差,但几乎可以始终保证成功。

数据格式

QGIS 支持许多矢量数据格式,如 Shapefile、MapInfo 图层和 ArcInfo coverage。矢量数据要求的存储要比栅格数据少得多,因为弧节点列表简化并减少了表示地图中包含的特征所需的数据。这些数据还非常便于搜索地图或用矢量表示的各种图层。在 20 世纪 90 年代初,商业软件套件 ArcInfo GIS 进入市场以后,促进了对 Shapefile 的使用。后来出现了其他文件和数据格式,但是,没有免费的开源编程社区,这些成果仍局限于地理学者的理论研究和军事规划。





回页首


GRASS

最近推出了 GRASS 6.0 版,该版本支持大约 40 种数据格式。它突破了二维栅格格式,可包括体元(voxel) 或三维栅格格式。大量的成像和成图模块大大方便了 GIS 专家以新的方法分析数据。这使得长期模拟和完善的地图制作成为可能。

不过,用户仍需要解决的问题是,由于该版本针对的是 UNIX 和 Linux 专家,其界面相当杂乱而且安装过程也比较复杂。进一步说,导致这一复杂性的原因是随 GRASS 提供了大量的库和工具。所幸的是,各个 GIS 技术级别的学生编写了许多有关 GRASS 的文档,因此使 Linux 和 UNIX 新手能够对付 GRASS 安装时的复杂要求。

GDAL 和 OGR

在谈到 GIS 数据格式时,必然会考虑到要支持的大量格式,以使每个数据查看器和 GIS 应用程序可以广泛适用。开源 GIS 应用程序必须包括从 ArcInfo 到 X 窗口系统的大多数开放数据格式标准。诸如 GeoTIFF 的格式是通常由多数应用程序支持的开放标准的常见示例。

在开源范围中,GRASS、QGIS、Thuban 和许多其他 GIS 应用程序使用基本库,即 Geospatial Data Abstraction Library (GDAL)。GDAL 用 C 和 C++ 编写,只能包括一种栅格格式。另一个库叫做 OGR Simple Features Library(以前称为 OpenGIS Simple Features Reference Implementation ),尽管是为了适应 GDAL 源树中存在的矢量格式而构建的,但 OGR 依赖于 GDAL。事实上,如果没有开源许可的 GDAL,多数现代地理空间数据查看器将是不可思议的。该库为编程人员提供了通用的数据模型,包括所有栅格数据格式和矢量数据格式(通过 OGR)。GDAL 还可让编程人员在世界地理坐标(即地理参考坐标)上设计栅格数据。





回页首


PostGIS 和 OpenGIS

在没有相当完善的数据存储机制的情况下,公共领域 GIS 数据不可能存在。然而,存储可能不像栅格数据那样重要,其空间构成可能包含相当简单的数字数据。编程人员只须知道一些较高级别的结构,但是多数编程人员认为栅格数据较为复杂并且占用大量的存储。

OpenGIS 标准通过生成矢量数据解决了这些问题,矢量数据也指几何对象,如点、线、多边形及其组合,可以在启用三维数据库的 PostgreSQL 中存取(PostgreSQL 的 OpenGIS 实现标准称为 PostGIS)。存储在 PostgreSQL 数据库中的 GIS 数据完全可以使用 SQL-92 搜索。

目前,编程人员能够访问整个开源 GIS 应用程序在很大程度上依赖于 UNIX 和 Linux 系统。这些成果得益于通常的开放标准,而且多数 Internet 地图制作领域正在趋向于依赖这些标准。以任何形式处理地理数据的所有编程人员都会遇到与 Linux 系统编程人员在 glibc 上遇到的相同的基础库问题。GIS 编程人员即使只是想利用键盘编写数据过滤器的脚本或删改工具链,也无法判断要使用哪些数据格式和基础库。





回页首


结束语

在谈到有关开源和全球环境现象时,很难让人联想到地理数据集和 GIS 应用程序领域。但是,开源应用程序,如 GRASS 和 QGIS 在尝试使公共领域 GIS 数据集可用于编程人员和技术用户,以便他们不必使用商业产品。GDAL 和 OGR 之类的库可以将 GIS 数据放在通用开源基础上处理,而不会影响开放 GIS 数据标准的完整性。





回页首


参考资料

  • 您可以参阅本文在 developerWorks 全球站点上的 英文原文

  • 访问 Quantum GIS,查看文档的全部下载链接和参考。

  • GRASS 包含有关 GRASS 的重要下载链接。

  • 请将 GRASS 教程 放到收藏夹,它的重要性不在于到 GRASS 文档的链接,而是提供了到多种语言 GIS 软件的链接。英语教程非常优秀。GRASS 6 教程对于德国读者极为重要。

  • 如果需要全球数据集,可以在 Global GIS Data Resources 中进行搜索。

  • GDAL 包含完整的 API 参考和所有数据格式,可供 GDAL 编程人员和命令行用户使用。

  • 面向 C++ 编程人员的矢量数据信息很难获得,简洁而详实的 OGR Simple Features Library 提供了所需的文档。

  • 访问 PostGIS,了解有关 PostgreSQL 的信息。

  • 有关标准文档,包括 OpenGIS 及其他内容以及 GIS 标准方面的新闻综述,请访问 Open Geospatial Consortium

  • USGS National Map Viewer 整合了美国政府各种资源的地图,并显示在浏览器中。

  • 访问 developerWorks 开放源代码专区,获得丰富的 how-to 信息、工具和项目更新,帮助您使用开源技术进行开发,并将其用于 IBM 的产品。

  • 使用 IBM 试用软件,创新下一个开源开发项目,可通过下载或 DVD 得到。

  • 可从 Developer Bookstore 的开放源代码专区找到数百种 有关开放源代码主题的打折书籍

  • 通过参与 developerWorks blogs 加入 developerWorks 社区。




回页首


关于作者

作者照片

Frank Pohlmann 以前研究的是中东宗教历史,后来各基金会认为研究宗教辩证历史与当今世界相去甚远,从此他便专攻自己热爱的领域 —— 免费软件。他获准成为英国的 Linuxuser and Developer 的技术编辑,在学习中古波斯文期间他对脚本和字符集产生了浓厚的兴趣。

使用 Qt 制作 Skin

2003 年 1 月 01 日

Skin(表皮) 是制作比较酷的软件界面的有利工具. 一个软件可以同时使用多种Skin 以取得不同的外观, 使同一个软件有截然不同的风格. 用户可以根据自己的喜好选择 不同的风格. 本节介绍使用 Qt 制作 Skin 的方法.

软件界面的风格变化可以通过两种机制完成,一种是通过设置主题(Themes),它使用 界面库本身所具有的对界面组件(Components)的控制能力切换显示风格;另外一种是 通过提供不同系列的图片来切换显示界面,即这里所讲的 Skin。

制作表皮有几个重要的因素值得考虑:

1. 使用无边界的窗口

在 XWindow 下,无边界的窗口是指不受窗口管理器管理的边界不规则的窗口。由于 不受窗口管理器管理管理,所以软件窗口界面没有附加的标题条(Title Bar)等。 在Qt中,建立无边界窗口的最简单的方法 是设置 QWidget 的 WFlags 的值是 WStyle_NoBorder。它定义在 qnamespace.h 中。 不规则窗口的特点则要求对整个 窗口使用图像掩码。使用 X 窗口形状的扩展(X Shape Extension)来达到要求。在 Qt 中可以直接使用,


                QBitmap bm;
                bm = *(Pixmaps[MASK]);
                setMask(bm);
                setBackgroundPixmap(*Pixmaps[BACKGROUND]);
                





回页首


2. 窗口的移动

由于上述窗口不受窗口管理器的管理的特性,所以移动窗口需要特殊处理,一般的 方法是截取根Widget的鼠标按钮事件,自己处理鼠标点击和移动的事件。


                void SkinDemo::mouseMoveEvent(QMouseEvent *e)
                {
                        QPoint newpos = e->globalPos();
                        QPoint upleft = pos0 + newpos - last;
                        move(upleft);
                }

                void SkinDemo::mousePressEvent(QMouseEvent *e)
                {
                        last = e->globalPos();
                        pos0 = e->globalPos() - e->pos();
                }
                

这里我们取得的鼠标位置是绝对位置,即相对于根窗口的位置,同时也记录下窗口 左上角的位置,当鼠标移动时,取得新的绝对位置,则窗口左上角 的新位置应该 是原来位置与鼠标移动的位置之差。





回页首


3. 按钮的制作

对于表皮中的图像按钮,设置它的父类是 QButton,这种按钮由两幅图片构成, 一幅图片是正常状态(Normal),一幅图片是按钮按下时的状态(Activated)。有时 也可以设置成四种状态,即增加禁止状态(Disabled)和鼠标指针进入时的状态 (Hovered)。

在例子( qt-skin-example.tar.gz)中,我们重新定义了鼠标按下和鼠标移动的事件处理 函数, 并且含有按钮的所有信息,主工作区的所有信息等。主工作区的信息是它的 位置和尺寸,主窗口的信息是它所使用的背景图片和图片的掩码(用来制造不规则窗 口),所以整个主窗口的大小可以由图片的尺寸来决定。几个按钮的信息包含它们的 位置,它们的大小由图片的大小来决定。

下面是程序运行结果之一:







回页首






回页首


关于作者

IBM has authored this article

用QT创建新风格Howto


级别: 中级

姚延栋, 研究生, 中国科学院软件所2002级

2003 年 11 月 01 日

本文介绍了如何使用qt提供的接口来设计自己的GUI风格(look and feel),并通过一个具体的例子(使QSpinBox垂直显示)来详细说明过程。运行环境:redhat 9.0,qt-x11-free-3.2.*

1.Qt的风格

a) Qt简介
Qt是一个跨平台的C++图形用户界面应用程序开发库,使用Qt可以开发出高质量的图形用户接口,它是完全面向对象的、易于扩展且允许真正的组件编程。Qt获得了很大的成功,特别是它的信号-槽机制是非常值得研究的通信机制,它也是Linux发行版标准组件KDE(K Desktop Enviroment)的基础。

b) 风格机制
Qt的风格机制实现了不同平台上的图形用户接口(GUI)的观感(look and feel),例如Windows平台上通常使用Windows或Windows-xp风格,而Unix平台上通常使用Motif、CDE风格。

下图显示了Qt中与风格相关的类的继承关系



QStyle是所有风格类的基类,它控制着所有的部件(widget即windows编程中的控件)的界面风格或观感,它定义了大量的枚举类型和十几个函数。枚举类型表示界面上的不同元素(如组合框中的按钮,按钮的边框等);函数控制图形用户界面的绘制,但大多数函数基本上只是一些声明而没有函数实现,他们的实现在QCommonStyle、QWindowStyle、QMotifStyle及其子类中。QStyle只实现了3个函数drawItem(), itemRect(), visualRect()。

drawItem(): 负责绘制文本和象素图。
itemRect(): 返回文本或图像所占的区域。
visualRect(): 返回逻辑坐标,这个函数使Qt实现right-to-left风格(阿文、维文传统是文本从右向左显示,因此控件布局也是从右向左)。如下图所示:



可以看到菜单、工具条是右对齐、单选框的按钮在右边

c) 创建新风格的步骤
在Qt中实现一种新风格的步骤很简单:只需选择一个风格类(如QCommonStyle或QStyle)作为父类,然后实现感兴趣的函数即可。难点在于函数的实现。

选择父类:可以选择QStyle, QCommonStyle, QWindowStyle, QMotifStyle以及他们的子类的任意一个作为父类。通常可以选择QWindowsStyle或QMotifStyle,也可以选择QCommonStyle甚至是QStyle,但是工作量会比较大,因为很多界面的细节需要自己实现。

重新实现必要的函数:想修改界面风格的哪部分,就重新实现与绘制那部分相关的函数,下面解释一下我们要重载的QStyle中的几个函数,这几个函数控制着图形用户界面上不同元素的布局和观感。


                        
1)void drawPrimitive( PrimitiveElement pe,
                    	 QPainter *p,
                        const QRect & r,
                        const QColorGroup & cg,
                        SFlags flags = Style_Default,
                        const QStyleOption &opt = QStyleOption::Default ) ;
                        

功能:绘制基本图形元素,如QSpinBox中的带箭头的按钮 等。

参数: PrimitiveElement pe: 这个枚举型变量表示将要绘制的基本图形界面元素(这里说的基本图形用户界面元素指GUI中不可再分的一个原子元素,如组合框



中的这个绘有黑色三角形的按钮,spinBox中的按钮


QPainter *p:指向QPainter类的指针,Qt中的所有绘制操作不管是绘制文本、图形还是图像都由这个类来处理。
QRect &r: 表示一个矩形区域,Qt在这个区域中绘制基本界面元素(PrimitiveElement).
QColorGroup &cg: QColorGroup表示一个部件(widget)的颜色组(color group),color group含有部件绘制自己时使用的各种颜色,譬如前景色背景色等。下图展示了color group中的各种颜色属性

SFlags flags: 控制如何绘制图形界面元素的标志。
QStyleOption &opt: 绘制不同的部件(widget)时会需要不同的参数,如绘制面板(panel)可能需要线宽作为额外参数而绘制焦点矩形(focus rect)可能需要背景色作为额外参数,所以Qt专门提供了一个类QStyleOption来封装不同的widget可能需要的不同的参数,opt指向这样一个类的对象。


2)void drawComplexControl( 	ComplexControl control,
			QPainter *p,
			const QWidget *widget,
			const QRect &r,
			const QColorGroup &cg,
			SFlags flags = Style_Default,
			SCFlags controls = QStyle::SC_All,
			SCFlags active = QStyle::SC_None,
			const QStyleOption& opt = QStyleOption::Default)
				       			

功能:绘制复杂控制部件(widget)如SpinWidget,comboBox,slider,listView等

参数:

ComplexControl control:是一个枚举量,表示将要绘制的复杂控制部件(widget)如组合框、列表框等。
QPainter *p:指向QPainter的指针,Qt中的所有绘制操作不管是绘制文本、图形还是图像都由这个类来处理。
QWidget *widget:指向QWdget或其子类的指针,可以根据上面control的值转变(cast)成合适的类型,例如如果要绘制QSpinWidget,那么control取值为CC_SpinWidget,而widget指向一个QSpinWidget(QWidget的子类)的实例(instance)。使用这个变量可以访问QSpinWidget的成员函数和成员变量,譬如可以调用QSpinWidget的sizeHint函数获得这个部件的缺省大小(一个矩形空间)。
QRect &r: 表示一个矩形区域,Qt在这个区域中绘制控件或其子部件。
QColorGroup &cg: QColorGroup表示一个部件(widget)的颜色组(color group),color group含有部件绘制自己时使用的各种颜色,譬如前景色背景色等。
SFlags flags: 控制如何绘制图形界面元素的标志
SCFlags controls表示绘制复杂控制部件control的哪个子部件,缺省为SC_All,即绘制整个control而不是其某个子部件(注意control, controls是两个不同的参数)
QStyleOption &opt: 在绘制不同的部件时可能需要不同的额外的参数,这个变量在绘制不同的widget时提供不同的信息。

									
		3) QRect querySubControlMetrics(ComplexControl control,
    								const QWidget* widget,
									SubControl sc,
									const QStyleOption&
									 = QStyleOption::Default)
									

功能:获取子部件的坐标和尺寸信息。这个函数控制着一个复杂控件的布局,重载这个函数可以使的组合框的下拉按钮绘制在左边 而不是默认的右边。

参数:
ComplexControl control: 枚举量,表示将要绘制的复杂控制部件(widget)如组合框、列表框等。
QWidget *widget:指向QWidget或其子类的指针,可以根据上面control的值转变(cast)成合适的类型,例如如果要绘制QSpinWidget,那么control取值为CC_SpinWidget,而widget指向一个QSpinWidget(QWidget的子类)的实例。使用这个变量可以访问QSpinWidget的成员函数和成员变量,譬如可以调用QSpinWidget的sizeHint函数获得这个部件的缺省大小(一个矩形空间)。
SubControl sc:枚举量,一个复杂部件可能由多个的子部件组成,使用sc变量说明要获取那个子部件的坐标和尺寸信息。
QStyleOption &opt: 计算不同部件的尺寸时可能需要不同的额外信息,QStyleOption封装了这些信息。





回页首


2.创建新风格

下面用一个例子来介绍一下创建新风格的整个过程,在编程之前,先看一下最终的结果是什么样的。(在Qt内部QSpinBox类是通过QSpinWidget实现的)

默认风格的效果: 使用新风格的效果:

可以看到在新风格中我们的SpinBox有了垂直显示的效果。下面我们按上面说明的步骤来创建一种新的风格。

1)选择基类:我们选择QWindowsStyle类作为我们新风格类的基类,当然也可以选择QMotifStyle,在这个例子种也可以选择QCommonStyle。一般不建议选择QCommonStyle作为基类,因为QCommonStyle只实现了一部分界面部件,如果要实现一个完整的风格类,我们需要重新写很多代码。

2)重载相关的函数:在这个例程中我们只修改了spinBox的风格,实现这个部件(widget)只与QStyle类的三个函数drawPrimitive, drawComplexControl, qureySubControlMerics相关,所以我们只需重载这三个函数的相关部分代码.下面对代码中的关键部分做一下注释,不重要的部分省略了。详细的代码可以从后面下载。

绘制spinbox中按钮的函数:


void CustomStyle::drawPrimitive( PrimitiveElement pe,
                        	QPainter * p,
                            const QRect & r,
                            const QColorGroup & cg,
                            SFlags flags,
                            const QStyleOption & opt ) const
{
/*PE_SpinWidgetUp,PE_SpinWidgetDown表示spinBox中的下按钮和上按钮,
下面的代码使得这两个按钮中的三角形分别向左和向右*/
if ((pe == PE_SpinWidgetUp) || (pe == PE_SpinWidgetDown)){
		int fw = pixelMetric( PM_DefaultFrameWidth, 0 );//fw表示边框宽度,默认为2
		QRect br;  //spinBox上按钮的边界矩形不是spinBox的边界矩形。
		br.setRect( r.x() + fw, r.y() + fw, r.width() - fw*2,
		    r.height() - fw*2 );
		p->fillRect( br, cg.brush( QColorGroup::Button ) );
		int x = r.x(), y = r.y(), w = r.width(), h = r.height();
		int sw = w-4;
		int sh = sw/2 + 2;      // Must have empty row at foot of arrow
		int sx = x + w / 2 - sw / 2 - 1;
		int sy = y + h / 2 - sh / 2 - 1;

		QPointArray a;
/* 设置三角形的三个点的坐标,修改这三个点可以使得QSpinBox上按钮里的三角型呈现任意的形状,
下面的设置使得三角形表示的箭头分别向左和向右。*/
		if ( pe == PE_SpinWidgetDown )
		    a.setPoints( 3,  0, sh/2,  sw-1, 1,  sw-1, sh-1 );
  	else
	    	a.setPoints( 3,  0, 1,  0, sh-1,  sw-1, sh/2 );
		...........
		p->drawPolygon( a );    //绘制三角形
}else if((pe == PE_ButtonBevel) || (pe == PE_ButtonCommand) || (pe == PE_ButtonTool) 
|| (pe == PE_ButtonDropDown) || (pe == PE_HeaderSection))
    { //绘制按钮的各种效果使得看起来凸起或凹下。
    	qDrawShadePanel(p, r, cg, flags & (Style_Sunken | Style_Down | Style_On), 
		1, &cg.brush(QColorGroup::Button));
    }else{ 
/*对于其他基本图形元素(PrimitiveElement)的绘制我们不作处理只是简单的调用父类的函数。*/
		QWindowsStyle::drawPrimitive( pe, p, r, cg, flags, opt);
    }
}

绘制整个spinBox的函数:


void CustomStyle::drawComplexControl( ComplexControl control,
				       QPainter *p,
				       const QWidget *widget,
				       const QRect &r,
				       const QColorGroup &cg,
				       SFlags flags,
				       SCFlags controls,
				       SCFlags active,
				       const QStyleOption& opt ) const
{    
//下面的代码使得spinWidget呈现垂直显示的风格而不是通常的水平显示
if (control == CC_SpinWidget) {
		const QSpinWidget * sw = (const QSpinWidget *) widget;
		//绘制向上按钮部分,controls默认为SC_All,即绘制整个spinwidget
		if ( controls & SC_SpinWidgetUp ) {
			
	    	if ( sw->buttonSymbols() == QSpinWidget::PlusMinus )
				pe = PE_SpinWidgetPlus;  // 使用加减号




		    else
				pe = PE_SpinWidgetUp;   // 使用三角形 




	    	QRect re = sw->upRect();
		    QColorGroup ucg = sw->isUpEnabled() ? cg : sw->palette().disabled();
	    	drawPrimitive(PE_ButtonBevel, p, re, ucg, flags); //绘制按钮的边框
			drawPrimitive(pe, p, re, ucg, flags); 			//绘制按钮
		}
		//绘制向左按钮部分。
		if ( controls & SC_SpinWidgetDown ) {
		  /*与绘制向下按钮相似*/
		}
}else{//不处理spinbox之外的其他复杂控制部件,调用父类函数处理
QWindowsStyle::drawComplexControl(control, p, widget, r, cg, flags, controls, active, opt);
    }
}

获取部件(widget)中各个子部件布局信息的函数,这个函数控制着一个widget的外观


QRect CustomStyle::querySubControlMetrics( ComplexControl control,
					    const QWidget *widget,
					    SubControl sc,
					    const QStyleOption &opt ) const
{
    if(control == CC_SpinWidget){
    	int fw = pixelMetric( PM_SpinBoxFrameWidth, widget);
/*QSize 定义二维对象的大小,也就是宽和高. 坐标类型是QCOORD定义为int)*/
		QSize bs;  //此处bs表示每个按钮的大小,因为有两个按钮所以下面除以2.
		bs.setWidth(widget->width()/2 -fw);
		if(bs.width() < 8) bs.setWidth(8);
		/*按钮高度设置为QMIN{按钮宽度的1.6倍, 部件高度的四分之一}
		bs.setHeight(  QMIN(bs.width()*8/5, widget->height() / 4) ); 
		bs = bs.expandedTo( QApplication::globalStrut() );
		
		int x = fw;
		int y, ly, ry;
		y = widget->height() - x - bs.height();
		ly = fw;
		ry = y - fw;
	//下面定义了QSpinWidget的各个子部件的坐标位置.
	switch ( sc ) {
	case SC_SpinWidgetUp:
		//返回向右按钮的坐标信息
	    return QRect(x + bs.width(), y, bs.width(), bs.height());
	case SC_SpinWidgetDown:
		//返回向左按钮的坐标信息
	    return QRect(x, y, bs.width(), bs.height());
	case SC_SpinWidgetButtonField:
		//返回两个按钮的总区域大小
	    return QRect(x, y, widget->width() - 2*fw, bs.height());
	case SC_SpinWidgetEditField:
/*返回可编辑框的坐标信息*/
	    return QRect(fw, ly, widget->width() - 2*fw, ry);
	case SC_SpinWidgetFrame:
		//返回整个spinBox的坐标信息
	    return widget->rect();
	default:
	    break;
	}
    }else{//其它部件的布局信息调用父类的函数来处理。
		return QWindowsStyle::querySubControlMetrics(control,widget,sc,opt );
    }
    return QRect();
}





回页首


3.使用新风格

有两种方法使用新风格,一种是作为插件,一种是在应用程序里直接使用。作为插件的风格可以在不用修改代码、不用重新编译的情况下使用新风格。由于本文着重介绍如何创建风格所以我们使用第一种方法。这种方法很简单,只需在应用程序中包含相应风格类的头文件,然后把main()函数第一句可执行代码设置为QApplication::setStyle(new MyStyle())即可。

下面我们用一个小例子来看看效果。


#include <qapplication.h>
#include <qspinbox.h>
#include "customstyle.h"
int main( int argc, char **argv )
{
    QApplication::setStyle(new CustomStyle()); //使用新风格类来绘制界面。
    QApplication a( argc, argv );
    QSpinBox spin( 0, 15 );
    spin.resize( 20, 100 );
    a.setMainWidget( &spin );
    spin.show();
    return a.exec();
}

然后编译运行即可看到效果。

Ps. qt中编译使用qmake,步骤为

  • 创建源程序
  • 同一目录下运行qmake -project
  • qmake
  • make
  • 运行可执行程序。




回页首


4.进一步工作

1)默认大小:细心的朋友可能看到上面的代码中有一句:spin.resize( 20, 100 ),这一句设置spinbox的长度为20象素,宽度为100个象素。如果没有这一句的话,显示的结果会一团糟,两个按钮几乎看不到



,因为qt默认的显示是水平显示而根本没有考虑垂直显示的情况。

如果想让spinbox在默认情况下看起来长度窄而宽度高需要修改QSpinBox类中的sizeHint函数,这个函数功能是设置部件(widget)的默认尺寸。在qt中几乎每个GUI部件类都有sizeHint这个函数来设置它自己的默认的长和宽。

文本垂直显示:在此例中虽然控件spinbox达到了垂直显示的效果,但是文本仍旧是水平显示的,因此要达到真正的垂直显示需要了解qt的文本显示机制。这些工作是很有意义的,因为有些民族(如蒙文)的语言传统就是垂直显示的,而现在没有一个真正满足这种需求的系统。笔者现在正在看qt-x11-free-3.2.2的源码,目前对文本显示机制只有初步了解,还没有真正弄清,非常希望和感兴趣的朋友相互交流、学习。





回页首


参考资料





回页首


关于作者

姚延栋:中国科学院软件所2002级研究生,方向为系统软件和中文信息处理,对复杂文本处理和系统编程(Qt, Gtk等)很感兴趣,联系方式: ydyao@mails.gscas.ac.cn

转自 http://www-128.ibm.com/developerworks/cn/linux/l-qtstyle/

Qt中的多线程编程

2004 年 4 月 01 日

Qt 作为一种基于 C++ 的跨平台 GUI 系统,能够提供给用户构造图形用户界面的强大功能。为了满足用户构造复杂图形界面系统的需求,Qt 提供了丰富的多线程编程支持。

Qt 作为一种基于 C++ 的跨平台 GUI 系统,能够提供给用户构造图形用户界面的强大功能。为了满足用户构造复杂图形界面系统的需求,Qt 提供了丰富的多线程编程支持。从 2.2 版本开始,Qt 主要从下面三个方面对多线程编程提供支持:一、构造了一些基本的与平台无关的线程类;二、提交用户自定义事件的 Thread-safe 方式;三、多种线程间同步机制,如信号量,全局锁。这些都给用户提供了极大的方便。不过,在某些情况下,使用定时器机制能够比利用 Qt 本身的多线程机制更方便地实现所需要的功能,同时也避免了不安全的现象发生。本文不仅对 Qt 中的多线程支持机制进行了讨论,还着重探讨了利用定时器机制模拟多线程编程的方法。

1、系统对多线程编程的支持

不同的平台对 Qt 的多线程支持方式是不同的。当用户在 Windows 操作系统上安装 Qt 系统时,线程支持是编译器的一个选项,在 Qt 的 mkfiles 子目录中包括了不同种类编译器的编译文件,其中带有 -mt 后缀的文件才是支持多线程的。

而在 Unix 操作系统中,线程的支持是通过在运行 configure 脚本文件时添加 -thread 选项加入的。安装过程将创建一个独立的库,即 libqt-mt,因此要支持多线程编程时,必须与该库链接(链接选项为-lqt-mt),而不是与通常的 Qt 库(-lqt)链接。

另外,无论是何种平台,在增加线程支持时都需要定义宏 QT_THREAD_SUPPORT(即增加编译选项-DQT_THREAD_SUPPORT)。在 Windows 操作系统中,这一点通常是在 qconfig.h 文件中增加一个选项来实现的。而在 Unix 系统中通常添加在有关的 Makefile 文件中。





回页首


2、Qt中的线程类

在 Qt 系统中与线程相关的最重要的类当然是 QThread 类,该类提供了创建一个新线程以及控制线程运行的各种方法。线程是通过 QThread::run() 重载函数开始执行的,这一点很象 Java 语言中的线程类。在 Qt 系统中,始终运行着一个GUI 主事件线程,这个主线程从窗口系统中获取事件,并将它们分发到各个组件去处理。在 QThread 类中还有一种从非主事件线程中将事件提交给一个对象的方法,也就是 QThread::postEvent()方法,该方法提供了 Qt 中的一种 Thread-safe 的事件提交过程。提交的事件被放进一个队列中,然后 GUI 主事件线程被唤醒并将此事件发给相应的对象,这个过程与一般的窗口系统事件处理过程是一样的。值得注意的是,当事件处理过程被调用时,是在主事件线程中被调用的,而不是在调用QThread::postEvent 方法的线程中被调用。比如用户可以从一个线程中迫使另一个线程重画指定区域:



QWidget *mywidget;
QThread::postEvent(mywidget, new QPaintEvent(QRect(0,0,100,100)));

                

然而,只有一个线程类是不够的,为编写出支持多线程的程序,还需要实现两个不同的线程对共有数据的互斥访问,因此 Qt 还提供了 QMutex 类,一个线程在访问临界数据时,需要加锁,此时其他线程是无法对该临界数据同时加锁的,直到前一个线程释放该临界数据。通过这种方式才能实现对临界数据的原子操作。

除此之外,还需要一些机制使得处于等待状态的线程在特定情况下被唤醒。QWaitCondition 类就提供了这种功能。当发生特定事件时,QWaitCondition 将唤醒等待该事件的所有线程或者唤醒任意一个被选中的线程。





回页首


3、用户自定义事件在多线程编程中的应用

在 Qt 系统中,定义了很多种类的事件,如定时器事件、鼠标移动事件、键盘事件、窗口控件事件等。通常,事件都来自底层的窗口系统,Qt 的主事件循环函数从系统的事件队列中获取这些事件,并将它们转换为 QEvent,然后传给相应的 QObjects 对象。

除此之外,为了满足用户的需求,Qt 系统还提供了一个 QCustomEvent 类,用于用户自定义事件,这些自定义事件可以利用 QThread::postEvent() 或者QApplication::postEvent() 被发给各种控件或其他 QObject 实例,而 QWidget 类的子类可以通过 QWidget::customEvent() 事件处理函数方便地接收到这些自定义的事件。需要注意的是:QCustomEvent 对象在创建时都带有一个类型标识 id 以定义事件类型,为了避免与 Qt 系统定义的事件类型冲突,该 id 值应该大于枚举类型 QEvent::Type 中给出的 "User" 值。

在下面的例子中,显示了多线程编程中如何利用用户自定义事件类。

UserEvent类是用户自定义的事件类,其事件标识为346798,显然不会与系统定义的事件类型冲突。



class UserEvent : public QCustomEvent   //用户自定义的事件类
{
public:
 UserEvent(QString s) : QCustomEvent(346798), sz(s) { ; }
 QString str() const { return sz; }
private:
 QString sz;    
};

                

UserThread类是由QThread类继承而来的子类,在该类中除了定义有关的变量和线程控制函数外,最主要的是定义线程的启动函数UserThread::run(),在该函数中创建了一个用户自定义事件UserEvent,并利用QThread类的postEvent函数提交该事件给相应的接收对象。



class UserThread : public QThread      //用户定义的线程类
{
public:
 UserThread(QObject *r, QMutex *m, QWaitCondition *c);
QObject *receiver;
}

void UserThread::run()     //线程类启动函数,在该函数中创建了一个用户自定义事件
{UserEvent *re = new UserEvent(resultstring);
   QThread::postEvent(receiver, re); 
}

                

UserWidget类是用户定义的用于接收自定义事件的QWidget类的子类,该类利用slotGo()函数创建了一个新的线程recv(UserThread类),当收到相应的自定义事件(即id为346798)时,利用customEvent函数对事件进行处理。



void UserWidget::slotGo()    //用户定义控件的成员函数
{ mutex.lock();  
 if (! recv)
  recv = new UserThread(this, &mutex, &condition);
 recv->start();
 mutex.unlock();
}

void UserWidget::customEvent(QCustomEvent *e)   //用户自定义事件处理函数
{ if (e->type()==346798) 
 {
  UserEvent *re = (UserEvent *) e;
        newstring = re->str();
    }
}

                

在这个例子中,UserWidget对象中创建了新的线程UserThread,用户可以利用这个线程实现一些周期性的处理(如接收底层发来的消息等),一旦满足特定条件就提交一个用户自定义的事件,当UserWidget对象收到该事件时,可以按需求做出相应的处理,而一般情况下,UserWidget对象可以正常地执行某些例行处理,而完全不受底层消息的影响。





回页首


4、利用定时器机制实现多线程编程

为了避免Qt系统中多线程编程带来的问题,还可以使用系统中提供的定时器机制来实现类似的功能。定时器机制将并发的事件串行化,简化了对并发事件的处理,从而避免了thread-safe方面问题的出现。

在下面的例子中,同时有若干个对象需要接收底层发来的消息(可以通过Socket、FIFO等进程间通信机制),而消息是随机收到的,需要有一个GUI主线程专门负责接收消息。当收到消息时主线程初始化相应对象使之开始处理,同时返回,这样主线程就可以始终更新界面显示并接收外界发来的消息,达到同时对多个对象的控制;另一方面,各个对象在处理完消息后需要通知GUI主线程。对于这个问题,可以利用第3节中的用户自定义事件的方法,在主线程中安装一个事件过滤器,来捕捉从各个对象中发来的自定义事件,然后发出信号调用主线程中的一个槽函数。

另外,也可以利用Qt中的定时器机制实现类似的功能,而又不必担心Thread-safe问题。下面就是有关的代码部分:

在用户定义的Server类中创建和启动了定时器,并利用connect函数将定时器超时与读取设备文件数据相关联:



Server:: Server(QWidget *parent) : QWidget(parent)
{
readTimer = new QTimer(this);   //创建并启动定时器
   connect(readTimer, SIGNAL(timeout()), this, SLOT(slotReadFile()));   //每当定时器超时时调用函数slotReadFile读取文件
   readTimer->start(100);
}

                

slotReadFile函数负责在定时器超时时,从文件中读取数据,然后重新启动定时器:



int Server::slotReadFile()    // 消息读取和处理函数
{
  readTimer->stop();     //暂时停止定时器计时
  ret = read(file, buf );   //读取文件
if(ret == NULL)
{    readTimer->start(100);     //当没有新消息时,重新启动定时器
    return(-1);
}
  else
       根据buf中的内容将消息分发给各个相应的对象处理……;
readTimer->start(100);    //重新启动定时器
}

                

在该程序中,利用了类似轮循的方式定时对用户指定的设备文件进行读取,根据读到的数据内容将信息发送到各个相应的对象。用户可以在自己的GUI主线程中创建一个Server类,帮助实现底层的消息接收过程,而本身仍然可以处理诸如界面显示的问题。当各个对象完成处理后,通过重新启动定时器继续进行周期性读取底层设备文件的过程。当然,这种方法适合于各对象对事件的处理时间较短,而底层设备发来消息的频率又相对较慢的情况。在这种情况下,上述方法完全可以满足用户的需求,而又避免了处理一些与线程并发有关的复杂问题。

当然,利用定时器机制实现多线程编程在某些方面具有一定的局限性,有关到底如何实现多线程编程,如何编写出效率更高的代码,还有待于开发者进一步研究和探讨。





回页首


参考资料

(1)Qt官方文档 http://doc.trolltech.com/3.2/index.html

(2)Qt源代码:QThread, QCustomEvent,QTimer.

(3)Advanced Programming in the UNIX Environment, W. Richard Stevens.





回页首


关于作者

续欣:联系方式 xxin76@hotmail.com

19 juin

Qt 的内部进程通信机制

续欣 (xxin76@hotmail.com)
2004 年 4 月

Qt 作为一种跨平台的基于 C++ 的 GUI 系统,能够提供给用户构造图形用户界面的强大功能。自从 1996 年 Qt 被 Trolltech 公司发布以来,该系统成为世界上很多成功的图形用户应用所使用的主要系统。更为重要的是,Linux 操作系统的桌面环境系统 KDE 也是基于 Qt 构造的。目前,Qt 已经提供了对包括 MS/Windows、Unix/X11 和嵌入式平台的支持,得到了越来越广泛的应用。

Qt 系统中,不仅有着构造完善的系统结构,而且为了满足用户对编写图形用户界面应用的种种需求,它还创建了许多新的系统机制,其中 Qt 所特有的内部进程通信机制尤其值得一提。 本文分析了基于 QT 的应用进程之间通信常用的三种机制:QCOP 协议,Signal-Slot 机制和 FIFO 机制。给出了各自的使用方法,并指出了各自的使用场合。

1、 QCOP协议
QCOP 是 Qt 内部的一种通信协议,这种协议用于不同的客户之间在同一地址空间内部或者不同的进程之间的通信。目前,这种机制还只在 Qt 的嵌入式版本中提供。


为实现这种通信机制,Qt 中包括了由 QObject 类继承而来的 QCopChannel 类,该类提供了诸如 send()、isRegistered() 等静态函数,它们可以在脱离对象的情况下使用。为了在 channel 中接收通信数据,用户需要构造一个 QCopChannel 的子类并提供 receive() 函数的重载函数,或者利用 connect() 函数与接收到的信号相联系。


值得一提的是,在 Qt 系统中,只提供了 QCOP 协议机制和用于接收消息的类,而如何发送消息则没有提供相应的类供用户使用。


在基于 Qt 的桌面系统 Qtopia(QPE)中,则提供了相应的发送类:QCopEnvelope。用户可以通过该类利用 channel 向其他进程发送消息。该类将通过 QCopChannel 发送 QCop 消息的过程进行了封装,用户只需要调用该类中的相关函数就可以方便地实现进程之间的通信过程。一方面,QCop 消息的发送要利用 QCopEnvelope 类,另一方面,接收消息则是通过与一个 QCopChannel 相关联。


在发送消息时,将利用如下的协议机制:
代码:

QCopEnvelope e(channelname, messagename);

对于需要携带参数的消息,必须使用"<<()"运算符将参数添加到envelope中。
代码:

e << parameter1 << parameter2 << ...;

对于不带参数的消息,只需要利用:
代码:

QCopEnvelope e(channelname, messagename);

在Qtopia中,所有的channels名都以"QPE/"开始,而messagename则是一个函数的标识符。


在接收消息时,通常只需要利用在应用程序中预先定义好的QPE/Application/{appname}管道,当然,也可以根据需要自己定义管道,并将其与一个slot函数相关联:
代码:

myChannel = new QCopChannel( "QPE/FooBar", this );
connect( myChannel, SIGNAL(received(const QCString &, const QByteArray &)),
          this, SLOT(fooBarMessage( const QCString &, const QByteArray &)) );

下面将具体的通信过程举例如下:


在需要接收消息的类(如Window1)中定义管道:
代码:

        QCopChannel *doChannel = new QCopChannel("QPE/Do", this);
        connect(doChannel, SIGNAL(received(const QCString &, const QByteArray &)), this, SLOT(doMessage(const QCString &, const QByteArray &)));

同时,需要在该类中定义相应的消息处理函数doMessage,
代码:

void Window1::doMessage(const QCString &msg, const QByteArray &args)
{
        QDataStream stream(args, IO_ReadOnly);
        if(msg == "Message1(QString)")
        {
                QString text;
                stream >> text;
                button->setText(text);
        }
        else if(msg == "Message2()")
        {
                close();
        }
}

其中的Message1(QString)和Message2(QString)都是用户自己定义的消息,该函数中分别对这些消息进行了相应的处理。在该例中当收到带有参数的Message1消息时,将该字符串参数stream显示在按钮button上;当收到Message2消息时,将执行关闭Window1窗口的动作,当然用户可以根据需要自行编写相应的处理过程。


另一方面,在类Class2中需要发出消息的函数function中利用QCopEnvelope发送消息:
代码:

void Class2::function()
{   QCopEnvelope e("QPE/Do", "Message1(QString)");
    e << param; }

这里发出了Message1消息,并将需要携带的参数param发送到管道中。


通过这样的过程,用户可以很方便地实现不同对象、不同进程之间通信过程,而且可以根据需要在通信过程中任意传递参数。


2、 信号-槽(Signal-Slot)机制
Qt中,有一种用于对象之间的通信:信号-槽机制,这种机制是Qt 的核心机制,也是它区别于其他GUI工具的最主要的特征。在大多数GUI工具中,通常为可能触发的每种行为定义一个回调函数,这个回调函数是一个指向函数的指针。在Qt中,信号-槽机制取代了这种繁杂的函数指针,能够实现同样的功能。信号-槽机制可以携带任意类型、任意数量的参数,而且完全是安全的,不会引起系统的崩溃。


所有由QObject类继承而来的类,或者是它的一个子类,都可以包括信号-槽机制。信号通常是当对象改变他们的状态时发出的,这就是一个对象在需要与其他对象通信时所需要做的一切,它并不知道是否有其他对象在另一端接收该信号。从这个意义上来说,这种机制实现了真正的信息封装,确保了对象可以被当作一个独立的软件构件来使用。


而槽可以被用于接收信号,它们通常是类中的成员函数。一个槽并不知晓是否有一个信号与自己相联系,同样,包含有槽函数的对象也对通信机制一无所知,它们也可以作为一个独立的软件构件。


用户可以按照需要将许多信号与一个单独的槽函数相联系,一个信号也可以按需要被联系到很多不同的槽函数。甚至还可以将一个信号直接与另一个信号相联系,这样当第一个信号被发出时立刻发出第二个信号。


这样,信号-槽相结合就产生了一种功能强大的编程机制。


例如:
代码:

button = new QAction(tr("button"), QIconSet(QPixmap("button.png")), 0, 0, this);
connect(button, SIGNAL(activated()), this, SLOT(slotButton()));

程序中定义了一个按钮,并利用connect()函数将该按钮button的activated()信号与slotButton()函数相关联,当用户触发按钮时,就会执行相应的槽函数。当然,这里的信号是QAction类中预先定义好的信号,用户在使用该机制时,可以根据需要自行定义信号,同时在适当的时候利用emit语句发出该信号。另外,在信号和相应的槽函数之间还可以传递任意参数,如:
代码:

emit signal(parameter);

3、 FIFO机制
当然,除了 Qt 内部所特有的通信机制之外,一般操作系统中常用的进程间通信机制同样可以用于 Qt 系统内部不同进程之间的通信。如消息队列、共享内存、信号量、管道等机制,其中有些机制,如信号量,在 Qt 中重新进行了封装;有些机制则可以直接调用操作系统的系统调用来实现。这里,有名管道是一种简单实用的通信机制,用户在对Qt内部机制


不甚了解的情况下,同样可以使用这种方法实现对象进程之间的通信。下面就对利用这种机制实现Qt内部进程之间的通信过程进行介绍。


首先,需要创建 FIFO,这个过程类似于创建文件,在系统中可以利用 mkfifo 命令来创建,这样就可以用 open 函数打开它,同时,一般的文件 I/O函数(close、read、write)都可以用于 FIFO。


在基于 Qt 的应用中,有很多应用采用了一种客户机-服务器模式,这时就可以利用 FIFO 在客户机和服务器之间传递数据。例如,有一个服务器,它负责接收底层程序发来的消息,同时,它与很多客户机有关,服务器需要将收到的不同消息发送到不同的客户机,而每个客户机也有请求需要发给服务器,进而发给底层程序。


下面是服务器端的程序示例:(架设已有客户端进程为读而打开/dev/fifoclient1和/dev/fifoclient1)
代码:

fd = open("/dev/fifoserver", O_NONBLOCK|O_RDONLY);
   file = fdopen(fd, "r");
ret = fgets(buf, MAX_LINE, file );
  if(buf[0] == '0')
  {
          QFile fd_file("/dev/fifoclient1");
          QString temp(buf);
          if(fd_file.open(IO_WriteOnly|IO_Append)) {
                QTextStream t(&fd_file);
                t<< temp;               
          fd_file.close();
  }
  else if(buf[0] == '1')
  {
          QFile fd_file("/dev/fifoclient2");
          QString temp(buf);
          if(fd_file.open(IO_WriteOnly|IO_Append)) {
                QTextStream t(&fd_file);
                t<< temp;               
          fd_file.close();
  }
……

在该程序中,服务器接收底层发来的信息(这里假设也是由 FIFO 管道传来),然后根据收到的信息内容,如第一个字节的内容,将信息发到不同客户端的管道中,实现对信息的正确分发。


客户端程序示例如下:(假设服务器端已经为读而打开 /dev/fifo 管道)
代码:

QFile out_file("/dev/fifo");
        if(out_file.open(IO_WriteOnly|IO_Append)) {
                QTextStream t(&out_file); 
t << text << "\n";  }

当任意一个客户端需要向服务器发送消息时,就可以通过 /dev/fifo 这个公共的管道发出。


通过这种方式,同样可以实现GUI内部不同进程或应用之间的通信过程,但是,当客户端数量较多时,这种方法就显示出了一定的局限性,整个通信过程布局变得过于繁杂,管道越来越多使得出错的可能性也越来越大。因此,利用 FIFO 实现 Qt 中上述客户端和服务器端的通信过程,更适用于客户端应用较少时。


参考资料

Qt 官方文档:http://doc.trolltech.com/3.2/index.html
Qt 和 Qtopia 源代码:QCopChannel, QCopEnvelope,QAction.
Advanced Programming in the UNIX Environment, W. Richard Stevens.

关于作者
续欣:博士、大学讲师,目前从事无线网络拥塞控制方面的研究,以及嵌入式GUI的开发。联系方式 xxin76@hotmail.com 

 
17 juin

CvsIn - CVS Developer Studio Add-in

CvsIn is integrating with IDE in a different way than other tools. It is a DevStudio Add-in. CvsIn's development strategy is to have the maximum gain with the minimum effort and to have the working and operational tool FAST(!).
 

VC addin macro

Workspace Whiz is a powerful add-in for Visual Studio .NET 2002/2003 and Visual C++ 6.  It was designed to improve developers' productivity by making available such features as:
  • Incremental searching of files throughout the workspace, global include/source directories, or user specified directories.
  • Incremental tag searches either by identifier name or a parent class or namespace.
  • Switching between the declaration and definition of functions.
  • Tag completion at the press of a key while typing.
  • Powerful code templates.
  • And a whole lot more...
As of version 3.0, Workspace Whiz is free software for non-commercial uses.  Those people using Workspace Whiz to develop software for profit (that includes commercial and shareware developers) need to purchase a license to use the software
 

Workspace Whiz! is chock full of useful features. This tour provides an overview of the most major Workspace Whiz! features.

http://www.workspacewhiz.com/index.html

12 juin

微軟WinCE相關技術研究

微軟WinCE相關技術研究
說明:

Microsoft Windows CE 是開放式、可擴充的 Windows 平台,適用各種通訊、娛樂和行動電腦裝置。 標準架構的 Windows CE 平台是全新的作業系統,從基礎開始進行建構, 讓新類型的企業和消費者能夠使用非個人電腦裝置與彼此進行通訊, 利用 Windows 架構的個人電腦分享資訊,並連接到網際網路。實作如下:

(1) 建構模擬器的Run-Time映像檔
為了熟悉WinCE開發的軟體環境,我們需要一個擬真的環境來做為開發與及偵錯的環境,可依下列步驟:

a. File->New Platform 開啟Platform wizard

b. 選擇build出來的image所要執行的平台。在這步驟,選擇在 emulator platform 上執行

c. 根據想做出來的映像檔有什麼目標功能,選擇相對的樣式

d.這些應用程式根據你所選的樣式不同會有所不同。上面所勾選的為 Enterprise Wed Pad 預設的,按照預設的即可

e. Build之前可選擇Release或Debug mode。在Build OS -> Set Active Configuration 中可選擇

f. Platform -> setting -> Build Option windows 中將 Run-time Image 選項勾選起來

g. 接著可開始Build image。Build OS -> Build and System。

h. 當image build結束後,執行 Target -> Attach Device。上面的畫面即是 image 在 emulator 執行的畫面, 那些 item application 皆為預設的

i. 我們可以在「執行」,鍵入cmd,再鍵入ipconfig觀察現在網路界面的設定,可以發現已設定IPv6的位址。

(2) 建構x86平台的Run-Time映像檔
我們接著將WinCE porting 到x86的平台上,可依下列步驟:

a. 首先如同(1),製作出一個欲在x86上執行的workspace

b. Build 完成之後,可在「安裝的目錄\PBWorkspaces\專案名稱\RelDir\選擇的模式\」找到一個nk.bin檔。 我們將之燒錄至光碟片上

c. 再行準備下列設備:x86 PC、光碟機、開機片。其中含bootloader開機片的製作檔案, 可由C:\program files\windows ce platform builder\5.00\cepb\utilities\下, 找到一個cepcboot1.44.exe的檔案,執行後便會製作出含有bootloader的開機片

d. 使用此開機片開機,同時啟動光碟機。

e. 在提示符號下指令 loadcepc :l/800x600x16 nk4.bin。其中nk4.bin 為我們自行命名的WinCE image

f. 執行之後,bootloader會將image 載入,並將控制權交到WinCE kernel,此時已可看見WinCE 執行環境

(3) 網路卡安裝及IPv6測試
由於WinCE中所預設的NIC為NE2000,但一般實驗室使用的網路卡可能是非預設的,因此我們可依下列步驟自行加入我們的NIC。

a. 先依照之前的步驟,作出一個workspace

b. 將E100CE5.CEC import 到PB裡面(File->Manage Catalog items->import)

此時會在Catalog 的地方出現Third party\device driver\Intel(r) Fast Ethernet Drivers

c. 將intel選項拖曳到左邊去(OSDesignView)此時左邊出現Intel(r) Networking Drivers\Intel(r) Fast Ethernet Drivers, 但我們可以看到他會在應該是箭頭的地方變成X。觀察錯誤訊息後,我們可以知道是project.bib 檔的設定不對

d. 在ParameterView 中,找到C:\安裝wince的目\cepc\project specific files\project.bib。 將此行加入「e100ce5.dll $(_FLATRELEASEDIR)\e100ce5.dll NK SH」。 按滑鼠右鍵選擇refresh,此時X的地方就會變成往下的箭頭

e. 找到project.reg,將下列資訊附加進去
;Intel(R) 8255x Registry configuration, if enabled
IF CEPB_INTELFE_PCI
;Create registry information for Intel(R) Miniport Driver

[HKEY_LOCAL_MACHINE\Comm\NDIS\Parms]
"DebugLevel"=dword:00000000
"DebugSystems"=dword:000030F3
"DebugBreakPoint"=dword:00000000

[HKEY_LOCAL_MACHINE\Comm\E100CE5]
"DisplayName"="Intel(R) Fast Ethernet Controller"
"Group"="NDIS"
"ImagePath"="e100ce5.dll"
;Create registry information for template (will be copied to ALL instances of
;adapters using the Intel Miniport Driver)

[HKEY_LOCAL_MACHINE\Drivers\BuiltIn\PCI\Template\E100CE5]
;PCI Bus Enumeration Information
"Class"=dword:02
"SubClass"=dword:00
"ProgIF"=dword:0
"VendorID"=multi_sz:"8086","8086","8086","8086","8086","8086","8086","8086","8086","8086","8086", "8086","8086","8086"
"DeviceID"=multi_sz:"1209","1229","2449","1031","1032","1033","1034","1035","1036","1037","1038", "1039","103A","103B"
"Dll"="NDIS.dll"
"Entry"="NdisPCIBusDeviceInit"
"Transceiver"=dword:3
"MiniPort"="E100CE5"
;Installable ISR Handler Information
"IsrDll"=" giisr.dll"
"IsrHandler"="ISRHandler"
"PortIsIO"=dword:0
"PortOffset"=dword:0
"PortSize"=dword:4
"PortMask"=dword:20F0

;Settings for DHCP IP Configuration, if enabled
IF DHCP
[HKEY_LOCAL_MACHINE\COMM\PCI\E100CE51\Parms\TcpIp]
"EnableDHCP"=dword:1
; This should be MULTI_SZ
"DefaultGateway"=""
; Use zero for broadcast address? (or 255.255.255.255)
"UseZeroBroadcast"=dword:0
; This should be MULTI_SZ, the IP address list
"IpAddress"=" 0.0.0.0"
; This should be MULTI_SZ, the subnet masks for the above IP addresses
"Subnetmask"="0.0.0.0"
ENDIF ;//DHCP

;Settings for static IP configuration, if enabled
IF STATIC_IP
[HKEY_LOCAL_MACHINE\COMM\PCI\E100CE51\Parms\TcpIp]
"EnableDHCP"=dword:0
; This should be MULTI_SZ
"DefaultGateway"=" 1.2.3.0"
; Use zero for broadcast address? (or 255.255.255.255)
"UseZeroBroadcast"=dword:0
; This should be MULTI_SZ, the IP address list
"IpAddress"="1.2.3.4"
; This should be MULTI_SZ, the subnet masks for the above IP addresses
"Subnetmask"="255.0.0.0 "
ENDIF ;//Static IP
ENDIF ;//Intel(R) miniport registry additions

以上動作請記得存檔

f. Build OS->Build and Sysgen 但仍然會有錯誤訊息。

g. 複製e100ce5.dll到C:\WINCE500\PBWorkspaces\network(之前命名的)\RelDir\CEPC_x86_Release 下。

h. Build OS ->Open release directory。此時會出現command line 模式,打入makeimg,等待make 完畢之後的nk.bin 就能使用網路了。

i. 當然一進去還是得設定IP位址,因為在project.reg中裡面並沒有先行設定。

WinCE 跟 WinXP Embedded 的区别

微软在嵌入式方面主要有两个产品:WinCE 和 WinXP Embedded 。需要注意的是,这两个产品都是嵌入式产品,他们都可以定制安装,也就是,你可以选择安装这两个操作系统。比如,某些功能不安装等。

这两个产品的应用场景区别如下:

WinCE 可以适用于多种CPU的环境,而不仅仅是X86的CPU(ARM, MIPS, SHx and x86)。
WinXP Embedded 仅仅适用于 X86 的CPU

WinCE 使用的是WIN 32 API的一个子集和不完全版的.net即(.net Compact Framework),不是所有。
WinXP Embedded 使用的是所有的Win32API和完全版的.net(当然,如果你配置WinXP Embedded的时候,某个功能没有配置上,这个功能对应的API也就没有了)

WinCE 最小可以做成 350KB大小
WinXP Embedded 最小可以做到8MB,这还仅仅是显示一个"Hello World"

WinCE 的实时性要比WinXP Embedded 要好。

下面我们来据两个例子,来说明它们的应用场景:

一个高速公路收费系统,它用到的机器是普通的台式机,但是希望使用到的机器除了做收费用外,不要有其他功能,比如没有我的电脑,没有IE,没有媒体播放器等等,开机进入的就是收费系统界面。这时候,就可以使用WinXP Embedded,而且比起WinXP 来说 WinXP Embedded 等便宜。

又比如,一个工控系统,由于用的不是X86的CPU,而且实时性要求比较高,甚至这个系统没有界面,是一个及时记录,及时反映的系统,这时候,我们就可以用WinCE来完成这个需求。

转自http://blog.joycode.com/ghj/archive/2004/11/17/39060.aspx