2006年4月30日星期日

设计一个面向Photoshop用户的CSS设计器,应该很畅销!

很多美工,还是习惯用Photoshop设计页面然后Slice,这样的设计方式对他们来说比较直观,不需要在脑袋里想着效果然后写HTML和CSS然后再看实现得如何。能够脑袋里想着结果然后闭上眼睛写代码的,估计主要还是程序员,美工需要能够看得到摸得着一步一步去fit的东西。虽然现在也有些美工人员在尝试摆脱HTML table排版改用CSS,但是用Photoshop输出的table改div和CSS既不方便结果也不一定是最优的(相对能够闭上眼睛写HTML+CSS的人来说)。于是,我就想如果能够开发一个有点类似Photoshop那样的绘图见面,但又能够直接用于设计CSS的设计器,应该会很多人喜欢。

其实Frontpage和Dreamweaver已经很“所见即所得”,专门的CSS编辑器如TopStyle也是能够编辑CSS的同时显示效果,为什么还要像Photoshop那样?因为我觉得美工是靠feel工作的,例如导航栏的高度和文字定位,你要求美工修改一下height然后又调整一下padding和margin,尝试一下px做单位,又换用一下%做单位,这应该是比较痛苦的事情。人家在Photoshop里面,导航栏高度喜欢拉多高就拉多高,文字定文也是用鼠标放,这完全是靠看着整个页面的构图感觉来做的,对着一堆CSS改一下代码再看一下结果就很不爽。所以,这个类似Photoshop的CSS设计器一定要完全实现在可视化界面上实现所有操作,也就是说用户也是直接通过拖动就能够实现字体定位的,至于margin和padding就让设计器自己去管理,没必要让用户操心,用户最多选一下按px计算还是按%计算然后看看不同情况下变化的效果。

要说明的是,因为美工面对的往往不是一个fixed的页面,而是随浏览器可视区域变化而变化的页面,所以设计器本身应该支持不同可视区域大小的preview。例如我当前在1024*768可视区域的模式下设计,但是旁边有缩略图显示这个CSS在800*600中的显示效果,通过鼠标放置字体位置后,改变按px计算和按%计算不会影响1024*768模式下的效果(因为这是当前设计模式),但是800*600模式下的preview就会跟随改变,因为当前的设计效果按照不同的单位来定位的话在其它模式下的显示结果是不同的。

这个设计器的重点,应该就是div和ul的放置了,因为这两个东西现在用得最多。div的放置应该支持dock(停靠)的效果,或者是现在WinForm所支持的anchor效果(比dock更好)。在说这个之前,大家可以来看看一个在线的CSS Creator的效果:
http://www.csscreator.com/version2/pagelayout.php
这个东西允许你设置常见的排版模式,例如有没有Header和Footer,它们的高度多少,按px还是按%算,body部分分为几个column(最多3个),宽度又如何算,页面的最大最小高度如何。我所谓的div支持dock,就是类似现在主流所做的那样啦,3栏结构的话,左栏dock左边、右栏dock右边、中栏占满剩余空间(这不废话),如果超过3栏也能dock/anchor的话就更好了。anchor是一个比dock更好用的东西,dock只能依赖于4页面边缘,anchor则是任何一个控件的4边缘都可以依赖于仁和另外4个附近控件的边缘,如果CSS能够实现anchor效果就好了。另外就是要有一个preview显示无CSS时的效果,能够看到完全无CSS时出来的样子如何。

2006年4月21日星期五

ASP.NET 2.0 的编译模型并非完全像 MS 说的那样

上次说到了ASP.NET 2.0解决了Code-Behind需要同步声明控件的问题,说MS的图例解释2.0中aspx和cs的内容不再是继承的关系,而是partial的关系,是合并编译。然后我说了,如果是partial关系,那么处理过程就很复杂。因为partial不能增量编译,也就不能跨语言编译,必须把aspx部分内容完全翻译为cs或者其他对应Code-Behind语言,然后才能够多partial一起编译。但实际上当然不是这样做,现实中aspx既是partial,又像1.x那样要做为继承类再编译一次。

发现这个问题,是由于我在cs声明了一个private的函数,想在aspx中的数据绑定语句中使用,结果调用时竟然告诉我找不到该函数,然后我把private改成protected就可以了。如果aspx和cs真的完全是partial关系,那么private函数是能够找到的,private找不到说明aspx中数据绑定语句和cs肯定不是在同一个类中,也就是不是partial关系,而是好像1.x那样的继承关系。实际上,我说的aspx完全翻译为Code-Behind所用语言应该是做不到的,最多把aspx中的所有服务器控件声明翻译。因为都是声明,可能如果一个partial里面有的只是声明,那编译器内部就支持增量编译,因为声明在编译结果里就是一个比较独立的部分。然后aspx中的其他逻辑都是好像1.x那样,再继承编译好的Code-Behind代码,把UI代码加上去。

2006年4月5日星期三

监视你的用户

正如之前的StealthMeter或者GamePlaysMan一样,我时不时都利用流行的技术和设计想出一些坏东西来,这次轮到Keep Your Visitors Under Surveillance了,咔咔……为什么那么多个词不用,偏要用Surveillance,因为我就想做到好像闭路电视监视网一样的效果,能够在中央控制室同时监视多个用户,而且要是实时的。

Analytics和MeasureMap出来了都还没有机会用上,就又听说有一个新的更强的站点分析平台能够分析用户鼠标在页面不同部位停留的时间长度,然后用不同的颜色把鼠标停留时间的梯度显示出来,虽然实际效果如何还不知道(该平台还在Alpha阶段),不过通过用户鼠标停留时间应该能够看出用户的阅读方式(还是有不少人习惯用鼠标跟随目光的),以及用户对页面的哪些链接感兴趣(即使没有点击)。另外最近也看了Sliver(1993),觉得在监控室中对着满墙的监视屏幕看着不同房间的人在做不同的事会挺有趣,于是就产生了监视Web用户这个想法。

首先,要实现一种ClientViewState,也就是客户端的ViewState。和ASP.NET的服务器端ViewState用于保持页面生命周期间View的状态不同,ClientViewState就是显示反映客户端浏览器当前的状态,例如页面滚动到何处,用户鼠标停留在哪里。这个ClientViewState要是实时更新的,也就是客户端的所有操作相应都实时更新ClientViewState,对于好像鼠标移动这样的事情ClientViewState会产生Frame,而对于点击这样的更新产生KeyFrame,Frame和KeyFrame会使用Ajax模式直接传输到服务器以更新服务器端存储的ClientViewState,再传输时KeyFrame拥有更高的优先级,一旦产生KeyFrame则KeyFrame之前产生但未传输完的Frame就丢弃,仅当没有连续KeyFrame时才传送Frame。

然后,监视页面从服务器下载ClientViewState下来(使用Comet模式),把每一个ClientViewState释放到一个对应的IFrame,在该IFrame还原对应的被监视用户的当前ClientViewState,通过IE的DOM的按百分比缩小显示功能把IFrame内的内容缩小,这时候监视者就可以在监视页面看到用户实际操作的情况了。

实现ClientViewState不是难事,现在的DOM足以应付,然后把ClientViewState串行话传导服务器端然后再传到另一个客户端并还原也不难,关键就是传输途中用的Comet模式是否有效率。所谓Comet模式就是现在Talk in GMail和Meebo等WebIM采用的Ajax模式,和普通Ajax唯一不同的就是Comet使用keep-alive的HTTP连接从而实现服务器端主动Push数据到客户端,但到底这样的长连接能够应付多大的负载我实在还不知道,不过应该应付串行话的ClientViewState传输还是绝对没问题的。

至于实时监视有没有什么意义,这个我暂时不知道,对于成熟的站点来说可能没什么意义,但是对于Beta阶段需要知道用户使用方式的站点来说或许有用,正如软件易用性测试可以用走廊测试一样(在走廊上随机拦几个人来试用你的软件然后看他们的操作方式),你可以通过监视用户如何使用你的站点从而知道易用性方面是否有问题,同时也无需让用户填一堆关于使用方式的调查问卷。

2006年4月3日星期一

LoadPostData 最多只有两次

在很多MSDN的一般文档中,都回避了LoadPostData这个问题,直接就说Page由Init到Load然后就是控件事件了。而根据我的实验,在Load的前后会出现两次的LoadPostData,而最后在MSDN也找到了独立的文章证实了这个流程。

一般的HTML中静态声明的控件初始化应该发生在Init,Init之后这些控件也就初始化完成。然后此时会有一次LoadPostData,这是面向仅使用HTML静态声明控件的情况,这样在Load的时候这些静态声明的控件就已经成功加载了数据,可以直接访问这些数据做一些事情了(例如根据这些数据动态创建控件)。在Load之后,如果确实有动态创建了控件,那么第二次LoadPostData就会被调用,用于为这些动态创建的控件加载数据,如果在上一次页面生命周期这些控件也有被动态创建,那么此时它们的数据就得以加载到本次页面生命周期的对应控件实例。再之后就是控件事件阶段了,所有控件都根据自己加载回来的数据判断是否要引发什么事件。

动态创建控件的最迟不能晚于第二次LoadPostData,否则创建的控件在下一个生命周期就算再次创建同样的控件也不能成功加载数据,这就确定了动态创建控件不能放到控件事件中进行,因为控件事件发生在LoadPostData后。(理论上页面生命周期可以设计为递归型的,也就是每次引发控件事件后再检查是否有添加新控件,有的话再LoadPostData,LoadPostData后再检查这些控件是否需要引发事件,不过实际上页面生命周期没有这样设计。)基本上动态创建控件必须在Load前后完成,例如开发复合控件或者模板控件时常在CreateChildControls和DataBinding中做此项工作。

如果涉及复杂的控件创建,这些控件创建必须依赖于事件改怎么办?这就只有一个办法,把动态创建变成创建所有并动态隐藏。例如制作一个有很复杂规则的调查问卷,根据答题者不同的选择转跳来转跳去(这时候你可能首先会想到ASP.NET 2.0的Wizard控件或者CrossPagePostBack,但这样每次仅显示当前的一个问题),先在页面上创建所有题目并且在Load时根据当前答题情况隐藏不应该出现的问题就是一个解决方案。这里隐藏的关键,就在于隐藏的控件不呈现到HTML上(或呈现到HTML但隐藏),但它们的ViewState却照样保存,这就使得所有的题目实际上都能够跨越页面生命周期而存在并且实现数据持久。

不过Page级别的隐藏方法好像不那么美观,所以我们应该这些逻辑封装到一个WebControl里。既然封装到WebControl里,也就没必要让各个隐藏状态的控件自行保存ViewState,直接把它们的属性统一集中到父控件并保存到其ViewState就行了。此时所有子控件的数据加载变成了由父控件统一控制,当一个子控件不需要出现时就不再需要先创建再隐藏而根本就不用创建,因为父WebControl会继续负责此子控件数据的持久。

其实ASP.NET 2.0的Wizard可能也是用类似的方式实现。在ASP.NET 1.1时我就提出过Wizard,觉得这是一个非常容易实现的控件(就按照上面的父控件统一实现子控件数据持久的方法),而且也很有价值,就是奇怪为什么各大WebControl开发商不做一个出来,结果到ASP.NET 2.0官方就做了一个出来了。至于CrossPagePostBack,用的也就是没有控件但ViewState保留的做法,每次CrossPagePostBack都把PreviousPage的整个ViewState保留下来,不过唯一问题就是如果连续用很多个CrossPagePostBack实现一个Wizard那么多个ViewState堆叠下来占用的传输带宽也一定不少。

所以说,在ASP.NET里面用好ViewState很关键(这个下次详细说),如果还是没ViewState的思想,还是按照其它动态页面语言的思路去做,那是用不好ASP.NET的控件模型的。