<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    PC的blog

    Finding... Thinking... Solving...

    BlogJava 首頁 新隨筆 聯(lián)系 聚合 管理
      9 Posts :: 0 Stories :: 54 Comments :: 0 Trackbacks
    這段時(shí)間真是忙得要死,一方面要開發(fā)公司項(xiàng)目的系統(tǒng)框架,要將項(xiàng)目分成不同的子項(xiàng)目,編寫核心代碼;另一方面要將極限編程(XP)引入團(tuán)隊(duì)開發(fā),部署各類 XP需要的服務(wù)例如subversion啦,ant+ivy啦,Hudson啦等等。順便說句題外話,ubuntu還真是不是一般的好用,建議有能力的全 部轉(zhuǎn)到ubuntu上去開發(fā)。

    我目前開發(fā)的這個(gè)框架的客戶端是具肥的客戶端,也就是Swing客戶端了。Swing應(yīng)用相對(duì)于Web應(yīng)用有很多優(yōu)勢(shì),因?yàn)樗省?shù)據(jù)驗(yàn)證就是 其中一個(gè)。當(dāng)然現(xiàn)在的Web應(yīng)用通過使用Ajax也要比以前強(qiáng)很多了,但是還是避免不了在驗(yàn)證數(shù)據(jù)時(shí)向服務(wù)段發(fā)出請(qǐng)求,至少你無法避免驗(yàn)證結(jié)果從Web服 務(wù)器傳輸?shù)接脩魹g覽器上這段過程。而Swing這類肥客戶端可以實(shí)現(xiàn)完全在本地對(duì)數(shù)據(jù)進(jìn)行驗(yàn)證,甚至可以斷網(wǎng)繼續(xù)工作(這也是Web應(yīng)用目前在研發(fā)的一個(gè) 重要課題)。

    前段時(shí)間開發(fā)出了一個(gè)可以應(yīng)用于所有Swing應(yīng)用的通用數(shù)據(jù)驗(yàn)證模塊,發(fā)現(xiàn)它在項(xiàng)目中使用后,對(duì)于普通的數(shù)據(jù)驗(yàn)證,程序員幾乎不需要編碼,效率提高了不少,就寫了一篇博文拿出來和大家分享。原文是用英文寫的,在這里:http://polygoncell.blogspot.com/2008/07/validation-module-for-swing-application.html。英文好的朋友可以直接去那里看。

    編寫這個(gè)模塊使用了很多不同的開源框架和類庫,其中很重要的一個(gè)就是JXLayer。文章寫完后,我就跑去邀請(qǐng)JXLayer的作者Alexp來指點(diǎn)一下,然后就在我的文章后面開始了一段討論,挺有意思的,他不愧為是Swing team里面的牛人啊!厲害啊!呵呵。

    ok,回到今天這篇文章的正題。今天的主要目的是將我的英文博文翻譯成中文(自己的文章,我就不逐字逐句翻譯了,意思到了就行了,可能還會(huì)隨興展 開一番討論)在這里展示給大家,與大家分享開發(fā)經(jīng)驗(yàn),希望大家能夠從中獲益,也希望能夠以文會(huì)友,廣交朋友。廢話少說,切入正題。

    數(shù)據(jù)驗(yàn)證(Validation)一直是軟件開發(fā)中非常重要的一環(huán),有了它,你的系統(tǒng)會(huì)讓客戶感到更加友善,同時(shí)你的系統(tǒng)也得到了一定程度的保 護(hù)。一般來說,數(shù)據(jù)驗(yàn)證既可以在客戶端也可以在服務(wù)端。默認(rèn)的JSF數(shù)據(jù)驗(yàn)證就是在服務(wù)端,數(shù)據(jù)只能在被提交以后才能夠被驗(yàn)證,然后把錯(cuò)誤信息傳遞回用戶 的瀏覽器。后來大規(guī)模使用Ajax后,基本可以實(shí)現(xiàn)對(duì)修改的數(shù)據(jù)“即時(shí)”驗(yàn)證,注意這里是個(gè)打了引號(hào)的即時(shí),數(shù)據(jù)事實(shí)上還是要在瀏覽器和服務(wù)端之間進(jìn)行傳 遞的,只不過Ajax將這種傳遞改為隱式了而已,理論上并沒有真正實(shí)現(xiàn)(斷網(wǎng))即時(shí)驗(yàn)證。而在Swing應(yīng)用上就能夠達(dá)成這種愿望。

    事實(shí)上,開發(fā)Swing應(yīng)用時(shí),數(shù)據(jù)驗(yàn)證一直比較棘手,需要手工編碼的地方太多,效率不高。后來出了JGoodies Validation 結(jié)合JGoodies binding后,好了一些。這個(gè)JGoodies Validation既可以實(shí)現(xiàn)model層面的驗(yàn)證,也可以實(shí)現(xiàn)Bean層面的驗(yàn)證,但是多年使用下來,發(fā)現(xiàn)其實(shí)它比較適用于中小項(xiàng)目,而且要編寫的代 碼其實(shí)一點(diǎn)不比自己手動(dòng)編寫的少。

    JGoodies流行了一段時(shí)間后,sun開始推出自己的bean綁定方案:beansbinding(JSR 295),我個(gè)人感覺要比JGoodies binding好用(JGoodies的作者Karsten也在專家組里,這個(gè)人我以前和他一起共事過,我的msn space里面還有跟他的合影,絕對(duì)是Swing界的牛人)。這個(gè)beansbinding也提供數(shù)據(jù)驗(yàn)證,但是它的這個(gè)數(shù)據(jù)驗(yàn)證只是在target被改 動(dòng)后,數(shù)據(jù)被同步回source之前才會(huì)起作用,使用起來局限性比較大,而且編碼量也不小。

    由于目前絕大部分項(xiàng)目是基于POJO的,Hibernate validator已經(jīng)提供了一個(gè)很好的數(shù)據(jù)驗(yàn)證框架,我們完全沒必要再重復(fù)發(fā)明輪子,我們應(yīng)該努力站在巨人的肩膀上,這樣我們才能站得更高,看得更遠(yuǎn)。 于是我考慮結(jié)合beansbinding和Hibernate Validator開發(fā)數(shù)據(jù)驗(yàn)證。還有一個(gè)重要的問題,那就是數(shù)據(jù)錯(cuò)誤的時(shí)候,需要在用戶界面上展示相應(yīng)的信息,例如Error icon和錯(cuò)誤提示,這部分我考慮使用JXLayer。

    你可以在如下鏈接中找到相關(guān)框架的具體信息:

    1. Hibernate Validator: 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/

    閱讀這篇文章,不需要你熟悉這些類庫,不過了解這些類庫能夠幫助你更好地理解這篇文章。

    我的這個(gè)通用模塊是參考JXLayer里面的一個(gè)demo類TextValidationDemo的,這個(gè)JXlayer是由Alexander Potochkin開發(fā)的,我很喜歡,使用起來很順手,強(qiáng)烈推薦使用。

    下面開始介紹代碼。首先是建立一個(gè)java項(xiàng)目,對(duì)于這個(gè)小項(xiàng)目,我使用netbeans。這里說句題外話,中型和大型的Swing應(yīng)用,建議最 好還是不要使用netbeans的GUI Builder,一方面它生成的代碼超級(jí)爛,另一方面很難測(cè)試。目前市面上有很多好用的layout的框架,例如 JGoodies form和MigLayout,開發(fā)效率絕對(duì)不比netbeans的GUI builder差,你還不需要面對(duì)令人頭疼的機(jī)器成的代碼。

    項(xiàng)目創(chuàng)建好后,加入類庫:



    然后寫一個(gè)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);
    }
    }


    這里我為了強(qiáng)調(diào)可以在Swing客戶端直接使用和驗(yàn)證persistence bean,故意寫了一個(gè)persistence bean,實(shí)際應(yīng)用中,這個(gè)類只需要是一個(gè)pojo就行了。

    這個(gè)Country類代表一個(gè)國(guó)家,它有兩個(gè)屬性,code和name,我給他們分別加上個(gè)各自的驗(yàn)證限制。code不能為空,且必須正好是兩個(gè) 字符,例如CN,DE,US。name不能為空。這些annotaion均出自Hibernate Validator。那個(gè)父類AbstractBean出自SwingX類庫,我們的Country類繼承了它之后就可以支持property change event了。

    ok, 下面可以開始編寫這個(gè)模塊的核心代碼了。前面說過,我會(huì)使用JXlayer。使用它的好處是:所有JXlayer的painting event都會(huì)被轉(zhuǎn)到UI類來,我們只需要編寫一個(gè)集成Hibernate Validator的UI類就可以了,我稱這個(gè)類為HibernateValidationUI,代碼如下:

    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() 
    - 8null);
            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);
        }
    }
    }



    這個(gè)HibernateValidationUI類只有一個(gè)構(gòu)建器,它接收兩個(gè)參數(shù),一個(gè)是source object,也就是我們要修改的那個(gè)Bean類的實(shí)例,另外一個(gè)是這個(gè)bean的一個(gè)屬性,這個(gè)HibernateValidationUI就負(fù)責(zé)驗(yàn)證這個(gè)屬性。

    在installUI()方法中,我們啟動(dòng)對(duì)屬性變化的觀察類,而在uninstallUI()方法里面,我們需要卸載這個(gè)觀察類。

    當(dāng)給定對(duì)象的屬性值發(fā)生變化時(shí),PropertyChangeHandler的propertyStateChanged()方法就會(huì)被調(diào)用,這 個(gè)功能是通過elProperty和PropertzChangeHandler相結(jié)合來實(shí)現(xiàn)的。在propertyStateChangeed()方法 里UI類的方法setDirty()會(huì)被調(diào)用,該方法的調(diào)用會(huì)導(dǎo)致UI類的狀態(tài)變化,進(jìn)而引發(fā)(re)painting,之后經(jīng)過一系列的方法調(diào)用傳 遞,paintLayer(Graphics2D g2, JXLayer<jTextComponent> l)這個(gè)方法將會(huì)被調(diào)用,這個(gè)方法要做的就是我們這個(gè)數(shù)據(jù)驗(yàn)證模塊的核心功能:

    1. 調(diào)用Hibernate Validator驗(yàn)證該屬性。
    2. 如果數(shù)據(jù)不正確,則在GUI上顯示一個(gè)error icon,并且將錯(cuò)誤信息作為tooltip展示給用戶。

    在第二點(diǎn)里面產(chǎn)生了一個(gè)問題,謝謝Alexp對(duì)我的指點(diǎn)。Swing team里面有一些規(guī)定,其中之一就是,在paint()方法里面最好不要改變Component的狀態(tài),而setTooltip()方法將會(huì)改變 component的狀態(tài),因此需要在paint()方法之外調(diào)用。我目前使用下來,還沒有發(fā)現(xiàn)什么嚴(yán)重的錯(cuò)誤,決定暫時(shí)不改了,回頭有時(shí)間在將這個(gè)代碼 翻新一下。

    類中用到的Java2DIconFactory代碼如下:

    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(78);
    }

    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(
    00, width, height);
        g2.setColor(Color.WHITE);
        g2.drawLine(
    00, width, height);
        g2.drawLine(
    0, height, width, 0);
        g2.dispose();
        
    return icon;
    }
    }


    沒什么太多好解釋的,就是使用Java 2D畫一個(gè)Error icon。

    接著,我們需要編寫一個(gè)Factory類,構(gòu)建一個(gè)JTextField,盡量把復(fù)雜技術(shù)封裝起來,這樣程序員開發(fā)起來可以提高效率,代碼如下:

    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()方法主要將給定對(duì)象屬性的值與JTextField的text綁定,然后將JTextField納入到 JXLayer的管理之下。這樣一來,一旦用戶在JTextField里面修改數(shù)據(jù),這個(gè)改變就會(huì)同步到該對(duì)象屬性上,然后就引發(fā)了前面描述的一系列邏 輯,最終改變的數(shù)據(jù)就會(huì)被Hiberante Validator加以驗(yàn)證。

    最后,我們可以編寫一個(gè)Demo application來看看效果如何,代碼如下:

    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(360150));
        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;
    }
    }


    這個(gè)類比較簡(jiǎn)單了,我簡(jiǎn)單解釋一下:

    在main()方法里面,我們創(chuàng)建了一個(gè)JFrame,然后放入一個(gè)JPanel

    setCenter()方法負(fù)責(zé)將窗口至于屏幕的正中間。

    在構(gòu)建器里面,我們創(chuàng)建了Country和BindingGroup的對(duì)象實(shí)例。

    在buildPanel()方法里面,我們使用MigLayout構(gòu)建了一個(gè)Panel,其中codeField和nameField對(duì)應(yīng)各自的對(duì)象屬性。更多關(guān)于MigLayout的信息看這里:http://www.miglayout.com/。這也是一個(gè)例子,大家可以看到使用MigLayout開發(fā)Swing真的是非常方便。

    從這個(gè)Demo里面也可以看出,編寫好pojo后,程序員只需要調(diào)用createTextField(bg, country, Country.PROPERTYNAME_CODE); 就可以創(chuàng)建一個(gè)支持?jǐn)?shù)據(jù)驗(yàn)證的JTextField,編碼量已經(jīng)可以說是最大限度的降低了。

    運(yùn)行程序,你會(huì)看到:



    這個(gè)code和name的數(shù)據(jù)都不合法,用戶看到了error icon。

    將鼠標(biāo)移到Text field上,你會(huì)看到:



    填好合法數(shù)據(jù)后,Error icon就不見了:



    總結(jié):

    使用這個(gè)通用數(shù)據(jù)驗(yàn)證模塊有很多好處:

    1. 如果項(xiàng)目使用ORM,例如Hibernate,這個(gè)方案應(yīng)該是解決數(shù)據(jù)驗(yàn)證的最好方案之一。
    2. 對(duì)于普通的數(shù)據(jù)驗(yàn)證,例如非空,email,長(zhǎng)度等等,程序員根本不需要編碼,只要在POJO上使用相應(yīng)的Hibernate Validator annotation就可以了。
    3. 對(duì)于復(fù)雜的數(shù)據(jù)驗(yàn)證,Hibernate Validator提供了很好的擴(kuò)展機(jī)制,只要寫一個(gè)annotation外加一個(gè)Validator就可以了。Swing應(yīng)用這邊仍然不需要編寫任何代碼。

    綜上所述,可以看出通過使用這個(gè)通用數(shù)據(jù)驗(yàn)證模塊,開發(fā)效率會(huì)提高很多。




    聲明:本文版權(quán)歸作者所有,如需轉(zhuǎn)載請(qǐng)注明出處。

    posted on 2008-07-29 18:27 polygoncell 閱讀(2347) 評(píng)論(5)  編輯  收藏

    Feedback

    # re: Swing通用數(shù)據(jù)驗(yàn)證模塊 2008-07-29 19:47 隔葉黃鶯
    你的截圖也不是 ubuntun 下的。  回復(fù)  更多評(píng)論
      

    # re: Swing通用數(shù)據(jù)驗(yàn)證模塊 2008-07-30 14:37 卡卡西
    如果有興趣的話,請(qǐng)到這里討論這個(gè)話題。
    http://www.douban.com/group/topic/3604639/



     很不錯(cuò)的一個(gè)Tip
      
    https://balloontip.dev.java.net/

    基于該庫寫了個(gè)檢查框架,用在了我的一個(gè)項(xiàng)目中。
      效果如下
      
    http://www.douban.com/photos/photo/111403280/  回復(fù)  更多評(píng)論
      

    # re: Swing通用數(shù)據(jù)驗(yàn)證模塊 2008-07-30 14:52 卡卡西
    我在豆瓣上有個(gè)swing小組,歡迎加入。  回復(fù)  更多評(píng)論
      

    # re: Swing通用數(shù)據(jù)驗(yàn)證模塊 2013-04-26 22:22 lego
    代碼編譯都過不了。垃圾  回復(fù)  更多評(píng)論
      

    # re: Swing通用數(shù)據(jù)驗(yàn)證模塊 2013-04-26 22:23 lego
    好不容易編譯過了,加載類還拋異常。  回復(fù)  更多評(píng)論
      


    只有注冊(cè)用戶登錄后才能發(fā)表評(píng)論。


    網(wǎng)站導(dǎo)航:
     
    主站蜘蛛池模板: 日韩一区二区在线免费观看 | 亚洲人成人无码网www国产| 88xx成人永久免费观看| 一级做a爰黑人又硬又粗免费看51社区国产精品视| 99人中文字幕亚洲区| 久久亚洲中文字幕精品一区四| 最近的中文字幕大全免费版| 免费A级毛片无码A∨中文字幕下载| 一级毛片不卡免费看老司机| 激情无码亚洲一区二区三区| 亚洲人成免费网站| 亚洲一区二区三区日本久久九| 国产亚洲综合网曝门系列| 亚洲国产精品一区二区第一页免 | 亚洲s码欧洲m码吹潮| 亚洲一级毛片免费观看| 亚洲国产成人久久综合一| 亚洲色中文字幕无码AV| 亚洲一级片免费看| 亚洲综合国产精品第一页| 国产免费131美女视频| 国产男女性潮高清免费网站| 妞干网手机免费视频| 大学生一级毛片免费看| 成人无遮挡裸免费视频在线观看| 免费国产成人高清在线观看网站| 亚洲精品在线免费看| 在线观看www日本免费网站| 中文字幕免费视频| 50岁老女人的毛片免费观看| 免费A级毛片无码视频| 5g影院5g天天爽永久免费影院| 51视频精品全部免费最新| 嘿嘿嘿视频免费网站在线观看| 99久久精品日本一区二区免费| 亚洲网站在线免费观看| 最近免费中文字幕视频高清在线看| 成年私人影院免费视频网站| 日韩视频在线免费观看| 免费一级做a爰片性色毛片| 亚洲国产成人五月综合网|