http://justin.everett-church.com/fp10demos/cloth.html
一个模拟布料飘荡的flex应用demo,处理的效果很好,你可以通过拖拽布夹子来看看布抖动的效果
[Flex]RIA Meeting周刊
[Flex]ActionScript3的垃圾回收机制
最近在看GC(垃圾收集器)相关的东西,发现了几篇好文,虽然比较老了(06年的),但是很值得看;翻译了一下,这里的GC(垃圾收集器)主要是说flash player9的。偶英文比较糟糕,有非常少量的语句没有翻译,因为偶看不懂囧 大家多担待,欢迎拍板砖^_^
本文大部分是来自:http://www.gskinner.com/blog/archives/2006/06/as3_resource_ma.html;其中part1的译文来自:http://bbs.airia.cn/ActionScript/thread-3656-1-1.aspx。其他为本人翻译大概集合了5篇文章。
建议先将“前奏”看完,这样会对理解本文有很大的帮助。
前奏:
理解delete关键字
在我的一篇博文中how the garbage collector works in Flash Player 9,塞德里克尼希米记(原名:Cédric Néhémie,名字是google的囧)小盆友提了一个非常好的问题:为什么delete一个非动态类的属性时会抛异常呢?delete关键字是否是真的将对象从内存中删除了呢?
提及到这个话题文档并不是很多,即使有也是非常难找,大部分解答都是在根据相关的测试来猜测或推测。但是我的解释肯定是完全正确的(译者:突然想起 疯狂的赛车 里面的一句话:不烧不专业)。如果不是,请告诉我,我不想误导任何人(=。=!)
我的理解是:d elete 关键词删除的不仅仅是变量的值,还包括实际变量的定义。无疑这样会释放其拥有的所有引用,借此将其交给GC(垃圾收集器)完成释放。也就是说:delete不会直接从内存删除引用所指向的对象。
在AS2中,这种行为的效果并不明显,因为播放器不支持非动态类运行。这就意味着:删除一个变量的定义等同于设置变量的值为undefined。正是因为这点,编译器永远也不会抛出与delete相关的异常。他们之间仅仅有一点点不同。例如:在AS2 中试图去删除MovieClip实例onPress和onMouSEOver事件的的处理函数,不论你删除哪一个操作都会返回一个"undefined",假如你设置其一为undefined(不用delete操作),你会发现直接设置为undefined的对象还是会出现对应操作的光标,而delete操作则不会出现。我灰常确信这点,当判断是否要现实光标时,播放器会确认onPress事件处理函数的定义是否存在,而非根据其值(value)。
另一方面 AS3则支持非动态类的运行。所有的类都是事先封装好的,除非他们用explicitly来声明动态类。在运行时,你是不可以修改一个已经封装好的类或者其实例的成员。正因为如此,并且根据ECMAScript说明,delete仅仅是删除动态类的动态属性,不会删除非动态的变量(或方法)的。
在ActionScript 2.0中,你可以使用delete来删除一个对象或对象的属性。在ActionScript 3.0中,delete操作符是遵循ECMAScript的,这就意味着delete只能用在删除对象的动态属性上。
如果你试图删除一个对象的非动态属性,他就会触发编译器的一个错误提示:
1189 > Delete removes dynamically defined properties from an object. Declared properties of a class can not be deleted. This error appears only when the compiler is running in strict mode.
在AS3中,delete将会返回一个布尔值来说明它是否删除成功,你可以试试下面的代码:
var t:* = new Timer(15); // no typing to get around the compiler error
trace(delete(t.delay)); // traces false, object is sealed so can't delete
trace(t.delay); // 15 – delete never occurred
var o:* = {fun:"stuff"};
trace(delete(o.fun)); // traces true, object is dynamic so can delete
trace(o.fun); // undefined – delete occurred
正题
part1
目前我暂时在研究ActionScript3.0,它的能力让我很激动。它的原生执行速度带来诸多可能(此句原文The raw execution speed by itself provides so many possibilities. raw本意未加工,原始的,这里的意思是指引入AVM2之后,ActionScript3.0在执行速度上有了很大提高,所以使支持更复杂的组件成为可能,译者注)。它引入了E4X、sockets、byte 数组对象、新的显示列表模型、正则表达式、正式化的事件和错误模型以及其它特性,它是一个令人炫目的大杂烩。
能力越大责任越大(译者:出自蜘蛛侠),这对ActionScript3.0来说一点没错。引入这些新控件带来一个副作用:垃圾收集器不再支持自动为你收集垃圾等假设。也就是说Flash开发者转到ActionScript3.0之后需要对关于垃圾收集如何工作以及如何编程使其工作更加有效具备较深入的理解。没有这方面的知识,即使创建一个看起来很简单的游戏或应用程序也会出现SWF文件内存泄露、耗光所有系统资源(CPU/内存)导致系统挂起甚至机器重启。
要理解如何优化你的ActionScript3.0代码,你首先要理解垃圾收集器如何在FlashPlayer 9中工作。Flash有两种方法来查找非活动对象并移除它们。本文解释这两种技术并描述它们如何影响你的代码。 本文结尾你会找到一个运行在FlashPlayer9中的垃圾收集器模拟程序,它生动演示了这里解释过的概念。
关于垃圾收集器 垃圾收集器是一个后台进程它负责回收程序中不再使用的对象占用的内存。非活动对象就是不再有任何其他活动对象引用 它。为便于理解这个概念,有一点非常重要,就是要意识到除了非原生类型(Boolean, String, Number, uint, int除外),你总是通过一个句柄访问对象,而非对象本身。当你删除一个变量其实就是删除一个引用,而非对象本身。 以下代码很容易说明这一点: ActionScript代码
// create a new object, and put a reference to it in a:
var a:Object = {foo:"bar"}
// copy the reference to the object into b:
var b:Object = a;
// delete the reference to the object in a:
delete(a);
// check to see that the object is still referenced by b:
trace(b.foo); // traces "bar", so the object still exists.
如果我改变上述示例代码将b也删除,它会使我创建的对象不再有活动引用并等待对垃圾收集器回收。ActionScript3.0 垃圾回收器使用两种方法定位无引用的对象 : 引用计数法和标识清除法。
引用计数法 引用计数法是一种用于跟踪活动对象的较为简单的方法,它从ActionScript1.0开始使用。当你创建一个指向某个对象的引用,该对象的引用计数器加1;当你删除该对象的一个引用,该计数器减1。当某对象的计数器变成0,该对象将被标记以便垃圾回收器回收。 这是一个例子: ActionScript代码
var a:Object = {foo:"bar"}
// the object now has a reference count of 1 (a)
var b:Object = a;
// now it has a reference count of 2 (a & b)
delete(a);
// back to 1 (b)
delete(b);
// down to 0, the object can now be deallocated by the GC
引用计数法简单,它不会非CPU带来巨大的负担;多数情况下它工作正常。不幸地是,采用引用计数法的垃圾回收器在遇到循环引用时效率不高。循环引用是指对象 交叉引用(直接、或通过其他对象间接实现)的情况。即使应用程序不再引用该对象,它的引用计数器仍然大于0,因此垃圾收集器永远无法收集它们。下面的代码 演示循环引用是怎么回事: ActionScript代码
var a:Object = {}
// create a second object, and reference the first object:
var b:Object = {foo:a};
// make the first object reference the second as well:
a.foo = b;
// delete both active application references:
delete(a);
delete(b);
上述代码中,所有应用程序中活动的引用都被删除。我没有任何办法在程序中再访问这两个对象了,但这两个对象的引用计数器都是1,因为它们相互引用。循环引用 还可以更加负责 (a 引用 c, c引用b, b引用a, 等等) 并且难于用代码处理。FlashPlayer 6 和 7的XML对象有很多循环引用问题: 每个 XML 节点被它的孩子和父亲引用,因此它们从不被回收。幸运的是FlashPlayer 8 增加了一个叫做标识–清除的新垃圾回收技术。
标识–清除法 ActionScript3.0 (以及FlashPlayer 8) 垃圾回收器采用第2种策略标识–清除法查找非活动对象。FlashPlayer从你的应用程序根对象开始(ActionScript3.0中简称为root)直到程序中的每一个引用,都为引用的对象做标记。 接下来,FlashPlayer遍历所有标记过的对象。它将按照该特性递归整个对象树。并将从一个活动对象开始能到达的一切都标记。该过程结束后,FlashPlayer可以安全的假设:所有内存中没有被标记的对象不再有任何活动引用,因此可以被安全的删除。图1 演示了它如何工作:绿色引用(箭头)曾被FlashPlayer 标记过程中经过,绿色对象被标记过,白色对象将被回收。

Figure 1.FlashPlayer采用标记清除方法标记不再有活动引用的对象 标记–清除法非常准确。但是,由于FlashPlayer 遍历你的整个对象结构,该过程对CPU占用太多。FlashPlayer 9 通过调整迭代标识–清除缩减对CPU的占用。该过程跨越几个阶段不再是一次完成,变成偶尔运行。
延期(执行)垃圾回收器和不确定性
FlashPlayer 9垃圾回收器操作是延期的。这是一个要理解的非常重要的概念:当你的对象的所有引用删除后,它不会被立即删除。而是,它们将在未来一个不确定的时刻被 删除(从开发者的角度来看)。垃圾收集器采用一系列启发式技巧诸如查看RAM分配和内存栈空间大小以及其他方法来决定何时运行。作为开发者,你必须接受这 样的事实:不可能知道非活动对象何时被回收。你还必须知道非活动对象将继续存在直到垃圾收集器回收它们。所以你的代码会继续运行(enterFrame 事件会继续)、声音会继续播放、装载还会发生、其它事件还会触发等等。
记住,在FlashPlayer中你无权控制何时运行垃圾收集器去回收对象。作为开发者,你需要尽可能把你的游戏或应用程序中无用的对象应用清除。
两个例子:
http://www.adobe.com/devnet/flashplayer/articles/garbage_collection.html#
http://www.adobe.com/devnet/flashplayer/articles/garbage_collection.html#
part 2
AS3给开发者带来了更快的代码运行速度和更多功能更强大的API。但不幸的是,伴随着功能的增强,对开发者的专业能力要求也越来越高。这篇文章将重点讲述AS3在资源管理方面的特性和这些特性可能会引起的一些让人头疼的问题。下一篇文章,我将介绍一些可供我们使用的对策。
在AS3中对资源管理影响最大的是其新引入的display list 模型。在flash8和其以前的版本中,当一个display object被移除后(用removeMovie 或unloadMovie),那么这个对象和其所有的子节点将会被立即从内存中删除,并且终止运行该对象的所有代码。Flash Player 9 引入了更灵活的display list 模型,把display objects(Sprites, MovieClips, etc)看做一般的对象。这就意味着开发者可以做很多有趣的事情,比从新定义display object的容器(将一个display object从一个display list移到另一个中),从已生成的swf文件中读取已实例化的display object。不幸的是:这些display object将会被垃圾收集器等同于一般对象来处理。这就会引发一些潜在的问题。
问题1:动态的内容
其中一个比较明显的问题出现在与Sprite相关或其他容器的动态实例中,当你想要在某段时间后移除该对象时,Sprites(或其他容器)就会出现一个比较明显的问题:当你从舞台(stage)上将其移除后,此时display object已经不在display list上了,但事实上它仍然在内存中没有被清除。如果你没有清空这个剪辑的所有的引用或监听器,它可能用远也不会被从内存中删除。
灰常值得我们注意的是:display object不仅仅仍然占用着内存,而且它仍然在执行其内部的代码,例如Timer,enterFrames和其相关的外部监听器。
现在有一个应用于游戏的sprite正在执行一个内部的enterFrame事件,每一帧它都会执行一些运算,并判断出它附近的其他游戏元素。在AS3中,即使你将其从display list中删除(removeChild)并且将其引用全部设置为null,在其被垃圾收集器回收之前,它将继续执行enterFrame内的代码。在删除sprite之前,你必须明确:“enterFrame的监听器已经被删除”。
假设有一个影片剪辑监听来自舞台的鼠标移动事件,在你移除其监听器之前,即使是该剪辑已经被删除(deleted),每有一次鼠标移动事件,其代码都会被执行,也就是这个剪辑将会被永远运行下去,作为一个来自舞台事件派发的引用。
假设:在一些相互关联而且各自处于不同的状态的sprite(例如一些处于实例化,一些已经被删除),又或是在你删除对象前没能将其所有的引用清空时。你可能不会发觉就是这个细节令你的cpu使用率直线上升,降低你的程序或者游戏的执行速度,或者使用户的电脑进入无响应状态。没有方法可以强制flash播放器去立即删除一个display object并且停止其代码执行。你必须在从一个容器中删除一个display object时手动的做一些处理。下一篇文章我将介绍对策。
这里有一个例子(需要flash 9播放器),单击create(创建) 按钮来创建一个新的Sprite事例,Sprite将会带有一个计数器,并会显示出来。单击remove按钮并且观察计数器是怎么变化的,事实上sprite的所以引用都已经被置空。你可以多创建几个实例来观察这个问题是如何让一个程序恶化的。代码会在本篇文章的最底端。
http://www.gskinner.com/blog/archives/2006/07/as3_resource_ma_1.html
问题2:加载的内容
设想把加载的swf文件也同样当做一般的对象来对待,很容易想到一些你会遇到的问题。正如display objects一样,没有一种明确而有效的方法将swf的加载内容从内存中删除,或者立即停止其运行。置空其调用的Loader对加载的swf的所有引用,它最终还是会被GC(垃圾收集器)收集的。
考虑2个这样的情景:
1.你创建了一个shell来进行flash实验。这个实验是最前沿的,并且它会使cpu使用率到达最高。一个用户点击按钮加载了一个实验,观察后,又点击按钮加载了第二个实验。即使是第一个的引用全部被清空,它仍然在后台运行,在第二个实验运行的同时,两个实验的负荷已经超出了cpu最大运算能力。
2.一个客户要求你建立一个程序来读取其他开发者的swf文件。那些开发者给舞台添加了监听器,或者是用其他方法创建了一些指向swf内部的一些引用。你现在没办法删除它的内容,直到关闭应用程序之前,它将一直在内存中并占用cpu。即使它们没有“内部引用”,它们也将继续执行下去,知道下一次GC(垃圾收集器)将它们释放。
出于项目安全的考虑,当你加载第三方内容时,你必须要了解的是:你是无法控制其删除或执行的。当一个swf被加载后,其很容易伴随你的程序一直在运行,即使是被删除了,当发生交互时被加载的swf可能会继续捕捉或干扰用户。
另一个例子和第一个问题一样,只是每次加载swf时,不使用动态实例。
上述有两个提及两个情景事例在下面的页面中:http://www.gskinner.com/blog/archives/2006/07/as3_resource_ma_1.html
问题3:时间轴
希望这个问题能在最终版本中解决。(译者:此篇文章是2006年的)
在as3中时间轴是可以用代码来操作的。当执行播放时,它会动态的实例或删除display object。这就意味着会遇到与问题一相同的问题。在某一帧虽然剪辑从舞台被删除,但是它仍然会继续保存在内存中,在被回收前,它会继续执行其内部的所有代码。这不是程序员所期望的,当然也不是flash设计者所期望的。
这里有一个例子,同样的原理,不过这次是在两帧之间删除和实例化。
http://www.gskinner.com/blog/archives/2006/07/as3_resource_ma_1.html
Adobe在想啥?或者,为什么会出现这个问题?
Java开发者在看到这一问题时可能会说:“那又如何?”。这种差异是可以理解的, flash开发人员并不习惯于手动干涉内存管理(因为以前就没这问题),而Java、C++的开发人员又已经习惯了强大的GC(无论是自动的还是手动的)。这些问题是最新的内存管理语言自身带来的缺陷,不幸的是这些问题是无法被避免的。
另一方面,Flash带来了很多在其他语言中罕见的问题(包括Flex中的一部分也是)。Flash内容中往往会有很多空闲或被动代码在被执行,尤其是 java和Flex在交互时(通常来说:很多密集型运算都是和入户的输入紧密相连的)。Flash工程会更经常读取外部第三方内容(其代码质量通常是很差的)。Flash 开发人员可利用的工具,资料和框架都比较少。而且据我所知负责开发flash的工作者的背景一般都是来自:音乐,艺术,商业,哲学或是其他的什么,但是除了专业程序员。
这种多元化的组合带来了令人惊奇的创造力和内容,但是他们并没有准备去讨论资源管理的问题。
总结
资源管理将会成为AS3开发的一个重要部分。忽略这一问题,可能会导致程序运行缓慢,或是完全的拖垮用户的系统。目前为止还没有明确的方法能立即将display object从内存中删除并停止其内部代码运行,这就意味这我们有责任去妥善的处理我们创造出来的对象。希望经过交流,可以探索出一个最佳的方法和框架来更轻松解决这个问题。
本篇涉及代码:http://www.gskinner.com/blog/assets/resMgt2/resourceManagement2.zip
part 3
在第三部分,我们会集中于讲解一些新的工具(AS3 / Flex2)使内存管理更有效。在内存管理方面官方的只有两个函数和内存管理直接相关,但是它们都灰常有用。下面是一些非官方的补充,当然这并不是唯一方法。
System.totalMemory
这是一个简单的工具,但是它灰常重要,因为它是开发者在flash中第一个可以实时应用的工具。它可以让你监听到flash播放器实时的内存占用大小,更重要的是:你可以利用这个来判断是否抛出异常,来终止即将给用户带来的负面体验。
下面是一个例子:
import flash.system.System;
import flash.net.navigateToURL;
import flash.net.URLRequest;
…
// check our memory every 1 second:
var checkMemoryIntervalID:uint = setInterval(checkMemoryUsage,1000);
…
var showWarning:Boolean = true;
var warningMemory:uint = 1000*1000*500;
var abortMemory:uint = 1000*1000*625;
…
function checkMemoryUsage() {
if (System.totalMemory > warningMemory && showWarning) {
// show an error to the user warning them that we're running out of memory and might quit
// try to free up memory if possible
showWarning = false; // so we don't show an error every second
} else if (System.totalMemory > abortMemory) {
// save current user data to an LSO for recovery later?
abort();
}
}
function abort() {
// send the user to a page explaining what happpened:
navigateToURL(new URLRequest("memoryError.html"));
}
这一行为可以通过很多方法来实现,但是至少证明了这一行为的目的是好的。
灰常值得我们注意的是:totalMemory是一个被一个进程(a single process)使用的“全局变量”(shared value),一个进程可能只有一个窗口,或多个浏览窗口,这些取决与浏览器,操作系统或是有多少个窗口被打开。
弱引用
在AS3众多的新特性中,我灰常高兴的看到“weak references”。这种引用不会被垃圾收集器作为判定object是否被回收的依据。它的作用是:如果当一个对象仅仅剩下弱引用时,这个对象将会被垃圾收集器在下一轮回收。但是弱引用只支持两种类型:第一种是经常会因为内存管理机制带来麻烦的事件监听器,我强烈的建议:每当添加监听器时,都将其第五个参数选项,即弱引用设置为true。下面是其对应的参数设置的例子:
someObj.addEventListener("eventName",listenerFunction,useCapture,priority,weakReference);
stage.addEventListener(Event.CLICK,handleClick,false,0,true);
// the reference back to handleClick (and this object) will be weak.
更多关于弱引用请浏览: http://www.gskinner.com/blog/archives/2006/07/as3_weakly_refe.html
另一个弱引用支持的是Dictionary object。一般情况下在初始化时设置其第一个参数为true,下面是例子:
var dict:Dictionary = new Dictionary(true);
dict[myObj] = myOtherObj;
// the reference to myObj is weak, the reference to myOtherObj is strong
更多关于 dictionaries 在 ActionScript 3的介绍和应用, 请点击 http://www.gskinner.com/blog/archives/2006/07/as3_dictionary.html
比较爽的就是可以利用弱引用支持Dictionary这个特性,将弱引用“钩”到其他内容上。例如:使用弱引用创建WeakReference 和WeakProxyReference类来实现任何对象都可以创建弱引用。
WeakReference 类
WeakReference利用了Dictionary可以存储弱引用的特点来实现将弱引用“钩”到其他任意对象的功能。这个类在实例化和访问上会有一些开销,所以我建议将其应用在一些可能得不到释放而且较大的对象上。这些代码虽然不能取代那些使对象正常“分解”的代码,但是它可以帮你确保大型数据对象被垃圾收集器正常分解。
import com.gskinner.utils.WeakReference;
var dataModelReference:WeakReference;
function registerModel(data:BigDataObject):void {
dataModelReference = new WeakReference(data);
}
…
function doSomething():void {
// get a local, typed reference to the data:
var dataModel:BigDataObject = dataModelReference.get() as BigDataObject;
// call methods, or access properties of the data object:
dataModel.doSomethingElse();
}
从良好的代码结构来说,这是一个好的解决方案,因为它保证了你的数据类型带来良好的安全性,而且没有二义性(non-ambiguou)。这些代码是那些希望快速实现这一功能的人准备的,我还将其整合到另一个WeakProxyReference类(同时也是一个学习代理(Proxy)的好例子 )中。
WeakProxyReference 类
WeakProxyReference 使用了Proxy类来代理弱引用对象。它的效果和WeakReference类是基本是一样的,WeakProxyReference可以直接调用弱引用对象的方法并且是直接传递给目标。WeakProxyReference的问题就是失去了类型安全性,并且有一点点二义性代码。也就是说它可能会抛出运行时错误。(特别是当你试图去访问一个对象中不存的属性)但是不会出现编译错误。
import com.gskinner.utils.WeakProxyReference;
var dataModel:Object; // note that it is untyped, and not named as a reference
function registerModel(data:BigDataObject):void {
dataModel = new WeakProxyReference(data);
}
function doSomething():void {
// don't need to get() the referent, you can access members directly on the reference:
dataModel.doSomethingElse();
dataModel.length++;
delete(dataModel.someProperty);
// if you do need access to the referent, you need to use the weak_proxy_reference namespace:
var bdo:BigDataObject = dataModel.weak_proxy_reference::get() as BigDataObject;
}
一种可以强制执行垃圾收集的方法(不推荐使用)
在我的前一篇文章中,我说过在AS3中垃圾收集周期是不确定的,没有方法可以知道它下一次什么时候运行。严格的讲这句话也不完全的对,有一个技巧可以强制让flash播放器执行一次垃圾收集,这个技巧很方便你去探索垃圾收集和在开发期内测试你的程序,但是它绝不能出现在开发完成的产品中,因为它会破坏处理器的负载能力。同时官方也是不推荐使用的,所以你不能靠它的功能来完成实质功能上提升。
强制执行垃圾收集(表示计数法或引用清除法),你所要做的就是 执行两次相同的LocalConnection。这样做系统会抛出一个异常,所以你必须为它准备好异常捕捉(try/catch)
try {
new LocalConnection().connect('foo');
new LocalConnection().connect('foo');
} catch (e:*) {}
// the GC will perform a full mark/sweep on the second call.
再次重复一次:这个方法仅仅可以用于开发周期内的测试。它绝不能出现在开发完成的产品中!
总结
毫无疑问的是:ActionScript 3给开发者在资源管理方面带来了更多的工作。虽然我们只有刚刚提到的一些应对工具,但是有对策总是比没有的好,而且 Adobe至少也注意到这个问题了。采取有效的对策和方法并合理的搭配这些工具,相信你可以很好的管理Flash 9和 Flex 2的工程。
Download WeakReference and WeakProxyReference.
http://www.gskinner.com/blog/assets/WeakReference.zip
尾声
补充资料:
原文出处:http://www.tan66.cn/?p=11
在AIR程序中发现了一种快速激活GC sweep的办法。
那就是将窗口最小化,瞬间就环保了。
当然为了做到悄无声息,还要将窗口还原。
这两句代码写在一起就可以了。前提条件是当前状态不是最小化。
stage.window.minimize();
stage.window.restore();
问题是,这样会看到窗口缩小又变回来的过程。所以在实际开发程序的时候可以将主程序窗口隐形
visible=false;
将要显示的内容放在其它窗口中,当然不要忘记加入控制主程序窗口退出的代码。
[C#]微软的All-In-One框架 CodeFx
Codeproject 上的文章: http://www.codeproject.com/kb/threads/codefx_ipc.aspx
博客堂上的文章:http://blog.joycode.com/vsto/archive/2009/03/04/115496.aspx
框架下载地址: http://cfx.codeplex.com/
[C#]ASP.Net MVC 1.0 发布
看到Silverlight 3 Beta发布了后,听说ASP.NET MVC 1.0发布了,就去ASP.NET MVC的官方站看了下,果然看到是1.0发布了,下面是下载地址:
http://go.microsoft.com/fwlink/?LinkId=144444
下面是ASP.NET MVC的在线文档:
http://go.microsoft.com/fwlink/?LinkId=145989
看了下Release Notes(带目录才6页),基本和RC2没啥变化。
已知下面的Visual Studio add-ins 会和MVC的安装程序有冲突:
· PowerCommands
· Clone Detective
· Azure Tools
可以下载下面的Hotfix修复
经过5个preview和2个RC后,好不容易终于发了1.0,可喜可贺,哈哈…
下面是一些资源:
ASP.NET MVC 入门系列教程(基本是Preview5的,和正式版有一点点出入)
重典的教程:
老赵的视频教程:http://www.microsoft.com/china/msdn/events/webcasts/shared/webcast/Series/MVC.aspx
从零开始学习ASP.NET MVC(未完) by ziqiu.zhang
如果你英文比较好的话,可以直接到ASP.NET MVC 官网上看,上面有入门教程.
一些ASP.NET MVC可用的模板引擎:
上面的4个都包括在MVC Contrib中了
还有我比较喜欢的一个:Spark
一个用于ASP.NET MVC的业务实体验证的开源项目:xVal,介绍见xVal – a validation framework for ASP.NET MVC、xVal 0.8 (Beta) Now Released。
[MVC]ASP.NET MVC File Manager
一个管理ASP.NET MVC 的View的开源项目,具体功能还没详细了解,先帖出来备查
进入系统之后即进入文件及目录管理界面,而在这里比之上一版本的“文件修改、重命名、新建文件、删除文件”几个功能,又多出了对文件夹的几个操作。而且使用了一下老赵强推的强型Model。
我将程序的源码共享在Codeplex上:http://file.codeplex.com/
源码下载:http://file.codeplex.com/Release/ProjectReleases.aspx?ReleaseId=24834
[MVC]ASP.NET MVC Contact Manager Sample Applicatio
|
In this series of tutorials, Stephen Walther demonstartes how to use the ASP.NET MVC framework to build an entire Contact Manager application using unit tests, test-driven development, Ajax, and software design principles and patterns. Read all 7 tutorials in C# or VB.NET. |
- Iteration #1 – Create the application
- Iteration #2 – Make the application look nice
- Iteration #3 – Add form validation
- Iteration #4 – Make the application loosely coupled
中文翻译地址:http://www.cnblogs.com/024hi/archive/2009/03/17/asp_net_mvc_sample_contact_manager_index.html
[C#]从零开始学习ASP.NET MVC(四) View/Model 全解
一.摘要
本文讲解在Action中向View传递Model的几种方式.以及View获取Model以后如何编写显示逻辑.还详细的介绍了ASP.NET MVC框架提供的Html Helper类的使用及如何为Html Helper类添加自定义扩展方法.
二.承上启下
上 一篇文章中我们学习了Controller处理一次请求的全过程.在Controller的Action中, 会传递数据给View,还会通知View对象开始显示.所以Model是在Action中获取的, 并由Action传递给View. View对象接到Action通知后会使用自己的显示逻辑展示页面.
下面首先让我们学习如何将Model传递给View对象.
三.传递数据给View
在MVC中,Model对象是指包含了数据的模型. Controller将Model传递给View以后, View对象中不应该做任何的业务逻辑处理, 仅仅根据Model对象做一些显示逻辑的处理.
传递Model对象时, 我们有两种选择:
1.传递一个弱类型的集合, 即成员为object类型的集合, 在View中需要将每个成员转换成我们需要的类型,比如int, string,自定义类型等.
2.传递强类型对象, 这些类型是我们自定义的. 在View中直接使用我们传递的强类型对象, 不需要再转换类型.
如果让我们自己设计一个MVC框架, 我们也会想到上面两种实现方式,接下来看看在ASP.NET MVC中的实现.
1.传递弱类型的集合
(1) 如何传递
ASP.NET MVC框架定义了ViewContext类, 直译后是"View上下文", 其中保存和View有关的所有数据, 其中Model对象也封装在了此类型中.
ViewContext对象包含三个属性:
- IView View
- ViewDataDictionary ViewData
- TempDataDictionary TempData
其中ViewData集合和TempData集合都是用来保存Model对象的.在一个Controller的Action中, 我们可以用如下方式为这两个集合赋值:
/// <summary> /// 传递弱类型Model的Action示例 /// </summary> /// <returns>ViewResult</returns> public ActionResult WeakTypedDemo() { ViewData["model"] = "Weak Type Data in ViewData"; TempData["model"] = "Weak Type Data in TempData"; return View("WeakTypedDemo"); }
在页面中, 是用如下方式使用这两个集合:
<div> <% = ViewData["model"] %><br /> <% = TempData["model"] %><br /> </div>
(2) 传递过程
请注意Action中的赋值语句实际上操作的是Controller类的ViewData和TempData属性, 此时并没有任何的数据传递.上一篇文章中我们已经学到, return View语句会返回一个ViewResult对象, 并且接下来要执行ViewResult的Executeresult方法. Controller的View方法会将Controller类的ViewData和TempData属性值传递给ViewResult,代码如下:
protected internal virtual ViewResult View(IView view, object model) { if (model != null) { ViewData.Model = model; } return new ViewResult { View = view, ViewData = ViewData, TempData = TempData }; }
然后在ViewResult中根据ViewData和TempData构建ViewContext对象:
public override void ExecuteResult(ControllerContext context) { //... ViewContext viewContext = new ViewContext(context, View, ViewData, TempData); View.Render(viewContext, context.HttpContext.Response.Output); //... }
ViewContext对象最终会传递给ViewPage, 也就是说ViewData和TempData集合传递到了ViewPage. 我这里简化了最后的传递流程, 实际上ViewData对象并不是通过ViewContext传递到ViewPage中的, ViewPage上的ViewData是一个单独的属性, 并没有像TempData一样其实访问的是ViewContext.TempData. 这么做容易产生奇异, 本类ViewContext是一个很好理解职责十分清晰的类. 作为使用者的我们暂时可以忽略这点不足, 因为如此实现ViewData完全是为了下面使用强类型对象.
(3)ViewData和TempData的区别
虽然ViewData和TempData都可以传递弱类型数据,但是两者的使用是有区别的:
- ViewData的生命周期和View相同, 只对当前View有效.
- TempData保存在 Session中, Controller每次执行请求的时候会从Session中获取TempData并删除Session, 获取完TempData数据后虽然保存在内部的字典对象中,但是TempData集合的每个条目访问一次后就从字典表中删除. 也就是说TempData的数据至多只能经过一次Controller传递, 并且每个元素至多只能访问一次.
2.传递强类型对象
我在系统中建立了一个模型类:StrongTypedDemoDTO
从名字可以看出, 这个模型类是数据传输时使用的(Data Transfer Object).而且是我为一个View单独创建的.
添加一个传递强类型Model的Action,使用如下代码:
public ActionResult StrongTypedDemo() { StrongTypedDemoDTO model = new StrongTypedDemoDTO() { UserName="ziqiu.zhang", UserPassword="123456" }; return View(model); }
使用了Controller.View()方法的一个重载, 将model对象传递给View对象.下面省略此对象的传输过程, 先让我们来看看如何在View中使用此对象.
在创建一个View时, 会弹出下面的弹出框:
勾选"Create a strongly-typed view"即表示要创建一个强类型的View, 在"View data class"中选择我们的数据模型类.
在"view content"中如下选项:
这里是选择我们的View的"模板", 不同的模板会生成不同的View页面代码. 虽然这些代码不一定满足我们需要, 但是有时候的确能节省一些时间,尤其是在写文章做Demo的时候. 比如我们的View是添加数据使用的,那就选择"Create".如果是显示一条数据用的, 就选择"Detail".
以选择Detail为例, 自动生成了下列代码:
<%@ Page Language="C#" Inherits="System.Web.Mvc.ViewPage<DemoRC.Models.DTO.TransferModelController.StrongTypedDemoDTO>" %> ... <body> <fieldset> <legend>Fields</legend> <p> UserName: <%= Html.Encode(Model.UserName) %> </p> <p> UserPassword: <%= Html.Encode(Model.UserPassword) %> </p> </fieldset> <p> <%=Html.ActionLink("Edit", "Edit", new { /* id=Model.PrimaryKey */ }) %> | <%=Html.ActionLink("Back to List", "Index") %> </p> </body> ...
页面的Model属性就是一个强类型对象, 在这里就是StrongTypedDemoDTO类实例.page页面指令可以看出, 这里的页面继承自ViewPage<T>类, 而不是ViewPage, 用T已经确定为StrongTypedDemoDTO类型, 所以Model的类型就是StrongTypedDemoDTO.
3.传递数据的思考
使用强类型数据要优于弱类型数据, 老赵也曾提出过. 强类型有太多的好处, 智能提示, 语意明确, 提高性能,编译时发现错误等等.
所以在实际应用中, 我们应该传递强类型的数据.
目前ASP.NET MVC还存在一些不足. 将页面类型化, 导致了只能传递一种数据类型实例到页面上. 而且内部代码的实现上并不十分完美.尤其是虽然我们已经知道传递的是StrongTypedDemoDTO类型, 页面的Model属性也是StrongTypedDemoDTO类型, 但是仍然要经过装箱和拆箱操作. 原因是Controller的View(object model)方法重载接收的是一个object类型.
还有, 为每个View建立一个模型类, 也是一件繁琐的工作. 也许我们的业务模型中的两个类组合在一起就是View页面需要的数据, 但是却不得不建立一个类将其封装起来.模型类的膨胀也是需要控制一个事情. 尤其是对于互谅网应用而非企业内部的系统, 页面往往会有成百上千个.而且复用较少.
当然目前来说既然要使用强类型的Model, 我提出一些组织Model类型的实践方法.下面是我项目中的Model类型组织结构:
这里Model是一个文件夹, 稍大一些的系统可以建立一个Model项目. 另外我们需要建立一个DTO文件夹, 用来区分Model的类型. MVC中的Model应该对应DTO文件夹中的类.在DTO中按照Controller再建立文件夹, 因为Action和View大部分都是按照Controller组织的, 所以Model也应该按照Controller组织.
在Controller文件夹里放置具体的Model类. 其实两个Controller文件夹中可以同相同的类名称, 我们通过命名空间区分同名的Model类:
namespace DemoRC.Models.DTO.TransferModelController { /// <summary> /// Action为StrongTypedDemo的数据传输模型 /// </summary> public class StrongTypedDemoDTO { /// <summary> /// 用户名 /// </summary> public string UserName { get; set; } /// <summary> /// 用户密码 /// </summary> public string UserPassword { get; set; } } }
使用时也要通过带有Controller的命名空间使用比如DTO.TransferModelController.StrongTypedDemoDTO, 或者建立一些自己的约定.
四.使用Model输出页面
View对象获取了Model以后, 我们可以通过两种方式使用数据:
1.使用内嵌代码
熟悉ASP,PHP等页面语言的人都会很熟悉这种直接在页面上书写代码的方式.但是在View中应该只书写和显示逻辑有关的代码,而不要增加任何的业务逻辑代码.
假设我们创建了下面这个Action,为ViewData添加了三条记录:
/// <summary> /// Action示例:使用内嵌代码输出ViewData /// </summary> /// <returns></returns> public ActionResult ShowModelWithInsideCodeDemo() { ViewData["k1"] = @"<script type=""text/javascript"">"; ViewData["k2"] = @"alert(""Hello ASP.NET MVC !"");"; ViewData["k3"] = @"</script>"; return View("ShowModelWithInsideCode"); }
在ShowModelWithInsideCode中, 我们可以通过内嵌代码的方式, 遍历ViewData集合:
<html xmlns="http://www.w3.org/1999/xhtml" > <head runat="server"> <title>使用内嵌代码输出ViewData</title> <% foreach(KeyValuePair<string, object> item in ViewData ) {%> <% = item.Value %> <% } %> </head> <body> <div> <div>此页面运行的脚本代码为:</div> <fieldset> <% foreach(KeyValuePair<string, object> item in ViewData ) {%> <% = Html.Encode(item.Value) %> <br /> <% } %> </fieldset> </div> </body> </html>
页面上遍历了两遍ViewData,第一次是作为脚本输出的, 第二次由于进行了HTML编码,所以将作为文字显示在页面上.
使用这种方式, 我们可以美工做好的HTML页面的动态部分, 使用<% %>的形式转化为编码区域, 通过程序控制输出.由于只剩下显示逻辑要处理,所以这种开发方式往往要比CodeBehind的编码方式更有效率, 维护起来一目了然.
最让我高兴是使用这种方式后,我们终于可以只使用HTML控件了.虽然ASP.NET WebFrom编程模型中自带了很多服务器控件, 功能很好很强大, 但是那终究是别人开发的控件, 这些控件是可以随意变化的, 而且实现原理也对使用者封闭. 使用原始的页面模型和HTML控件将使我们真正的做程序的主人.而且不会因为明天服务器控件换了个用法就要更新知识, 要知道几乎所有的HTML控件几乎是被所有浏览器支持且不会轻易改变的.
2.使用服务器控件
注意虽然我们同样可以在ASP.NET MVC中使用服务器端控件, 但是在MVC中这并不是一个好的使用方式.建议不要使用.
要使用服务器端控件, 我们就需要在后台代码中为控件绑定数据. ASP.NET MVC框架提供的添加一个View对象的方法已经不能创建后台代码, 也就是说已经摒弃了这种方式.但是我们仍然可以自己添加.
首先创建一个带有后台代码的(.cs文件),一般的Web Form页面(aspx页面),然后修改页面的继承关系, 改为继承自ViewPage:
public partial class ShowModelWithControl : System.Web.Mvc.ViewPage
在页面上放置一个Repeater控件用来显示数据:
<body> <form id="form1" runat="server"> <div> <asp:Repeater ID="rptView" runat="server"> <ItemTemplate> <%# ((KeyValuePair<string, object>)Container.DataItem).Value %><br /> </ItemTemplate> </asp:Repeater> </div> </form> </body>
在Page_Load方法中, 为Repeater绑定数据:
public partial class ShowModelWithControl : System.Web.Mvc.ViewPage { protected void Page_Load(object sender, EventArgs e) { rptView.DataSource = ViewData; rptView.DataBind(); } }
在Controller中创建Action, 为ViewData赋值:
/// <summary> /// Action示例:使用服务器控件输出ViewData /// </summary> /// <returns></returns> public ActionResult ShowModelWithControlDemo() { ViewData["k1"] = @"This"; ViewData["k2"] = @"is"; ViewData["k3"] = @"a"; ViewData["k4"] = @"page"; return View("ShowModelWithControl"); }
运行结果:
再次强调, 在ASP.NET MVC中我们应该尽量避免使用这种方式.
3.使用 HTML Helper 类生成HTML控件
HTML Helper类是ASP.NET MVC框架中提供的生成HTML控件代码的类. 本质上与第一种方式一样, 只是我们可以不必手工书写HTML代码,而是借助HTML Helper类中的方法帮助我们生成HTML控件代码.
同时,我们也可以使用扩展方法为HTML Helper类添加自定义的生成控件的方法.
HTML Helper类的大部分方法都是返回一个HTML控件的完整字符串, 所以可以直接在需要调用的地方使用<% =Html.ActionLink() %>的形式输出字符串.
(1)ASP.NET MVC中的HtmlHelper类
在ViewPage中提供了Html属性, 这就是一个HtmlHelper类的实例. ASP.NET MVC框架自带了下面这些方法:
- Html.ActionLink()
- Html.BeginForm()
- Html.CheckBox()
- Html.DropDownList()
- Html.EndForm()
- Html.Hidden()
- Html.ListBox()
- Html.Password()
- Html.RadioButton()
- Html.TextArea()
- Html.TextBox()
上面列举的常用的HtmlHelper类的方法,并不是完整列表.
下面的例子演示如何使用HtmlHelper类:
<div> <% using (Html.BeginForm()) { %> <label style="width:60px;display:inline-block;">用户名:</label> <% =Html.TextBox("UserName", "ziqiu.zhang", new { style="width:200px;" })%> <br /><br /> <label style="width:60px;display:inline-block;">密码:</label> <% =Html.Password("Psssword", "123456", new { style = "width:200px;" })%> <% }%> </div>
上 面的代码使用Html.BeginForm输出一个表单对象, 并在表单对象中添加了两个Input, 一个使用Html.TextBox输出, 另一个使用Html.Password输出,区别是Html.Password输出的input控件的type类型为password.效果如图:
(2)扩展Html Helper类
我们可以自己扩展HtmlHelper类, 为HtmlHelper类添加新的扩展方法, 从而实现更多的功能.
在项目中建立Extensions文件夹, 在其中创建SpanExtensions.cs文件.源代码如下:
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Mvc; namespace System.Web.Mvc { public static class SpanExtensions { public static string Span(this HtmlHelper helper,string id, string text) { return String.Format(@"<span id=""{0}"">{1}</span>", id, text); } } }
上面的代码我们为HtmlHelper类添加了一个Span()方法, 能够返回一个Span的完整HTML字符串.
因为命名空间是System.Web.Mvc,所以页面使用的时候不需要再做修改,Visual Studio会自动识别出来:
请大家一定要注意命名空间, 如果不使用System.Web.Mvc命名空间, 那么一定要在页面上引用你的扩展方法所在的命名空间, 否则我们的扩展方法将不会被识别.
接下来在页面上可以使用我们的扩展方法:
<div> <!-- 使用自定义的Span方法扩展HTML Helper --> <% =Html.Span("textSpan", "使用自定义的Span方法扩展HtmlHelper类生成的Span") %> </div>
(3) 使用TagBuilder类创建扩展方法
上面自定义的Span()方法十分简单, 但是有时候我们要构造具有复杂结构的Html元素, 如果用字符串拼接的方法就有些笨拙.
ASP.NET MVC框架提供了一个帮助我们构造Html元素的类:TagBuilder
TagBuilder类有如下方法帮助我们构建Html控件字符串:
| 方法名称 | 用途 |
| AddCssClass() | 添加class=””属性 |
| GenerateId() | 添加Id, 会将Id名称中的"."替换为IdAttributeDotReplacement 属性值的字符.默认替换成"_" |
| MergeAttribute() | 添加一个属性,有多种重载方法. |
| SetInnerText() | 设置标签内容, 如果标签中没有再嵌套标签,则与设置InnerHTML 属性获得的效果相同. |
| ToString() | 输出Html标签的字符串, 带有一个参数的重载可以设置标签的输出形式为以下枚举值:
|
同时一个TagBuilder还有下列关键属性:
| 属性名称 | 用途 |
| Attributes | Tag的所有属性 |
| IdAttributeDotReplacement | 添加Id时替换"."的目标字符 |
| InnerHTML | Tag的内部HTML内容 |
| TagName | Html标签名, TagBuilder只有带一个参数-TagName的构造函数.所以TagName是必填属性 |
下面在添加一个自定义的HtmlHelper类扩展方法,同样是输出一个<Span>标签:
public static string Span(this HtmlHelper helper, string id, string text, string css, object htmlAttributes) { //创意某一个Tag的TagBuilder var builder = new TagBuilder("span"); //创建Id,注意要先设置IdAttributeDotReplacement属性后再执行GenerateId方法. builder.IdAttributeDotReplacement = "-"; builder.GenerateId(id); //添加属性 builder.MergeAttributes(new RouteValueDictionary(htmlAttributes)); //添加样式 builder.AddCssClass(css); //或者用下面这句的形式也可以: builder.MergeAttribute("class", css); //添加内容,以下两种方式均可 //builder.InnerHtml = text; builder.SetInnerText(text); //输出控件 return builder.ToString(TagRenderMode.Normal); }
在页面上,调用这个方法:
<% =Html.Span("span.test", "使用TagBuilder帮助构建扩展方法", "ColorRed", new { style="font-size:15px;" })%>
生成的Html代码为:
<span id="span-test" class="ColorRed" style="font-size: 15px;">使用TagBuilder帮助构建扩展方法</span>
注意已经将id中的"."替换为了"-"字符.
五.总结
本来打算在本文中直接将ViewEngine的使用也加进来, 但是感觉本文已经写了很长的内容, (虽然不多,但是很长……)所以将ViewEngine作为下一章单独介绍.
前些天 Scott Guthrie's的博客上放出了"ASP.NET MVC免费教程", 里面介绍了创建一名为"NerdDinner"项目的全过程, 使用LINQ+ASP.NET MVC, 但是其中对于技术细节没有详细介绍(和本系列文章比较一下就能明显感觉出来), 下面提供本书的pdf文件下载地址以及源代码下载地址:
源代码是英文版本, 其实我最近有做一个中文的"Nerd Dinner"的想法, 但是因为要写文章而且还要工作已经让我焦头烂额, 写文章真的是一件体力活.如果有人同样有兴趣翻译代码, 我可以提供域名和服务器空间.
差点提供忘记本篇文章的示例源代码下载:
[JQuery]非常简洁的动态加载js和css的jquery plugin
1
// plugin author : chenjinfa@gmail.com
2
// plugin name : $.include
3
// $.include('file/ajaxa.js');$.include('file/ajaxa.css');
4
// or $.includePath = 'file/';$.include(['ajaxa.js','ajaxa.css']);
5
6
$.extend({
7
includePath: '',
8
include: function(file) {
9
var files = typeof file == "string" ? [file]:file;
10
for (var i = 0; i < files.length; i++) {
11
var name = files[i].replace(/^\s|\s$/g, "");
12
var att = name.split('.');
13
var ext = att[att.length – 1].toLowerCase();
14
var isCSS = ext == "css";
15
var tag = isCSS ? "link" : "script";
16
var attr = isCSS ? " type='text/css' rel='stylesheet' " : " language='JavaScript' type='text/JavaScript' ";
17
var link = (isCSS ? "href" : "src") + "='" + $.includePath + name + "'";
18
if ($(tag + "[" + link + "]").length == 0) document.write("<" + tag + attr + link + "></" + tag + ">");
19
}
20
}
21
});
放在JavaScript文件头部,就像C#(using…)或Java(import …)
[UI]80 年代以来的操作系统 GUI 设计进化史(下)
70年代,施乐公司 Xerox Palo Alto Research Center (PARC) 的 研究人员开发了第一个 GUI 图形用户界面,开启了计算机图形界面的新纪元,80年代以来,操作系统的界面设计经历了众多变迁,OS/2,Macintosh, Windows, Linux, Symbian OS ,各种操作系统将 GUI设计带进新的时代。本文介绍了80年代以来各种操作系统 GUI 界面设计的进化史。这是本文的第二部分,第一部分参阅80年代以来的操作系统GUI 设计进化史(上)。
Windows 3.1 (1992)
该版本的 Windows 支持预装的 TrueType 字体,第一次使 Windows 成为可以用于印刷的系统。Windows 3.0 中,只能通过 Adobe 字体管理器(ATM)实现该功能。该版本同时包含一个叫做 Hotdog Stand 的配色主题。
配色主题可以帮助某些色盲患者更容易看清图形。

Source: Wikipedia
OS/2 2.0 (1992)
这是第一个获得世界认可并通过可用性与可访问性测试的 GUI,整个 GUI 基于面向对象模式,每个文件和文件夹都是一个对象,可以同别的文件,文件夹与应用程序关联。它同时支持拖放式操作以及模板功能。

IBM OS/2 2.0, Source: toastytech.com

IBM OS/2 2.0, Source: toastytech.com
Windows 95 (1995)
Windows 3.x 之后,微软对整个用户界面进行了重新设计,这是第一个在窗口上加上关闭按钮的 Windows 版本。图标被赋予了各种状态(有效,无效,被选中等),那个著名的“开始”按钮也是第一次出现。对操作系统和 GUI 而言,这是微软的一次巨大飞跃。

Microsoft Windows 95, Source: guidebookgallery.org

Microsoft Windows 95, Source: guidebookgallery.org
1996 – 2000 OS/2 Warp 4 (1996)
IBM 终于争气地推出了 OS/2 Warp 4。桌面上可以放置图标,也可以自己创建文件和文件夹,并推出一个类似 Windows 回收站和 Mac 垃圾箱的文件销毁器,不过一旦放进去进不能再恢复。

IBM OS/2 Warp 4, Source: toastytech.com

IBM OS/2 Warp 4, Source: toastytech.com
Mac OS System 8 (1997)
该版本的 GUI 支持默认的256色图标,Mac OS 8 最早采用了伪3D图标,其灰蓝色彩主题后来成为 Mac OS GUI 的标志。

Apple Mac OS 8, Source: guidebookgallery.org
Windows 98 (1998)
图标风格和 Windows 95 几无二致,不过整个 GUI 可以使用超过256色进行渲染,Windows 资源管理器改变巨大,第一次出现活动桌面。

Microsoft Windows 98, Source: toastytech.com
KDE 1.0 (1998)
KDE是 Linux 的一个统一图形用户界面环境。

KDE 1.0, Source: ditesh.gathani.org
GNOME 1.0 (1999)
GNOME 桌面主要为 Red Hat Linux 开发,后来也被别的 Linux 采用。

Red Hat Linux GNOME 1.0.39, Source: visionfutur.com
2001 – 2005 Mac OS X (released in 2001)
2000年初,苹果宣布推出其 Aqua 界面,2001年,推出全新的操作系统 Mac OS X。默认的 32×32, 48×48 被更大的 128×128 平滑半透明图标代替。
该 GUI 一经推出立即招致大量批评,似乎用户都如此大的变化还不习惯,不过没过多久,他们呢就接受了这种新风格,如今这种风格已经成了 Mac OS 的招牌。

Apple Mac OS X 10.1 Source: guidebookgallery.org
Windows XP (released in 2001)
每一次微软推出重要的操作系统版本,其 GUI 也必定有巨大的改变,Windows XP 也不例外,这个 GUI 支持皮肤,用户可以改变整个 GUI 的外观与风格,默认图标为 48×48,支持上百万颜色。

Microsoft Windows XP Professional, Source: guidebookgallery.org
KDE 3 (released in 2002)
自1.0版发布以来,KDE 有了长足的改进,对所有图形和图标进行了改进并统一了用户体验。

KDE 3.0.1, Source: netbsd.org
2007 – 2009 (current)
Windows Vista (released in 2007)
这是微软向其竞争对手做出的一个挑战,Vista 中同样包含很多 3D 和动画,自 Windows 98 以来,微软一直尝试改进桌面,在 Vista 中,他们使用类似饰件的机制替换了活动桌面。

Microsoft Windows Vista, Source: technology.berkeley.edu
Mac OS X Leopard (released in 2007)
对于第六代 Max OS X,苹果又一次对用户界面做出改进。基本的 GUI 仍是 Aqua,但看上去更 3D 一些,也包含了 3D 停靠坞以及很多动画与交互功能。

Apple Mac OS X 10.5 Leopard, Source: skattertech.com
KDE (v4.0 Jan. 2009, v4.2 Mar. 2009)
KDE 4 的 GUI 提供了很多新改观,如动画的,平滑的,有效的窗体管理,图标尺寸可以很容易调整,几乎任何设计元素都可以轻松配置。相对前面的版本绝对是一个巨大的改进。

Source: Wikipedia
鸣谢
延伸阅读
苹果的设计演化史:1977-2008
本文国际来源:Operating System Interface Design Between 1981-2009
中文翻译来源:COMSHARP CMS 官方网站
Mikel
$.extend(
include: