[Java]Java程序员ActionScript 3入门

mikel阅读(771)

Java程序员ActionScript 3入门

作者 Jack Herrington译者 张凯峰 发布于 2008年11月6日 上午3时21分

社区
Java
主题
RIA,
富客户端/桌面
标签
Flex

我 们还是勇敢面对吧:客户端对于Java程序员来说,一直都不是个友好的地方。Java在客户端的技术,包括applet、Swing和JavaFX到目前 为止只取得了有限的成绩。JavaScript除了它的名字外,几乎没有什么地方像Java语言。而Adobe Flash呢,它看起来的确像JavaScript,真的吗?也许在几年前说Flash就像JavaScript一样是可以理解的,但随着 ActionScript 3的出现,一切都改变了。而且我相信你会喜欢它的很多东西。

首先,ActionScript这门针对Adobe Flex和Flash的编程语言,现在是强类型的了。它也是一流的面向对象语言,包括有类和接口。它还拥有你在Java中找不到的东西——特别地,它包含属性的getset方法,以及一个叫做ECMAScript for XML(E4X)的语言扩展,可以将任何XML文档转换成对象,这样你就可以通过“.”操作符直接引用它们,就跟普通对象一样。

这篇文章会引领你浏览ActionScript的基础内容,以及展示它与你所熟悉的Java环境的不同。到最后,你就会放弃你对 ActionScript 的任何偏见,并开始有兴趣把玩它。关于Flex、Flash和ActionScript的最伟大的事情之一就是它们完全是免费的。只要下载了Adobe Flex Builder 3就可以开始了。Flex Builder是一个复杂的集成开发环境(IDE),而且不是免费的,但它用于构建Flash应用的Flex软件开发工具包(SDK)是完全免费的。

对阅读本文章的语言发烧友的一句忠告是:我并不是个语言教师,因此我可能忽略掉一些语言的细节。我也不会在这篇文章中演示ActionScript 3的所有内容。如果你的确需要这方面的内容,有很多非常棒的ActionScript 3的书籍。我能给予你的就是你对这门语言的初次的感觉。让我们开始吧。

类和接口

就和Java一样,在ActionScript 3中一切皆是对象。虽然有一些基本类型,比如integer,但除了这些,一切皆是对象。类似地,就像Java一样,ActionScript也有命名空间和包,比如com.jherrington.animals,其表示了company/jack herrington/animal下的类。你可以把类放到缺省的命名空间,但更好的方法是由你自己来控制自己的命名空间。

要定义一个类,你要使用class关键字,这也跟Java一样。请看示例:

package com.jherrington.animals
{
public class Animal
{
public function Animal()
{
}
}
}

在这个例子中,我定义了一个Animal类,以及什么也没干的构造函数。我还可以很容易地添加一些成员变量并完善这个构造函数,请看示例:

package com.jherrington.animals
{
public class Animal
{
public var name:String = "";
private var age:int = 0;
private function Animal( _name:String, _age:int = 30 )
{
name = _name;
age = _age;
}
}
}

这里,我给一个Animal对象定义了两个成员变量:name,一个公有的字符串,以及age,一个私有的整数。(很明显,小动物们对于它们的年龄 都很害羞。:) )构造函数可以接受一个或两个参数:要么是单独的name,要么name和age。你也可以在函数声明中为参数提供缺省的值。

你会注意到这里的类型定义是跟Java相反的。在Java中,类型在变量之前;而在ActionScript中,类型在变量之后。这是因为强类型定义是追加到ActionScript上的。所以为了支持旧的、没有定义类型的代码,类型就需要放在变量名的后面。

让我添加一些方法来扩展这个示例:

package com.jherrington.animals
{
import flash.geom.Point;
public class Animal
{
public var name:String = "";
private var age:int = 0;
private var location:Point = new Point(0,0);
public function Animal( _name:String, _age:int = 30 )
{
name = _name;
age = _age;
}
public function moveTo( x:int, y:int ) : void {
location.x = x;
location.y = y;
}
public function getLocation( ) : Point {
return location;
}
}
}

正如你所看到的,我又添加了一个私有成员变量location,类型是我从Flash的geometry包中引入的Point类型。而且我还添加了两个方法来操作location:moveTo,用来移动animalgetLocation,用来返回当前的位置。

到目前为止,这还是以Java的方式去get和set一个值。但ActionScript方式会清晰很多,请看示例:

package com.jherrington.animals
{
import flash.geom.Point;
public class Animal
{
public var name:String = "";
private var age:int = 0;
private var myLocation:Point = new Point(0,0);
public function Animal( _name:String, _age:int = 30 )
{
name = _name;
age = _age;
}
public function set location( pt:Point ) : void {
myLocation = pt;
}
public function get location( ) : Point {
return myLocation;
}
}
}

这里我使用getset函数,它们会在客户代码获取或设置成员变量location时被调用。对于客户代码来说,location变量看起来就像是个普通的成员变量。但事实上,你可以用你喜欢的任何代码来响应成员变量的设值,以及处理变量的获取。

如何来使用它呢?你可以添加一个事件,这个事件会在location发生改变时被触发。请看示例代码:

package com.jherrington.animals
{
import flash.events.Event;
import flash.events.EventDispatcher;
import flash.geom.Point;
public class Animal extends EventDispatcher
{
public var name:String = "";
private var age:int = 0;
private var myLocation:Point = new Point(0,0);
public function Animal( _name:String, _age:int = 30 )
name = _name;
age = _age;
}
public function set location ( pt:Point ) : void {
myLocation = pt;
dispatchEvent( new Event( Event.CHANGE ) );
}
public function get location( ) : Point {
return myLocation;
}
}
}

现在,我指定Animal类是一个事件分发者——也就是说,客户代码可以从这个对象监听到事件发生。接着,当location改变时,我发出了一个新的事件。

下面就是客户代码,它创建了一个animal对象,并开始监听事件是否发生,然后就改变了animallocation

	var a:Animal = new Animal();
a.addEventListener(Event.CHANGE, function( event:Event ) : void {
trace( "The animal has moved!" );
} );
a.location = new Point( 10, 20 );

这段代码在animal移动时会记录一条跟踪信息。在ActionScript中,你可以定义任何类型的消息。大多数的类都是EventDispatcher类,你可以为它们的事件添加监听器。

接口

就像Java一样,ActionScript 3语言也支持接口,并使用类来实现它们。下面的示例中,就是一个我们可以用Animal类来实现的接口:

package com.jherrington.animals
{
import flash.geom.Point;
public interface IAnimal
{
function get name() : String;
function set name( n:String ) : void;
function get location() : Point;
function set location( pt:Point ) : void;
}
}

在这个例子中,我为接口定义了两个可以set和get的成员变量。没错,你可以在ActionScript接口中定义方法和成员变量。是不是很酷?

为了实现这个接口,我对Animal类做了一点修改。请看示例:

package com.jherrington.animals
{
import flash.events.Event;
import flash.events.EventDispatcher;
import flash.geom.Point;
public class Animal extends EventDispatcher implements IAnimal
{
private var myName:String = "";
public function get name() : String
{
return myName;
}
public function set name( n:String ) : void
{
myName = n;
dispatchEvent( new Event( Event.CHANGE ) );
}
private var myLocation:Point = new Point(0,0);
public function set location ( pt:Point ) : void {
myLocation = pt;
dispatchEvent( new Event( Event.CHANGE ) );
}
public function get location( ) : Point {
return myLocation;
}
public function Animal( _name:String )
{
name = _name;
}
}
}

当然,我也可以为这个类添加特定的变量和方法,或者实现除了IAnimal接口之外的其他接口。但是和Java一样,我只能继承一个基类。

静态和常量

ActionScript 3支持常量和静态成员变量,以及静态方法。常量定义起来很方便,请看示例:

		public const MINIMUM_AGE:int = 0;
public const MAXIMUM_AGE:int = 2000;

常量可以是你期望的任何类型,但它们必须是在编译时定义。如果你愿意,你也可以把它们定义成受保护的或者是私有的作用域。

为了演示一下静态方法,我在Animal类中写了一个工厂方法:

		public static function buildAnimal( n:String ) : IAnimal {
return new Animal( n );
}

使用静态方法的另外一种方式是单例模式。下面就是一个针对Animal类的单例工厂类:

package com.jherrington.animals
{
public class AnimalFactory
{
private static var _factory:AnimalFactory = new AnimalFactory();
public static function get instance() : AnimalFactory {
return _factory;
}
public function build( n:String ) : Animal {
return new Animal( n );
}
}
}

我使用该单例工厂的instance成员变量来获得其对象,并调用它:

private var b:Animal = AnimalFactory.instance.build( "Russell" );

这句代码使用单例工厂对象创建了一个新的名叫Russell的animal对象。

继承

为了演示继承,我写了三个接口和类。第一个是之前的IAnimal接口,第二个是Animal类,第三个是名叫Dog的继承类,它覆写了一个方法。

接口IAnimal定义如下:

	public interface IAnimal
{
function get name() : String;
function set name( n:String ) : void;
function move( x:int, y:int ) : void;
}

我对它进行了简化,这样它只有一个name成员变量和一个move()方法。第一个实现这个接口的是Animal类:

public class Animal extends EventDispatcher implements IAnimal
{
private var myName:String = "";
public function get name() : String
{
return myName;
}
public function set name( n:String ) : void
{
myName = n;
dispatchEvent( new Event( Event.CHANGE ) );
}
public function Animal( _name:String )
{
name = _name;
}
public virtual  function move( x:int, y:int ) : void
{
}
}

然后,Dog类在Animal类的基础上构建起来,它具有自己的构造函数,并覆写了move()方法:

public class Dog extends Animal
{
public function Dog(_name:String)
{
super(_name);
}
public override function move( x:int, y:int ) : void
{
trace( 'Moving to '+x+', '+y );
}
}

这看起来非常像Java代码,所以你会感觉到用ActionScript来实现自己的面向对象设计会非常轻松。

操作符和条件语句

ActionScript中的操作符和你在Java中看到的完全一样。类似地,算术和布尔操作符也是一样的:

	var a:int = 5;
var b:int = 6;
var c:int = a * b;
c *= 10;
var d:Boolean = ( c > 10 );
var e:int = d ? 10 : 20;

这些实例演示了一些不同的操作符。在这些示例中,ActionScript和Java的唯一不同在于定义变量的语法不一样。

跟操作符一样,条件语句也是完全一样的,请看示例:

	if ( a > 10 ) {
trace( 'low' );
}
else if ( a > 20 ) {
trace( 'high' );
}
else {
threw new Exception( "Strange value" );
}

这里演示了条件语句的语法,以及如何抛出异常。异常处理和Java中的完全一样。你可以定义自己的异常类型,或者直接使用标准的Exception类。

下面是trycatchfinally语法的使用:

	try
{
location = new Point( -10, 10 );
}
catch( Exception e )
{
trace( e.toString() );
}
finally
{
location = null;
}

这段代码试图设置location,并在错误发生时跟踪错误信息。不管哪种情况,最终,location都会被设为null

迭代

ActionScript 3没有强类型的容器类,但数组和哈希表使用起来还是非常容易的。这里是一个使用for循环来迭代一个数组的例子:

	var values:Array = new [ 1, 2, 5 ];
for( var i:int = 0; i < values.length; i++ )
trace( values[i] );

但这并不是你在ActionScript中迭代数组应该使用的方式。最好的方式是使用for each语法,请看示例:

	var values:Array = new [ 1, 2, 5 ];
for each ( var i:int in values )
trace( i );

这段代码迭代访问数组中的每个元素,并把i的值设置为每个元素的值。

要创建一个哈希表,你可以使用ActionScript中基本的Object类型:

	var params:Object = { first:'Jack', last:'Herrington' };
for( var key:String in params )
trace( key+' = '+params[key] );

ActionScript起源于JavaScript意味着基础对象类型是基于插槽(slots-based)的容器,这样你可以轻而易举地把它作为哈希表来使用。

正则表达式

正则表达式是ActionScript中的基础语法。比如下面这段代码:

	if ( name.search( /jack/i ) )
{
trace('hello jack');
}

是对一个字符串的简单检查。

这段代码是使用正则表达式来执行分割操作:

	var values:String = "1,2,3";
for each( var val:String in values.split(/,/) ) {
trace( val );
}

你是否应该把正则表达式嵌在自己的核心代码里面,是值得商榷的。Java的架构师们显然认为这些表达式应该留在一个外部的库中。但我认为,它们非常有用,所以它们应该像在ActionScript中这样被集成。

E4X

XML应用得很广泛,以至于ActionScript直接把它构建在语言的语法里面以示支持。如果你是个XML爱好者,你会非常喜欢这个的。请看示例:

var myData:XML = <names>
<name>Jack</name>
<name>Oso</name>
<name>Sadie</name>
</names>;
for each ( var name:XML in myData..name ) {
trace( name.toString() );
}

这段代码定义了一个XML文档,然后对它进行搜索并打印出所有的标签

下面这段代码也是获取<name> 标签,但只获取那些type是dog的标签。

var myData:XML = <names>
<name type="person">Jack</name>
<name type="dog">Oso</name>
<name type="dog">Sadie</name>
</names>;
for each ( var name:XML in myData..name.(@type='dog') ) {
trace( name.toString() );
}

@语法有点类似于XPath和XSLT。它用来指定我们要查看的是属性而不是XML元素本身。

E4X是对这门语言的梦幻增强。它把XML解析从繁琐变成了轻松愉快的事情。Web services甚至也可以以E4X的格式返回以便于解析。

总结

Adobe对于ActionScript做了一些非凡的改进。它是一门比人们想象的成熟得多的语言。我认为你会最终发现Adobe所做的,就是吸取了Java的得失教训,并把它们合并进ActionScript 3语言的开发中。你会很乐意看到最后的结果。

获取更多的信息

  • 要获取更多关于ActionScript和Java语法的相似性,请阅读Yakov Fain的文章,"Comparing the syntax of Java 5 and ActionScript 3" (JDJ, 2006年11月12日)。
  • 可以从Open Source Flash下载到Java-to-ActionScript转换器,这样你就可以使用Java而不是ActionScript来创建Flash内容了。
  • 要寻找资源列表来帮助你在ActionScript、Flex、Java和JavaScript开发之间游走,请查看RIAdobe的比较列表。
  • Flex.org上有你想知道的关于Adobe Flex的一切。

[C#]BlogEngine.Net架构与源代码分析系列part6:开放API——MetaWeblo

mikel阅读(1131)

     一款优秀的 Blog系统少不了一些公开的API。BlogEngine.Net实现了标准的MetaWeblog API接口来允许用户通过客户端软件来发布自己的Blog,此外它还实现了将其它Blog系统中的文章(标准格式的BlogML或Rss)导入到 BlogEngine.Net中来的BlogImporter接口,在这篇文章里我将对这些开放部分进行详细的介绍,对于涉及到的一些相关知识点也给出链 接或做简单的描述。

MetaWeblog API使用标准的Http协议封装的XMLRPC实现(类似于WebService中的Soap协议)

1.首先让我们了解一下什么是MetaWeblog API

MetaWeblog API (MWA)是一套编程接口,允许外面的程序能取得和设置Blog文章的文本或属性。它是建立在XMLRPC接口之上,并且已经有了很多的实现。

2.MetaWeblog API有三个基本的函数规范:

metaWeblog.newPost (blogid, username, password, struct, publish) 返回一个字符串,可能是Blog的ID。
metaWeblog.editPost (postid, username, password, struct, publish) 返回一个Boolean值,代表是否修改成功。
metaWeblog.getPost (postid, username, password) 返回一个Struct。
其中blogid、username、password分别代表Blog的id(注释:如果你有两个Blog,blogid指定你需要编辑的blog)、用户名和密码。
由于篇幅有限,关于MetaWeblog API的更多信息请参考文末的链接部分。

3.BlogEngine.Net中的MetaWeblog API的实现分析

BlogEngine.Net的XMLRPC调用主要是由BlogEngine.Core.API.MetaWeblog命名空间下的几个类型来完成的。

首先客户端软件通过Http协议向MetaWeblogHandler提交了一个 标准的XML请求,MetaWeblogHandler是一个HttpHandler,之后MetaWeblogHandler执行 ProcessRequest来处理这个请求,最后将处理结果再封装为XML返回给客户端软件。

下面让我对这部分涉及到的几个类型做一个简单的介绍:
MetaWeblogHandler:不用说了,处理的主逻辑部分,ProcessRequest是处理的入口点,将一些具体处理的部分委托给一些私有成员,例如:

internal string NewPost(string blogID, string userName, string password, 

MWAPost sentPost, bool publish)

XMLRPCRequest:是对一个HttpRequest信息提取以后的封装,里面是一些解析XML提取信息的相关属性与方法,例如远程调用方法名,参数,文章信息等。
XMLRPCResponse:与XMLRPCRequest是对应的,它的Response方法会将执行结果生成XML并传递给HttpResponse之后返回给客户端。
还有一些类似于MWABlogInfo,例如MWAMediaObject,MWAPost等的结构是对于业务对象类型数据提取信息的封装,主要是为了交换信息而定义的。

从BlogEngine.Net的实现上看,它支持很多标准的协议,这些协议很多都是基于XML进行通信的,而BlogEngine.Net一般都是通过HttpHandler来处理这些标准的。

BlogEngine.Net的导入和导出Blog文章的实现分析

     BlogEngine.Net的导入支持Rss和BlogML两种格式,导出支持 BlogML格式(也具有Rss的请求链接,后续文章将讲解)。登录以后进入Settings我们发现在页面的最底部提供了导入和导出功能的两个按钮,查 看codebefore的代码我们看到这两个按钮的请求地址为:

<input type="button" value="<%=Resources.labels.import %>" 

onclick="location.href='http://dotnetblogengine.net/clickonce/blogimporter/

blog.importer.application?url=<%=Utils.AbsoluteWebRoot %>&username=<%=Page.User.Identity.Name %>'" />
<input type="button" value="<%=Resources.labels.export %>" 

onclick="location.href='blogml.axd'" />

原来在导入时会向 http://dotnetblogengine.net/clickonce/blogimporter /blog.importer.application发一个请求,后面跟上相关参数信息,这是一个使用clickonce部署的客户端应用程序,打开以 后可以看到:

使用它我们可以完成将其它Blog系统中的Blog文章导入到BlogEngine.Net中来,同样,这些文件的格式都是标准的XML文件。

     在BlogEngine.Net的Web站点中我们可以看到一个api的目录,那里面有一些Blog导入所使用的WebService接口,这个客户端导入工具同样也是调用站点中的这些接口函数来完成导入的,注意

http://dotnetblogengine.net/clickonce/blogimporter/blog.importer.application

后面的参数url就是告诉导入工具WebService的所在站点地址,username就是导入的目标用户名。这个导入的WebService应该属于BlogEngine.Net自己定义的接口,并不是标准的接口,但是可以被标准调用(Soap),例如我们也可以自己写一个导入的程序调用这个接口来完成导入功能。

     对于这个WebService的实现比较简单,我不想多说了,但是希望大家注意两点比较有特色的地方:
1.在部分接口中使用了SoapHeader来完成用户信息的验证。例如:

 

Code

2.对于一些被导入的Blog中的图片和文件等的存储和处理。例如下载文件和链接的加入:

Code

     此外在api中还有一个TagMiniView.aspx,它的作用我想就是允许在其它站点中引用本站点的云标签信息。对于Blog的导出功能通过请求的URL可以看出,又是通过HttpHandler来处理生成BlogML文档的。

总结

1.一个开放的系统需要支持很多标准,包括通信标准,标准文档等,BlogEngine.Net在这方面做得很到位。
2.对于使用clickonce部署的客户端来导入Blog文章感觉很值得借鉴。

参考文章

1.基于XML-RPC的BloggerAPI学习

2.metaWeblog API 学习笔记

     一边写文章,一边阅读源代码,有些东西让我恍然大悟.

     上一篇:BlogEngine.Net架构与源代码分析系列part5:对象搜索——IPublishable与Search

 

     返回到目录

版权声明
作者:Thriving.country
出处:http://thriving-country.cnblogs.com/
本文版权归作者和博客园共同所有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接.

[C#]项目管理实践【三】每日构建【Daily Build Using CruiseControl.

mikel阅读(778)

在上一篇项目管理实践教程二、源代码控制【Source Control Using VisualSVN Server and TortoiseSVN】中我们已经讲解了如何使用TortoiseSVN和VisualSVN Server来做简单的版本控制,这一篇我们将会讲解使用CruiseControl.NET和MSBuild来搭建每日构建系统。

在第一篇项目管理实践教程一、工欲善其事,必先利其器【Basic Tools】 中我们已经安装了CruiseControl.NET 1.4,因为我们还要用到MSBuild,所以如果你的系统没有安装Visual Studio,那么你需要首先安装Visual Studio 2005/2008,我们在这里使用的是Visual Studio 2008,准备好这些了吗?OK,我们正式开始今天的课程! 

首先,我们要配置CruiseControl.NET【下面简写为CCNET】,配置完成后,我们每次提交源代码到SVN服务器后,CCNET就可 以自动从SVN服务器上迁出源代码,并调用MSBuild自动进行编译。我们以昨天的教程中创建的StartKit项目为实例,先看看下面的配置文件:

CCNET配置文件代码

好了,我们已经对CCNET的配置文件有了大致的了解,接下来,你打开CCNET的安装路径,找到子目录server下的ccnet.config文件, 把上面的配置信息Copy到ccnet.config文件中,记得把配置文件中的一些路径修改为自己的实际路径啊,修改好后,保存。这时候,检查 Windows服务CruiseControl.NET Server是否启动,如果没有则启动它,启动该服务后,打开浏览在地址栏输入上面配置文件中的webUrl地址:http://202.196.96.55:8080/server/local/project/StartKit/ViewProjectReport.aspx 也可以直接输入http://202.196.96.55:8080/server/ ,这里是演示地址,要根据自己的实际情况修改为正确的地址,OK,看到类似下图的效果,好了,搞定!如果你遇到了什么麻烦,请在下面留言,我一定会及时回复!
点击StartKit,转入下图所示的页面:
OK,到这里,我们提交更新到SVN服务器后,CCNET就会根据我们配置自动编译项目,而且我们也可以通过Web Dashboard来查看具体的编译信息了,提示如果配置了邮件发送,那么我们还可以通过邮件收到详细的编译信息,怎么样?够方便吧!
其实,CCNET的功能是相当强大的,上面只是最常用的配置,其他还有很多非常好的功能。你想知道吗?那你可以在这里查看CCNET官方文档 ,实际上,你安装CCNET后,文档也已经安装到你的电脑了,在CCNET的安装目录下的webdashboard的子目录doc中就是。
好了,我们今天的教程就到这里,本来我应该把如何使用CruiseControl.NET Tray来监视每次更新后的编译状态,但是今天真的太晚了,明天还要做项目,所以我明天补上,请大家见谅!
补充部分:
下面我简单讲一下,如何使用CruiseControl.NET Tray【以下简称CCTray】来监视每次提交后的编译状态。
安装好打开CCTray后,运行CCTray程序,点击左上角的菜单File下的Settings…,如下图:

点击Settings…会弹出下面的窗体:

切换到Build Projects选项卡,如下图:

点击Add…按钮,添加我们的CCNET服务器,如下图:

输入我们的CCNET服务器后,CCNET服务器上的项目就会在右侧显示出来,如下图:

选中右侧的项目后,点击OK按钮,返回CCTray打开时的界面,我们的二个项目已经添加进来了,如下图:

我们在桌面的右下角的任务栏,可以看到如下图所示的图标:

绿色的那个标示就是CCTray的标示,绿色表示所有的项目都通过了编译,紫红色表示至少有一个项目没有通过编译,橘黄色表示有项目正在编译,橘红色表示有项目被强制编译,显示为灰色则说明和CCNET服务器失去了链接。
OK,大家可以使用CCTray实时监视提交更新后项目的编译状态了。
如果大家有什么问题,欢迎和我交流!

 


作者:ttzhangTechnology Life
出处:http://ttzhang.cnblogs.com/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

[C#] BlogEngine.Net架构与源代码分析系列part5:对象搜索——IPublisha

mikel阅读(831)

   从这篇文章开始我会引入一些领域上的知识或者给出一些参考文章来供大家阅 读,我们对这些领域上的知识没有必要深挖,只要大概了解一下就可以了,这对于我们分析BlogEngine.Net会有很大的帮助。在这篇文章中我主要向 大家介绍一下BlogEngine.Net的搜索部分的实现以及支持的相应标准等。

先用自己的话解释一下什么是开放搜索

     开放搜索应该也是一种标准了,现在它越来越流行起来,记得我第一次见到这个东东是在codeproject中。大家一定注意到了IE7右上角的那个小工具栏了,

我们可以在那里选择一个搜索提供程序例 如:Google,当我们输入信息回车以后就会发现页面跳转到了Google的结果页面。那么这些提供程序信息是怎么获得的呢?IE又是怎么知道要去 Google的页面呢?原来是通过一个标准的XML文件,这个文件的具体格式可以参照一下codeproject的开放搜索文件http://www.codeproject.com/info/OpenSearch.xml,当把

<link rel="search" type="application/opensearchdescription+xml" title="CodeProject" href="http://www.codeproject.com/info/OpenSearch.xml"></link>

这种代码加入到一个Html的Head 中,在浏览器打开这个Html文当时就会发觉里面的开放搜索文件,然后在那个小工具栏中增加一个提供程序,当我们在工具栏中输入信息并回车以后就可以直接 跳转到相应的结果页面,可以看出这个XML文件充当着一种中间桥梁的作用。这种功能不仅需要浏览器的支持,同时也需要网站本身提供相应的支持,这就是开放 搜索。这个和IE7中的Rss源发现机制很类似。

那么BlogEngine.Net中对于开放搜索支持是如何实现的呢

     进入BlogEngine.Net的首页查看生成的Html源代码我们会看到

<link type="application/opensearchdescription+xml" rel="search" title="Name of the blog" href="http://hostname/opensearch.axd" />,

这里请求了 opensearch.axd,查看Web.config我们注意到opensearch.axd交给了 BlogEngine.Core.Web.HttpHandlers.OpenSearchHandler处理,这个XML是通过一个 HttpHandler来生成的。在Html源代码的Head中我们会发现很多*.axd的引用,实际上它们都是使用自定义的HttpHandler来处 理的,对于这些HttpHandler我会在后续有关文章中进行说明。从OpenSearchHandler生成的XML我们可以看出,执行搜索的Url 为(类似Google的处理方式)http://hostname/search.aspx?q={searchTerms},searchTerms就 是输入的部分,具体的搜索由页面search.aspx调用核心层内部逻辑来完成。

内部逻辑的主要部分IPublishable与Search的实现分析

     BlogEngine.Net 的搜索目标不是像Google那种页面抓取,也不是像我们一般的企业应用直接查询数据库数据,而是一种内存中对象的搜索。当对象完成填充以后数据就驻留在 内存中,所以BlogEngine.Net的搜索目标应该是对象实例。主要通过IPublishable与Search两个类完成。首先 IPublishable定义了可被搜索对象的类的共有特征,以便统一搜索模型,凡是实现了这个接口的类的对象都有机会被搜索到,例如 Comment,Page,Post。

Code

void OnServing(ServingEventArgs eventArgs);这个东西现在我也不是很确定是干嘛的?还没有看到使用到的相关代码,我猜好像可以做一些统计什么的,包括阅读量等,欢迎大家一起讨论。

     Search类的实现代码看起来很繁杂,但是脉络是很清晰的。它只有两个方法对外公开,分别为:

Code

其中Hits是直接根据内容进行查找,ApmlMatches是为支持Apml而提供的(下文做了进一步的解释)。在Search类的静态构造函数 中我们可以看到Search类监听了很多实现IPublishable接口的BusinessBase的事件以便来重新构造目录,这个目录也是在内存中已 经存在的,每当有新的文章等可以被搜索到的对象(实现IPublishable接口)被保存时,它就会被重新建立,以Post为例:

Code

这里又体现了BusinessBase提供的静态事件确实很灵活(请参照我的第二篇文章)。Hits方法的的主逻辑是执行BuildResultSet来构造结果集,BuildResultSet使用正则表达式来进行匹配,并按照一定的匹配度算法进行了结果排序:

Code

ApmlMatches的原理也差不多,这里就不再重复了。

     在Search类中,请大家在阅读源代码时注意一下对于一些字符的过滤处理是如何实现的,还有Search中对外公布的几个事件。 Entry和Result是为查询时数据的交换而定义的,而Result还实现了IComparable<Result>来对结果排序使用。 Search这部分代码给我的感觉就是结构很清晰,但是处理的逻辑很复杂。

那么客户端如何使用Search中的方法?

     对于Search方法的调用,一般有两种方式,一种形式如:

List<IPublishable> list = Search.Hits(term, includeComments);
term就是输入的关键词,includeComments为是否包含评论,list是已排序的搜索结果。另一种形式是:

list = Search.ApmlMatches(docs[key], 30);

这是根据一个Uri上的一个Apml文件来对查找结果进行输出,Apml也是一种标准文件,用来定义互联网上一些关键词的活跃程度(自己的话),在 BlogEngine.Net中实际上也支持这种Apml,同样也是通过HttpHandler实现的。对于客户端的使用大家可以具体参照一下Web项目 中Search.aspx文件中的CodeBehind,此外BlogEngine.Net在Web项目中的Wdiget中也有一个客户端查询,这个以后 会在讲解Wdiget时做更多的说明。

总结

1.BlogEngine.Net的搜索还是很经典的,支持开放搜索和Apml等标准。
2.IPublishable的定义很有必要而且处理得很巧妙,解决了搜索对象的统一性问题,以便统一处理。
3.Search类的内核实现比较好,结构很清晰。

领域知识文章参考

1.使用 RSS 和 Atom 实现新闻联合

2.浅述RDF,畅想一下FOAF应用

3.OPML 详解

     三人行,必有我师!

     上一篇:BlogEngine.Net架构与源代码分析系列part4:Blog全局设置——BlogSettings

版权声明
作者:Thriving.country
出处:http://thriving-country.cnblogs.com/
本文版权归作者和博客园共同所有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接.

[C#]揭示常见的重构误区

mikel阅读(795)

揭示常见的重构误区

作者 Danijel Arsenovski译者 张逸 发布于 2008年11月3日 下午10时49分

社区
.NET,
Agile
主题
编程,
工件和工具,
敏捷技术
标签
重构

公正地说,.NET社区对于重构技术的研究起步太晚。直到今天,.Net开发的旗舰产品Visual Studio仍然无法在C#中突破重构的界限(http://www.martinfowler.com /articles/refactoringRubicon.html)。Visual Basic以及最新的C++情况略好,但却需要你下载和安装一个免费的重构插件Refactor!,它是Developer Express为VB或C++开发的。

之后的所有替代品都不再是免费的晚餐。虽然这些产品完全配得上你的投入,然而当我们开始关注那些诸如“代码质量”等虽非必要却极为深奥的要素,并达 成一致意见时,这些产品却难以成为开发者的主流工具。即使不使用工具,你仍然可以进行重构,但手工方式会由于太过复杂而会将开发者拒之门外。无怪 乎.Net社区对重构的引入会大大地滞后,因为我们对于重构的所有问题及其作用,依旧混乱不堪。

本文试图列出一些我经常遇到的使用重构的误区。这些误区与某些传统的对编程的偏执一样,总是会成为吸取技术精华的壁垒。紧接着,我还会列举某些先入 为主的误解,试图阐释其起源,并给出有力的证据驳斥这些论点。我希望本文能为每个人澄清对重构本质的怀疑,让他们学会成为一个重构者,或者在他的团队中建 立并推广这种实践。

“如果没有坏,就不要修复它”

这句话老少相传,可谓工程智慧的真实写照,然而,“如果没有坏,就不要修复它”只会滋生得过且过的情绪。重构有足够充分的理由来摒弃这种思想。

在你的编程生涯的早期,一定明白“千里之堤,溃于蚁穴”的道理,并为之而付出过深深地代价。即使一个细微的变化,都会导致软件在最糟糕的时刻以令人 莫名惊 诧的方式而停止运转。一朝被蛇咬,十年怕井绳,故而你害怕任何变化的发生,只要变化不是必须的。然而这只能够苟延残喘维持一时。一旦形势急转直下,出现的 错误不得不解决,新的功能需求也不能一拖再拖了。此时你面对的代码即使是相同的,但实际却已养成了大患。

那些接受了“如果没有坏,就不要修复它”信条的开发人员认定重构是可有可无的,甚至会干扰既定的开发目标。实际上,这种试图维持“现状”的顺从姿态,来源于某种心理,他们认为对代码的恐惧,以及无法掌控的事实都是合理的。

许多经验丰富的程序员之所以认可这种观点,是因为对一些不必要工作“偷工减料”,乃人之常情,是合情合理的。譬如说,如果应用程序已经具备优越的性 能,就无需为了性能对处理器周期耗费心机。类似的投机性设计,通常会用来搪塞那些具有前瞻性的编程观点,诸如“我们可能会在将来的某一天需要这一特性”。

从这个意义上讲,重构需要随时进行。在重构时,你需要消除冗余代码,避免投机性设计或预先优化。

而对于重构的老手来讲,这样的软件完全是“金玉其外,败絮其中”。如果设计有瑕疵,例如拙劣的代码和糟糕的结构,那么在软件外部是看不到这些问题 的。然而 即使应用程序在某个时候能够正常运行,我们仍然需要对此进行重构,对设计进行优化。从这个意义上讲,重构坚持在一些不太明显,但却具有决定性作用的特征上 作文章,例如设计、简单性,以及改善源代码的可读性,便于理解。

重构可以帮助你赢回对代码的支配权。这对于那些业已脱离控制的代码库而言并非易事,如果不诉诸于重构,那么唯一的解决之道就是彻底地重写。

重构不是新生事物

换而言之,这种误解可以这样说:“重构无非就是一种新瓶装旧酒的说辞罢了。”这意味着你对于如下种种早已熟谙于心:编写好的代码,面向对象设计,编码风格,最佳实践,如此等等,不一而足。重构无非是某些人的故作玄虚之语,编造出来用以兜售自己的新书,如此而已。

对,重构从一诞生之初,就从未放言像面向对象或面向方面编程那般会成为一种划时代的全新模式。它仅仅是从根本上改变你编码的方式:它定义了一些规 则,使得利用工具(例如点击按钮)完成代码的复杂转换成为可能。你不应将代码看作是不易修改的僵化的结构。相反,你应该看到自己有能力维持代码总是恰当好 处,有效地应对新的挑战,而无需害怕修改代码。

重构是高科技

编程并不易为。这是一项复杂活动,需要大量的知识积累。某些知识可能很难掌握。VB程序员如果要掌握Visual Basic .NET,必须具备熟练运用面向对象语言的能力。对于多数人而言,这是一大困惑。而其好处则在于学习一门新的技能绝对是物有所值的。

重构的伟大之处就在于它的简单。只需了解很小的一套简单规则,你就可以“盛装出发”了。再加上一个好的工具,迈开重构的第一步简直就是轻而易举。与 当前一 个高级程序员应该了解的其他技术相比,例如UML或设计模式,我得说重构有着最简单的学习曲线,就像VB和其他编程语言相比一样。

学习重构很快就会迎来你的收获季节。当然,与世间万事万物相同,知识的学习总是“一份耕耘,一份收获”的。

重构会导致性能低下

复杂点儿的说法是“因为重构通常会引入大量的细粒度元素(如方法和类),这种间接的设计会导致性能的损失。”

让我们把时钟往回调一小段,你会发现这样的观点似曾相识,当初在质疑面向对象编程时,就发出过类似奇怪的声音。

事实的真相是代码结构重构与否,在性能上的区别微乎其微,所以常常可以忽略不计,除非是某些特别的系统。

经验证明,性能总是受制于某一段确定的代码。在优化阶段修复它们,可以获得你需要的性能等级。能够轻易地识别关键代码是重中之重。减少代码的重复与数量,从而使得代码易于理解,一旦发生变化,也只会影响到单独的模块,这样的重构极大地改善了优化的过程。

当我们发现在一段日子里,CPU好似上足了马力一般,使用率不停上升,而代码的其他特性,例如可维护性、质量、可伸缩性以及可靠性又使得我们不得不将性能置诸脑后。而现在,我们再也不能以这样的理由为编写出性能糟糕的代码寻求托辞了,当然,我们也不能矫枉过正。

面临这样的境况,你可以看看本文提供的一些数据,也算是我的一点小小经验。我会使用两个代码示例。第一个例子代码结构简陋,且只有一个单独的 Main方 法。第二个例子的Main方法则被放到一个模块中,其中定义的一个类Circle包含了几个细粒度的方法。最初,我使用这些示例仅仅是为了演示非结构化代 码与结构化代码的编码风格,因而缺乏一些用于量化的代码。

我计算了执行一个简单几何公式(求圆周长)的时间,为了避免编写一些涉及计算敏感应用的代码,我增加了一些数据库查询代码。为了量化值的准确性,我 将执行放入到一个循环中,重复执行10000次。由于我并没有打算获得极端精确的值,因此我使用了 System.Diagnostics.Stopwatch类用以捕捉耗时值,毕竟在这个案例中,Stopwatch的值已经足够精确了。

代码示例1:非结构化代码

Option Explicit On
Option Strict On
Imports System.Diagnostics
Imports System.Data.SqlClient
Namespace RefactoringInVb.Chapter9
Structure Point
Public X As Double
Public Y As Double
End Structure
Module CircleCircumferenceLength
Sub Main()
Dim center As Point
Dim pointOnCircumference As Point
'read center coordinates
Console.WriteLine("Enter X coordinate" + _
"of circle center")
center.X = CDbl(Console.In.ReadLine())
Console.WriteLine("Enter X coordinate" + _
"of circle center")
center.Y = CDbl(Console.In.ReadLine())
'read some point on circumference coordinates
Console.WriteLine("Enter X coordinate" + _
"of some point on circumference")
pointOnCircumference.X = CDbl(Console.In.ReadLine())
Console.WriteLine("Enter X coordinate" + _
"of some point on circumference")
pointOnCircumference.Y = CDbl(Console.In.ReadLine())
'calculate and display the length of circumference
Console.WriteLine("The lenght of circle" + _
"circumference is:")
'calculate the length of circumference
Dim radius As Double
Dim lengthOfCircumference As Double
Dim i As Integer
'use stopWatch to measure transcurred time
Dim stopWatch As New Stopwatch()
stopWatch.Start()
'repeat calculation for more precise measurement
For i = 1 To 10000
'add some IO
Dim connection As IDbConnection = New SqlConnection( _
"Data Source=TESLATEAM;" + _
"Initial Catalog=RENTAWHEELS;" + _
"User ID=RENTAWHEELS_LOGIN;" + _
"Password=RENTAWHEELS_PASSWORD_123")
connection.Open()
Dim command As IDbCommand = New SqlCommand( _
"Select GETDATE()")
command.Connection = connection
Dim reader As IDataReader = command.ExecuteReader()
reader.Read()
reader.Close()
connection.Close()
radius = ((pointOnCircumference.X - center.X) ^ 2 + _
(pointOnCircumference.Y - center.Y) ^ 2) ^ (1 / 2)
lengthOfCircumference = 2 * 3.1415 * radius
Next
stopWatch.Stop()
Console.WriteLine(stopWatch.Elapsed)
Console.WriteLine(lengthOfCircumference)
Console.Read()
End Sub
End Module
End Namespace

代码示例2:结构化代码

Option Explicit On
Option Strict On
Imports System.Data.SqlClient
Namespace RefactoringInVb.Chapter11
Public Structure Point
Public X As Double
Public Y As Double
End Structure
Module CircleCircumferenceLength
Sub Main()
Dim circle As Circle = New Circle
circle.Center = InputPoint("circle center")
circle.PointOnCircumference = InputPoint( _
"point on circumference")
Console.WriteLine("The length of circle " + _
"circumference is:")
Dim circumference As Double
Dim i As Integer
'use stopWatch to measure transcurred time
Dim stopWatch As New Stopwatch()
stopWatch.Start()
'repeat calculation for more precise measurement
For i = 1 To 10000
circumference = circle.CalculateCircumferenceLength()
Next
stopWatch.Stop()
Console.WriteLine(stopWatch.Elapsed)
Console.WriteLine(circumference)
WaitForUserToClose()
End Sub
Public Function InputPoint(ByVal pointName As String) As Point
Dim point As Point
Console.WriteLine("Enter X coordinate " + _
"of " + pointName)
point.X = CDbl(Console.In.ReadLine())
Console.WriteLine("Enter Y coordinate " + _
"of " + pointName)
point.Y = CDbl(Console.In.ReadLine())
Return point
End Function
Private Sub WaitForUserToClose()
Console.Read()
End Sub
End Module
Public Class Circle
Private centerValue As Point
Private pointOnCircumferenceValue As Point
Public Property Center() As Point
Get
Return centerValue
End Get
Set(ByVal value As Point)
centerValue = value
End Set
End Property
Public Property PointOnCircumference() As Point
Get
Return pointOnCircumferenceValue
End Get
Set(ByVal value As Point)
pointOnCircumferenceValue = value
End Set
End Property
Public Function CalculateCircumferenceLength() As Double
QueryDatabase()
Return 2 * 3.1415 * CalculateRadius()
End Function
Private Function CalculateRadius() As Double
Return ((Me.PointOnCircumference.X - Me.Center.X) ^ 2 + _
(Me.PointOnCircumference.Y - Me.Center.Y) ^ 2) ^ (1 / 2)
End Function
Private Sub QueryDatabase()
Dim connection As IDbConnection = New SqlConnection( _
"Data Source=TESLATEAM;" + _
"Initial Catalog=RENTAWHEELS;" + _
"User ID=RENTAWHEELS_LOGIN;" + _
"Password=RENTAWHEELS_PASSWORD_123")
connection.Open()
Dim command As IDbCommand = New SqlCommand( _
"Select GETDATE()")
command.Connection = connection
Dim reader As IDataReader = command.ExecuteReader()
reader.Read()
reader.Close()
connection.Close()
End Sub
End Class
End Namespace

经过几次执行,可以看到两个例子的耗时相当接近。在我的机器上,它们的值在2.2到2.4秒之间。值得称许的一点细微差别是:非结构化示例的最小耗时为1.9114800,而结构化示例的则为2.0398497。在我看来,二者并无太大差异。

重构破坏好的面向对象设计

具有优美结构以及经过重构的代码,在菜鸟的眼里,是笨拙而粗陋的。方法是如此的短小,在他们看来简直言之无物。类显得不够重量级,仅仅包含屈指可数的几个成员。这样的代码看起来简直就等于没有嘛。

像类和方法那样,若要管理大量的元素,则意味着需要处理的复杂度会加大。

这样的观点常会引人误解。事实上,复杂度总是相同的。但重构后的代码会显得条理更清晰,结构更合理。

重构无法提供短期利益

一个占据主流的论调是重构可以使得你的程序更快。迄今为止,就我所知,并没有相关的研究(这是我强烈呼吁的)可以证明我的看法,但我的经验告诉我存 在这样的情形。这是唯一合乎逻辑的。由于你在整体上具有少量的代码,极少的重复以及清晰的意图,因此重构带来的益处很快就能彰显无遗,除非你处理的是一些 可有可无,也无任何实际意义的小规模代码。

重构只适宜敏捷团队

在敏捷方法学中,重构是被频繁提及的其中一项关键技术,因而通常的解释是,重构只有在遵循敏捷原则的团队中才能如鱼得水。

重构是敏捷团队不可或缺的

即使你的团队采取的方法别出蹊径,但在大多数时候,总是由你来负责管理编码方式,这时就是运用重构的时机。 其他的团队成员或管理方式可能会忽略这样一个事实,就是你需要不停地在IDE中使用“Refactor”选项。不管你的团队采取何种方法,都没有任何东西 可以阻止你对代码进行重构。如果你遵循小步重构,并在编码过程中定期执行,就能达到最佳的重构效果。某些实践例如严格的代码所有权或瀑布过程,可能会与重 构背道而驰。如果你能够证明重构从编程的视角来看是有意义的,你就可以开始构建你的支撑库,首先从你的伙伴开始,然后推广到整个团队。

重构可以在开发过程中作为独立的阶段实施,并由独立的团队执行

经理们通常对此深以为然。将重构视为一个独立的阶段,然后将其放在诸如实现阶段和测试阶段期间,从管理学的角度来看,容易给人一种错觉,认为重构就是甘特图的一根线条,可以轻易地将其压缩时间甚至移走。

事实上,为了成功地执行重构,你需要完整地理解整个问题域,了解需求、设计甚至实现阶段的细节。倘若你从一开始就没有将实施重构的人看作团队的一份子,也没有花时间与客户交流,分析需求和思考设计,你将很难改善最初的团队构建的内容。

遵循某种模型,则代码可以通过它在编码之后得到精化,就像工业生产过程中提炼出的某种物质那样,总会带来一些好处。若是不能切实地理解代码的意义, 则你真正能够对重构保有信心的只能是一些细微的改进。若在此种情形下,妄图通过重构使代码焕然一新,结果很有可能是南辕北辙,适得其反。代码若与问题域紧 密相关,就有可能使得事情变得更加糟糕,最终还会为你的应用程序引入bugs。

没有单元测试重构照样能够工作

我想,一些简单的重构可以在没有单元测试的情形下进行。重构工具与编译器自身可以提供一定的安全保障,不至于引入一些简单的人为错误。你也可以采用 传统方 式对代码进行测试,例如使用调试器或者执行功能测试。但这些手动的测试方法却是乏味而不值得信赖的。重构时,代码比以前对修改更为敏感与脆弱。若要避免不 必要的问题,则应添加NUnit单元测试放到项目中。在你执行每一小步重构时,就能够及时发现错误。

不要依赖于注释的观点未必正确

我敢担保这会导致某些看起来有趣的混乱与疑惑。毫无疑问,你已经千百次地被告知,在编写代码时一定要添加注释。作为一种好的编程实践,这种思想会帮 助别人理解你的代码。这通常意味着编码的方式是优秀的、有序的、专业的。因此,如果现在有人居然胆敢告诉你注释未必是一桩好事儿,你一定会大吃一惊。

添加注释的动机通常与代码重构一致。你应该竭力提高代码的可读性。在早期,编程工具受制于标识符的长度,故而注释成为了传达编程涵义的唯一选择。立 足于重 构,则要求代码是自解释性的,即选择正确的方法、类、变量以及其他标识符。同时,你应该避免为了相同的目的使用注释,因为注释不会被执行,且很容易作废。 在每日的编程成为急就章时,总是会忘记更新注释、文档、图示或其他次等级的工件。

匈牙利命名法怎么了?

并不只是Charles Simonyi的母语触发了创造匈牙利命名法的灵感。那些看起来像是匈牙利语的命名,例如a_crszkvc30LastNameCol和 lpszFile,并不会让我感到惊讶。如今,编译器会检查类型安全性,跟踪类型信息。在现代的IDE中,所有必要的类型信息都能被找到,并作为鼠标指针 的提示出现。在诸如C#和VB.Net的语言中,匈牙利命名法已经过时了。

然而,旧有的习惯总是很难改变。匈牙利命名法通常被看作是一种有效的然而却难以掌握的编程实践。难怪并非所有人都乐于看见自己掌握的传统命名规则原来已经过时。

使用那些类似人类的自然语言为变量命名,可以使得代码简明易懂。放弃那些有趣的前缀,改而使用别人能够轻松理解的词语吧。

结论

可以毫不夸张地说,重构是编程的一次变革,它从根本上改变了某些旧有的习惯。它必然会面对许多阻力,让不知所以者感到无比的困惑。

我希望本文能够为你打开重构之门。在你第一次展卷阅读时,不要惊讶于那些困扰你的问题。如果你正在倡导重构,或者试图向别人讲解重构,那么你应该时刻准备提出质疑。面对这种情形,我希望我已经提供了足够的论据,以证明采纳重构的原因与必要性是行得通的。

本文源于《Professional Refactoring in Visual Basic》一书,该书作者为Danijel Arsenovski,已由Wrox出版。

关于作者

Danijel Arsenovski是Wrox出版的《Professional Refactoring in Visual Basic》一书的作者。目前,他就职于Excelsys S.A(该公司为地区内的大量客户设计网上银行解决方案),担任产品和解决方案架构师。他对于重构的最初体验来自于对大型银行系统的整改, 从此,他就迷上了重构。他首创了利用重构完成代码从VB 6到VB.NET的升级。Arsenovski还是多个主要出版商的丛书作者,拥有微软认证解决方案开发专家(MCSD,Microsoft Certified Solution Developer)证书,并在2005年被提名为Visual Basic MVP。你可以通过电子邮件danijel.arsenovski@empoweragile.com与他取得联系,他的博客是 http://blog.vbrefactoring.com

查看英文原文:Debunking Common Refactoring Misconceptions

[Flash]Flash与3D编程探秘(六)- 全方位旋转摄像机

mikel阅读(755)

作者:Yang Zhou
感谢:Yunqing
日期:2008年11月

点此下载程序源文件

 

前面我们讨论过了如何横向旋转和移动摄像机,希望你已经完全理解,因为这本文中的内容是紧接着上一篇来的。回想一下你也许会意识到,在前面我们制做 的动画中,摄像机的旋转一直是围绕着y轴(竖直向上的轴)旋转,然而现实中我们可以上下旋转摄像机,甚至可以把摄像机倾斜一定角度,这就提醒了我们还需要 更深入的研究旋转这个课题。下面几个动画演示了我们摄像机(简单的摄像机轮廓)3种旋转模式,从左到右分别是横向旋转,纵向旋转,倾斜。从我们3D空间角 度来说,分别是沿y,x和z轴旋转。

      

横向和纵向旋转摄像机

倾斜摄像机

 

再介绍一些三角函数方程

事情变得复杂起来了!不过请你还是保持头脑清醒,这一篇文章你的任务就是学会纵向旋转和倾斜你的摄像机。你也许会想,我已经会了横向旋转摄像机,算法中我 们使用了一个panning的变量代表旋转角度,那么我再加两个变量,然后按顺序先沿x旋转,然后再沿y,最后z旋转不就好了。Well,这种想法很接近 但是是错的(不要把3D数学想的那么简单)。来看一下下面的两个图,我们还是以3D空间在2D平面上的投影举例,我们把线段OB沿着z轴(也就是2D平面 上 的原点)旋转大约78度好了,可以看到OB的长度就是我们的旋转半径。那么接下来,我们把OB沿y轴旋转,看一下图中的旋转半径是多少?不难看出,我们的 旋转半径明显的变小了。继续,如果你沿y旋转后,再打算沿x轴旋转,你还是会有同样“半径变化”的问题。

先沿z轴旋转再试图沿y旋转

 

在上面的例子中,如果我们想要保持旋转半径不变,那么一开始我们就不要沿z旋转。不过,我们拿着摄像机左转右转,怎可能保持角度不变!?因此你要在每一次摄像机旋转后,计算新的旋转半径。可是如果每一次我们都把旋转半径计算出来,那一定是很头疼!
于是我们聪明的想到了省力的方法。首先,让我们再看一下2D的三角函数(又是三角函数),我们根据旋转半径和旋转角度可以得到x和y:

= Math.cos(angle)*radius;
= Math.sin(angle)*radius;

需要注意一点,上面的方程式旋转点为原点,并且之前旋转角度为0。如果我们之前就有旋转角度a,再旋转b,那么我们的方程式就成了:

= Math.cos(a+b)*radius;
= Math.sin(a+b)*radius;

看起来眼熟,但是不知道是什么了?别担心:

cos(a+b) = cos(a)*cos(b)  sin(a)*sin(b);
sin(a
+b) = sin(a)*cos(b) + sin(b)*cos(a);

把cos(a+b)和sin(a+b)带入上面的x和y求值方程我们就有:

= radius*cos(a)*cos(b)  radius*sin(a)*sin(b);
= radius*sin(a)*cos(b) + radius*sin(b)*cos(a);

化简一下得到:

= x_before*cos(b)  y_before*sin(b);
= x_before*sin(b) + y_before*cos(b);

这样,我们使用上面两个方程,不用担心你在其他平面(xz或者yz平面)的旋转角度了,也不用每一次旋转后再去计算物体新的旋转半径了,我们只要关心旋转 后的x和y,并且把它们作为下一次旋转的x_before和y_before,问题就解决了。下面我把相应的方程式写上:
围绕y轴旋转pan角度:

= Math.cos(pan)*x_before  Math.sin(pan)*z_before;
= Math.sin(pan)*x_before + Math.cos(pan)*z_before;

围绕x轴旋转pitch角度:

= Math.cos(pitch)*y_before  Math.sin(pitch)*z_before;
= Math.sin(pitch)*y_before + Math.cos(pitch)*z_before;

围绕z轴旋转tilt角度:

= Math.cos(tilt)*x_before  Math.sin(tilt)*y_before;
= Math.sin(tilt)*x_before + Math.cos(tilt)*y_before;

那么基本的知识已经说完了。总结一下是,当你在使用这些方程式操作摄像机全方位旋转的时候,只要取得相应的变量,然后替换在方程里就可以了。是不是看起来 有点 难理解?不要担心,适应这些东西是需要花一点时间(特别是这些对你来说还是新课题的话),不过适应以后你应该就觉得很简单了。坦白的说,其实你并不需要知 道到底这些是怎样得来的,只要你知道如何使用它们得到你想要得结果就可以了(当然完全理解会对你以后的学习有一些帮助)。这些方程你可以写成一个函数,然 后命名它为“给我旋转”方程,当你需要摄像机旋转的时候,只要呼唤“给我旋转”就好了,至于“给我旋转”怎么做的工作,你就不需要担心了。

全方位旋转摄像机 

只说这些理论的东西,你肯定会觉得乏味,那么我举个例子来说明。下面这个程序演示了摄像机的全方位旋转,运行程序你会看到你置身在一个巨大的正方体 中,这个正方体是由很多我们的朋友小P组成的,不过这回我们用不同颜色的小P来代表不同的边。使用WS键控制纵向旋转,AD键控制横向旋转,QE控制倾斜 角度,鼠标点击屏幕禁止或者允许鼠标移动控制纵向和横向旋转。

全方位旋转摄像机,使用WS纵向旋转,AD横向旋转,QE倾斜角度,鼠标点击禁止或者允许鼠标控制

制作步骤:

1. 首先定义几个常量,MAX_OBJ是我们每条边上的物体数量,CUBE_WIDTH是我们正方体的边长。

// constants
var MAX_OBJ = 8;
var PI 
= 3.1415926535897932384626433832795;
var CUBE_WIDTH 
= 300;

 

2. 下面还是设置原点,场景,焦距等。

// same as usual
var origin = new Object();
origin.x 
= stage.stageWidth/2;
origin.y 
= stage.stageHeight/2;
origin.z 
= 0;
var scene 
= new Sprite();
scene.x 
= origin.x;
scene.y 
= origin.y;
this.addChild(scene);
var focal_length 
= 1400;

 

3. 设置摄像机,这回我们的摄像机多了几个新的属性,pitching是纵向旋转角度,tilt是倾斜的角度。

var camera = new Object();
camera.x 
= 0;
camera.y 
= 0;
camera.z 
= 0;
camera.panning 
= PI/8;                // init pan angle of our camera, pan left
camera.pitching = PI/8;               // pitch up
camera.tilt = 0;                           // and no tilt

 

4. 设置一些全局变量,在我们处理键盘和鼠标事件时会用到。

// global booleans for our keyboard control
var pan_left;
var pan_right;
var pitch_up;
var pitch_down;
var mouse_ctl 
= true;

5. 下面我们步置一个正方体,你完全不必要明白我是怎么布置场景的,因为布置场景的方式并不唯一,所以如果你愿意的话,你可以自己动手布置3D场景,当然我也 不介意直接拷贝我的设置场景的代码去用。总之,这些代码就是初始化一些小P,然后把它们摆放在合适位置(围绕着我们的摄像机)。

// ok, here you don't have to know how i acutally setup the cube
// cause every body has a different way of doing that, if you really
// interested in how i did it, then you may have a look
// you can just copy my code and it will set it up for you
var len = CUBE_WIDTH/2;
for (var seg = 0; seg < 3; seg++)
{
    var line_h;
    var line_v;    
    var line_z;
    
    
switch (seg)
    {
        
case 0:
            line_h 
= true;
            line_v 
= false;
            line_z 
= false;
            
break;
        
case 1:
            line_h 
= false;
            line_v 
= true;
            line_z 
= false;
            
break;
        
case 2:
            line_h 
= false;
            line_v 
= false;
            line_z 
= true;
            
break;
    }
    
    
for (var i = 0; i < MAX_OBJ; i++)
    {
        
if (line_h || (i == 0 || i == MAX_OBJ1))
        {
            
for (var j = 0; j < MAX_OBJ; j++)
            {
                
if (line_v || (j == 0 || j == MAX_OBJ1))
                {
                    
for (var k = 0; k < MAX_OBJ; k++)
                    {
                        
if (line_z || (k == 0 || k == MAX_OBJ1))
                        {
                            var ball;
                            
if (line_h)
                            {
                                ball 
= new SphereHorizontal();
                                
if ((i == 0 || i == MAX_OBJ1))
                                {
                                    ball 
= new SphereVertex();
                                }
                                
else
                                {
                                    ball 
= new SphereHorizontal();
                                }
                            }
                            
if (line_v)
                            {
                                ball 
= new SphereVertical();
                                
if ((j == 0 || j == MAX_OBJ1))
                                {
                                    
continue;
                                }
                            }
                            
if (line_z)
                            {
                                ball 
= new SphereStraight();
                                
if ((k == 0 || k == MAX_OBJ1))
                                {
                                    
continue;
                                }
                            }
                            ball.x_3d 
= len + (i)*(CUBE_WIDTH/MAX_OBJ);
                            ball.y_3d 
= len + (j)*(CUBE_WIDTH/MAX_OBJ);
                            ball.z_3d 
= len + (k)*(CUBE_WIDTH/MAX_OBJ);
                            scene.addChild(ball);
                        }
                    }
                }
            }
        }
    }
}

6. 下面的函数就是刷新小P位置和大小的函数,这也是这篇文章主要讲述的内容,所以,请集中。OK,首先我们要得出小P(其中一个小P)到摄像机的x,y和z 距离。对于横向旋转角度panning,我们使用本文前面讲述的方程,把相应的x距离和z距离带入,然后我们得出新的x距离和z距离。使用相同的方法得出 纵向旋转角度pitching后的y和z距离,对摄像机倾斜角度tilt我们在次使用上述方程。这样我们就得到围绕三个轴旋转后新的x,y和z距离,继而 我们便可以使用老办法算出物体的缩放和移动。最后别忘记加一个z_near变量,存储小P到摄像机的距离,以便于我们对所有小P进行z排序。

// update ball size and position
// here is what we really care about, so concentrate
function display(obj)
{    
    var x_distance 
= obj.x_3d  camera.x;         // first we determine x distance ball to camera
    var y_distance = obj.y_3d  camera.y;        // y
    var z_distance = obj.z_3d  camera.z;        // z distance
    
    var tempx, tempy, tempz;                       
// some temporary variables
    
    
// two more trig you need to know about, suppose a is the previous angle
    
// cos(a+b) = cos(a)*cos(b) – sin(a)*sin(b)
    
// sin(a+b) = sin(a)*cos(b) + cos(a)*sin(b)
    
// thus we have the following
    var angle = camera.panning;
    tempx 
= Math.cos(angle)*x_distance  Math.sin(angle)*z_distance;
    tempz 
= Math.sin(angle)*x_distance + Math.cos(angle)*z_distance;
    x_distance 
= tempx;
    z_distance 
= tempz;
    
    angle 
= camera.pitching;                    // the same thing we have for pitch angle
    tempy = Math.cos(angle)*y_distance  Math.sin(angle)*z_distance;
    tempz 
= Math.sin(angle)*y_distance + Math.cos(angle)*z_distance;
    y_distance 
= tempy;
    z_distance 
= tempz;
    
    angle 
= camera.tilt;                          // and tilt angle
    tempx = Math.cos(angle)*x_distance  Math.sin(angle)*y_distance;
    tempy 
= Math.sin(angle)*x_distance + Math.cos(angle)*y_distance;
    x_distance 
= tempx;
    y_distance 
= tempy;
    
    
if (z_distance > 0)                                           // if the ball isin front of the camera
    {
        
if (!obj.visible)                                
            obj.visible 
= true;                                    // make the ball visible anyway
            
        var scale 
= focal_length/(focal_length+z_distance);   // cal the scale of the ball
        obj.x = x_distance*scale;                             // calcualte the x position in a camera view 
        obj.y = y_distance*scale;                            // and y position
        obj.scaleX = obj.scaleY = scale;                    // scale the ball to a proper state
    }
    
else
    {
        obj.visible 
= false;
    }
    
    obj.z_near 
= z_distance;            // keep track of z distance to our camera
}

 

7. 写一个循环函数,不停的调用第6步的函数刷新所有的小P。如果你一直有看文章的话那么这些对你来说应该不难。

// loop to update the screen
function run(e:Event)
{
    
for (var i = 0; i < scene.numChildren; i++)                  // update all the balls on the screen
    {
        display(scene.getChildAt(i));
    }
    
    swap_depth(scene);
}
// bubble sort algo
function swap_depth(container:Sprite)
{
    
for (var i = 0; i < container.numChildren  1; i++)
    {
        
for (var j = container.numChildren  1; j > 0; j)
        {
            
if (Object(container.getChildAt(j1)).z_near < Object(container.getChildAt(j)).z_near)
            {
                container.swapChildren(container.getChildAt(j
1), container.getChildAt(j));
            }
        }
    }
}

 

8. 最后是设置一些键盘和鼠标事件响应函数,完成我们的程序。

function key_down(e:KeyboardEvent):void
{
    
if (e.keyCode == 65)            // a
        pan_left = true;
    
if (e.keyCode == 68)            // d
        pan_right = true;
    
if (e.keyCode == 87)            // w
        pitch_up = true;
    
if (e.keyCode == 83)            // s
        pitch_down = true;
}
function key_up(e:KeyboardEvent):
void
{
    
    
if (e.keyCode == 65)
        pan_left 
= false;
    
if (e.keyCode == 68)
        pan_right 
= false;
    
if (e.keyCode == 87)
        pitch_up 
= false;
    
if (e.keyCode == 83)
        pitch_down 
= false;
}
function key_response(e:Event):
void
{
    
if (pan_left)
        camera.panning 
-= 0.01;
    
if (pan_right)
        camera.panning 
+= 0.01;
    
if (pitch_up)
        camera.pitching 
-= 0.01;
    
if (pitch_down)
        camera.pitching 
+= 0.01;
        
    
if (mouse_ctl)                                // if allow mouse control pan and pitch
    {
        camera.panning 
+= scene.mouseX/22000;
        camera.pitching 
+= scene.mouseY/22000;
    }
    
    
// limit the pitch and tilt
    if (camera.pitching < 1*PI/3)
        camera.pitching 
= 1*PI/3;
    
if (camera.pitching > PI/3)
        camera.pitching 
= PI/3;
}
function clicked(e:Event)                        
// toggle mouse control
{
    mouse_ctl 
= !mouse_ctl;
}
// setup event listeners
this.addEventListener(Event.ENTER_FRAME, run);
this.addEventListener(Event.ENTER_FRAME, key_response);
stage.addEventListener(KeyboardEvent.KEY_DOWN, key_down);
stage.addEventListener(KeyboardEvent.KEY_UP, key_up);
stage.addEventListener(MouseEvent.CLICK, clicked);

总结一下,这篇文章中的三角函数部分可能有一些抽象,因为我们是在3D空间中完成的。不过还是那句话,不要担心,你只要知道如何使用这些方程就可以了。

注意:物体自身围绕中心的3D旋转

有一点,不知道你有没有注意,那就是在本文的开头,我做了几个摄像机的旋转演示。在演示里,摄像机(物体)都是本身在旋转,而并不是我们的眼睛(摄像机) 在旋转。虽然到目前为止的文章里,还没有讨论到如何让物体自身旋转,不过我们很快就会看到一些例子。我做这些演示唯一想说明的就是三种旋转的机制,所以不 要着急那些演示是怎么做出来的,会慢慢好起来的。

其实,关于Flash和3D空间的基本知识的介绍到这里我想应该结束了,从下篇文章开始我们就要关注3D物体,因此,如果你对前面文章中的基础知识还是模 模糊糊的话,你完全可以不必担心。不过我还是建议你自己多实验一些小例子,增加自己的空间感。相信你在不久的将来开发自己的3D Engine,加油,ALL THINGS ARE POSSIBLE!

点此下载程序源文件


作者:Yang Zhou
出处:http://yangzhou1030.cnblogs.com
感谢:Yunqing
本文版权归作者和博客园共有,转载未经作者同意必须保留此段声明。请在文章页面明显位置给出原文连接,作者保留追究法律责任的权利。

[C#]项目管理实践【三】每日构建【Daily Build Using CruiseControl.

mikel阅读(734)

在上一篇项目管理实践教程二、源代码控制【Source Control Using VisualSVN Server and TortoiseSVN】中我们已经讲解了如何使用TortoiseSVN和VisualSVN Server来做简单的版本控制,这一篇我们将会讲解使用CruiseControl.NET和MSBuild来搭建每日构建系统。

在第一篇项目管理实践教程一、工欲善其事,必先利其器【Basic Tools】 中我们已经安装了CruiseControl.NET 1.4,因为我们还要用到MSBuild,所以如果你的系统没有安装Visual Studio,那么你需要首先安装Visual Studio 2005/2008,我们在这里使用的是Visual Studio 2008,准备好这些了吗?OK,我们正式开始今天的课程! 

首先,我们要配置CruiseControl.NET【下面简写为CCNET】,配置完成后,我们每次提交源代码到SVN服务器后,CCNET就可 以自动从SVN服务器上迁出源代码,并调用MSBuild自动进行编译。我们以昨天的教程中创建的StartKit项目为实例,先看看下面的配置文件:


  1 <cruisecontrol xmlns:cb="urn:ccnet.config.builder">
  2 <!–项目名称–>
  3 <name>StartKit</name>
  4 <!–标示类型,有多种类型。下面为默认标示,作为每次编译时生成的日志文件的名称–>
  5 <labeller type="defaultlabeller">
  6     <!–前缀–>
  7      <prefix>StartKit-1-</prefix>
  8     <!–编译失败时是否增加–>
  9      <incrementOnFailure>false</incrementOnFailure>
 10     <!–格式–>
 11      <labelFormat>00000</labelFormat>
 12 </labeller>
 13 <!–项目的WebDashboard地址,CruiseControl.NET包括二部分,一是Server用来配置项目和监视文件修改,二是WebDashboard,是一个显示项目信息及编译信息的Website–>
 14     <webURL>http://202.196.96.55:8080/server/local/project/StartKit/ViewProjectReport.aspx</webURL>
 15 <!–触发器,包含多种,有兴趣可以查看官方文档–>
 16 <triggers>
 17 <!–时间间隔触发器,下面是60秒触发一次–>
 18       <intervalTrigger seconds="60" />
 19 </triggers>
 20 <!–如果发现修改,延迟多久开始编译,下面是2秒–>
 21 <modificationDelaySeconds>2</modificationDelaySeconds>
 22 <!–源代码控制系统,支持多种,有兴趣可以查看官方文档,下面采用svn–>
 23 <sourcecontrol type="svn">
 24 <!–源代码在SVN服务器上的路径–>
 25       <trunkUrl>http://zt.net.henu.edu.cn/svn/StartKit/StartKit/</trunkUrl>
 26 <!–svn服务器所在路径,在这里就是VisualSVN Server安装目录中的bin目录下的svn.exe –>
 27       <executable>C:/Program Files/VisualSVN Server/bin/svn.exe</executable>
 28   <!–用来迁出源代码的用户名,svn服务器进行验证–>
 29 <username>starter</username>
 30 <!–用来迁出源代码的用户名对应的密码–>
 31       <password>123456</password>
 32     <!–web获取源代码的地址,类似于开源网站上浏览代码的那部分功能,这里的类型是trac–>
 33 <!–<webUrlBuilder type="trac">
 34     <!–trac中对应项目的地址¬–>
 35       <tracProjectUrl>http://svn.net.henu.edu.cn/pojects/StartKit/</tracProjectUrl>
 36       <!–trac中对应项目的源代码库地址,相对于上面的路径–>
 37       <tracRepositoryRoot>/StartKit</tracRepositoryRoot>
 38       </webUrlBuilder>–>
 39 </sourcecontrol>
 40 <!–该节点用来配置具体执行那些任务–>
 41 <tasks>
 42 <!–msbuild任务配置,用来编译项目–>
 43       <msbuild>
 44         <!–MSBuild.exe的路径–>
 45         <executable>C:\WINDOWS\Microsoft.NET\Framework\v3.5\MSBuild.exe</executable>
 46         <!–从SVN迁出的源代码的存放位置,可以不配置,下面的即为默认值 –>
 47         <!–我这里的CruiseControl.NET 安装在D盘,你们使用时候,改成自己的安装路径即可–>
 48         <workingDirectory>D:\Program Files\CruiseControl.NET\server\StartKit\WorkingDirectory</workingDirectory>
 49         <!–对这个项目的监控过程的日志记录目录,可以不配置,下面的即为默认值–>
 50         <!–我这里的CruiseControl.NET 安装在D盘,你们使用时候,改成自己的安装路径即可–>
 51          <artifactDirectory> D:\Program Files\CruiseControl.NET\server\StartKit\ Artifacts</artifactDirectory>
 52         <!–要编译的项目名称 –>
 53 <projectFile>StartKit.sln</projectFile>
 54 <!– MSBuild编译时的参数,具体参数信息可以查看MSDN上的说明–>
 55         <buildArgs>/p:configuration=Debug</buildArgs>
 56         <!–指定日志记录模块–>
 57         <!–我这里的CruiseControl.NET 安装在D盘,你们使用时候,改成自己的安装路径即可–>
 58         <logger>ThoughtWorks.CruiseControl.MsBuild.XmlLogger,D:\Program Files\CruiseControl.NET\server\ThoughtWorks.CruiseControl.MsBuild.dll</logger>
 59         <!–编译目标–>
 60 <targets />
 61       </msbuild>
 62         <!–在这里还可以添加其他的程序,比如运行测试、部署项目等等–>
 63 </tasks>
 64 <!–项目编译状态信息的保存位置–>
 65 <!–我这里的CruiseControl.NET 安装在D盘,你们使用时候,改成自己的安装路径即可–>
 66     <state type="state" directory="D:\Program Files\CruiseControl.NET\server\CCState" />
 67 <!–发布和部署配置–>
 68 <publishers>
 69   <!–如果编译成功,那么下面的配置,会将源代码复制到指定目录HistoryVersion下,名称为版本标识(自动增长,labeller配置)的子目录下–>
 70   <buildpublisher>
 71     <!–源代码路径–>
 72     <!–我这里的CruiseControl.NET 安装在D盘,你们使用时候,改成自己的安装路径即可–>
 73      <sourceDir> D:\Program Files\CruiseControl.NET\server\StartKit\WorkingDirectory </sourceDir>
 74      <!–编译成功后保存源代码到该目录下名称为版本标示labeller的目录中–>
 75     <!–我这里的CruiseControl.NET 安装在D盘,你们使用时候,改成自己的安装路径即可–>
 76 <publishDir> D:\Program Files\CruiseControl.NET\server\StartKit\HistoryVersion </publishDir>
 77   </buildpublisher> 
 78 <!–该节点用来配置合并多个文件,当时有外部插件时,要把他们分别产生的输出文件合并–> 
 79       <merge>
 80         <!–要合并的文件,合并后的信息可以显示在Web Dashboard和邮件通知里–>
 81         <files>
 82             <!–我这里的CruiseControl.NET 安装在D盘,你们使用时候,改成自己的安装路径即可–>
 83           <file>D:\Program Files\CruiseControl.NET\server\StartKit\WorkingDirectory\results.xml</file>
 84         </files>
 85       </merge>
 86         <!–源代码路径–>
 87       <xmllogger />
 88         <!–显示历史修改记录列表, 在Web Dashboard中可以查看–>
 89       <modificationHistory />
 90         <!–所有编译信息的统计, 在Web Dashboard中可以查看–>
 91       <statistics />
 92         <!–邮件通知配置,每次编译后,都会邮件通知下面配置中添加的用户–>
 93       <!– mailhost 是发送邮件的主机,mailport是邮件发送端口,mailhostUsername发送邮件的邮箱用户名,mailhostPassword发送邮件 的邮箱密码,from希望显示在发件人中的邮箱地址, includeDetails邮件内容是否包含详细的编译信息 –>
 94 <email mailhost="smtp.qq.com" mailport="25"
 95                  mailhostUsername="******" mailhostPassword="******" from="******@qq.com" includeDetails="true">
 96         <!–接收邮件通知的用户 –>
 97 <users>
 98   <!–name是SVN服务器上存在的用户名,group是SVN服务器上存在的组,address是该用户的邮箱地址 –>
 99           <user name="zt" group="StartKit" address="******1@qq.com" />
100           <user name="***" group="StartKit" address="******2@qq.com" />
101           <user name="***" group="StartKit" address="******3@qq.com" />
102         </users>
103         <!–接收邮件通知的组–>
104         <groups>
105         <!–name必须是SVN服务器上存在的组,notification是什么时候发送通知,可选有Always/Success/Change/Fixed/Failed –>
106           <group name="StartKit " notification="always" />
107         </groups>
108       </email>
109     </publishers>
110   </project>
111   <!–可以同时添加多个项目
112 <project >
113 <name>test</name>
114 ……
115 </project>
116 –>
117 </cruisecontrol>
118

好了,我们已经对CCNET的配置文件有了大致的了解,接下来,你打开CCNET的安装路径,找到子目录server下的ccnet.config文件, 把上面的配置信息Copy到ccnet.config文件中,记得把配置文件中的一些路径修改为自己的实际路径啊,修改好后,保存。这时候,检查 Windows服务CruiseControl.NET Server是否启动,如果没有则启动它,启动该服务后,打开浏览在地址栏输入上面配置文件中的webUrl地址:http://202.196.96.55:8080/server/local/project/StartKit/ViewProjectReport.aspx 也可以直接输入http://202.196.96.55:8080/server/ ,这里是演示地址,要根据自己的实际情况修改为正确的地址,OK,看到类似下图的效果,好了,搞定!如果你遇到了什么麻烦,请在下面留言,我一定会及时回复!

 点击StartKit,转入下图所示的页面:

OK,到这里,我们提交更新到SVN服务器后,CCNET就会根据我们配置自动编译项目,而且我们也可以通过Web Dashboard来查看具体的编译信息了,提示如果配置了邮件发送,那么我们还可以通过邮件收到详细的编译信息,怎么样?够方便吧!

其实,CCNET的功能是相当强大的,上面只是最常用的配置,其他还有很多非常好的功能。你想知道吗?那你可以在这里查看CCNET官方文档 ,实际上,你安装CCNET后,文档也已经安装到你的电脑了,在CCNET的安装目录下的webdashboard的子目录doc中就是。 

好了,我们今天的教程就到这里,本来我应该把如何使用CruiseControl.NET Tray来监视每次更新后的编译状态,但是今天真的太晚了,明天还要做项目,所以我明天补上,请大家见谅! 

如果大家有什么问题,欢迎和我交流! 


作者:ttzhangTechnology Life
出处:http://ttzhang.cnblogs.com/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

[Flex]Flash技术入住亚马逊

mikel阅读(715)

FLASH技术入住亚马逊,其试推多媒体购物网站

Windowshop.com

北京时间11月6日消息,据国外媒体报道,亚马逊9月中旬悄无声息地推出了一个新站点,通过多媒体方式展示某些商品,试图藉此改变在线购物通常无法给消费者带来“感官盛宴”的局面。

  亚马逊这一新站点名为Windowshop.com,该站点每周二都会陈列出最新、最热销和经过精挑细选的商品。亚马逊鼓励访客使用键盘上的方向键浏览Windowshop.com,该站特点是在矩形方框中以视频方式介绍某种商品,这些方框则呈棋盘状依次排列。

   目前,在Windowshop上陈列的商品有音乐、书籍、视频游戏和电影。访客在用鼠标点击或使用键盘方向键移动至某种商品时,该商品所在方框将会变 大,供访客观看或收听相关内容,如《功夫熊猫》的视频剪辑,或是某本书籍的音频预览等。如果想要购买某种商品,只需点击方框右下方的按钮跳转至亚马逊主站 即可。

  亚马逊负责零售客户体验的副总裁伊娃·马诺里斯(Eva Manolis)称,推出Windowshop旨在给消费者带来身临其境的感觉,这是在线购物中很难获得的体验,对亚马逊这样一个规模庞大的网站来说更是如此。她表示:“我们希望尝试不同于以往的新事物。”

  此外,亚马逊还将Windowshop.com网站的技术和美学理念用于主站的一个版块,展示广受欢迎的节日玩具和深得儿童喜爱的视频游戏及DVD等商品。马诺里斯称,鞋和服装等其他类型的产品也可采用Windowshop风格。