Java桌面技術

          Java Desktop Technology

          常用鏈接

          統計

          友情連接

          最新評論

          通過xml配置文件定義及布局組件

          如果你使用過netBeans的GUI構件器Matisse,在感受到其拖拽帶來的方便的同時,也會發現自動生成的代碼“慘不忍睹”。例如Matisse默認的布局為GroupLayout,隨便拖拽3個組件生成的代碼如下

           // <editor-fold defaultstate="collapsed" desc="Generated Code">
              private void initComponents() {

                  jButton1 = new javax.swing.JButton();
                  jButton2 = new javax.swing.JButton();
                  jButton3 = new javax.swing.JButton();

                  jButton1.setText("jButton1");

                  jButton2.setText("jButton2");

                  jButton3.setText("jButton3");

                  javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
                  this.setLayout(layout);
                  layout.setHorizontalGroup(
                      layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                      .addGroup(layout.createSequentialGroup()
                          .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                              .addGroup(layout.createSequentialGroup()
                                  .addGap(60, 60, 60)
                                  .addComponent(jButton1))
                              .addGroup(layout.createSequentialGroup()
                                  .addGap(160, 160, 160)
                                  .addComponent(jButton2)))
                          .addContainerGap(165, Short.MAX_VALUE))
                      .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
                          .addContainerGap(191, Short.MAX_VALUE)
                          .addComponent(jButton3)
                          .addGap(134, 134, 134))
                  );
                  layout.setVerticalGroup(
                      layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                      .addGroup(layout.createSequentialGroup()
                          .addGap(45, 45, 45)
                          .addComponent(jButton1)
                          .addGap(18, 18, 18)
                          .addComponent(jButton2)
                          .addGap(46, 46, 46)
                          .addComponent(jButton3)
                          .addContainerGap(122, Short.MAX_VALUE))
                  );
              }// </editor-fold>


          無論Matisse發展得如何強大,但是其本質只是用來生成Java代碼而已,當你修改這代碼后,再逆向恢復成UI設

          計器時Matisse卻出于自己的一套安全考慮不允許你這樣做,所以最終不得迫使開發人員放棄拖曳方式設計UI,

          而統統采用面向代碼的方式。就我看來拖拽作業方式能解決80%的簡單布局,剩下的20%的專業細化GUI設計器是

          做不到的。《netBeans6.0咸魚翻身與Swing稱霸桌面應用》一文有人在評論中說到“VB,Delphi,C++Bulider的

          開發人員早已習慣了用拖曳的方式來畫頁面,界面都是保存成一個資配置源文件”,那么Java為什么不能模仿

          這一點呢?下面就詳細介紹利用外部配置文件實現組件的布局。

           

          第三部分:利用外部配置文件實現組件的布局

          在學習本章之前,需要對前文介紹的2種自定義布局有所了解,分別是、《自定義布局管理器-FormLayout》《自定義布局管理器-CenterLayout》本文介紹的配置文件均是以這兩種布局為基礎的。
          開門見山,程序運行結果如下圖。



          有4個組件,JButton、JScrollPane(內嵌JTree)、自定義組件ImageButton、一個JTextField。布局原則是JButton左邊界距離容器左邊界5像素、右邊界距離容器左邊界130像素、所以長度為130-5=125固定不變,JButton上邊界距離容器上邊界10像素,下邊界距離容器下邊界35像素,所以高度為35-10=25固定不變;JScrollPane位于容器的中央,其中左右兩邊距離容器兩邊均是20像素,所以JScrollPane的寬度隨著容器寬度的變化而變化,JScrollPane上下兩邊距離容器中心高度均是50像素,所以整體高度是100像素;ImageButton的上邊界距離容器的底部50像素,左邊界距離容器右邊界100像素,由于ImageButton會根據背景尺寸產生PreferredSize,所以右邊界、下邊界不用設置。剩下的JTextField不是通過配置產生,具體見后面介紹。

          下面來看看xml配置,如下。
          <ui-container>
          (1)    <layout-manager class="org.swingframework.layout.FormLayout" />
              <components>
          (2)       <component id="1101" class="javax.swing.JButton">
          (3)            <form-data>
                          <left percentage="0.0" offset="5" />
                          <right percentage="0.0" offset="130" />
                          <top percentage="0.0" offset="10" />
                          <bottom percentage="0.0" offset="35" />
                      </form-data>
                  </component>

                  <component id="1102" class="javax.swing.JScrollPane">
                      <form-data>
                          <left percentage="0.0" offset="20" />
                          <top percentage="0.5" offset="-50" />
                          <right percentage="1.0" offset="-20" />
                          <bottom percentage="0.5" offset="50" />
                      </form-data>
                  </component>

                  <component id="1103" class="org.swingframework.component.ImageButton">
                      <form-data>
                          <left percentage="1.0" offset="-100" />
                          <top percentage="1.0" offset="-50" />
                      </form-data>
                  </component>
              </components>
          </ui-container>

          (1)指定容器的布局類,目前僅支持FormLayout、CenterLayout兩種。
          (2)定義一個組件,指定唯一的id和完整類名。
          (3)為組件指定布局約束。
          由于xml文檔結構是固定的,因此xml解析采用XPath。XPath已在JDK1.5中被集成,而且相比DOM更加簡單,關于XPath的更多內容參考其他資料。

          定義布局注入LayoutInjection類,目的就是解釋一個給定的xml文件,然后去給一個容器生成內部組件并布局這些組件。

          public class LayoutInjection {

          private Container injectTarget;
           private InputStream layoutSource;
           private LayoutManager layoutManager;
           private Map<String, ComponentEntry> entryMap;

           public LayoutInjection(Container injectTarget, InputStream layoutSource) {
            this.injectTarget = injectTarget;
            this.layoutSource = layoutSource;
           }
          ......

          private class ComponentEntry {
            private Component component;
            private FormData formData;

            ComponentEntry(Component component, FormData formData) {
             this.component = component;
             this.formData = formData;
            }

            public Component getComponent() {
             return component;
            }

            public FormData getFormData() {
             return formData;
            }
           }
          }
          單獨定義一個ComponentEntry內部類是為了將組件-布局約束關聯。定義一個injectLayout方法來完成布局,在injectLayout方法內部,首先讀取外部配置文件并將組件與布局約束保存到entryMap,然后為容器設置根據配置得到的布局管理器,下一步插入自定義屬性,包括非配置產生的組件,與組件修飾、組件事件監聽器等。最后一步是遍歷entryMap根據每個組件與其布局約束完成組件創建與布局。
          public void injectLayout(){
            if (!load()) {
                      Logger.getLogger(LayoutInjection.class.getName()).log(Level.WARNING, "load components failed");
                      return;
                  }
                  EventQueue.invokeLater(new Runnable() {

                      @Override
                      public void run() {
                          injectTarget.setLayout(layoutManager);
                          /* Notifies all listeners that have registered interest for notification. */
                          Object[] listeners = listenerList.getListenerList();
                          // Process the listeners last to first, notifying those that are ICustomer type.
                          for (int i = listeners.length - 2; i >= 0; i -= 2) {
                              if (listeners[i] == ICustomer.class) {
                                  try {
                                      ((ICustomer) listeners[i + 1]).customProperties(LayoutInjection.this);
                                  } catch (Throwable ex) {
                                      Logger.getLogger(LayoutInjection.class.getName()).log(Level.WARNING, ex.getLocalizedMessage(), ex);
                                  }
                              }
                          }
                          synchronized (injectTarget.getTreeLock()) {
                              Set<String> idSet = entryMap.keySet();
                              for (String id : idSet) {
                                  Component component = getComponentById(id);
                                  FormData formData = getFormDataById(id);
                                  try {
                                      injectTarget.add((Component) component, formData);
                                  } catch (Throwable ex) {
                                      Logger.getLogger(LayoutInjection.class.getName()).log(Level.WARNING, ex.getLocalizedMessage(), ex);
                                  }
                              }
                              injectTarget.doLayout();
                          }
                      }
                  });
           }
          customProperties方法是空實現,需要自己去實現邏輯。

          protected void customProperties() {

           }
          injectLayout其他部分的實現參見完整源碼。
          最后給一個Test測試這個類。

          import java.awt.BorderLayout;
          import java.awt.Color;
          import java.awt.Dimension;
          import java.io.FileInputStream;

          import javax.swing.BorderFactory;
          import javax.swing.ImageIcon;
          import javax.swing.JButton;
          import javax.swing.JFrame;
          import javax.swing.JPanel;
          import javax.swing.JScrollPane;
          import javax.swing.JTextField;
          import javax.swing.JTree;
          import javax.swing.WindowConstants;

          import org.swingframework.component.ImageButton;
          import org.swingframework.layout.FormAttachment;
          import org.swingframework.layout.FormData;
          import org.swingframework.layout.LayoutInjection;

          public class Test {

           public static void main(String[] args) throws Exception {

                  JFrame frm = new JFrame();
                  frm.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
                  final JPanel panel = new JPanel();
                  panel.setPreferredSize(new Dimension(350, 250));
                  frm.getContentPane().add(panel, BorderLayout.CENTER);
                  FileInputStream input = new FileInputStream("C:/layout.xml");
                  LayoutInjection injection = new LayoutInjection(panel, input);
                  injection.addCustomer(new ICustomer() {
                      public void customProperties(LayoutInjection source) {
                          JButton button = (JButton) source.getComponentById("1101");
                          button.setText("this is a JButton");
                          JScrollPane jsp = (JScrollPane) source.getComponentById("1102");
                          jsp.setBorder(BorderFactory.createLineBorder(new Color(128, 128, 128), 2));
                          jsp.getViewport().add(new JTree());
                          ImageButton imageButton = (ImageButton) source.getComponentById("1103");
                          imageButton.setBackgroundImage(new ImageIcon("button_up.png"));
                          imageButton.setRolloverBackgroundImage(new ImageIcon("button_over.png"));
                          imageButton.setPressedBackgroundImage(new ImageIcon("button_down.png"));
                          // add extend component
                          JTextField jtf = new JTextField();
                          jtf.setBorder(BorderFactory.createLineBorder(new Color(128, 128, 128), 2));
                          FormData jtfFormData = new FormData();
                          jtfFormData.top = new FormAttachment(0.8f, 0);
                          jtfFormData.left = new FormAttachment(0.2f, 0);
                          jtfFormData.right = new FormAttachment(0.2f, 100);
                          jtfFormData.bottom = new FormAttachment(0.8f, 25);
                          panel.add(jtf, jtfFormData);
                      }
                  });
                  injection.injectLayout();
                  input.close();
                  frm.pack();
                  frm.setVisible(true);
           }
          }
          加紅的需要解釋以下,xml數據源是java.io.InputStream的實例,當然不局限與文件讀取;一般地,都要實現customProperties完成自定義邏輯,本例是修飾了組件外觀,另外以非配置方式加載了JTextField組件,以非配置方式添加組件是很必要的,因為實際應用的時候,你添加的更多的是已有的組件實例而不是xml文件中寫死的。
          至此,這一節內容介紹完畢。下一節我想做個有關布局管理器的總結,并穿插我在開發當中的一些感悟。
          源代碼這里下載


          posted on 2007-12-31 00:49 sun_java_studio@yahoo.com.cn(電玩) 閱讀(9413) 評論(4)  編輯  收藏 所屬分類: NetBeansGUI Design

          評論

          # re: 通過xml配置文件定義及布局組件 2008-01-01 10:51 zr

          呵呵,前些日子有個公司來我公司做項目,.net的,所有的界面都是從XML文件解析,然后生成的,挺有意思的  回復  更多評論   

          # re: 通過xml配置文件定義及布局組件 2008-01-01 10:55 sitinspring

          解析XML生成界面,這個技術不難的,關鍵是有實用價值.  回復  更多評論   

          # re: 通過xml配置文件定義及布局組件[未登錄] 2009-08-17 18:20 sharper

          一種復雜度轉化成另外一種復雜度  回復  更多評論   

          # re: 通過xml配置文件定義及布局組件 2011-05-23 12:13 Visco

          感謝偉大的樓主,最近正在為這個發愁呢!  回復  更多評論   

          TWaver中文社區
          主站蜘蛛池模板: 习水县| 泸定县| 江都市| 杭锦旗| 广灵县| 绥阳县| 盘锦市| 同仁县| 庆安县| 广东省| 湟中县| 青浦区| 南平市| 南宁市| 九龙坡区| 商洛市| 克拉玛依市| 陵水| 青海省| 固原市| 温宿县| 吴堡县| 龙胜| 乌鲁木齐县| 隆德县| 尉氏县| 克东县| 三穗县| 茶陵县| 五河县| 图片| 永州市| 仁布县| 禹城市| 海原县| 彝良县| 南皮县| 安吉县| 霍邱县| 枝江市| 黔江区|