上一篇我们自定义CPU和内存的展示界面效果,这篇我们将继续采用HT完成一个新任务:实现一个能进行展开和合并切换动作的刀闸控件。对于电力SCADA和工业控制等领域的人机交互界面常需要预定义一堆的行业标准控件,以便用户能做可视化编辑器里,通过拖拽方式快速搭建具体电力网络或工控环境的场景,并设置好设备对应后台编号等参数信息,将拓扑图形与图元信息一并保存到后台,实际运行环境中将打开编辑好的网络拓扑图信息,连接后台实时数据库,接下来就是接受实时数据库发送过来的采集信息进行界面实时动态刷新,包括用户通过客户端对设备进行的各种下发遥控等操作,发送到后台最终实现对硬件设备的控制,这个过程就是典型的实时监控系统的基本架构流程。我们今天只做好小小螺丝钉工作,提供一个可控制的刀闸开关控件。

具体实现之前先看看我们要达到的最终效果图片和视频

记得十多年前我刚毕业的第一份工作就是负责电力SCADA的人机界面交互模块,当时大部分电力行业都是采用VC/MFC或QT来实现界面呈现,其实至今也依然如此,前端时间和老朋友聚会了解到他们还在用VC6编译系统,如今的VS20**根本跑不动他们庞大的古老系统,当然也许他们没配置好工具参数,但从一个侧面你可以感受到老系统迁移之重,大部分程序员处于为项目业务功能疲于奔命状态,上百号人这么多年在根本无力优化和重构的架子上不断堆积功能,我记得当时一个mousedown函数居然堆了六千多行代码,各种图元类型的draw代码也是长得不堪入目,这些老系统虽然不好维护但也考这么多程序员活生生的维护下来了,我们每天能正常的用水用电用气,背后都是靠着众多程序员的血汗维护着以如今眼光看完全不堪入目的烂代码,不得不承认在中国能用是第一位,其他问题只要堆人能解决的都不是问题。有点扯远了,上几张我以前电力实现的图库工具:

实现功能并不难,当时也实现了组合和分解图元,能进行图库管理和用户自定义,我相信全世界肯定不下几百上千套绘图软件,刚开始我还是很兴奋,每天学习不同的绘制API,就能捣鼓出新效果,我也不在乎代码架构,每天就是以学习掌握更多的庞大MFC库为荣,但当你掌握大部分绘图技巧后,我发现自己每天维护这种庞大到无法以个人力量进行大规模重构,又不得不持续维护每天堆积功能性体力活代码时,我感觉自己在浪费生命,于是跳槽到了另外一家公司打算做电子商务,结果阴差阳错又被安排到电力部门干起来绘图工具,还好这次我能换个新语言Java,没有历史包袱完全自己重头设计图形架构,于是地球上出现了第1001个绘图工具:

这一版设计上还是有很大的改进,图形绘制逻辑,交互代码以及界面布局等都进行了较合理的分工设计,那个Java和设计模式很火,人手一本Martin Fowler《Refactoring: Improving the Design of Existing Code》,犹如宗教信仰坚决执行一个函数不超过几十行的时代,一个mousedown几千行的代码已经绝迹了,但我还是很不满意,数据模型和界面绘制没有很好的有机结合机制,虽然电力要求界面有***的毫秒级响应,但大部分公司都是像游戏刷新机制那样不断repaint界面,是的,当时的数据模型没有任何事件派发机制,就是内存中的一堆数据,你无法知道哪个数据什么时候change了,因而只能不断的repaint界面,刷新周期太短对于大的网络拓扑图根本来不及更新,更新周期太长又达不到响应要求,至于所谓的***毫秒级响应我只能呵呵了,为了上这个系统一堆兄弟在沈阳某农村封闭了八个多月,我很好奇那个老系统现在是否健在…

回到我们的任务,一个刀闸最主要的就是可开闭的部分,其他部分都是装饰物效果而已,因此我采用HT的矢量来描述整个刀闸外观,其中需要开闭部分采用type为shape的一个线段来描述,并将其的rotation旋转参数通过func: ‘style@switch.angle’的描述来绑定到Node图元的switch.angle样式属性上

ht.Default.setImage('switch', {
width: 100,
height: 50,
comps: [
{
type: 'roundRect',
rect: [0, 0, 100, 50],
background: '#2C3E50',
gradient: 'linear.north'
},
{
type: 'circle',
rect: [10, 10, 10, 10],
background: '#34495E',
gradient: 'radial.center'
},
{
type: 'circle',
rect: [80, 10, 10, 10],
background: '#34495E',
gradient: 'radial.center'
},
{
type: 'shape',
points: [10, 40, 40, 40],
borderWidth: 8,
borderColor: '#40ACFF',
border3d: true
},
{
type: 'shape',
points: [60, 40, 90, 40],
borderWidth: 8,
borderColor: '#40ACFF',
border3d: true
},
{
type: 'shape',
points: [5, 40, 35, 40, 65, 40],
segments: [1, 1, 2],
borderWidth: 8,
borderColor: '#40ACFF',
border3d: true,
borderCap: 'round',
rotation: {
value: -Math.PI/4,
func: 'style@switch.angle'
}
},
{
type: 'circle',
rect: [30, 35, 10, 10],
borderColor: 'red',
borderWidth: 5,
border3d: true
},
{
type: 'circle',
rect: [60, 35, 10, 10],
borderColor: 'red',
borderWidth: 5,
border3d: true
}
]
});

以上是在矢量编辑器中打开的效果图,你可以清晰的看得到我们定义的几个元素的位置大小演示等,这样应用时只要构建一个Node对象,将其image设置为switch矢量,那么将来只需要调用node.setStyle(‘switch.angle’, Math.PI/6)就可以随时随地控制刀闸展开角度 。

这样封装还不够完美,对应用着来说他们只关心刀闸的打开和关闭的操作,他们并不关心旋转角度,开和关是业务角度的理解,而旋转角度是底层实现图形上的参数,并且用户还需要开关过程有动画效果,于是我们进行了进一步的封装,设计了ht.Switch的类,提供了setExpanded的函数,在函数里面操作底层绑定图形的‘switch.angle’属性,以及启动动画封装:

ht.Switch = function(){
ht.Switch.superClass.constructor.call(this);
this.s('switch.angle', 0);
};
ht.Default.def('ht.Switch', ht.Node, {
_image : 'switch',
_icon: 'switch', toggle: function (anim) {
this.setExpanded(!this.isExpanded(), anim);
},
isExpanded: function () {
return this.s('switch.angle') !== 0;
},
setExpanded: function (expanded, anim) {
if(anim == null){
anim = true;
}
var self = this,
animation = self._animation,
oldValue = self.isExpanded(); if(animation){
animation.stop(true);
delete self._animation;
} if (oldValue !== expanded) {
var targetAngle = expanded ? -Math.PI/4 : 0; if(anim){
oldValue = self.s('switch.angle');
self._animation = ht.Default.startAnim({
action: function(t){
self.s('switch.angle', oldValue + (targetAngle-oldValue)*t);
}
});
}else{
self.s('switch.angle', targetAngle);
}
}
}
});

在我们的视频操作中你会发现通过属性页的拉条可以任意控制刀闸张角,同时通过isExpanded/setExpanded的boolean类型属性也可以勾选动画切换刀闸的开与关,细心的程序员你会发现不仅仅拓扑图上的刀闸动起来了,连TreeView上的刀闸对应的icon图标也是和矢量描述的效果一样,更惊喜的是树上的icon也是实时显示刀闸的展开角度,这是传统图片作为树的icon图片无法实现的,这也是我们一直强调的HT for Web整体架构已经为矢量打下基础,并非为了拓扑才实现矢量,所有通用组件都享有矢量的功能特性,这个后续我们会有更多的应用案例让大家体会到这种结合的强大之处,当然可维护性已经不用我多说了,传统的通用组件tree上自定义renderer也能实现一个能动的icon,但你可以想想工作量,我们没有写一行绘制代码,仅仅通过定义一个json的矢量就把GraphView和TreeView的事都干了,并且业务接口对上层应用人员来说就是一个node.setExpanded(true/false)之简单。

这里我只是随手搞了个非常ugly的刀闸,你可以让美工采用矢量绘图工具可视化的绘制更漂亮的效果,界面操作上你也可以通过graphView.mi监听交互事件,例如监听到双击刀闸时进行开关切换,甚至可以参考《透过WebGL 3D看动画Easing函数本质》的章节采用更洋相的Easing动画效果。

最后几点设计控件的建议:

  1. 切换到使用者角度,即站在上层应用者角度提供最简洁符合业务逻辑的API接口,尽量不暴露图形相关参数,图形参数对上层使用着是晦涩的,暴露了你自己也是非常难改动和维护
  2. 不要一开始设计就考虑如何操作,如何动画,操作和动画都可以在基础API基础上扩展再封装,某种程度上来说,如何操作和如何动画甚至不属于控件封装该干的,至少可再提供进一层的封装,这样可随意切换操作和动画逻辑,而不影响底层控件的数据模型和绘制逻辑
  3. 尽量让绘制代码和业务逻辑代码分离,这点如果采用最基础的绘制代码的确很难分离,这也是HT尽量采用矢量描述,不让用户控制底层绘制代码的初衷

HT图形组件设计之道(二)的更多相关文章

  1. HT图形组件设计之道(四)

    在<HT图形组件设计之道(二)>我们展示了HT在2D图形矢量的数据绑定功能,这种机制不仅可用于2D图形,HT的通用组件甚至3D引擎都具备这种数据绑定机制,此篇我们将构建一个3D飞机模型,展 ...

  2. HT图形组件设计之道(三)

    上篇我们通过定制了CPU和内存展示界面,体验了HT for Web通过定义矢量实现图形绘制与业务数据的代码解耦及绑定联动,这类案例后续文章还会继续以便大家掌握更多的矢量应用场景,本篇我们先切换个话题, ...

  3. HT图形组件设计之道(一)

    HT for Web简称HT提供了涵盖通用组件.2D拓扑图形组件以及3D引擎的一站式解决方案,正如Hightopo官网所表达的我们希望提供:Everything you need to create ...

  4. HTML5拓扑图形组件设计之道(一)

    HT for Web(http://www.hightopo.com/guide/readme.html)提供了涵盖通用组件.2D拓扑图形组件以及3D引擎的一站式解决方案,正如Hightopo官网所表 ...

  5. 数百个 HTML5 例子学习 HT 图形组件 – 拓扑图篇

    HT 是啥:Everything you need to create cutting-edge 2D and 3D visualization. 这口号是当年心目中的产品方向,接着就朝这个方向慢慢打 ...

  6. HTML5 例子学习 HT 图形组件

    HTML5 例子学习 HT 图形组件 HT 是啥:Everything you need to create cutting-edge 2D and 3D visualization. 这口号是当年心 ...

  7. 数百个 HTML5 例子学习 HT 图形组件 – WebGL 3D 篇

    <数百个 HTML5 例子学习 HT 图形组件 – 拓扑图篇>一文让读者了解了 HT的 2D 拓扑图组件使用,本文将对 HT 的 3D 功能做个综合性的介绍,以便初学者可快速上手使用 HT ...

  8. 数百个 HTML5 例子学习 HT 图形组件 – 3D建模篇

    http://www.hightopo.com/demo/pipeline/index.html <数百个 HTML5 例子学习 HT 图形组件 – WebGL 3D 篇>里提到 HT 很 ...

  9. 数百个 HTML5 例子学习 HT 图形组件 – 3D 建模篇

    http://www.hightopo.com/demo/pipeline/index.html <数百个 HTML5 例子学习 HT 图形组件 – WebGL 3D 篇>里提到 HT 很 ...

随机推荐

  1. GTD时间管理(2)---管理收集箱

    通过上面一篇文章,相信大家对GTD收集有了原理大致的了解,如果大家对收集不是很了解,可以去看一下. 当我们收集到很多想法和事情之后,在晚会的时候必须要清空收集箱,否则收集箱会堆积如山,最终收集箱成了垃 ...

  2. CentOS6.5下安装Open vSwitch

    准备 # yum install openssl-devel redhat-rpm-config kernel-devel -y #yum install kvm libvirt python-vir ...

  3. mac os 中安装memcahed

    1.先安装macport   sudo port selfupdate  #更新当前Marport  (如果port 不可以时可以考虑此操作) sudo prot -d selfupdate #替换更 ...

  4. 1、Orchard商城开发——开发需求

    需要开发的功能: 1.商品详情,可添加商品属性,如颜色,尺寸等. 2.商品类别,可显示该类别下的所有商品,可按品牌.颜色.尺寸等检索,并可按价格.销量等排序游览. 3.商品游览记录,收藏商品,加入购物 ...

  5. .net core 使用Autofac依赖注入

    Startup中: public IContainer ApplicationContainer { get; private set; } // This method gets called by ...

  6. 20个最漂亮的基于WordPress的企业网站

    20个最漂亮的基于WordPress的企业网站 每个人都知道很好很强大的WordPress是开源的,并且有一个很强的的团队和更强大的支持社区.它被世界上的广大设计师.程序员和商业人员广泛使用.它已经成 ...

  7. RabbitMQ学习笔记3-使用topic交换器

    topic的路由规则里使用[.]号分隔单词,使用[*]号匹配1个单词,使用[#]匹配多个.和多个*. 在下面的例子中: logger.*可以匹配logger.error和logger.warning, ...

  8. SQL Server 2012 各版本功能比较

    有关不同版本的 SQL Server 2012 所支持的功能的详细信息. 功能名称 Enterprise 商业智能 Standard Web Express with Advanced Service ...

  9. Flash Builder中“Error: #2036 加载未完成”错误的解决方法

    复制了一个名称为A的widget包,重命名为B,包含B.mxml和B.xml(配置文件),编译后无法加载B包创建的widget,报错为: 解决办法: 1.在工程的根目录下找到.actionScript ...

  10. android中自定义view---实现竖直方向的文字功能,文字方向朝上,同时提供接口,判断当前touch的是哪个字符,并改变颜色

    android自定义view,实现竖直方向的文字功能,文字方向朝上,同时提供接口,判断当前touch的是哪个字符,并改变颜色. 由于时间比较仓促,因此没有对代码进行过多的优化,功能远远不如androi ...