复制整理https://www.zhihu.com/question/23480014回答
UI设计原则
各个UI系统,包括MFC、WPF、Qt,也包括其它,诸如Android SDK、Cocoa的构建建立在UI设计3大原则的基础上:
- 面向对象;
- MVC;
- 消息队列驱动;
图形库构建“套路”
对于一个图形界面的程序,大致可以分为3个层
+----------------------+
| user application |
+----------------------+
| ui framework |
+----------------------+
| operation system api |
+----------------------+
最下面的是操作系统API,无论是UI库还是用户程序,要实现某功能最终都是要依赖于操作系统API的。关键点在于,操作系统需要提供多少数量的API函数,才足以构建一个图形界面程序。答案是:很少
首先,操作系统需要为UI框架和用户程序提供一块屏幕区域
struct window *window_create();
其次,这块区域需要能够接收用户事件(这里以鼠标点击为例)
enum mouse_event {
mouse_event_down,
mouse_event_move,
mouse_event_up
};
typedef void (*mouse_handler)(struct window *, mouse_event event, int x, int y);
void window_set_mouse_handler(struct window *, mouse_handler handler);
最后,UI框架与用户程序需要在这块区域绘制各种控件(下例是矩形)
struct context *context_get_form_window(struct window *);
void context_set_color(struct context *, int rgba);
void context_fill_rect(struct context *, int x, int y, int width, int height);
只要有了这3类API,一个图形界面程序就可以构建出来了,即使如今复杂的3D图像程序也是如此。这些作为基础API的函数数量很少,往大的说不到一百个,往小的说,十多个也能成事。庞大的UI框架,诸如Qt、Xamarin、WinForm,仅仅是以这几十个操作系统API函数作为自己的基础;对操作系统极小的依赖,也是可移植的保障;也是UI框架构建的“套路”
Windows SDK
Windows 1.0在1985年发售,Windows SDK时随Windows 1.0所提供的操作系统API,尽管在此之前已有施乐的Star、苹果的Lisa和Mac OS这样的图形界面操作系统,但Windows 1.0毕竟是第一个大规模发行的图形操作系统,需要直面各样的开发者与普通用户的问题,既然是第一个,当然是不成熟的。这种不成熟即有当时技术条件的限制,也有经验上的匮乏
// Windows SDK demo
int WinMain() {
//1 register a window class
WNDCLASS wndclass;
wndclass.style = CS_HREDRAW | CS_VREDRAW;
wndclass.lpfnWndProc = WndProc;
wndclass.cbClsExtra = 0;
wndclass.cbWndExtra = 0;
wndclass.hInstance = NULL;
wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);
wndclass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
wndclass.lpszMenuName = NULL;
wndclass.lpszClassName = "MainWndClass";
RegisterClass(&wndclass);
//2 create a winow
HWND hwnd = CreateWindow(
"MainWndClass",
"Window Title",
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
NULL,
NULL,
hInstance,
NULL);
//3 message loop
MSG msg;
while (GetMessage(&msg, NULL, 0, 0)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return 0;
}
那个叫CreateWindow的函数的有11个参数,按照面向对象的观念与简洁性的做法,它不应该是这样的吗
// Gtk demo
GtkWidget *window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
gtk_window_set_title(window, "window title");
gtk_window_set_position(window, GTK_WIN_POS_CENTER_ALWAYS);
gtk_window_set_default_size(window, 400, 300);
gtk_window_set_resizable(window, TRUE);
原因是当时的机器内存太小,一个函数的链接符号要占有一个字长的地址空间,所以能省就省。这也是当时诸多系统API设计的惯例,如之后苹果的Carbon库,参数多到惨不忍睹的函数也是比比皆是,这就是所谓的“技术限制”的一例
此外上例中第一步“注册窗口类”和第三步“消息循环”有必要强制要求用户来指明各个参数项吗。从后来的实践看,答案是没有必要。但作为“第一个吃螃蟹的”,怎么会知道,这就是所谓的“经验上的匮乏”一例
作为后起之秀的Qt,同样是显示一个窗口,用户代码就简洁得多
// Qtdemo
int main(int argc, char *argv[]){
//1 create application instance
QApplication app(argc, argv);
//2 create a window
QWidget *window = new QWidget;
window->setWindowTitle("title");
window->show();
//3 message loop
return app.exec();
}
MFC
由于Windows SDK的不如意,于是就出现的MFC,MFC只是对Windows SDK的封装,按后世的看法,MFC不按“套路”出牌!包括作为同时代竞争产品的Borland公司的VCL,还有稍晚的Java awt,也是不按“套路”出招!
微软的MFC、Borland的VCL不走“套路”,因为它们都是对Windows SDK的封装,而不是选择少量Windows SDK核心函数并在其上构建自己的工作逻辑。因为Windows SDK使用繁琐,于是有了MFC,但MFC是对Windows SDK的直接封装,虽然提升了易用性,但终究要沿用Windows SDK所定义的工作流程。当然这种做法也是有好处的,例如工作量更小、用户可以更方便地直接调用系统API。
说穿了,MFC(当然也包括VCL)需要解决的是“有无”的问题。Windows 1.0这样的图形界面操作系统已经开始在大众领域普及,开发者也需要一套框架来开发图形界面程序。MFC在上世纪90年代取得了巨大的商业成功,但以后世程序员的眼光看,MFC还是太稚气了。我曾经接触过一些学习Windows程序开发的新人,他们对MFC充满鄙夷,认为MFC是上个世纪的遗留产物,它概念多、晦涩难懂、代码量大、界面也丑,他们更倾向于Qt、WPF这样的新秀。他们的结论是对的,MFC确实是上个世纪的遗留产物;但推导过程不对,MFC与Qt、WPF本质上是一样的东西,在MFC上遇到的问题,在使用Qt、WPF时极可能也会遇到,不过是早还是晚。只能说“MFC对新人极不友好”
在MFC同时代,也正是上世纪80、90年代,出现了对后世UI框架设计有极大影响的技术方案:用web浏览器作为图形界面程序的容器。由于篇幅的限制,我这里直接说结论:作为一个整体性的技术方案,它失败了。失败的原因可以大致归为3点:
- 其一是观念上的落后。基于html、js、css的网页技术,最初的设计意图是解决文档浏览的问题,采用html为主,js、css相辅的设计方式。这样的设计在界面表现上有优势,但也仅此而已,其它方面诸如程序生命周期控制、数据处理等就太糟糕了
- 其二是基础设施的不如意。相关的人才与技术储备太欠缺,所能实现的产出物,无论在功能上还是性能上都无法与MFC、VCL这样的传统程序相同并论
- 其三是技术开拓者的急功近利。当时热衷于以浏览器作为图形程序容器的技术方案的厂商,如网景,还有后来的google和facebook,它们的意图很明显,就是挑战微软的平台霸权,建立有利于自身的生态环境(说人话就是:招揽开发者、提供生产工具、生产特定的程序、平台吃流量)。充满鼓动性的价值观营销在具有叛逆属性的年轻人中可以快速取得影响力,但得不到主流的支持和强势技术团队的示范,一切都是空
尽管在新世纪的第二个10年以web前端为技术基础的UI框架,如electron、reactive native,取得了喜人的成果,但与上世纪的雏形相比,它们所在的大环境与技术本质已有了根本的区别
上世纪90年代末到本世纪初是UI库百花齐放的年代,如今在PC领域,为人熟知的Qt、Gtk、wxWidgets都出现在这个时间段,这是与大环境有关:
- 首先MFC解决了“有无”的问题,接下来就是“好用难用”的问题;
- 其次由于前一个10年图形系统的普及,业界在图形程序的开发上有了大量的人才、技术、经验上的积累,开发一套UI库对于小团队来说也是敢想敢干的事
- 再次定制化的需求开始出现,是需要能实现快速开发的程序框架(WTL),还是可以实现跨平台的程序框架(Qt)?是需要能实现常见功能的中小程序框架(wxWidgets),还是得安装几个G的包但可以实现复杂应用的程序框架(WPF)?不同的团队面对不同的任务,一定会有不同的选择。
- 此外当时微软作为处于垄断地位但又不思进取的操作系统厂商,也有推波助澜的作用。本世纪第一个10年,MFC已很老旧,但微软却一直没有给出升级方案;微软一味将开发者向.Net上推,但迁移成本太高,并且后者的性能问题也迟迟不能解决。于是开发者只能自己想办法
那时整个UI界的设计思想都比较落后(除了Apple),MFC又背负了沉重的兼容性包袱,比如vc++ 1.52的MFC程序到了vc2003稍加修改都可以编译,导致MFC后期没有什么发展,就是沿着老的思路完善了些细节,添加了些组件,但是根本性的设计问题没有改进
WTL
WTL都算不上什么Framework,就是利用泛型特性对Win API做了层封装,设计思路也没摆脱MFC的影响,实际上用泛型做UI Framework也只能算是一次行为艺术,这个思路下继续发展就会变得没法用了,比如代码过于复杂,编译太慢,出错不好调试等问题难以解决
而且封装得也不完全,还是随处可见 HWND HDC之类的东西
GTK
这个吃了语言的亏,用C写面向对象实在是痛苦,虽然在思想上比MFC要先进了些,但是写出来的代码比MFC要罗嗦很多了。相比MFC,多了Layout的概念,事件处理上有了Signal/slot,虽然用起来很麻烦
wxWidgets
这个基本就是个跨平台的MFC,对各个平台的差异做了抽象,实际上后端大多还是用平台原生的API实现,好多控件都是直接用系统原生的。有wxWidgets for GTK+的版本,后端就是GTK+,wxWidgets就是一层壳。这也是wxWidgets的优点,它编译出来的程序发行包比较小,性能也不错
Qt
虽然它也是上世纪90年代出现的,但是它在21世纪有了长足的进步。应该说它的起点就比较高,一开始就定位跨平台,而且不满足于简单封装系统API,而是要自己创造出一套完整的API和框架,甚至要代替系统API,所以不仅仅是做UI,而是涉及到了APP开发所用到的所有东西,包括网络,数据库,多媒体,脚本引擎等。signal/slot是Qt发明的,这是事件通知模型里C++语言的最佳实现了,甚至我都觉得这该写进C++标准,估计C++委员会的老顽固们是从不写GUI的。
早期的QT也是没有DirectUI的概念的,每一个QWidget都对应一个原生窗口,从Qt4.4开始,只有顶层QWidget才是原生窗口,而Child Widget是Alien Widget,只是个抽象的图层不对应原生窗口,这就实现了DirectUI的概念,很多图形效果也就变得可能了,比如窗口层叠透明效果
在4.8后实现了QPA(Qt Platform Abstraction),这就使移植Qt变得很容易,目前Qt是支持平台最多的框架没有之一。
由于早期授权的问题,Qt对于开源社区不是很友好,导致推广不太顺利,直到它改成了LGPL方式,如果Qt能早点想开了,恐怕就没有wxWidgets的生存空间了。
Qt的缺点也是有的,就是太大,不过可以自己剪裁,我可以把QT库剪裁到发行包压缩后2.5MB
WPF
微软在Win Form的思路上走到死胡同后,终于痛下决心用正确的方法开发UI库了。21世纪的UI一定是定义出来的,绝对不能是代码写出来的,所以有了XAML这个强大的定义工具,不但可以定义UI布局,还包括图形动画效果,消息响应方式等。配合C#这种优秀的语言,更是如虎添翼。但是问题也很明显,就是过于庞大,不仅开发时要用到庞大的IDE和设计工具,发行的安装包也十分巨大,所以目前还是很少有人拿他写通用软件客户端的,大多是做企业项目时写专用客户端
cocoa
Apple的成功有很多原因,其中之一就是cocoa,cocoa理念十分先进,而且出来得早,我都怀疑Qt和WPF有不少思想都是借鉴cocoa的
定义式的UI,用xib就可以定义UI的绝大部分细节,而且提供所见即所得的可视化设计工具
严格的MVC,而且定义非常清晰,分工明确
signal/slot,虽然不叫这个名字,但思想就是,而且真的是拖动鼠标就能connect
提供了ARC,闭包和反射,给UI开发带来巨大的便利性,当然这得益于Objective-C这个语言
OWL和VCL
我是从Borland C++3.0和Delphi 1.0开始用的,那时的Borland看来很有前途的,可惜后来一系列决策失误导致现在这个公司几乎消失了,同学们不要再往这个坑里跳了
OWL曾经和MFC是竞争对手,设计思想也差不多,个人感觉OWL的API设计更优雅一点,但是在市场上OWL被MFC彻底击败
Delphi是神作,它在RAD(快速应用开发)领域长时间没有对手,直到BS架构取代CS架构。Delphi的特点就是简单、开发快,单纯就写个基本可用的应用来说,可能至今都没有比他更快的,但是缺点就是丑,基本大多数Delphi应用都是一大堆控件堆积在一起,很不美观,另外由于Pascal语言的限制无法和现有大量的C/C++代码融合。虽然后来有C++ Builder,但是Builder里简单和快的优点也消失了。Borland的C++编译器越做越差,导致后来开源项目都不太愿意兼容这个编译器了
RAD快速应用开发
RAD特别适合(尽管不限于)开发由用户界面要求驱动的软件。 图形用户界面构建器通常被称为快速应用程序开发工具。 其他快速开发方法包括自适应、敏捷、螺旋和统一模型。
从开发过程本身获得的知识可以反馈到解决方案的需求和设计,计划驱动的方法试图严格定义需求、解决方案和实施计划,并有一个不鼓励更改的过程。 RAD方法认识到软件开发是一个知识密集型过程,并提供灵活的过程,有助于利用项目期间获得的知识来改进或调整解决方案
与创建规范相比,用户更擅长使用和反应。在瀑布模型中,用户签署一组需求是很常见的,但是当呈现给一个已实现的系统时,突然意识到给定的设计缺少一些关键特性或太复杂了。一般来说,大多数用户在体验正在运行的系统的原型时会给出更多有用的反馈,而不是抽象地定义该系统应该是什么。
原型可以使用并且可以演变成完整的产品,一些RAD方法中使用的一种方法是将系统构建为一系列原型,这些原型从最小的功能发展到适度有用的最终完整系统,用户可以在此过程中更早地获得有用的业务功能
几乎所有RAD方法都有一个共同点,即在用户和开发人员之间的整个生命周期中都有更多的交互。在瀑布模型中,用户将定义需求,然后随着开发人员创建系统而大部分时间消失。在RAD中,用户从一开始就参与到几乎整个项目中。这就要求企业愿意投入应用领域专家的时间
GUI builder图形用户界面构建器
图形用户界面构建器(GUI构建器 GUI设计器),是一种软件开发工具,它允许设计者使用拖放所见即所得的方式排列图形控制元素(通常称为小部件),从而简化GUI的创建编辑。 如果没有GUI构建器,则必须通过在源代码中手动指定每个小部件的参数来构建GUI,在程序运行之前没有视觉反馈
用户界面通常使用事件驱动架构进行编程,因此GUI构建器还简化了创建事件驱动代码的过程, 此支持代码将小部件与触发提供应用程序逻辑的函数的传出和传入事件连接起来
一些图形用户界面构建器会自动生成图形控件元素的所有源代码,生成序列化的对象实例,然后由应用程序加载