winx官方站点改版了!
网站建设了一个星期,正式对外发布了。
中文:http://www.winxcn.com 或者 http://www.winxgui.cn
英文:http://www.winxgui.com(建设中,目前指向中文站点)
网站建设了一个星期,正式对外发布了。
中文:http://www.winxcn.com 或者 http://www.winxgui.cn
英文:http://www.winxgui.com(建设中,目前指向中文站点)
Because of blockage, the readers from china were unable to read these posts. So I decide to migrate my blog, which is written in chinse, to CSDN.net. Here is the homepage of my new blog: http://blog.csdn.net/xushiweizh. And late I will write something in english to http://codeproject.com.
由于blogspot被盾,不得不考虑转移战场了...
原先winxcn.blogspot.com主要访问来源有二:搜索引擎和一些一直关注WINX的朋友。目前只剩下几个骨灰级的朋友,和从sourceforge的链接(http://winxcn.com)无意中过来一些老外。
决定中文blog主要在CSDN上维护,网址:http://blog.csdn.net/xushiweizh。
下阶段可能会增加英文文档,目前考虑转战CodeProject。
解释了消息分派机制后,接下来我们开始介绍WINX的窗口类。为了产生比较的效果,我决定从之前我写的“SW系统”的窗口类讲起。在你理解了SW系统的窗口类后,我们再来看6年后WINX中的窗口类在设计上发生了什么样的变化。——这自然也是我个人在窗口类观念上的改变。
1、SW系统的“Hello,World!”程序
#define Uses_SApp
#include <sw.h>
// SW系统中,你需要记住头文件只有
// 你只需要告诉它,你用了什么,它可以为你检测需要的头文件并包含它们。
// 例如,这里我们用了SApp类,故有 #define Uses_SApp 一句。
class SHelloApp : public SApp
{
public:
void OnDraw(SHDC dc);
};
void SHelloApp::OnDraw(SHDC dc)
{
dc.TextOut(1, 1, _T(“Hello, World!”));
}
// SW系统没有封装WinMain函数,这应该是一个好消息吧?
// 你不必对主函数是main()而非是WinMain太过关心。它只是为了与DOS
// 习惯兼容以及书写的方便而实现的宏而已。
int main()
{
return Desktop.Execute(new SHelloApp);
// Desktop就是Windows系统桌面,它是一个特殊的窗口对象。
// 特别注意,SW系统中App类是一个窗口!
}
2、SW系统的类体系图

3、独特的窗口(视图)模型
注: 请注意术语上的变化,由于SW系统在Windows下实现,使用了Windows中的一些术语。在这里视图被称为窗口,事件被称为消息。
窗口应该具备哪些行为才合理?象显示/隐藏、选择(激活)、移动、改变大小、关闭等这些基本行为是很容易想到的。SW系统与经典应用程序框架如MFC不同的是,它引入了另两个非常有用的方法:
1)Insert操作:
SW系统中,窗口都通过Insert操作插入到父窗口中。
2)Execute操作:
SW系统中,可以在一个窗口A中运行(Execute)另一个窗口B。此时,窗口A作为窗口B的父窗口,已经不再接受消息,即窗口B是模态视图。不过,在窗口A是Desktop(这里Desktop是Windows系统桌面)时稍微有点不同。这是由于我们使用得是多任务系统所致。
在SW系统中,应用程序是一个窗口,由SWnd类派生;这不同于MFC,在那里应用程序是一个线程,由CWinThread类派生。这一点也许MFC的设计可能严谨一些,但是SW系统的做法的优点是,程序的逻辑变得相当简单。SW系统如何运行程序?非常清楚,只要一句话:
Desktop.Execute(new SxxxApp);
这个我们在例子程序中已经看到了。
Execute操作很复杂吗?打开SW系统的源代码,你可能很失望,代码只有几句话:
UINT SHWnd::Execute(SWnd *pWnd, int nCmdShow)
{
if (this->Insert(pWnd))
{
pWnd->Show(nCmdShow);
return pWnd->MsgLoop();
}
return –1;
}
很显然,Desktop.Execute(new SxxxApp)的含义是:
1)将应用程序插入(Insert)到Desktop;
2)显示应用程序;
3)进入消息循环;
Insert操作又做了什么?它的代码就相对复杂,且与Windows系统密切相关,这里不具体写出它的代码。它完成的工作主要是:
1)检测窗口类是否已经注册;如果没有,注册窗口类;
2)调用窗口创建函数创建窗口对象;
3)由事件发送器将WM_CREATE发送给窗口对象;
消息循环(MsgLoop)的实现流程我们一开始就已经给出,即“事件驱动”应用中的消息流动过程。我们也提到其中事件发送器Windows只实现了一部分。现在问题的关键在于,如何将消息由窗口过程发送给具体的窗口对象?
MFC采用一张Hash表将窗口句柄与窗口实例指针关联起来。在这一点上SW系统采取了一个取巧的办法,避免去维护一张Hash表。它利用窗口句柄的USERDATA字节保存对象指针。具体代码如下:
// 为了突出问题的关键,本文的代码一般比较简略,省略了没有大多数的出错
// 处理。作为实际的程序,其风格当然不应该这样。
LONG CALLBACK WndProc(
HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
SWnd* pWnd = (SWnd*)GetWindowLong(hwnd, GWL_USERDATA);
if (pWnd)
{
SMsg ev;
ev.uMsg = uMsg;
ev.lParam = lParam;
ev.wParam = wParam;
return pWnd->HandleMsg(ev);
}
if (uMsg == WM_CREATE)
{
pWnd = (SWnd*)((CREATESTRUCT*)lParam)->lpCreateParams;
// 这是从CreateWindow函数传进来的窗口实例指针!
pWnd->m_hWnd = hwnd;
SetWindowLong(hwnd, GWL_USERDATA, (LONG)pWnd);
return pWnd->OnCreate() ? 0 : -1;
}
return ::DefWindowProc(hwnd, uMsg, wParam, lParam);
}
在这个技巧中存在的问题是,所有在WM_CREATE之前的消息被“遗失”了。解决这个限制的方案是简单的。核心思想是用全局变量传递窗口实例指针,而不是在CreateWindow函数的参数中传递。见下:
创建窗口代码:
LOCK();
x_pThisWnd = 窗口实例指针;
调用CreateWindow创建窗口;
UNLOCK();
窗口过程代码:
LONG CALLBACK WndProc(
HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
SWnd* pWnd = (SWnd*)GetWindowLong(hwnd, GWL_USERDATA);
if (!pWnd)
{
pWnd = x_pThisWnd;
pWnd->m_hWnd = hwnd;
SetWindowLong(hwnd, GWL_USERDATA, (LONG)pWnd);
}
SMsg ev;
ev.uMsg = uMsg;
ev.lParam = lParam;
ev.wParam = wParam;
return pWnd->HandleMsg(ev);
}
这里LOCK()/UNLOCK()是多线程互斥代码。由于使用了全局变量,线程互斥是必要的。
可以看到,所有的消息都是经由HandleMsg处理的。这是一个进步。但是这中间还有一个不妥当的地方。事实上我们已经习惯于在WM_CREATE消息中对窗口实例进行初始化。因此在WM_CREATE之前的消息处理时,对象其实还没有初始化完毕。这一点如果被遗忘就会犯错。而且,从概念上说,初始化总应该是对象接收到第一个消息。正是由于这一点,SW系统决定仍然使用原先的方案。
《应用程序框架设计》是我大学毕业时(2000年)写的毕业论文。在我给公司内部作“应用程序架构”方面的讲座时,曾经作为入门级的参考资料附上。后来不知如何就流传到Internet上,不过是不完整的版本(可尝试在Google中搜索“应用程序框架设计:SW系统”)。回头看这篇文字,最大的感受觉得自己的文字功底是越来越退步了:-) 由于与界面库有关,大家不妨看看。
应用程序框架设计
许式伟
2000年6月
一、摘要
随着面向对象技术的发展成熟,已经出现了许多著名的应用程序框架,如在Windows平台下有MFC、VCL、OWL等;在旧的DOS系统下有Turbo Vision。在这里我希望通过我设计的“SW系统”来阐述我对设计应用程序框架一些想法。其中涉及的内容主要有:
二、应用程序框架设计的基本内容
一个应用程序到底有多少“骨头”,多少“肉”?这里所说的“肉”是指程序中用于解决问题的那一部分,而“骨头”是指“肉”所依附的程序框架部分,它们是为了实现与用户交互、使界面友好必需做的事情。
对于解决问题的逻辑,我们很难能够找到一个一般做法来简化这件事。只有在具体定位到某一具体的方向时,才有可能做到这一点。例如你要进行数值计算,可能会需要一个功能完善的数学包;你要进行图象处理,可能需要一个图象处理库;等等。严格的说,这些东西说不上是一个框架,只是一个个工具包(Utilities)。因为它们一般没有复杂的调用规则,函数之间相当独立。
对于应用程序与用户的交互,在DOS时代编程的人一定对此感触很深。DOS时期基本上程序与用户交互的动作都是自己完成的。这样做的结果往往不是觉得在界面设计上浪费了太多时间,就是觉得界面设计得不尽人意。各个程序的框架代码有大量的反复,但由于编程方法的局限,程序代码的重用效率往往比较低。
面向对象思想的成熟促使了种种应用程序框架的诞生。面向对象语言中,类的继存可以完成对大量现成代码重用的重用;动态束定(即虚函数机制)技术有效地将具体的实现代码延迟到设计阶段。而一种称为“事件驱动模式”的应用程序结构使程序的框架代码与实现细节彻底发生了分离。尽管现在的应用程序框架种类挺多,但它们在实现思想上相当一致:所有的这些应用程序框架都是“事件驱动模式”的一个应用。这也包括本文要介绍的SW系统。
“事件驱动”的核心自然是事件。从事件角度说,事件驱动程序的基本结构是由一个事件收集器、一个事件发送器和一个事件处理器组成。事件收集器专门负责收集所有事件,包括来自用户的(如鼠标、键盘事件等)、来自硬件的(如时钟事件等)和来自软件的(如操作系统、应用程序本身等)。事件发送器负责将收集器收集到的事件分发到目标对象中。事件处理器做具体的事件响应工作,它往往要到实现阶段才完全确定,因而需要运用虚函数机制(函数名往往取为类似于HandleMsg的一个名字)。对于框架的使用者来说,他们唯一能够看到的是事件处理器。这也是他们所关心的内容。
视图(即我们通常所说的“窗口”)是“事件驱动”应用程序的另一个要元。它是我们所说的事件发送器的目标对象。视图接受事件并能够对其进行处理。当我们将事件发送到具体的视图时,实际上我们完成了一个根本性的变化:从传统的流线型程序结构到事件触发方式的转变。这样应用程序具备相当的柔性,可以应付种种离散的、随机的事件。
由于Windows本身是基于“事件驱动”模型的。因而在Windows操作系统下实现应用程序框架有相当的便利。在事件驱动程序的基本单元中,事件收集器已经由Windows系统完成;事件发送器也已经由Windows完成了部分内容。之所以是部分而非完全是因为Windows是用C语言实现的,而不是C++。由于没有对象,Windows将事件发送到所谓的“窗口函数”中(尽管不是发送到具体的对象,但应该说这是面向对象方式实现的一个变体)。要感谢Windows做了这件事。确定事件的目标所要做的工作的复杂可能要超出我们的想象。我们要对此进行定性的讨论。
根据事件的发送路线,事件可以分为以下几种:
你已经了解了WINX的消息分派,这里我们总结一下,并交代一些前文为了思路紧凑而略过的一些细节,内容包括:
开发WINX的时候,尽管我决定尽量重用WTL,以便这个界面库不至于和Sourceforge上其他众多的界面库一样,最后只是一个实验品(它们无法流行的原因多数在于体系封闭而个人精力有限而无法提供与当前流行的界面库同等的功能),只是拥有少量的忠实拥护者,但是最终无法成为工业级的产品(在我看来,C++界面库比较成功的有MFC、QT、wxWidgets,而WTL只能算半个)。但是,消息分派机制上我决定不沿袭WTL,而是自己提供全新的实现。
WTL的消息机制是极其灵活的,通过它你很容易将功能划分为一个个独立而且含义完整的功能切片。但是问题在于,这种灵活给用户带来了困惑:深奥的模板技术、复杂的消息机制、晦涩而丑陋的代码,用户望而却步了。
WINX的消息机制给用户最简洁的界面,并尽量与MFC的消息兼容。另一方面,WINX的消息分派的智能带来了另一个好处:WINX不断可以增加新的消息,只要派生类没用响应它,就没有任何额外开销。进而,我们可以为WINX加入任何新特性,只要用户没用使用该特性,那么就没用额外代价。——这很有趣。你马上可以想到,WTL的一个个功能切片,WINX也可以提供,并且可以以更为简洁的方式提供。
WINX的消息分派是高效的。一部分原因你已经了解到了:WINX它可以智能的了解派生类并做出优化。还有一个细节,同样与性能有关:WINX的消息响应次序在DispatchMessage中已经精心安排好了。你可以想象,把WM_PAINT消息放到switch..case的最后,还是放在最前,这对消息函数的执行效率产生了怎样的影响。而WTL中是由你自己去安排消息响应的次序,这对使用者是一个额外的负担。——这不只是因为性能的因素,WTL中某些功能切片的安放是有次序要求的,交换次序后会有细节上的行为差异。
WINX的消息机制最大的问题在于,由于我们随时可能会为了实现一个作用于所有窗口的新特性而添加新消息,故此,为了方便你未来升级WINX到更高的版本,你派生的窗口类需要小心定义其成员函数名。我的一个建议是,请尽量不要自定义一些以On开头的函数名。如果确实需要,建议引入类似命名空间的法则:例如,所有响应命令的函数统一以OnCmdXXX命名之。
MFC在你响应消息中希望执行默认处理时,除了调用基类的同名方法外,更为常见的方法是调用Default()函数。同样,WINX也提供了该函数,并且实现方式类似,大体代码如下:
__declspec(thread) PackedMessage _g_currMsg;
template <class T>
class WindowMessage
{
public:
LRESULT Default()
{
T* pThis = static_cast<T*>
return pThis->InternalDefault(
_g_currMsg.hWnd, _g_currMsg.message,
_g_currMsg.wParam, _g_currMsg.lParam);
}
LRESULT ProcessMessage(
HWND hWnd, UINT message ,
WPARAM wParam, LPARAM lParam)
{
LRESULT lResult = 0;
PackedMessage oldMsg = _g_currMsg;
_g_currMsg.hWnd = hWnd;
_g_currMsg.message = message;
_g_currMsg.wParam = wParam;
_g_currMsg.lParam = lParam;
BOOL fProcess = DispatchMessage(
hWnd, message, wParam, lParam, lResult);
if (!fProcess)
lResult = Defalut();
_g_currMsg = oldMsg;
return lResult;
}
};
我们继续Inside WINX's Message Dispatch。现在开始我们进入了最为关键的部分——WINX是怎么进行消息分派的。
从原理上来讲,WINX的消息分派函数(DispatchMessage)其实与上一篇:《WINX的消息分派机制(续)》中的并无多大的不同,只不过更加智能而已。其中最为关键的是,WINX引入了一种技巧,它可以在编译期判断一个函数是否被重载。简单来说,WINX的消息分派伪代码如下:
template <class T>
class WindowMessage
{
...
BOOL DispatchMessage(
HWND hWnd, UINT message,
WPARAM wParam , LPARAM lParam, LRESULT& lResult)
{
T* pThis = static_cast<T*>(this);
if (派生类重载了OnPaint && message == WM_PAINT)
pThis->OnPaint(hWnd);
else if (派生类重载了OnKeyDown && message == WM_KEYDOWN)
pThis->OnKeyDown(hWnd, wParam, lParam);
else if (...)
...
else
return FALSE;
return TRUE;
}
};
简单看一个实际的例子,这样做的好处就很明了了。设想WindowMessage的派生类只重载了OnPaint,那么WindowMessage类看起来是这样的:
template <class T>
class WindowMessage
{
...
BOOL DispatchMessage(
HWND hWnd, UINT message,
WPARAM wParam , LPARAM lParam, LRESULT& lResult)
{
T* pThis = static_cast<T*>(this);
if (true && message == WM_PAINT)
pThis->OnPaint(hWnd);
else if (false && message == WM_KEYDOWN)
pThis->OnKeyDown(hWnd, wParam, lParam);
else if (...)
...
else
return FALSE;
return TRUE;
}
};
并最终被编译器优化为:
template <class T>
class WindowMessage
{
...
BOOL DispatchMessage(
HWND hWnd, UINT message,
WPARAM wParam , LPARAM lParam, LRESULT& lResult)
{
T* pThis = static_cast<T*>(this);
if (message == WM_PAINT)
pThis->OnPaint(hWnd);
else
return FALSE;
return TRUE;
}
};
特别地,如果WindowMessage派生类没有响应任何消息,则优化后DispatchMessage为一个空函数,如下:
template <class T>
class WindowMessage
{
...
BOOL DispatchMessage(
HWND hWnd, UINT message,
WPARAM wParam , LPARAM lParam, LRESULT& lResult)
{
return FALSE;
}
};
这就是WINX的消息分派机制为何比MFC、WTL以及其他任何界面库高效(无论是编译后的代码尺寸上,还是执行效率上)的原因。
好了,现在该是解释WINX如何做到这一点——检测派生类是否重载某个函数的时候了。我们假设,基类(名为Base)中有一个成员函数Func(假设有两个参数),现在有另一个成员函数Caller希望根据派生类是否重载Func来做事情。如下:
template <class T>
class Base
{
RetType Func(ArgType1 arg1, ArgType2 arg2) { ... }
void Caller() {
if (派生类重载了Func) { ... }
else { ... }
}
};
一个办法是,略微修改一下基类中的Func原型,加上一个无用参数int unused:
RetType Func(ArgType1 arg1, ArgType2 arg2, int unused = 0);
或者直接改为可变参数:
RetType Func(ArgType1 arg1, ArgType2 arg2, ...);
当然,派生类重载Func原型还是需要按我们预期的:
RetType Func(ArgType1 arg1, ArgType2 arg2);
如此,判断“派生类是否重载了Func”就变成了判断函数原型是否为
RetType Func(ArgType1 arg1, ArgType2 arg2);
而这正是编译器的拿手好戏。
最后提醒一下,阅读WINX源代码时,你可以发现这个技巧有不少变种(消息分派的实现就与此有细节上的不同),但是其中的道理是完全一致的。
和MFC、WTL等界面库不太一样的是,WINX认为消息分派是一个可独立于窗口存在的基础服务。所以WINX中负责消息分派的不是 winx::Window<T>类,而是 winx::WindowMessage<T>类。 winx::Window<T>只是从winx::WindowMessage <T>继承。
上一篇我故意买了个关子。如果有读者在看了《WINX的消息分派机制 》一文后去亲自看winx的头文件了解实地了解一下的话,我将觉得很安慰。这一篇我们继续这个话题。
WindowMessage<T>的基本规格是这样的:
template <class T>
class WindowMessage
{
void OnDestroy(HWND hWnd);
void OnPaint(HWND hWnd);
void OnKeyDown(HWND hWnd , UINT uVKChar, UINT uKeyData);
...
LRESULT InternalDefault (
HWND hWnd, UINT message,
WPARAM wParam , LPARAM lParam);
BOOL DispatchMessage (
HWND hWnd , UINT message,
WPARAM wParam , LPARAM lParam, LRESULT& lResult);
LRESULT ProcessMessage(
HWND hWnd, UINT message ,
WPARAM wParam, LPARAM lParam);
};
按 WindowMessage<T>的契约,其客户必须将发送给窗口的所有消息全部转发给ProcessMessage函数进行处理。涉及的几个关键函数功能如下:
WINX的消息分派是卓越的。我们先简单回顾一下WINX的SDI风格的Hello程序与MFC/WTL/SDK的对比(我们关注的是窗口类中的消息处理相关):
MFC和WTL有着类似MessageMap(尽管内部机制大不一样),是通过宏实现消息分派的。也许你已经习惯了响应消息时提供MessageMap,但在WINX中这不需要响应任何消息你均只需要直接覆盖消息处理函数即可。示意如下:
class MyWindow : public winx::Window<MyWindow>
{
public:
void OnPaint(HWND hWnd) { ... }
};
你可能担忧WINX的消息分派的便利,是牺牲性能为代价的。——可是我郑重告诉你,这种担忧是多余的。随着本文对winx消息机制的一步步剖解,你将发现,事实恰恰相反,WINX在消息分派的性能上考虑甚多,其消息分派的代码的无论是编译后的执行代码尺寸,还是效率,均优于MFC、WTL。
to be continued ...
出差中,无意中发现我的Blog(blogspot.com)不能访问了。网上搜索了一下,确认被封。暂时的解决方案如下:
你可以使用镜像网站进行访问:http://www.pkblogs.com/winxcn
感谢fireseed提供该信息:http://groups.google.com/group/winxcn/msg/4d85a8382e240db0
你也可以使用以下方法:
为了访问winxcn.blogspot.com,可通过修改hosts文件进行访问。具体方法是:编辑WINDOWS\system32\drivers\etc\hosts 文件,然后加入72.14.219.190 winxcn.blogspot.com,保存即可。对于其他BlogSpot的博客类似,只要将winxcn改为相应的用户名。例如: 72.14.219.190 xushiwei.blogspot.com。
2006-8-20日,WINX发布了它的第一个包:winx-1.0.01
2006-10-20日,也就是今天,WINX发布刚好两个月整。
小小庆贺一下。
本Blog六天后也将迎来这一天,一并Congratulations!
标签: congratulation, release, winx
由于业余时间相对比较少,并且接下来要出差(大约十天左右),看样子winxcn.com短期内不能出来。为了WINX的文档不至于遥遥无期,我决定还是现在开始在blog上连载WINX的核心文档。或许对于这些文档而言,blog不是一个很好的载体,因为blog更关注的是社会性,强调的是参与,而体系性较差。
这是开篇第一篇。你可能觉得惊奇——不是要讲WINX吗?怎么,讲ATL来着?勿须奇怪,WINX是基于ATL/WTL的,所以,在讲WINX的原理之前,先要了解ATL的基本概念,它们一定程度上是相通的。
ATL/WTL的界面开发,网上的教程还是少了点。相信大家都知道《MFC程序员的WTL开发指南》,这的确是一套难得的好教材。作为WINX的入门篇,我们这一篇是回顾《MFC程序员的WTL开发指南》第一篇:“ATL界面类”(注:为了不至于重复制造轮子,我这里不再赘述该文章已经叙述较为详细的一些技术细节)。
接触ATL/WTL久了,类似下面这样的代码你一定见得多了:
class MyClass : BaseClassT<MyClass>
{
};
把派生类MyClass,作为模版参数传递给基类BaseClassT,这种写法多多少少显得有点古怪。但这是合法的。之所以要这样,是因为我们要实现“编译期的晚绑定”(ATL界面类中把它称为“编译期间的虚函数调用机制”,这是不太准确的说法)。
基于虚函数技术的“运行期(Runtime)的晚绑定”大家都已经很熟悉了,这也就是OOP中所谓的“多态”。而基于模板技术,所依赖的并非vtable,而是类型不确定型(类型晚绑定),来达到“编译期的晚绑定”。这也就是所谓的“泛型”。
选择“多态(虚函数技术)”,还是选择“泛型(模板技术)”?
模板的好处是“零开销”——即额外的时间、空间开销均为零。而虚函数技术在时间上多了一次间接调用(由vptr找到vtable中相应的函数地址),并导致inline失效(无法展开);在空间上每个类对象多了4字节(这里假设是32bit系统)的vptr,全局多了一个vtable(大小与该类的虚函数个数有关)。
虚函数的好处是“可定义二进制规范”,从而建立语言无关的调用约定。这就是COM(组件对象模型)技术关注的内容。进一步来说,虚函数技术比较适合应用于大型程序中的模块划分,用以描述组件间调用契约——interface(接口)。
我并不是模板技术的推崇者。应当认识到,模板与虚函数技术一样,只适合有限的场合。WINX中,消息分派机制使用了模板,是因为它是最适合的人选。
作为结束,我提醒读者,理解泛型中的“编译期的晚绑定”是重要的,如果你希望深入理解WINX的话。下一节我们开始进入最关键的章节——WINX的消息机制。
使用blogger的最初原因是因为MSN Space太慢了,害得我不得不搬家。刚使用blogger的那会,最大的体验是“够简单”,甚至是“太简单”。有一次想要使用表格发现,blogger居然不支持,觉得很不可思议,很过分。
然而,随着使用的深入,越来越觉得blogger“很不简单”。下面让我们细数blogger的种种特色:

关于blogger.com,我相信还有很多需要发掘的,还有更多无尽深处的体验...
“PostShow网络侵害图片版权”最近相当热闹。风言疯语之IT罗盘亦在其Blog上谈论了该事件:PostShow事件:美丽 丑恶 功利 无聊 期望。他在报道该事件的同时援引了图片,须不知自己也无形中发生了侵权行为。关于该图片的授权声明如下:
You have limited rights to personally view the images with your web browser and to use them as your personal computer wallpaper (or background image) on your own computer. These photos may not otherwise be reproduced, distributed, cropped, resized, or otherwise altered without the written permission of the photographer. No commercial use of these photos may be made in any way. All rights are reserved.
You may not use these photos on any web page, commercial or non-commercial, for profit or non-profit, without written permission from the photographer. You may however link to the photos in the manner described below. …
详细参考: http://www.airliners.net/usephotos/
类似的侵权行为完全可能会发生在我身上。远的不说,就说在引用图片、文章、源代码或其他网络资料时,我真的认真的去了解其出处及相关的版权声明了吗?我很惭愧因为我并没有。所以我认为这场闹剧很有价值,着实可以让我们反省一些自身觉得相当习惯的行为。
互联网是很特殊的媒体,你很难真的做到我们熟知版权声明方式“保留所有权利”(All Rights Reserved)。对它的滥用意味着任何人都可能触犯版权法律,意味着很多优秀的作品将无法得到最大价值的利用或最广泛的传播。现在,越来越多的人们开始意识到自己并非需要保留所有权利,相反他们更愿意选择“保留部分权利”(Some Rights Reserved),甚至“不保留权利”(No Rights Reserved)。
互联网上一种很常见(但绝不是唯一)的一种授权———创作共用授权(Creative Commons),其出发点就是Some Rights Reserved,有兴趣的读者建议看一看。
修订记录 1.1.01 (2006-10-14)
-----------------------------------------
*) 示范代码(tutorials)
- Hello, SmartWin! tutorials/winx/step001/hello,smartwin (a)
- XSL转换(XSLT) tutorials/winx/step018-xslt (b)
- 直方图均衡化(OpenCV样例) tutorials/opencv/step001-histgram (c)
*** 注意 ***
要编译标记了(a)的那些样例,你需要下载从sourceforge下载SmartWin++:
http://sourceforge.net/projects/smartwin/
并且让目录树看起来是这样的:
├─winx
│ └─include
├─wtl
│ └─include
└─smartwin
├─lib
└─include
要编译标记了(b)的那些样例,并且你使用Visual C++ 6.0,你需要更新你的
平台SDK(Windows Platform SDK)。
你可以到http://sourceforge.net/projects/winx/下载winsdk.zip。
并且让目录树看起来是这样的:
├─winx
│ └─include
├─wtl
│ └─include
└─winsdk
└─include
要编译标记了(c)的那些样例,你需要下载从sourceforge下载OpenCV:
http://sourceforge.net/projects/winx/
或者:
http://sourceforge.net/projects/opencv/
并且让目录树看起来是这样的:
├─winx
│ └─include
└─opencv
├─bin
├─lib
└─include
*) WINX扩展组件
- msxml
XSL转换(XSLT)
- OpenCV
class CvWindowT, CvWindow, CvMainFrame
现在,我们回答smithfox提的第二个问题:如何在MyCustomControl中再实现,鼠标右键处动态生成一个AnotherMyCustomControl,进行操作后消失,回来MyCustomControl呢?
这个问题包含三个要点:
WINX强调的是可视化,因此一直以来,均未提及动态创建窗口这个话题。要动态创建窗口,可以有三种方式:
后两者Create函数只是对Windows API - CreateWindowEx的简单包装。这里我们给一个动态创建IE控件的例子。
这里,我们要回答smithfox在winx论坛上提的第一个问题: 在WINX内,怎么写一个Custom Control,也就是说自己实现Paint的Control。
其实,winx不只是可以实现自定义控件,而且允许你将这些控件直接放到对话框中,就如你放置一个Static、Button、Edit等等标准控件一样。winx的tutorials中有专门的demo告诉你如何作到这一点。
虽然目前没有专门针对WINX进行可视化界面开发的软件,但是你可以在Visual Studio中使用WINX进行半可视的开发。虽然简陋,但是在最让人烦心的控件布局的所见即所得(WYSIWYG)上,基本上还算可以。
WINX支持三类自定义控件(Custom Control):
1)完全自定义的控件(通常从winx::Window派生)。代码参考:
tutorials/winx/step004-user-ctrl/1.basic/hello.cpp
2)从现有控件派生(通常从现有控件类winx::Static、winx::Button、winx::Edit等派生),对现有控件进行细节上的行为修改。代码参考:
tutorials/winx/step004-user-ctrl/2.superclass/hello.cpp
3)通过多种现有控件组合出一个复杂的自定义控件。如何做到这一点?
其实,有一个与Delphi之Form类似的概念,就是非模态对话框(Modaless Dialog)。所以,这一类控件我们通常从winx::ModalessDialog派生。这样,你就利用Visual Studio的对话框编辑器,进行这类复杂的自定义控件的可视化开发。代码参考:
tutorials/winx/step004-user-ctrl/3.superdialog/hello.cpp
看了这几个demo,并尝试过运行这些demo的人,多多少少会有magic的感觉。让人感到疑惑的主要是,“这些自定义控件是如何被创建出来的?”
的确,在代码中,你看不到创建自定义自定义控件的代码。这就如你看不到标准控件(如Static、Button等)的创建代码一样。——在点到这一点后,灵光在你心中一闪:是的,你猜得没错,这些自定义控件和标准控件一样,是对话框加载对话框资源后自动创建的。
Visual Studio的对话框编辑器除了标准控件如Static、Button、Edit等外,还有一个特殊的控件,叫Custom Control。插入该控件,并且将Class设为要创建的控件类的类名(也就是你实现窗口类时通过WINX_CLASS指定的)。如下图(我们假设WINX_CLASS为“MyView”):
这样,对话框加载资源后,就明白需要创建一个窗口类类名为“MyView”的窗口。正因为这样,所以“MyView”这个窗口类需要在对话框生成之前注册(RegisterClass)。
——现在,你已经明白,我为什么说,WINX特别强调窗口类以及注册窗口类的概念。因为,WINX区别与传统界面库(WTL/ATL、MFC等等)的另一个显著特征是,WINX中的用户自定义控件(Custom Control)与标准控件一样,是可以直接放到对话框上创建的。这很有趣。你认为呢?
上一篇我们谈到WINX与其他库共存的问题。空口无凭,我们这里就给出一个WINX中使用OpenCV的样例。
数字图像的直方图均衡化是常用的图像增强方法。这个样例本身的源代码取之:数字图像的直方图均衡化(C/C++源代码)。作者HUNNISH,是OpenCV方面的方家。
样例的源代码:
winx/tutorials/opencv/step001-histgram/hello.cpp - 使用HighGUI
winx/tutorials/opencv/step001-histgram(winx)/hello.cpp - 使用WINX
为了比较,我们提供了两个样例,其一使用OpenCV自带的HighGUI,另一个使用WINX提供的窗口。WINX提供的窗口(winx::CvWindow)是新增的一个窗口类。其代码参考:winx/OpenCV.h。这是我在WINX中新加入了一个头文件。提供这样的头文件只是为了方便,与WINX体系并无关系,多数代码也是取自HighGUI。
为了编译这两个样例,你需要下载OpenCV。你可以从http://sourceforge.net/projects/winx/下载它,并让目录结构看起来是这样的:
- opencv
- bin
- lib
- include
- winx
- lib
- include
偶尔也会听到这样的一些疑问:WINX支持DirectX,OpenCV吗?也会听到SmartWin支持OpenCV这样的说法。下面我们分析一下这个问题。
我们知道,库之间共存的障碍,主要有以下几点:
其一:编译期的符号(指类名、函数名、宏名等)冲突。主要表形在:
其二:链接期的符号冲突。根据我的经验,这主要表现在:
其三:框架的假设。一些库是以框架方式提供的,要求用户按照其预定的方式进行调用。如果两个库均提供了框架,那么他们互不知晓的情况下,通常很难在一起工作。
其四:调用的假设。在你调用一个库的代码时,你需要模拟出它需要的环境。可分为两种情形:
WINX如何解决这些问题?
其一:编译期的符号冲突。WINX使用namepsace尽量减少对全局命名空间的污染。对宏名亦采用类namespace的解决方案,多数WINX的宏名均以“WINX_”开头。
其二:链接期的符号冲突。和STL一样,WINX的代码尽量以纯头文件的方式提供。
其三:框架的假设。WINX不是框架,以便有更好的适应性。
其四:调用的假设。WINX的函数规格尽量减少采用WINX特有的数据结构。另外,类似MFC的AfxGetApp(),WTL的_Module的全局性数据,这在WINX中是明令禁止的(只是由于WINX使用部分WTL的代码,一些时候,_Module的使用不能避免)。
所有这一切,均是为了让WINX在最大程度上和更多的库可以协作。而我们在前面也已经提到了,WINX尽量采用了更为开放的结构。相比之下,它更懂得与其他库一起协作的道理。
本博客内容除非特殊说明均属原创,如需转载、引用其中的部分文字,请注意以下几点:
1)请在转载(引用)的内容开始添加本人署名,并提供本博客中相应文章的链接。如你的作品为非电子读物或纯文本,请给出链接的url。
2)请勿用于商业用途。
3)如果愿意,请给我邮件:xushiweizh@gmail.com,让我知道我的东西到哪去了。谢过。
WINX是卓越的,您需要了解以下内容:
*) 卓越的消息分派机制。正是因为有这个核心支撑,使得WINX区别于传统的界面库(如MFC、WTL)。
*) 简单易用(SIMPLE)是第一目标,尽量使可视化(WYSIWYG)界面开发成为可能。
*) WINX是一个界面库,不是开发框架。WINX代码是可以和WTL、MFC等界面库的代码共存的。
*) 兼容。尽管有更简洁的方法,但WINX还是提供了MFC程序员熟悉的调用界面,并尽量使得MFC代码可以轻松移植到WINX下。
*) 不重复制造轮子。在没有一个卓越的解决方案以区别于现有系统之前,先沿用现有的。事实上,WINX建立于WTL之上,重用了多数的WTL组件。