最近看了一个朋友的博客文章《打造专业外观-九宫图》,冒出个想法来:做一款能够可视化设计Swing LookAndFeel的工具。基本原理是先自定义一个灵活LookAndFeel,这种LookAndFeel根据xml配置文件加载绘制组件的资源,比如颜色、字体、图标、GradientPaint,甚至包括Java2D绘图原语。这种配置文件是直观可读的,可以使用简单编辑器进行编辑,也可以做个可视化设计工具,来定制这些资源,最后产生配置文件。再调用UIManager.setLookAndFeel设置外观时,传入配置文件,让标准组件的UI Delegate类从这些配置文件中获取资源,从而达到最终所需效果。

比如一个JButton,基本的外观情况包括按下去、抬起来、rollover、获得焦点等几种情况,每种外观又可以根据九宫格原理定制按钮边框,使用xml配置文件定义的颜色、图片或GradientPaint来画出边框。中间的文本区也可根据配置动态获取填充颜色、文字字体及颜色等。开发者使用这个工具只需做些图标,选择颜色、字体,定制颜色渐变,选择几种有限图片就可以了。然后工具生成xml配置文件。这样普通开发者比较容易就能制作自己的皮肤外观,就像是网页的CSS一样,将组件外观和主题分离开来。

没想到早有人就有这种想法,今天看到JavaLobby编辑Daniel Spiewak介绍了著名Swing/Java2D专家Romain Guy的一个开源项目Swing Fuse,其思想基本上满足上面的需要,但缺少一个可视化定制工具,另外资源定制使用properties配置文件的形式,这对于定制颜色、字体、图标、GradientPaint等已经足够,但是定制组件形状就有困难了。比如JCheckBox不仅仅要能改变其前后背景颜色和文本字体,还想改变check box的形状使用圆形,也就是不仅仅改变外观的颜色主题,还要外观的形状。这篇介绍文章在:

 Easier Custom Components with Swing Fuse

Swing Fuse的核心思想可以用下面几行代码和配置文件来说明: 

代码
配置文件

JButtonUI在初始化时调用ResourceInjector将资源注入,然后在其渲染方法paint中使用这些资源。其中使用annotation标识需要资源注入的字段,ResourceInjector在加载这些资源使用反射机制将这些资源插入到UI实例的字段中。

这儿的配置文件已经是直观可读的了,开发者可编写这样的配置文件来配置UI类。如果写一套LookAndFeel以这种方式加载资源,不就可以部分达到文章开始所说的目标了吗?更进一步可编写一个可视化制作软件帮助开发者生成这种配置文件。

我的本来想法更要激进一些,不仅仅像颜色、字体、图标等可以定制,连组件的形状都可以通过描述来定制。这种定制可能就需要表达能力更强的xml配置文件了。其实只要实现这种配置文件的可视化编辑,就能满足许多外观定制要求了。

大家有什么想法?这种可视化定制皮肤的工具有没有市场?有没有价值呢?

评论:

想法不错,成立一个开源项目吧

发表于 邬杰 在 2007年11月10日, 12:42 下午 CST #

“使用annotation标识需要资源注入的字段”,不由想到
swing app framework,应该有不少这样的应用吧,我只知道这个。

发表于 Matthew Chen 在 2007年11月10日, 11:31 下午 CST #

我想普通的程序员不会过多的对可视化定制皮肤的工具感兴趣
吧,一般他们都是对做好了皮肤感兴趣,而不是自己做皮肤。
当然这只是我的想法,我也不知道具体情况怎样。

发表于 小虫 在 2007年11月11日, 10:16 上午 CST #

关于专业外观,我的原则是:组件按界面效果划分应分为两种,招牌组件 和 一般组件。比如IM(Instant Message)通讯客户端,没必要把设定界面、颜色对话框、文件对话框也装扮得很绚丽,这类组件可认为是一般组件。而需要装饰的则是交互窗口,比如发送文本消息的按钮,这类组件是招牌组件。

L&F应该具备通用性,我觉得像一般组件的外观应该用L&F解决,而像招牌组件,最好采用自定义组件,因为这类组件出现不是很多,而且自定义组件不受L&F机制的限制。这是我的看法。

设计UI,漂亮是一方面,UI与业务分离更重要。而这两点正是目前GUI工具缺乏的,用配置文件生成UI组件不仅能做到前后台分离,而且因为不是生成Java Code,所以还可能将这部分工作分配给美工。

发表于 电玩 在 2007年11月11日, 01:29 下午 CST #

LZ,我自己写过一个L&F,但是还是个半成品,有些细节的组件UI还没有实现,当初的想法是把这个工程当成模板,但是如果有工具支持当然更好了。因此想和您合作完成 自定义L&F 的介绍,每一个标准swing组件都有对应的...UI,所以采取每帖介绍一个组件的UI实现,再整个到一起。您看?
不过最近还有不少事情要做,可能时不我待。

发表于 电玩 在 2007年11月11日, 01:45 下午 CST #

请教:netbeans's swing在 jdk1.5下需要 'swing布局扩展包',那在 jre1.4 下是否同样这样运行?还是会出现问题?

发表于 crystalK 在 2007年11月11日, 02:37 下午 CST #

使用 jdk1.4 + 'swing布局扩展包',可以在 jre 1.4 下运行么?

发表于 crystalK 在 2007年11月11日, 02:39 下午 CST #

发表于 222.94.3.198 在 2007年11月11日, 09:28 下午 CST #

我的留言为什么被删除了?

发表于 zc 在 2007年11月12日, 08:35 下午 CST #

你的留言肯定有链接。这套博客系统有个防止spam的功能,把可能的广告给直接过滤去了。如果你想要加链接,需要加空格处理。

发表于 williamchen 在 2007年11月13日, 11:30 上午 CST #

有个问题请教楼主,如何实现jtree针对不同节点的组件有不同的行高度,我现在被这个问题困扰了很久,想用拖拽来改变节点的行高,在DefaultTreeCellRenderer里处理重绘出来,但是在getTreeCellRendererComponent()里面用setSize()怎么改都不行,网上没搜到这样的例子。

发表于 Matthew Chen 在 2007年11月13日, 11:33 上午 CST #

我在实际项目中还没遇到过你这样的需求,但是如果结点高度恒定,可以通过重写DefaultTreeCellRenderer类的getPreferredSize()得到。注意不要调用JTree的setRowHeight(int )方法。
如果是动态调整高度,比如单击选择Node,这个Node要变大,这个我还没研究过。
getPreferredSize很重要,布局管理器都是靠组件的getPreferredSize方法判断定位的,JTree里面的每一个Node也是如此,有时候比setSize能实现更多的东西。
像swing这类刁钻问题网上基本没有解决的,唯一的出处就是看源代码调用关系。

发表于 电玩 在 2007年11月13日, 01:59 下午 CST #

发恨了,在getTreeCellRendererComponent()里的return之前加了句tree.setRowHeight(-1);当然按电玩说的setPreferredSize()了,结果发现节点在展开和折叠的时候可以改变行高了(因为我是用JLabel作Renderer,height用随机正值),其他情况都还不行。去看看expand的时候发生了什么。

发表于 Matthew Chen 在 2007年11月13日, 06:45 下午 CST #

Hi Matthew,
最近比较忙,没看到你的问题。
你最好研究一下JTee的代码看看每个cell的尺寸是怎么决定的。
根据Jtree的源码:
/**
* Sets the height of each cell, in pixels. If the specified value
* is less than or equal to zero the current cell renderer is
* queried for each row's height.
*
* @param rowHeight the height of each cell, in pixels
* @beaninfo
* bound: true
* description: The height of each cell.
*/
public void setRowHeight(int rowHeight)
{
int oldValue = this.rowHeight;

this.rowHeight = rowHeight;
rowHeightSet = true;
firePropertyChange(ROW_HEIGHT_PROPERTY, oldValue, this.rowHeight);
invalidate();
}
你可以发现:
首先你需要调用Jtree.setRowHeight(-1);
然后你在你的TreeCellRenderer中设置renderer component的preferedSize就可以了。

发表于 William Chen 在 2007年11月14日, 05:12 下午 CST #

顺便问问LZ,“动态调整Cell的高度,比如单击选择Node,这个Node要变大”这个功能实际项目中没遇到过,所以没考虑。

我知道TreeCellRenderer中的getTreeCellRendererComponent有个boolean sel,的参数借助它调整preferedSize,但是这个Cell就只显示部分区域了,怎么刷新树呢?invalidate()?

发表于 电玩 在 2007年11月14日, 05:50 下午 CST #

基于delegate实现的mvc调用链好长啊,还没时间好好看下(也有本人技术薄弱的原因),我开eclipse的debug看得头都大了,其实知道table结构可以setRowHeight(row,height)的,所以考虑过找个treetable来做,lz blog里提到的那个其实有些问题(在调整行高时),打算研究下swingx或者l2fprod的。另外,lz有没有发现swing画图的时候大写的O在新宋体下有缺口。我在菜单或是自己画图的时候都出现了。

发表于 Matthew Chen 在 2007年11月14日, 09:44 下午 CST #

最近www.easynth.com出了一个外观编辑器,大家看看?

发表于 Lanbo 在 2008年04月14日, 06:51 下午 CST #

发表一条评论:
  • HTML语法: 禁用

This blog copyright 2009 by williamchen