q段旉真是忙得要死Q一斚w要开发公叔R目的pȝ框架Q要项目分成不同的子项目,~写核心代码Q另一斚w要将极限~程QXPQ引入团队开发,部v各类
XP需要的服务例如subversion啦,ant+ivy啦,Hudson啦等{。顺便说句题外话Qubuntuq真是不是一般的好用Q徏议有能力的全
部{到ubuntu上去开发?
我目前开发的q个框架的客L是具肥的客户端,也就是Swing客户端了。Swing应用相对于Web应用有很多优势,因ؓ它更肥。数据验证就?
其中一个。当然现在的Web应用通过使用Ajax也要比以前强很多了,但是q是避免不了在验证数据时向服务段发出hQ至你无法避免验证l果从Web?
务器传输到用h览器上这D过E。而Swingq类肥客L可以实现完全在本地对数据q行验证Q甚臛_以断|l工作(q也是Web应用目前在研发的一?
重要NQ?
前段旉开发出了一个可以应用于所有Swing应用的通用数据验证模块Q发现它在项目中使用后,对于普通的数据验证,E序员几乎不需要编码,效率提高了不,写了一博文拿出来和大家分享。原文是用英文写的,在这里:http://polygoncell.blogspot.com/2008/07/validation-module-for-swing-application.html。英文好的朋友可以直接去那里看?
~写q个模块使用了很多不同的开源框架和cdQ其中很重要的一个就是JXLayer。文章写完后Q我p去邀请JXLayer的作者Alexp来指点一下,然后在我的文章后面开始了一D讨论,挺有意思的Q他不愧为是Swing team里面的牛人啊Q厉宛_Q呵c?
okQ回C天这文章的正题。今天的主要目的是将我的英文博文译成中文(自己的文章,我就不逐字逐句译了,意思到了就行了Q可能还会随兴展
开一番讨论)在这里展C给大家Q与大家分n开发经验,希望大家能够从中LQ也希望能够以文会友Q广交朋友。废话少_切入正题?
数据验证QValidationQ一直是软g开发中非常重要的一环,有了它,你的pȝ会让客户感到更加友善Q同时你的系l也得到了一定程度的?
护。一般来_数据验证既可以在客户端也可以在服务端。默认的JSF数据验证是在服务端Q数据只能在被提交以后才能够被验证,然后把错误信息传递回用户
的浏览器。后来大规模使用Ajax后,基本可以实现对修改的数据“x”验证Q注意这里是个打了引LxQ数据事实上q是要在览器和服务端之间进行传
递的Q只不过Ajax这U传递改为隐式了而已Q理Zq没有真正实玎ͼ断网Q即旉证。而在Swing应用上就能够达成q种愿望?
事实上,开发Swing应用Ӟ数据验证一直比较棘手,需要手工编码的地方太多Q效率不高。后来出了JGoodies Validation
l合JGoodies binding后,好了一些。这个JGoodies
Validation既可以实现model层面的验证,也可以实现Bean层面的验证,但是多年使用下来Q发现其实它比较适用于中项目,而且要编写的?
码其实一点不比自己手动编写的?
JGoodies行了一D|间后Qsun开始推qbeanl定ҎQbeansbindingQJSR
295Q,我个人感觉要比JGoodies
binding好用QJGoodies的作者Karsten也在专家l里Q这个h我以前和他一起共事过Q我的msn
space里面q有跟他的合影,l对是Swing界的牛hQ。这个beansbinding也提供数据验证,但是它的q个数据验证只是在target被改
动后Q数据被同步回source之前才会起作用,使用h局限性比较大Q而且~码量也不小?
׃目前l大部分目是基于POJO的,Hibernate
validator已经提供了一个很好的数据验证框架Q我们完全没必要再重复发明轮子,我们应该努力站在巨h的肩膀上,q样我们才能站得更高Q看得更q?
于是我考虑l合beansbinding和Hibernate
Validator开发数据验证。还有一个重要的问题Q那是数据错误的时候,需要在用户界面上展C相应的信息Q例如Error
icon和错误提C,q部分我考虑使用JXLayer?
你可以在如下链接中找到相x架的具体信息Q?
1. Hibernate ValidatorQ?http://www.hibernate.org/hib_docs/validator/reference/en/html_single/
2. Beansbinding: https://beansbinding.dev.java.net/
3. JXlayer: http://weblogs.java.net/blog/alexfromsun/
阅读q篇文章Q不需要你熟悉q些cdQ不q了解这些类库能够帮助你更好地理解这文章?
我的q个通用模块是参考JXLayer里面的一个democTextValidationDemo的,q个JXlayer是由Alexander Potochkin开发的Q我很喜Ƣ,使用h很顺手,强烈推荐使用?
下面开始介l代码。首先是建立一个java目Q对于这个小目Q我使用netbeans。这里说句题外话Q中型和大型的Swing应用Q徏议最
好还是不要用netbeans的GUI
BuilderQ一斚w它生成的代码烂,另一斚w很难试。目前市面上有很多好用的layout的框Ӟ例如 JGoodies
form和MigLayoutQ开发效率绝对不比netbeans的GUI builder差,你还不需要面对o人头疼的机器成的代码?
目创徏好后Q加入类库:
然后写一个persistence bean:
package de.jingge.domain;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import org.hibernate.validator.Length;
import org.hibernate.validator.NotEmpty;
@Entity
public class Country extends AbstractBean {
private static final long serialVersionUID = 5341382564159667599L;
public static final String PROPERTYNAME_NAME = "name";
public static final String PROPERTYNAME_CODE = "code";
private String name;
private String code;
private Long id;
public Country() {
}
public Country(String code, String name) {
super();
setCode(code);
setName(name);
}
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
@NotEmpty
public String getName() {
return name;
}
public void setName(String name) {
firePropertyChange(PROPERTYNAME_NAME, this.name, this.name = name);
}
@Length(min=2, max= 2, message="Code length must be 2")
@NotEmpty
public String getCode() {
return code;
}
public void setCode(String code) {
firePropertyChange(PROPERTYNAME_CODE, this.code, this.code = code);
}
}
q里我ؓ了强调可以在Swing客户端直接用和验证persistence beanQ故意写了一个persistence beanQ实际应用中Q这个类只需要是一个pojop了?
q个CountrycM表一个国Ӟ它有两个属性,code和nameQ我l他们分别加上个各自的验证限制。code不能为空Q且必须正好是两?
字符Q例如CNQDEQUS。name不能为空。这些annotaion均出自Hibernate
Validator。那个父cAbstractBeanSwingXcdQ我们的Countrycȝ承了它之后就可以支持property
change event了?
ok, 下面可以开始编写这个模块的核心代码了。前面说q,我会使用JXlayer。用它的好处是Q所有JXlayer的painting
event都会被{到UIcLQ我们只需要编写一个集成Hibernate
Validator的UIcd可以了,我称q个cMؓHibernateValidationUIQ代码如下:
package de.jingge.view;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import javax.swing.BorderFactory;
import javax.swing.JComponent;
import javax.swing.text.JTextComponent;
import org.hibernate.validator.ClassValidator;
import org.hibernate.validator.InvalidValue;
import org.jdesktop.beansbinding.ELProperty;
import org.jdesktop.beansbinding.PropertyStateEvent;
import org.jdesktop.beansbinding.PropertyStateListener;
import org.jdesktop.jxlayer.JXLayer;
import org.jdesktop.jxlayer.plaf.AbstractLayerUI;
/**
* Header:
* Description: A layerUI which will validate the referenced property value of
* the object each time when the paint(
) method is called.
* The value of the given object property will be observed.
* Note: This UI works only with {@link JXLayer}. Any change of the property
* will force repainting the UI. The work process looks like: property changed ->
* jxlayer will be repainted -> the paint(
) method of this UI will be called.
* The logic of validation will be handled by the Hibernate validator
* framework.
*
*/
public class HibernateValidationUI extends AbstractLayerUI<jTextComponent> {
private Object object;
private String propertyName;
private ClassValidator validator;
private ELProperty elProperty;
private PropertyStateListener propertyChangeHandler;
public HibernateValidationUI(Object obj, String propertyName) {
this.object = obj;
this.propertyName = propertyName;
propertyChangeHandler = new PropertyChangeHandler();
validator = new ClassValidator(obj.getClass());
elProperty = ELProperty.create("${" + propertyName + "}");
}
public void installUI(JComponent c) {
super.installUI(c);
elProperty.addPropertyStateListener(object, propertyChangeHandler);
}
public void uninstallUI(JComponent c) {
super.uninstallUI(c);
elProperty.removePropertyStateListener(object, propertyChangeHandler);
}
protected void paintLayer(Graphics2D g2, JXLayer<jTextComponent> l) {
super.paintLayer(g2, l);
InvalidValue[] validationMessages = validator.getInvalidValues(object,
propertyName);
if (validationMessages.length > 0) {
BufferedImage image = Java2DIconFactory.createErrorIcon();
g2.drawImage(image, l.getWidth() - image.getWidth() - 1,
l.getHeight() - 8, null);
l.getView().setToolTipText(validationMessages[0].getMessage());
return;
}
l.getView().setToolTipText(null);
}
boolean isValid() {
return validator.getInvalidValues(object, propertyName).length == 0;
}
class PropertyChangeHandler implements PropertyStateListener {
@Override
public void propertyStateChanged(PropertyStateEvent pse) {
setDirty(true);
}
}
}
q个HibernateValidationUIcd有一个构建器Q它接收两个参数Q一个是source objectQ也是我们要修改的那个Beancȝ实例Q另外一个是q个bean的一个属性,q个HibernateValidationUIp责验证这个属性?
在installUI()Ҏ中,我们启动对属性变化的观察c,而在uninstallUI()Ҏ里面Q我们需要卸载这个观察类?
当给定对象的属性值发生变化时QPropertyChangeHandler的propertyStateChangedQ)Ҏ׃被调用,q?
个功能是通过elProperty和PropertzChangeHandler相结合来实现的。在propertyStateChangeed()Ҏ
里UIcȝҎsetDirty()会被调用Q该Ҏ的调用会DUIcȝ状态变化,q而引?re)paintingQ之后经q一pd的方法调用传
递,paintLayer(Graphics2D g2, JXLayer<jTextComponent>
l)q个Ҏ会被调用,q个Ҏ要做的就是我们这个数据验证模块的核心功能Q?
1. 调用Hibernate Validator验证该属性?
2. 如果数据不正,则在GUI上显CZ个error iconQƈ且将错误信息作ؓtooltip展示l用戗?
在第二点里面产生了一个问题,谢谢AlexpҎ的指炏VSwing
team里面有一些规定,其中之一是Q在paint()Ҏ里面最好不要改变Component的状态,而setTooltip()Ҏ会改变
component的状态,因此需要在paint()Ҏ之外调用。我目前使用下来Q还没有发现什么严重的错误Q决定暂时不改了Q回头有旉在将q个代码
L一下?
cM用到的Java2DIconFactory代码如下Q?
package de.jingge.view;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.image.BufferedImage;
public class Java2DIconFactory {
public static BufferedImage createErrorIcon() {
return createErrorIcon(7, 8);
}
public static BufferedImage createErrorIcon(int width, int height) {
BufferedImage icon = new BufferedImage(width, height,
BufferedImage.TYPE_INT_ARGB);
Graphics2D g2 = (Graphics2D) icon.getGraphics();
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g2.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL,
RenderingHints.VALUE_STROKE_PURE);
g2.setColor(Color.RED);
g2.fillRect(0, 0, width, height);
g2.setColor(Color.WHITE);
g2.drawLine(0, 0, width, height);
g2.drawLine(0, height, width, 0);
g2.dispose();
return icon;
}
}
没什么太多好解释的,是使用Java 2DM个Error icon?
接着Q我们需要编写一个Factoryc,构徏一个JTextFieldQ尽量把复杂技术封装v来,q样E序员开发v来可以提高效率,代码如下Q?
package de.jingge.view;
import javax.swing.JTextField;
import javax.swing.text.JTextComponent;
import org.jdesktop.beansbinding.AutoBinding;
import org.jdesktop.beansbinding.BeanProperty;
import org.jdesktop.beansbinding.BindingGroup;
import org.jdesktop.beansbinding.Bindings;
import org.jdesktop.beansbinding.ELProperty;
import org.jdesktop.jxlayer.JXLayer;
import static org.jdesktop.beansbinding.AutoBinding.UpdateStrategy.*;
public class GuiComponentFactory {
public static JXLayer<jTextComponent> createTextField(
BindingGroup bindingGroup, Object sourceObject,
String sourceProperty) {
JTextField field = new JTextField();
AutoBinding binding = Bindings.createAutoBinding(READ_WRITE,
sourceObject, ELProperty.create("${" + sourceProperty + "}"),
field, BeanProperty.create("text"));
bindingGroup.addBinding(binding);
bindingGroup.bind();
return new JXLayer<jTextComponent>(field, new HibernateValidationUI(
sourceObject, sourceProperty));
}
}
createTextField()Ҏ主要给定对象属性的gJTextField的textl定Q然后将JTextFieldU_?
JXLayer的管理之下。这样一来,一旦用户在JTextField里面修改数据Q这个改变就会同步到该对象属性上Q然后就引发了前面描q的一pd?
辑,最l改变的数据׃被Hiberante Validator加以验证?
最后,我们可以~写一个Demo application来看看效果如何,代码如下Q?
package de.jingge.main;
import de.jingge.domain.Country;
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.Toolkit;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.text.JTextComponent;
import net.miginfocom.swing.MigLayout;
import org.jdesktop.beansbinding.BindingGroup;
import org.jdesktop.jxlayer.JXLayer;
import static de.jingge.view.GuiComponentFactory.*;
public class ValidationApplicaton {
private BindingGroup bg;
private Country country;
private JXLayer<jTextComponent> codeField;
private JXLayer<jTextComponent> nameField;
/**
* @param args the command line arguments
*/
public static void main(String[] args) {
try {
UIManager.setLookAndFeel(
"com.sun.java.swing.plaf.nimbus.NimbusLookAndFeel");
} catch (UnsupportedLookAndFeelException ex) {
System.err.println(
"Nimbus L&F does not support. Default L&F will be used.");
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InstantiationException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
ValidationApplicaton app = new ValidationApplicaton();
JFrame frame = new JFrame("Demo Validation Application");
frame.setPreferredSize(new Dimension(360, 150));
frame.getContentPane().add(app.buildPanel(), BorderLayout.CENTER);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setCenter(frame);
frame.setVisible(true);
frame.pack();
}
private static void setCenter(JFrame frame) {
Toolkit toolkit = Toolkit.getDefaultToolkit();
Dimension screenSize = toolkit.getScreenSize();
// Calculate the frame location
int x = (screenSize.width - (int) frame.getPreferredSize().getWidth()) / 2;
int y = (screenSize.height - (int) frame.getPreferredSize().getHeight()) / 2;
// Set the new frame location
frame.setLocation(x, y);
}
public ValidationApplicaton() {
country = new Country();
bg = new BindingGroup();
}
private JPanel buildPanel() {
codeField = createTextField(bg, country, Country.PROPERTYNAME_CODE);
nameField = createTextField(bg, country, Country.PROPERTYNAME_NAME);
JPanel panel = new JPanel(new MigLayout("",
"[50px, right]10[200px:250px:300px]", "[center]"));
panel.add(new JLabel("Code:"), "cell 0 0");
panel.add(codeField, "cell 1 0, w 200px:250px:300px");
panel.add(new JLabel("Name:"), "cell 0 1");
panel.add(nameField, "cell 1 1, w 200px:250px:300px");
return panel;
}
}
q个cL较简单了Q我单解释一下:
在main()Ҏ里面Q我们创Z一个JFrameQ然后放入一个JPanel
setCenter()Ҏ负责窗口至于屏q的正中间?
在构建器里面Q我们创ZCountry和BindingGroup的对象实例?
在buildPanel()Ҏ里面Q我们用MigLayout构徏了一个PanelQ其中codeField和nameField对应各自的对象属性。更多关于MigLayout的信息看q里Q?a target="_blank">http://www.miglayout.com/。这也是一个例子,大家可以看到使用MigLayout开发Swing真的是非常方ѝ?
从这个Demo里面也可以看出,~写好pojo后,E序员只需要调用createTextField(bg, country,
Country.PROPERTYNAME_CODE); 可以创Z个支持数据验证的JTextFieldQ编码量已经可以说是最大限度的降低了?
q行E序Q你会看刎ͼ
q个code和name的数据都不合法,用户看到了error icon?
鼠标移到Text field上,你会看到Q?
填好合法数据后,Error icon׃见了Q?
ȝQ?
使用q个通用数据验证模块有很多好处:
1. 如果目使用ORMQ例如HibernateQ这个方案应该是解决数据验证的最好方案之一?
2. 对于普通的数据验证Q例如非I,emailQ长度等{,E序员根本不需要编码,只要在POJO上用相应的Hibernate Validator annotation可以了?
3. 对于复杂的数据验证,Hibernate Validator提供了很好的扩展机制Q只要写一个annotation外加一个Validator可以了。Swing应用q边仍然不需要编写Q何代码?
lg所qͼ可以看出通过使用q个通用数据验证模块Q开发效率会提高很多?

]]>