`
sunjing21
  • 浏览: 157245 次
  • 性别: Icon_minigender_1
  • 来自: 武汉
文章分类
社区版块
存档分类
最新评论

Swing是一把刀

 
阅读更多

http://www.iteye.com/topic/699515 博文地址


要源代码的朋友:容我再整理数日,但一定会奉上。要下载该框架,并想了解该框架的更多用法,可以看这里:《Swing第二刀:枝间新绿一重重》

对Swing的抱怨和批评从来就没有停止过。不过从以前的铺天盖地,到后来的见怪不怪,到现在的冷冷清清。似乎人们早就已经浸淫在无处不在的web和满天飞的RIA之中,懒得去理会Swing这个老古董了。难道Swing真的到了日薄西山、穷途末路的地步?

大家对待Swing的态度从来就不缺乏偏见。作为Java的桌面UI技术,其实它和MFC、VCL、Winform、WPF以及Flex、Silverlight一样,只是做界面的技术工具而已,本身并没有什么不好。只要计算机还用屏幕、键盘、鼠标来进行交互,这些UI技术(或其后续改良继任者)就会始终存在。无论哪个UI技术,说到底都是在屏幕上画图,本质上都是画点、划线而已,只是有的用起来复杂,有的简单,有的功能强,有的功能弱。不过对于技艺高超者来说,给个邮票大的地方也能跳出激动人心的舞蹈,Flex不就是在Flash这块邮票上面不停的折腾么?这又和脚下的空间有多少关系呢?相比Flash/Flex这张小邮票,Swing则给了我们一个巨大的足球场。如果我们还是不能在这个舞台上吸引更多的观众,还是多反思一下自己的舞技吧,就算脚下的场地有点湿滑不平,我们也没必要喋喋不休。

Swing的设计的确有点过于概念化,导致结构比较抽象,对初学者来说确实比较费劲。记得最开始学习Swing时,弄个table还要创建什么TableModel,还有个AbstractTableModel,还有个DefaultTableModel...一头雾水。为什么不是table.addRow(String[]row)就ok么?直到慢慢理解了MVC,理解了更多设计模式,理解了action和listener监听,renderer/editor机制,UI代理、事件派发线程...才慢慢理解和掌握了Swing,编程水平也有了很大提高。而回头再看十年前用VB6写程序,DIM一个ADO、连接ODBC、拖拽一个文本框、设置属性绑定....简直就是工具的奴隶,一点软件设计的内涵也没学到,不客气的说真是害了一代人。

人类能发展在于善用工具。要把各种UI技术比喻成工具刀,Swing是哪类刀呢?有人说Swing连个TreeTable、日期控件都没有,真是原始社会(夸张点吧,怎么也算封建社会吧)!那么Swing就是这样一把刀了?




这把刀实在是吓坏了不少人。不过一些痛恨Swing的人会觉得很解气。要说有偏见吧,确实偏见是无处不在的。就像美国人鄙视日本人,日本人鄙视韩国人,韩国人鄙视中国人,中国上海人鄙视北京人,北京人鄙视天津人,天津人鄙视河南人,河南人鄙视新疆人,新疆人鄙视所有人..............扯远了。其实熟悉一段时间Swing后,只要掌握了一些基本的MVC设计思路和常用组件的使用方法,就会发现,这把刀可以很锋利,可以说是“宝刀未老”,而不是”锈迹斑斑“:





不过对于很多非常简单的UI任务来说,Swing会显得很啰嗦、很繁琐。API浩如烟海,文档缺乏,一头扎进去半天出不来。我们头大,我们发狂:我要拧一个螺丝而已,你却给我下面这把刀:




所以,你家里需要准备一个单独的螺丝刀,用来拧螺丝;准备一把剪刀,用来剪纸张;一个刮胡刀,用来刮胡子;一把菜刀,用来切菜;还要一个指甲刀,用来剪指甲。虽然菜刀也能凑合割指甲,刮胡刀剁饺子馅也没意见(疯了),但是你显然不想这么做。所以,别抱怨Flex这把剪刀功能不够强,Silverlight这个螺丝刀功能不够多,Swing太复杂。作为Java平台的厂商,Sun除了推出Swing这样的全功能工具刀,还能如何呢?现实世界需求很复杂,只能尽量全满足。这就是生活。





所以,上战场除了带上冲锋枪,扛上火箭筒的同时,挎上GPS,腿上还要再绑上一把刀。程序员也是一样,多一项技能就是多一项武器,关键时刻一个都不能少,一个武器就多一个生存的机会。





工作原因,使用Swing好多年了。我很喜欢Swing。它复杂,它啰嗦,它功能强,它发展慢。不过我还是很喜欢,并在一些项目中使用。慢慢的,积累了一些在项目中使用Swing的经验。不这里先秀一个今天上午还在改的项目界面,希望大家喜欢。

首先说明一下,这是一个免费的ERP产品界面,只是一个原型和Demo,还没有被最终确认。图上面的功能、模块、数据都是假的,大家不要较真。由于ERP软件的模块和产品功能众多、业务逻辑复杂,程序的界面需要体现“友好、方便、易用”的原则。再加上其“免费”的重要特性,这里选择了绿色和灰色为基本色调,以橙色作为主要的辅助色,既不过于死板,也不过于花哨,体现了“绿色、环保、低碳(好像不太搭边哦)”的软件设计理念。




上图是ERP软件的主界面。窗口上下分别是菜单和状态栏,左侧是模块树,右侧是快捷功能列表,中间是具体数据和内容的显示区域,用多tab方式显示每个页面。为了考虑到绝大多数用户的屏幕尺寸,这里都以1024*768作为基本尺寸进行设计和抓图。可以看到中间的数据显示区域已经不是很宽裕。所以,程序加了一个功能:双击中间的tab头可以将左右闭合,增大中间内容页面的可见区域,如下图效果。可以看到,左右区域闭合后,还会露出图标以及tooltip文字,不影响继续使用:



中间的内容页可以显示任意Swing组件,例如放一些销售业绩图、报表、股票图(ERP里头不管股票吧,懂行的纠正一下下)之类,例如下图:




当然在ERP系统中,最多的还是各种枯燥的数据和报表,这时候表格还是最常见和重要的呈现方法。但是放在咱这里,表格也要做的清新、耐看一点,不能太审美疲劳:

或者,查找一下全球供应商或者客户分布及其业务情况:


好了,先上这几张图。如果大家喜欢,以后再上传更多。


做了很多项目和界面,有几个体会:

  • 界面设计,一定要“美工先行”。也就是美术设计和效果图的设计工作要先于软件开发去做。在美工设计的阶段,大家一定不要考虑技术上的问题,别冲上去第一眼就“哎呦,这个不好搞”,”这个圆角和渐变咋弄?“,这是我们程序员的职业通病。大家是否发现,光靠程序员自己,几乎是无法做出真正好的UI?因为程序设计和艺术设计几乎用不同的大脑部位(具体是前脑后脑左脑右脑我还真没仔细研究),而我们程序员擅长的是逻辑思维,对发散思维的艺术设计则基本都“缺根弦”。比如我们写程序时候,要突出一个数据,我们就直接给设置颜色,先上setColor(Color.red);难看?换setColor(Color.green);靠,还不行?你啰嗦不啊?干脆直接setColor(Color.black)了事。你说,这界面怎么能好看。
  • 严格执行美工的设计。一旦美工设计好了效果图,程序员应当”一个像素不差“的把它实现出来,而不要自作聪明的随意修改,或以”技术上不好搞“为借口退缩。要把界面设计当做艺术品一样对待。记住细节才是成败的关键和真正出彩的地方。上面的抓图,我们都是把运行结果抓图放大后逐个边框圆角甚至像素比对审核,做到"一丝不差",这样才能真正的出设计精品。
  • 把握”以用为本“的原则。软件是艺术,但不是艺术品,光看不碰;软件之本在于用。如果脱离了”易用“和”以用户体验为中心“的设计宗旨,一味的追求华丽、花哨,忽视了用户的使用体验,则容易本末倒置,哗众取宠,结果自然是一败涂地。
  • 统一的风格。软件的整个界面设计要有鲜明的主题,甚至上升到其理念是什么。然后由专业的美工形成具体的色系,规定其使用方法,并体现到每个页面、每个按钮、每个像素中去。


相信通过这些手段,我们每个人每个团队都可以制作出高质量的软件界面,让使用者赏心悦目,让开发者心旷神怡,让老板心花怒放,让竞争对手心惊胆战!

后记:

如果大家对以上界面感兴趣,我将在后续文章中介绍本项目的设计思路、功能、实现方法,以及相关源代码。本项目主要用到的第三方的包有JGoodies和TWaver。同时希望通过这一系列文章,能够让大家重新认识Swing这把刀,用好这把刀,一路披荆斩棘、砍瓜切菜、攻城拔寨、再创辉煌!



不喜欢看文字的朋友:可直接下载可执行程序。要源代码的朋友:容我再整理几天,但一定会奉上。喜欢更多抓图的朋友:可以在上篇文章《Swing是一把刀》中看到更多抓图。

关于绿色

喜欢绿色,喜欢雅黑,无可救药。在这个吵吵闹闹的软件行业,绿色也忽然从“春风一拂千山绿”唯美变成俗不可耐的buzzword。比如:

  • 绿色软件:大大的buzzword。忽然一夜之间,所有的软件都绿色了,好像不“绿”就跟不上形势。比如绿色杀毒,绿色OFFICE,绿色ERP,绿色windows。反正全绿。
  • 绿色征途:看,精神鸦~片也可以很“绿”的;
  • 绿~坝-花季护航:呃,老好的软件,不多说了;
  • 绿色世博:嗯,喊的老响了。至今一头雾水。
绿色还有一些不好的词,比如人人避之不及的“绿帽子”之类。台湾的绿营也代表了大坏蛋那帮人(至少陈水扁带了个头)。还有“我把老板气的脸都绿了”、”老板整天灯红酒绿“,也都不是什么好词。

不过绿色更多的还是代表了“春天、自然、环保、低碳”等没好的东西。皮尤慈善信托基金(Pew)在一份名为《绿色改变世界》的研究报告中指出,计算机屏幕如果使用绿色可以节省大量电能并降低辐射,保护使用者双眼和皮肤。例如一个全屏显示的绿色软件界面可以让一台液晶显示器消耗功率降低13.7瓦;假设美国电脑拥有量为5299万台来计算,每年仅关机状态功耗一项可以节约6.2亿度电。如果我们把所有的软件都设计成“绿色”这个环保色,每台计算机都会减少5%的电能和辐射,人的情绪也会更加平缓舒畅,心脏和双肺由此减少12%的血液循环负担,从而降低人类对氧气的需求和二氧化碳的呼出达到14%。加上节省下来的对皮肤护理保养、心肺疾病治疗、近视以及眼镜相关行业对自然资源的消耗,每年全球可以节省1400亿美元的资源消耗,相当于减少砍伐8500平方公里的亚马逊热带雨林,对于处于正在复苏之中的世界经济具有说一不二的作用。看来,“绿色软件”,先把自己的界面搞“绿”了,就是地球的一大幸事!

(注意:上述报告和数据纯属胡诌八扯,如有雷同,纯属巧合。)

但是制作一个绿色的软件界面确实一个心愿。尤其是能够体现“枝间新绿一重重,小蕾深藏数点红”的那种感觉!现在终于有了,经过一个多星期的折腾,终于有了一个雏形,在上一篇博文《Swing是一把刀》中给大家看到的:




框架,还是框架

这个程序的设计初衷是快速建立一个美观的Swing应用程序外观。但是,具体来说,它又并非完全是下面几个东西:

  • LookAndFeel:这个程序并非一个LnF。一个LnF会对所有的Swing组件进行重新定义Paint并可以通过UIManager.setLookAndFeel进行启用。这个程序用到了大量LookAndFeel的机制,甚至也直接定义了不少UI。不过它并不是一个完整的LookAndFeel。这些定制完全是为这个程序框架服务的。也就是说,这些UI和重绘机制只有在当前的程序框架起作用,而无法指望一句UIManager.setLookAndFeel就将你的任意Swing程序变成上图风格。
  • 组件库:也不是组件库。其实里面的组件,除了这个OutlookPane(左侧的模块树)是完全新做出来的(而且没有从JTabbedPane继承,也许理论上还经不起太严谨的推敲),其他的组件都是很简单、现成的。例如列表、按钮、菜单等,都是直接用Swing的,只是重载了一些方法或者定义了UI而已。而且我并非是想让大家直接new OutlookPane()这样来使用,而是使用XML文件对整个界面进行配置使用;
  • GUI程序框架:似乎有点大。这仅仅是一个很小的程序而已。

做Java的喜欢满嘴Framework。你要是不能气定神闲一口气提到20个Framework并有意无意的暗示自己很精通,那~都不好意思跟人家说话;最好再能挑一个有点名气的,指手画脚、评头论足、怒其不争一下下,那就像大牛了(例如Hibernate就是个很不错的candidate)。所以,咱这个小程序也就死乞白赖往“框架”上凑凑,反正已经带上了“绿帽子”,也不怕丢人丢到底。

好吧,这是一个框架,虽然我也不知道框架该怎么定义。反正我的设计初衷是:如果你用Swing开发一个类似上图结构的应用程序,那么你可以直接用这个框架。这个程序框,包含了上面菜单、地下状态条、左边模块栏、右边功能快捷列表,中间多tab标签页的各种内容(也提供了几个常用的内容页风格,例如列表、流程图等)。这个窗口已经被封装好,通过XML配置文件来定义菜单、状态条、模块栏、流程图、右边的快捷列表。同时,这些都是联动的。例如:点击左边的模块栏中的子模块,一个对应的流程图会显示在中间tab页;选中流程图中的节点,可以把该节点相关的功能列在右侧。点击右侧列表,可以执行各种定义好的动作(动作通过动作码定义,后面会详细介绍)。使用么,直接new一个窗口类,set各个部分的XML文件名,然后setVisible(true)就OK了。至于中间的各个组件和大家关心的LookAndFeel,则都定义好了,基本上不用太关心细节。

不管怎么说,我们就叫它“框架”吧。

XML配置

每个应用程序都千奇百怪,功能各异。如何用一个同样的界面来组织呢?的确,这个界面并非适合所有人。不过这里的所有菜单、按钮、流程图、图标等,其动作都是可以用一个“动作码”类定义的,所有的动作都会回调一个统一的函数。而我们只要在这个函数处插入监听,就可以拦截具体动作码,执行我们想做的任何事情,例如格式化C盘、往aobama@whitehouse.com邮箱发个垃圾邮件啥的。

例如,要定义主菜单,通过这个XML:

Xml代码 收藏代码
  1. <?xmlversion="1.0"encoding="UTF-8"?>
  2. <menubar>
  3. <menutext="System">
  4. <menutext="OneSubModule">
  5. <menuitemtext="TestReportItem"tooltip="Tooltip"
  6. icon="/free/email.png"action="A001"/>
  7. <menuitemtext="TestReportItem"tooltip="Tooltip"
  8. icon="/free/email.png"action="A001"/>
  9. <menuitemtext="TestReportItem"tooltip="Tooltip"
  10. icon="/free/email.png"action="A001"/>
  11. <menuitemtext="TestReportItem"tooltip="Tooltip"
  12. icon="/free/email.png"action="A001"/>
  13. ...
  14. </menu>
  15. </menubar>

以上XML可以定义一个System的主菜单,以及一个One Sub Module的菜单项,以及一系列的二级菜单。每个菜单都可以设置icon图标、文字、tooltip文字,以及动作码(就是那个action)。如下图:


左侧的模块栏就是典型的Outlook的风格,很多软件干脆都叫它OutlookPane(我这里也是如此)。这个OutlookPane的配置,通过如下类似XML:

Xml代码 收藏代码
  1. <outlook>
  2. <moduletext="EngineeringBox"
  3. icon="/free/test/module_unselected.png"
  4. selected_icon="/free/test/module_selected.png"
  5. network="network.xml">
  6. </module>
  7. </outlook>

同样,主模块(也就是每个大分栏)包含了模块栏的文字、icon图标(选中和未选中两个),以及一个xml文件。这个xml文件包含了一个流程图,流程图包含了具体的子模块。点击展开大模块栏后,所有的子模块也会显示在栏目中,同时模块的流程关系会通过对应的xml文件中定义的方式,显示在一个图形化的流程图界面中,最终显示在中间的tab页上。


可以看到,左侧的模块列表和中间的图形节点是一一对应的。当鼠标选中节点后(变成橙色),左侧的列表对应的项也会被选中。同时,和这个节点(代表了一个具体子模块)相关的功能,都会显示在右侧的快捷列表中(这是通过指向的network.xml文件定义的)

上面例子中的Network.xml内容举例如下:

Xml代码 收藏代码
  1. <?xmlversion="1.0"encoding="UTF-8"?>
  2. <network>
  3. <nodex="30"y="50"
  4. text="APPrepay"
  5. network_text="APPrepay"
  6. tooltip="Thisisatooltipofanode"
  7. icon="/free/test/module.png"
  8. list_icon="/free/test/submodule.png">
  9. <button1tooltip="Tooltip"
  10. icon="/free/test/module_attachment.png"action="X002"/>
  11. <button2tooltip="Tooltip"
  12. icon="/free/test/module_attachment.png"action="X002"/>
  13. <button3tooltip="Tooltip"
  14. icon="/free/test/module_attachment.png"action="X002"/>
  15. <shortcuts>
  16. <separatortext="MostOftenUsedReports"/>
  17. <shortcuttext="AddaPart"tooltip="Tooltip"
  18. icon="/free/submodule.png"action="Z010"/>
  19. <shortcuttext="DeleteaPart"tooltip="Tooltip"
  20. icon="/free/submodule.png"action="Z010"/>
  21. <shortcuttext="APAgingReport"tooltip="Tooltip"
  22. icon="/free/chart.png"action="Z010"/>
  23. <shortcuttext="MRPforAllPartsUsedthisMonth"tooltip="Tooltip"
  24. icon="/free/user.png"action="Z010"/>
  25. <shortcuttext="DashboardofthisMonth"tooltip="Tooltip"
  26. icon="/free/email.png"action="Z010"/>
  27. <shortcuttext="AllOpenPurchaseOrders"tooltip="Tooltip"
  28. icon="/free/chart.png"action="Z010"/>
  29. <shortcuttext="SearchinAddressBook"tooltip="Tooltip"
  30. icon="/free/user.png"action="Z010"/>
  31. <shortcuttext="AllOnlineUsers"tooltip="Tooltip"
  32. icon="/free/email.png"action="Z010"/>
  33. <separatortext="CommonReports"/>
  34. <shortcuttext="AllOpenPurchaseOrders"tooltip="Tooltip"
  35. icon="/free/chart.png"action="Z010"/>
  36. <shortcuttext="SearchinAddressBook"tooltip="Tooltip"
  37. icon="/free/user.png"action="Z010"/>
  38. <shortcuttext="AllOnlineUsers"tooltip="Tooltip"
  39. icon="/free/email.png"action="Z010"/>
  40. <shortcuttext="AllOpenPurchaseOrders"tooltip="Tooltip"
  41. icon="/free/chart.png"action="Z010"/>
  42. <shortcuttext="SearchinAddressBook"tooltip="Tooltip"
  43. icon="/free/user.png"action="Z010"/>
  44. <shortcuttext="AllOnlineUsers"tooltip="Tooltip"
  45. icon="/free/email.png"action="Z010"/>
  46. <shortcuttext="AllOpenPurchaseOrders"tooltip="Tooltip"
  47. icon="/free/chart.png"action="Z010"/>
  48. <shortcuttext="SearchinAddressBook"tooltip="Tooltip"
  49. icon="/test/user.png"action="Z010"/>
  50. <shortcuttext="AllOnlineUsers"tooltip="Tooltip"
  51. icon="/free/email.png"action="Z010"/>
  52. </shortcuts>
  53. </node>
  54. </network>

其中,每个node定义了一个子模块节点。节点上包含x、y坐标信息、文本信息、tooltip、icon图标(中间大的主图标),以及三个按钮。每个node如图所示,可以携带3个按钮。每个按钮可以挂一个图标、tooltip、icon以及动作码。我们可以定义其任意动作。


然后,每个node又携带了一个shortcuts列表,包含了这个节点所有相关的功能点,在node被选中后,以右侧的列表方式列出。如图所示:


此外,流程图中的箭头是通过类似如下XML在network.xml中定义:

Xml代码 收藏代码
  1. <arrowx="170"y="80"direction="left"rotation="0"/>

其中x、y是坐标,direction是方向,可以是上下左右以及斜向共8个方向。此外rotation还提供了旋转角度。如果right_up这个45度的右上角度不符合要求,可以在增加rotation进行进一步调节。

右侧快捷列表的分割文字,也是通过如下xml进行定义:

Xml代码 收藏代码
  1. <shortcuts>
  2. <separatortext="MostOftenUsedReports"/>
  3. <shortcuttext="AddaPart"tooltip="Tooltip"
  4. icon="/free/test/submodule.png"action="Z010"/>
  5. ...

分隔条可以携带一个文字,用来对很多列表项进行分组:


还有工具条也是可以配置的。工具条在这个框架里被放在了模块栏的顶部。通过如下XML配置其按钮:

Xml代码 收藏代码
  1. <?xmlversion="1.0"encoding="UTF-8"?>
  2. <toolbar>
  3. <buttontooltip="Tooltip"icon="/free/test/message.png"action="B001"/>
  4. <separator/>
  5. <buttontooltip="Tooltip"icon="/free/test/user.png"action="B001"/>
  6. <separator/>
  7. <buttontooltip="Tooltip"icon="/free/test/email.png"action="B001"/>
  8. <separator/>
  9. <buttontooltip="Tooltip"icon="/free/test/viewer.png"action="B001"/>
  10. <separator/>
  11. <buttontooltip="Tooltip"icon="/free/test/chart.png"action="B001"/>
  12. <separator/>
  13. <buttontooltip="Tooltip"icon="/free/test/capture.png"action="B001"/>
  14. <separator/>
  15. <buttontooltip="Tooltip"icon="/free/test/image_edit.png"action="B001"/>
  16. <separator/>
  17. </toolbar>

其中<separator>定义了一个分割竖线 ,完全美观之用。显示效果如下:



最后一个配置项是软件右上角的LOGO。每个系统都想有一个地方漂亮的显示咱家的LOGO,那才有成就感!这里通过menubar菜单的XML文件如下配置:

Xml代码 收藏代码
  1. <logoimage="/free/test/logo_company.png"tooltip="WPTPowerTransmission"/>

可以对图片、tooltip进行定义。显示效果如下:


这样,一个完整的”纯绿色“应用程序框架就差不多被”配置“出来了。

程序接口

其实设计初衷是无需使用源代码,直接使用下方提供的jar包即可进行二次开发。因为毕竟通过XML就可以对界面元素进行定义了。如果要把这个框架集成到你的应用程序中,并执行你的具体动作,只需要对free.Shell这个类进行一个函数重载即可。

Java代码 收藏代码
  1. publicvoidcommand(Stringaction){
  2. Stringmessage="Performaction"+action+".";
  3. this.lbStatusMessage.setText(message);
  4. }

在Shell这个类中,界面上所有的按钮、菜单、列表等被点击后,都会回调这个Shell的command方法,并传回action字符串,也就是我们在XML中定义的动作码。接着你就用ifelse或者case啥的进行处理动作吧!当然也可以调用addTab方法在Shell界面中添加一个tab页面。其使用方法会下次详细阐述。

关于效率、内存、布局和其他

有朋友说很担心执行效率、内存占用。这是对Swing常被攻击的的一个老话题了。简单直接的回答是:Swing效率没问题。内存占用没问题。满眼哗哗一片字符串,就一定占内存吗?table有1000行,渲染的花里胡哨,就内存问题吗?完全不是这么一回事。例如,如果你了解Swing的table的renderer机制,其实无论表格有多少行,一个列是用同一个renderer实例来paint的。注意,是一个实例哈。editor也是一样。很多初学者以为这一列用JComboBox编辑,1000行就会create成1000个ComboBox。那就完全错了。Renderer就是一个“橡皮章”,一个章,不停的在每个格子里面“盖章”,重绘就重新盖;Editor就是当一个“萝卜”,哪个“坑”需要编辑时,table动态把它“放”在这个单元格(坑)上面,下次下个单元格需要编辑,再被挪过去。对于一个列来说,就一个萝卜,一个橡皮章。怎么会说“占用内存”?如果说1000行字符串占用内存,那这些字符串用什么语言和平台不占用内存呢?同样字符串在不同的语言和平台上可以说占用内存几乎没什么差别。这个以后可以专门讨论。

在这个本程序中,状态栏上有一个封装好的内存监控工具条:


它的作用是监测目前Java的总的申请的堆内存以及使用的内存。相关代码如下:

Java代码 收藏代码
  1. MemoryMXBeanmemorymbean=ManagementFactory.getMemoryMXBean();
  2. longusedMemory=memorymbean.getHeapMemoryUsage().getUsed();
  3. longtotalMemory=memorymbean.getHeapMemoryUsage().getMax();

上面图片中的“18M/63M”标明默认Java堆大小是64M目前使用了18M。尝试多打开一些表格界面、拓扑图界面,可见内存使用并不是很多。

说Swing的效率低,也不是很有说服力。其实Swing本质还是Java2D在paint东西,看看那些杂乱的Metal***UI源码就知道了。不过Swing确实设计的够复杂啰嗦,一个LnF就能绕死人。这些机制会导致Swing慢一些。不过说到底程序的快慢瓶颈还不是在Swing上,还是在如何设计和使用上。例如线程的处理等等,这些都不是初学者很容易搞定的东西。这也导致Swing总是被贴上“慢”的标签。

自动布局方面,也是比较啰嗦的。如果用IDE的话,推荐NetBeans里面的GUI编辑工具,它使用的实际是Matisse这个layout。这里有两篇官方文章介绍:
http://wiki.netbeans.org/UsingGUIEditor
http://netbeans.org/features/java/swing.html

如果手写代码,我还是推荐一个超强但比较复杂的TableLayout。这里有其介绍:
http://java.sun.com/products/jfc/tsc/articles/tablelayout/

其他简单的布局,大多可以用Swing内置的几个layout搞定。另外尽量多套用Panel进行嵌套,化繁为简。不要试图一次一个Panel+Layout把一个复杂的界面搞定,那样会很累。

有朋友很不齿这里使用到了JGoodies和TWaver,这里也说明一下。在JGoodies的基础上再去定制UI确实美观了不少,不过理论上确实可以彻底抛开JGoodies。这样就要完全彻底重写一整套的LnF了,包括文本框按钮啊所有的东西。目前确实还没有这样的时间和精力,以后会考虑出一个WithoutJGoodies版本。使用TWaver主要是为了做中间的流程图和拓扑图,并且使用的也比较顺手了。如果不需要中间的拓扑图流程图,也可以withoutTWaver,以后会考虑改一版。不过这些东西都是很不错的工具,能用就拿来为我所用,不喜欢用就借鉴一下了事。

程序下载

本程序可以在这里下载

执行方法:解压zip文件,双击其中的run.bat。

运行环境:Java 6。

其他说明:zip包中的free.jar就是本文提到的框架。其他两个jar包是jgoodies lnf和twaver做流程图的支撑包,可不用理会。

由于牵扯到公司的业务,程序的源代码还在整理之中,预计还要一周左右会整理完毕。到时候一定会跟大家分享整个程序的源代码。下一次我会详细介绍每一个组件的制作方法和相关代码。



在《Swing第一刀》和《Swing第二刀》中提到了一个我正在折腾的一个ERP界面小框架,不少童鞋表示灰常感兴趣,这里继续和大家分享一个新的小进展:一个全新的登录界面。也许可以改变一些你对Swing和UI设计的看法。

为啥又是登录界面

登录界面是一个软件系统中最简单的一个界面,也是最重要的一个界面。为什么?因为它是用户看到的第一个界面;因为它是用户每天都要看的界面。要想让用户爱上你的软件,一个美观、耐看、友好的登录界面是一个重要的前提。下功夫做一个好的登录界面,你的系统就成功了一半。这就是为什么很多美工初学者总是喜欢从模仿、创作登录界面开始。

不信咱仔细瞅瞅。瞧,这是你做的登录界面:

抓图来自网络,版权归作者所有

瞧,这是我的登录界面(来自本文案例):

美工设计效果图

程序实际运行图

哪个更讨人喜欢?你是凤姐,我是志玲姐;你是芙蓉姐姐,我是Lady Gaga姐,不大好比哦。嗯,正如你所说,是“各有所长”吧!不过,除非你有严重的自虐倾向,否则你的选择应当跟我一样。

估计有人已经在叫了:“你这是Swing做的?”“用JNI了?”“这透明圆角,这透明文字,这阴影…咋弄的?”“快扔源码!”“Flex做这个太容易了”“花里胡哨有P用?”…你看,程序员的“老毛病”又来了。

不错,这个界面是Swing做的;也没有JNI(从来也没整过JNI);代码也极其简单,就是窗户纸一捅就破;也谈不上多么高深技术;也无意和Flex、.NET之类的技术相比较。我只是想让大家更多的了解Java和Swing的能力,以及我们对待技术和UI的态度。如果急吼吼的看一下代码run一下demo “哦!”一下,继续扭头堆代码,那我就算是白费这劲了。

Swing变冷了,还是变热了

JavaEye上最近关于Swing的帖子络绎不绝,不少人惊呼:Swing似乎难道又要热了?面对这样的疑问,国内著名Java专家、《程序员》杂志特约撰稿人、美国密苏里州立大学计算机科学与技术学院客座教授、JavaEye创始人Robbin却表现的很坦然,他告诉我说“对于我们Swing程序员来说,这是很正常的事情。”在Robbin看来,这是再正常不过的现象了。“Swing本来就很强大,而最近频频出现的热帖也不是因为Swing发展加快了,只是媒体关注变多了。我使用Swing已经30多年了,在我上大学读书开始就接触Swing,”Robbin说,“毫无疑问,我相信Swing会取得成功”。“美洲大陆已经存在上亿年了,只是哥伦布发现了它而已。所以,不必惊呼新大陆很美很新鲜,只能怪我们很傻很无知。”

(注:以上访谈纯属自编自导个人杜撰,如有雷同,纯属巧合。)

Swing做界面,是快刀还是钝刀

用Swing做一个上面的登录界面,需要多少时间?答绝不可能是几分钟。而要做到“技惊四座、一鸣惊人”,用“鬼斧神工”搬的精致来彻底讨好和吸引用户,就必须下足功夫。这往往不是程序员一个人在战斗!美工师、程序员、熟悉业务的系统工程师…需要一起雕章琢句、废寝忘食。然而,好的设计带来的回报也会远远超过你的想象。所以,讨论所谓**技术更“快”和更“慢”毫无意义,当初快如闪电的PB、Delphi如今也难觅踪迹。所以,程序的价值和回报不在于速度,更多的在于我们的态度:精雕细琢成大器,还是粗制滥造堆垃圾呢?

程序员:我们并不在意程序是否好看

真的吗?你真的这样看吗?真是很可怕的一件事情。当然,在Java领域里,只要一提到J2EE、企业应用,大家就会立刻想到业务、重构、集群、云计算、松耦合、SOA、线程池、EJB、JPA、Hibernate、Spring、JBPM、SSH、JBoss、Lucene、Seam数之不尽的buzzword和framework。很多程序员浸淫在各种框架之中乐此不疲、无法自拔,甚至从来没有写下过例如“public classMyClass extends *** implements***”亲手create过一个自己的class,实在是挺可怕。软件说到底是拿来“用”的,不是用来“学”的。再多的框架和技术,最终还是要为用户服务,和用户交互就要有好的UI。UI无非就是UserInterface,是一切between在用户和机器之间的东西。忽视UI技术和UI设计,确实对一个程序员的成长是不利的。

程序员:我们需要左脑还是右脑

美国的斯佩里和日本角田等人的研究表明,左脑支配右半身的神经和感觉,是理解语言的中枢,主要完成语言、逻辑、分析、代数的思考认识和行为,它是进行有条不紊的条理化思维,即逻辑思维,是程序员的典型活跃区域,也是我们的“国家级重点保护区”。喜欢踢足球的程序员们注意了:尽量不要头球;用也要尽量用右脑;用左脑最好戴个摩托头盔先,别把吃饭的家什整坏了。右脑支配左半身的神经和感觉,是没有语言中枢的哑脑,但有接受音乐的中枢,主要负责可视的、综合的、几何的、绘画的思考认识和行为,也就是负责鉴赏绘画、观赏自然风光、欣赏音乐,凭直觉观察事物,纵观全局,把握整体。右脑具有类别认识能力、图形认识、空间认识、绘画认识、形象认识能力,是形象思维。看上去右脑似乎也恰恰是咱们程序员的软肋。但这能很好的解释为什么程序员弹吉他蓬蓬作响却像弹棉花,拉二胡一身汗还是像驴叫,跟大妈吵架被骂的插不上嘴,这不能怨我们自己,原来是有生理原因的。

但是从事逻辑思维的程序员恰恰还需要有很强的创造力。而右脑在创造性工作中具有不可替代的作用。美国科学家在《思维的艺术》一书中,将创造过程分为四个阶段,即准备阶段、酝酿阶段、闪光阶段和验证阶段。这其中,直觉和顿悟是创造的泉源,但是它必须经过语言的描述和逻辑的检验才具有价值。左右脑的这种协同关系是创造力的真正基础和源泉。如果我们一味的抱怨右脑发育不良,甚至自认右脑天生脑残,那就只能一生一世做一个不折不扣的coder编码机器了。

勤奋和聪明,哪个更重要

这个话题有点大。作为程序员,不管缺了哪样,都是致命的,还不如早点放弃这条路的好。这论调似乎很没新意,如果非要选择一个的话,我选择勤奋。不聪明,哪怕做不了程序员还可以干其他的嘛,比如当个官员、干个城管、踢个足球啥的(仅限国足,男队)。但要是不勤奋,恐怕就一事无成了,想做贪官都难(不勤奋你以为就能贪的到啊)。勤奋又聪明,再加上那么一点点激情,你就厉害了!

细节是魔鬼:登录还是登陆?

这真是一个问题。Google一下看,很多人都在争论和分析是“登陆”还是“登录”,这似乎不仅仅是一个软件问题,而变成了一个语言学术研究问题。《北京晚报》还在2007年5月专门发表解青的署名文章《“登陆网站”还是“登录网站”》进行深入的分析。经过一系列复杂的公式推导逻辑运算,作者最终得出的结论是应当使用“登陆网站”。我们程序员语言能力一向很差,虽然“登陆网站”还说得过去,但是软件的login界面如果用“登陆”那我们就会感觉有点点怪了,总让人想起彪炳史册的伟大的“诺曼底”这个地方。毫无疑问,“登录”是更正确的选择。别说程序员不用这样较真,别说这个跟我程序员无关。如果哪个大型软件的login界面用“登陆”两个字,请你立刻鄙视它、嘲笑它、讽刺它、挖苦它、打击它、永远别买它、永远离开它,离啊离开它,离~开~它!不服气现在就重启系统看看Windows界面用的什么,去Google一下主流软件抓图用的是什么。如果你做的软件真的由此而不能卖出去,此事也与你无关吗?细节决定成败,细节就是魔鬼,这不是口号。你不提防它,它就吃掉你。

Swing与不规则窗体的“哥德巴赫猜想”

不管你信不信,“如何用Swing做不规则窗体”是近10年来Swing被最常问到的几个问题之一。Google上搜索“Swing 不规则窗体”有将近10万个中文结果,而且从1999年就有人在致力于这一问题的解决方法,至今甚至已经出现多种不同“流派”的解决方案。

第一招:抓屏法

最早出现的比较靠谱的一个解决方法出现在《Swing Hacks》一书中。《SwingHacks》一书是著名的图书出版商O’Reilly在2007年出版的Swing技巧图书,作者是Swing开发组成员JoshuaMarinacci,他也是目前JavaFX开发组成员。在这本书的41章中介绍了一种制作透明窗体和不规则窗体的方法。由于当时的JDK功能所限,Java本身并未提供任何直接的API来制作半透明或不规则窗体,所以这一技巧是利用Robot来截取屏幕原有内容生成内存图片,然后将图片显示在Swing的窗体中作为背景,“欺骗”大家的眼睛误以为是窗口“透”过去了。这一招确实体现了程序员的聪明才智。

不过这一方法的缺陷是,当窗口被移动一下后就会露馅,所谓“透明”区域的抓图不知道位置变化,也不会随之改变。所以在该书的例子程序中,作者又将JFrame的setUndecorated设置为true去掉了标题栏,让你无法移动窗口;再启动了一个线程,每250毫秒就重新抓屏一次,更新Swing窗口的图片背景。不过由于Swing窗口显示出来后,它本身又遮挡了屏幕后面的物体,作者只好先frame.hide()把窗口隐藏一下,然后马上抓图,然后再frame.show恢复窗口显示。透明效果是勉强出来了,但是程序在那里有事没事的一直忽隐忽现,真是够怪异的,效率、实时性也都惨不忍睹。

以下是相关代码:

Java代码 收藏代码
  1. publicvoidrun(){
  2. try{
  3. while(true){
  4. Thread.sleep(250);
  5. longnow=newDate().getTime();
  6. if(refreshRequested&&
  7. ((now-lastupdate)>1000)){
  8. if(frame.isVisible()){
  9. Pointlocation=frame.getLocation();
  10. frame.hide();
  11. updateBackground();
  12. frame.show();
  13. frame.setLocation(location);
  14. refresh();
  15. }
  16. lastupdate=now;
  17. refreshRequested=false;
  18. }
  19. }
  20. }catch(Exceptionex){
  21. p(ex.toString());
  22. ex.printStackTrace();
  23. }
  24. }

《Swing Hacks》中介绍的方法代码片段

第二招:AWTUtilities.setWindowShape法

随着Sun公司对JavaFX技术的猛醒和大力持续投入,JDK从6就开始从底层为JavaFX的未来做好准备,提供更多底层功能支撑。作为“酷”、“炫”的UI技术的先锋,窗口透明、不规则窗口自然是将来JavaFX不可缺少的元素和特性。所以,Sun在JDK6中提供了几个新的函数,用来支持窗口透明度、窗口任意形状:

Java代码 收藏代码
  1. voidsetWindowOpacity(Windowwindow,floatopacity)//设置窗口透明度
  2. voidsetWindowShape(Windowwindow,Shapeshape)//设置窗口形状

这里有官方的具体介绍:

http://java.sun.com/developer/technicalArticles/GUI/translucent_shaped_windows/

setWindowOpacity方法提供了官方的、彻底的方法来生成不规则形状窗体。不过依旧有以下几个问题:

  • AWTUtilities并非JDK公开类,将来可能会发生变化。当然除了编译时的一个不爽的警告外,也不用过度担心,即使将来API发生变化,相信Sun和Oracle也会妥善处理好。
  • 用Shape形状定义的窗口边缘粗糙,显示效果差。使用setWindowShape函数对窗口设定形状后,其窗口切割的边缘并未做抗锯齿(anti-alias)处理,也没有相应的函数或参数进行控制,导致显示效果粗糙。看看Sun自己做出来的例子:http://java.sun.com/developer/technicalArticles/GUI/translucent_shaped_windows/9.jpg,一个简单的椭圆Shape,其边缘就已经粗糙不堪。更不用说更复杂的透明图片边缘了。本人经过好几天的反复尝试,发现其效果始终不甚理想(如图),无论对图片的透明边缘如何精细处理,甚至直接new Shape,都完全达不到美工设计出来的效果图。
  • 透明PNG图片的边缘Shape不好获取。如果我们的窗体不是一个规则的、可定义的几何形状Shape,而是一个任意透明PNG图片,该如何获取图片的透明边缘Shape,进而设置window的不规则形状呢?这确实是一个难题。在网上有人专门讨论这一算法,基本上是读取PNG图片的每一个像素,获得像素透明边界点,对边界点进行不断的合并与逼近,最后形成一个最终Shape。TWaver的TWaverUtil工具类中就有一个getImageShape方法用来获得任意图片的边缘shape。经反复测试验证,就是采用了这种算法。不过这种算法的缺点很明显:边缘必须是连续的,甚至必须是“外凸”的;如果png图片中间有一个透明的“洞”,甚至边缘有一个凹陷透明区域,生成的Shape都无法准确反映出来。

第三招:终极解决之道

经过反复的研究探索,终于获得了一个完美的解决方法:不用shape、不用抓图、不用workaround,真正的、彻底的、完全的、随意的在桌面上任意绘图、涂鸦、撒野,真正的属于程序员的Freedom!下面就来一起揭开这层窗户纸吧!

在程序中依次设置以下几个参数:

  • 设置窗口完全透明:AWTUtilities.setWindowOpaque(frame, false);
  • 设置窗口无边缘:frame.setUndecorated(true);
  • 设置窗口的ContentPane为要显示的Pane:frame.setContentPane(myPane);
  • 在myPane中放置具体要显示的内容,也可以重载paint方法进行Java2D绘制。这些paint会直接发生在桌面背景上。
  • 接下来,就是见证奇迹的时刻!

(不好意思,暴露我的桌面了)

通过上面方法,可以做一个任意大小、任意位置的window,在相应的桌面位置上随意显示Swing组件,或做任意Java2D画图。比如下面小例子可以在屏幕上直接画一个红色的立体矩形,而没有显示窗口:

Java代码 收藏代码
  1. importcom.sun.awt.AWTUtilities;
  2. importjava.awt.Color;
  3. importjava.awt.Graphics;
  4. importjavax.swing.JFrame;
  5. importjavax.swing.JPanel;
  6. publicclassTest{
  7. publicstaticvoidmain(String[]args){
  8. JFrameframe=newJFrame();
  9. frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
  10. frame.setUndecorated(true);
  11. frame.setBounds(500,500,300,300);
  12. AWTUtilities.setWindowOpaque(frame,false);
  13. JPanelpane=newJPanel(){
  14. @Override
  15. publicvoidpaint(Graphicsg){
  16. super.paint(g);
  17. g.setColor(Color.red);
  18. g.fill3DRect(10,10,100,100,true);
  19. }
  20. };
  21. frame.setContentPane(pane);
  22. frame.setVisible(true);
  23. }
  24. }

运行效果如下图:


窗口的拖拽移动

窗口不再规则,窗口标题栏不再出现,如何移动窗口?按照其他类似软件的习惯做法,应当允许用鼠标直接拖拽窗体任意位置进行窗口移动。做一个鼠标监听器对窗口中的元素进行拖动监听,对窗口进行相应拖动距离的移动:

Java代码 收藏代码
  1. privateMouseAdaptermoveWindowListener=newMouseAdapter(){
  2. privatePointlastPoint=null;
  3. @Override
  4. publicvoidmousePressed(MouseEvente){
  5. lastPoint=e.getLocationOnScreen();
  6. }
  7. @Override
  8. publicvoidmouseDragged(MouseEvente){
  9. Pointpoint=e.getLocationOnScreen();
  10. intoffsetX=point.x-lastPoint.x;
  11. intoffsetY=point.y-lastPoint.y;
  12. Rectanglebounds=FreeLoginUI.this.getBounds();
  13. bounds.x+=offsetX;
  14. bounds.y+=offsetY;
  15. FreeLoginUI.this.setBounds(bounds);
  16. lastPoint=point;
  17. }
  18. };

对窗体上的组件安装这一listener,就可以对窗口中任意元素进行拖拽,直接拖动窗体四处晃悠了。

图片的切割

要做好的界面,需要一个耐心、有创意的美工大力协助,例如图片的切割就很重要。下图展示了如何从效果图进行具体切割素材:


制作渐变背景Panel

仔细观察中间的输入区域部分,其背景是有渐变设计的。其制作方法也很简单:首先让美工帮助制作一个一个像素宽、整个panel高度的小图片作为素材;然后用这个图片创建纹理Paint;最后用这个纹理对真个panel进行fill。

Java代码 收藏代码
  1. privateJPanelinputPane=newJPanel(){
  2. privateStringbackgroundImageURL=FreeUtil.getImageURL("login_background.png");
  3. privateTexturePaintpaint=FreeUtil.createTexturePaint(backgroundImageURL);
  4. @Override
  5. protectedvoidpaintComponent(Graphicsg){
  6. super.paintComponent(g);
  7. Graphics2Dg2d=(Graphics2D)g;
  8. g2d.setPaint(paint);
  9. g2d.fillRect(0,0,this.getWidth(),this.getHeight());
  10. }
  11. };

肆虐你的桌面:六月飘雪!

既然窗户纸捅破了,在桌面上就随意折腾吧。这几天窗外酷热难耐,咱们就来个桌面飘雪,也许可以望梅止渴,带来丝丝清凉吧!

先准备一个雪花的png透明图片,然后在桌面上随机生成50个雪花坐标,每次paint让每个雪花的左右略微抖一下(snowX[i] +=TWaverUtil.getRandomInt(5) - 3),垂直距离下坠5像素(snowY[i] +=5),再旋转个小角度。然后,用一个线程不停的repaint窗口。

雪花png图片:


程序代码如下:

Java代码 收藏代码
  1. publicclassTestSnow{
  2. publicstaticvoidmain(String[]args){
  3. finalJFrameframe=newJFrame();
  4. frame.setAlwaysOnTop(true);
  5. frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
  6. frame.setUndecorated(true);
  7. frame.setExtendedState(JFrame.MAXIMIZED_BOTH);
  8. AWTUtilities.setWindowOpaque(frame,false);
  9. finalJPanelpane=newJPanel(){
  10. privateint[]snowX=null;
  11. privateint[]snowY=null;
  12. privateint[]angles=null;
  13. privateintcount=50;
  14. @Override
  15. publicvoidpaint(Graphicsg){
  16. super.paint(g);
  17. Rectanglebounds=frame.getBounds();
  18. if(snowX==null){
  19. snowX=newint[count];
  20. for(inti=0;i<snowX.length;i++){
  21. snowX[i]=TWaverUtil.getRandomInt(bounds.width);
  22. }
  23. snowY=newint[count];
  24. for(inti=0;i<snowY.length;i++){
  25. snowY[i]=TWaverUtil.getRandomInt(bounds.height);
  26. }
  27. angles=newint[count];
  28. for(inti=0;i<snowY.length;i++){
  29. angles[i]=TWaverUtil.getRandomInt(360);
  30. }
  31. }
  32. Graphics2Dg2d=(Graphics2D)g;
  33. Imageimage=TWaverUtil.getImage("/free/test/snow.png");
  34. for(inti=0;i<count;i++){
  35. snowX[i]+=TWaverUtil.getRandomInt(5)-3;
  36. snowY[i]+=5;
  37. angles[i]+=i/5;
  38. snowY[i]=snowY[i]>bounds.height?0:snowY[i];
  39. angles[i]=angles[i]>360?0:angles[i];
  40. intx=snowX[i];
  41. inty=snowY[i];
  42. intangle=angles[i];
  43. g2d.translate(x,y);
  44. doubleangleValue=Math.toRadians(angle);
  45. g2d.rotate(angleValue);
  46. g2d.drawImage(image,0,0,null);
  47. g2d.rotate(-angleValue);
  48. g2d.translate(-x,-y);
  49. }
  50. }
  51. };
  52. frame.setContentPane(pane);
  53. frame.setVisible(true);
  54. Threadthread=newThread(){
  55. @Override
  56. publicvoidrun(){
  57. while(true){
  58. try{
  59. Thread.sleep(10);
  60. }catch(Exceptionex){
  61. ex.printStackTrace();
  62. }
  63. pane.repaint();
  64. }
  65. }
  66. };
  67. thread.start();
  68. }
  69. }

快快运行代码,让雪花飘起来吧!


如果愿意折腾,还可以修改代码中的:

  • private int count = 50,调整雪花的数量;
  • 修改angles[i] += i / 5,调整雪花翻滚的速度;
  • 修改snowY[i] += 5,调整雪花下坠的速度;
  • 修改snowX[i] += TWaverUtil.getRandomInt(5) – 3,调整雪花左右摆动的速度;

别说你不知道怎么结束程序啊,不会Alt+F4的话,你这个程序员肯定不合格了。

秘密背后的秘密

当把透明窗口Frame设置特别大以后(例如10000*10000),你会发现不但界面变得极其缓慢,而且还会内存溢出。Sun的秘密不言自明了:还是使用了BufferedImage。否则,鼠标点击你画的椭圆或桌面的图标,它如何知道是点击了窗体,还是操作了桌面?只能生成内存图片,在里面进行像素判断了。要挖掘再深入的秘密,我也不清楚了,自己继续探索吧!

代码和可执行文件

作为一个完整的小Swing UI框架的一部分,可以在本文下方下载整个zip包,执行其中的login.bat来运行登录窗体、ERP程序框架、屏幕下雪例子。源代码还在整理中,请大家耐心等待。关于整个框架的更多介绍,可以查看《Swing第一刀》和《Swing第二刀》。希望大家喜欢,并给我留言,也希望各位高手批评指正!

谢谢!




分享到:
评论
1 楼 kevonwang 2015-03-13  
太震撼了,非常感谢,让我对Swing有了一个全新的认识。

相关推荐

Global site tag (gtag.js) - Google Analytics