程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> 關於JAVA >> JavaFX學習曲線日記-2:聲明用戶接口

JavaFX學習曲線日記-2:聲明用戶接口

編輯:關於JAVA

我已經使用Java語言定義用戶接口近十年了,當我第一次體驗JavaFX腳本時便馬上感到 了這兩種不同環境之間的差異。盡管程序員在Java語言中使用過程式代碼來定義用戶接口, 而在JavaFX腳本語言中你能夠使用聲明語句來定義用戶接口。這是兩者之間最大的不同,要 適應後者的確需要花費一定的時間和精力。

為了學習這種創建UI的全新聲明風格, 我決定將一個從前使用Java語言實現的應用UI移植到JavaFX腳本上。於是我挑選了一個在 Java語言中心的Swingworker教學中使用的圖片浏覽應用。原始應用演示了如何在JavaSE 6.0中使用Swingworker類,正因為其UI本身非常簡單,所以我將它作為移植的“原料 ”。

現存的用戶接口

現存的應用為用戶提供了查詢、列表、從Flickr 站點下載並顯示圖片的功能。用戶可以輸出查詢關鍵字,應用調用REST API來查詢Flickr以 獲取匹配的縮略圖片;而且用戶還可以從縮略圖片中進行選擇,以便查看更大更細致的圖片 。現存應用的查詢結果如下圖:

JavaFX學習曲線日記-2:聲明用戶接口

圖 1. 帶有查詢結果的應用UI

這個UI由下列組件構成,按照從上至下的順序:

• 主框架窗體容器

• 查詢標簽和查詢文本輸入欄

• 查詢匹配標簽和處理進度條

• 與簡短描述(檢索關鍵字)相匹配的縮略圖列表

• 選擇標簽和處理進度條

• 顯示被選擇圖片的標簽

此UI 由以下常見的Swing組件構成:JFrame、Jlabel、JprogressBar、JscrollPane、JList。 JList組件具有自定義渲染器,它能夠顯示縮略圖和相應的簡短描述。

但這還是一個相當簡單的UI,我們用它來研究如何使用JavaFX腳本描述UI。下一步,我 打算嘗試使用JavaFX實現整個應用;但是目前,只要完成一個對現存UI的近似實現就可以了 。下面展示了一個毫無生氣的UI,它代表了我使用JavaFX腳本進行UI描述來實現的最初目標 :

圖 2. 應用UI

我使用NetBeans IDE和它的Matisse GUI實現了這個原始的UI。所以源代碼都可從 Swingworker教學中下載;下面列出了用於生成UI的主要代碼。它告訴了我們如何在 NetBeans中使用GroupLayout來創建UI。

private void initComponents() {
   lblSearch = new javax.swing.JLabel();
   txtSearch = new javax.swing.JTextField();
   lblImageList = new javax.swing.JLabel();
   scrollImageList = new javax.swing.JScrollPane();
   listImages = new JList(listModel);
   lblSelectedImage = new javax.swing.JLabel();
   lblImage = new javax.swing.JLabel();
   progressMatchedImages = new javax.swing.JProgressBar();
   progressSelectedImage = new javax.swing.JProgressBar();
   setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);
   setTitle("Image Search");
   lblSearch.setText("Search");
   lblImageList.setText("Matched Images");
   // ...
   // event listeners, models, and cell renderers removed for this example
   //
   lblSelectedImage.setText("Selected Image");
   lblImage.setBorder(javax.swing.BorderFactory.createLineBorder(
       new java.awt.Color(204, 204, 204)));
   lblImage.setFocusable(false);
   lblImage.setMaximumSize(new java.awt.Dimension(500, 500));
   lblImage.setMinimumSize(new java.awt.Dimension(250, 250));
   lblImage.setOpaque(true);
   lblImage.setPreferredSize(new java.awt.Dimension(500, 250));
   javax.swing.GroupLayout layout = new javax.swing.GroupLayout (getContentPane());
   getContentPane().setLayout(layout);
   layout.setHorizontalGroup(
     layout.createParallelGroup (javax.swing.GroupLayout.Alignment.LEADING)
     .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
       .addContainerGap()
       .addGroup(layout.createParallelGroup (javax.swing.GroupLayout.Alignment.TRAILING)
         .addComponent(lblImage, javax.swing.GroupLayout.Alignment.LEADING,
             javax.swing.GroupLayout.DEFAULT_SIZE, 462, Short.MAX_VALUE)
         .addComponent(scrollImageList, javax.swing.GroupLayout.DEFAULT_SIZE,
             462, Short.MAX_VALUE)
         .addGroup(javax.swing.GroupLayout.Alignment.LEADING, layout.createSequentialGroup()
           .addGroup(layout.createParallelGroup (javax.swing.GroupLayout.Alignment.LEADING)
             .addComponent(lblImageList)
             .addComponent(lblSelectedImage))
           .addPreferredGap (javax.swing.LayoutStyle.ComponentPlacement.RELATED)
           .addGroup(layout.createParallelGroup (javax.swing.GroupLayout.Alignment.LEADING)
             .addComponent(progressMatchedImages, javax.swing.GroupLayout.DEFAULT_SIZE,
                 350, Short.MAX_VALUE)
             .addComponent(progressSelectedImage, javax.swing.GroupLayout.DEFAULT_SIZE,
                 350, Short.MAX_VALUE)))
         .addGroup(javax.swing.GroupLayout.Alignment.LEADING, layout.createSequentialGroup()
           .addComponent(lblSearch)
           .addPreferredGap (javax.swing.LayoutStyle.ComponentPlacement.RELATED)
           .addComponent(txtSearch, javax.swing.GroupLayout.DEFAULT_SIZE,
               411, Short.MAX_VALUE)))
       .addContainerGap())
   );
   layout.setVerticalGroup(
     layout.createParallelGroup (javax.swing.GroupLayout.Alignment.LEADING)
     .addGroup(layout.createSequentialGroup()
       .addContainerGap()
       .addGroup(layout.createParallelGroup (javax.swing.GroupLayout.Alignment.BASELINE)
         .addComponent(lblSearch)
         .addComponent(txtSearch, javax.swing.GroupLayout.PREFERRED_SIZE,
             javax.swing.GroupLayout.DEFAULT_SIZE,
             javax.swing.GroupLayout.PREFERRED_SIZE))
       .addPreferredGap (javax.swing.LayoutStyle.ComponentPlacement.RELATED)
       .addGroup(layout.createParallelGroup (javax.swing.GroupLayout.Alignment.LEADING)
         .addComponent(lblImageList)
         .addComponent(progressMatchedImages, javax.swing.GroupLayout.PREFERRED_SIZE,
             javax.swing.GroupLayout.DEFAULT_SIZE,
             javax.swing.GroupLayout.PREFERRED_SIZE))
       .addPreferredGap (javax.swing.LayoutStyle.ComponentPlacement.RELATED)
       .addComponent(scrollImageList, javax.swing.GroupLayout.PREFERRED_SIZE, 235,
           javax.swing.GroupLayout.PREFERRED_SIZE)
       .addPreferredGap (javax.swing.LayoutStyle.ComponentPlacement.RELATED)
       .addGroup(layout.createParallelGroup (javax.swing.GroupLayout.Alignment.LEADING)
         .addComponent(lblSelectedImage)
         .addComponent(progressSelectedImage, javax.swing.GroupLayout.PREFERRED_SIZE,
             javax.swing.GroupLayout.DEFAULT_SIZE,
             javax.swing.GroupLayout.PREFERRED_SIZE))
       .addPreferredGap (javax.swing.LayoutStyle.ComponentPlacement.RELATED)
       .addComponent(lblImage, javax.swing.GroupLayout.DEFAULT_SIZE, 305, Short.MAX_VALUE)
       .addContainerGap())
   );
   pack();
}

使用NetBeans IDE進行UI布局非常簡單——只需要鼠標的拖、拽操作便可。在本示例中 ,NetBeans通過使用javax.swing.GroupLayout生成了UI代碼,javax.swing.GroupLayout這 個類為我們提供了跨平台的正確大小、位置、組件之間的間隔。雖然生成的代碼並不便於閱 讀,但從工具支持性來講它確實是非常優秀的,而且無需任何人工編寫代碼。

聲明JavaFX腳本接口

盡管JavaFX腳本並沒有提供GUI工具,NetBeans也沒有提供支持JavaFX腳本語言的UI設計 界面,但我們可以使用JavaFXPad這個OpenJFX站點提供的演示應用。通過使用JFXPad,你能 夠人工輸入UI代碼,並馬上見到代碼所呈現的效果。盡管它是一個演示應用,但的確是一個 超級好用的簡單工具。

本接口所需的所有GUI組件都在javafx.ui包中。JavaFX腳本和Java語言一樣支持包 (package)結構和導入(import)語句。我在學習過程中並不是導入整個包,而是每次導 入要用到的組件類。這樣可以讓我們在查看代碼時能夠准確地識別出使用了哪些組件。

JavaFX腳本組件具有height、width、text、content等屬性。其中content屬性可以包含 子組件,每個content屬性可以包含被聲明為數組的多個組件。在聲明圖片檢索應用的用戶 接口時,我選用並使用了適合的JavaFX腳本組件,並設置了它們的屬性。

例如,下面簡短的腳本定義了一個不含有任何內容的空白框架。

import javafx.ui.Frame;
Frame {
   title: "JFX Image Search"
   height: 500
   width: 500
   visible: true
}

在JavaFX腳本中與Swing的Jframe等價的是javafx.ui.Frame。這裡的title屬性聲明了 window的標題。Height和width屬性則以像素為單元定義了整個框架的大小。最後,visible 屬性聲明了框架是否可見,這與在Swing中的Jframe類的setVisible方法類似。

FlowPanels 和 Boxes

javafx.ui包中包含了Label、SimpleLabel、Box、FlowPanel、ProgressBar、 ScrollPane和ListBox這些組件。它們的名稱聽起來與Swing組件非常相似,因此我首先嘗試 使用它們來實現目標UI。

盡管JavaFX腳本支持GroupLayout和GroupPanel組件,但我還是盡量在首次嘗試時避開這 些組件。我並不認為GroupLayout有多好,因為在Java語言中使用它時你不得不人工編寫代 碼,從這一點出發我對在JavaFX腳本中使用GroupLayout存在著同樣的顧慮。於是我選擇了 更加簡單的FlowPanel和Box組件來創建下面的JavaFXImageSearchUI1.fx代碼:

package com.sun.demo.jfx;
import javafx.ui.Frame;
import javafx.ui.Box;
import javafx.ui.FlowPanel;
import javafx.ui.SimpleLabel;
import javafx.ui.Label;
import javafx.ui.TextField;
import javafx.ui.ScrollPane;
import javafx.ui.ListBox;
import javafx.ui.ProgressBar;
import javafx.ui.LineBorder;
import javafx.ui.EmptyBorder;
import javafx.ui.Color;
Frame {
   title: "JFX Image Search"
   content: Box {
     border: EmptyBorder {
       top: 10
       left: 10
       right: 10
       bottom: 10
     }
     orientation: VERTICAL
     content: [
     FlowPanel {
       alignment: LEADING
       content: [
       SimpleLabel {
         text: "Search"
       },
       TextField {
         preferredSize: {width: 425}
       }
       ]
     },
     FlowPanel {
       alignment: LEADING
       content: [
       SimpleLabel {
         text: "Matched Images"
       },
       ProgressBar {
         preferredSize: {width: 378}

       }
       ]
     },
     ListBox {
       preferredSize: {height: 200 }
     },
     FlowPanel {
       alignment: LEADING
       content: [
       SimpleLabel {
         text: "Selected Image"
       },
       ProgressBar {
         preferredSize: {width: 382 }
       }
       ]
     },
     Label {
      opaque: true
       preferredSize: {
         height: 250
       }
       border: LineBorder
     }
     ]
   }
   visible: true
}

在JavaFXPad中輸入以上內容後,上方的window中將顯示相應的執行結果。如果使用 NetBeans IDE創建項目時,請將項目配置為使用FXShell類執行JFXImageSearchUI1.fx文件 ;這樣我們所期望的窗體框架將會被顯示出來。雖然我硬性編碼了文本輸入欄和進度條的寬 度以使其看起來和原始UI一樣大小,但對於第一次嘗試用JavaFX創建UI來講,其效果還算稱 得上成功。:-)

圖 3. 復制好的UI

框架的內容是一個Box組件。這個組件的orientation(朝向)屬性是VERTICAL,這意味 著在Box中的內容將被垂直放置,而不是水平放置。Box組件具有一個content屬性。你可以 在其content屬性中放置多個組件。如果你插入了多個組件,那麼則必須將這些組件以數組 的形式寫在方括號中,而在數組中的組件之間使用逗號分割:

content: [
   SimpleLabel {
     text: "Search"
   },
   TextField {
     preferredSize: {width: 425}
   }
]

FlowPanel組件常常用於創建一對Label組件。我使用多個FlowPanel來組織Label和其它 的組件,例如TextField或者ProgressBar。當將FlowPanel的alignment屬性設置為LEADING 時,框架中的面板將向左側對齊,這樣UI看上去最漂亮。

盡管這個UI看上去已經很不錯了,但其布局仍然需要硬性編寫進度條的寬度,以使其充 滿整個框架。不幸的是,我們完全不可能非常精確地將進度條對齊到框架的右側。你可以從 上面的UI中發覺查詢文本框和匹配圖片進度條並沒有完全對齊。

GroupPanel 和 GroupLayout

為了實現能夠自動設置組件大小並使其充滿容器空間的布局,我決定嘗試 javafx.ui.GroupPanel接口。這個接口使用了Swing的GroupLayout,因此它能夠實現更加精 確地表現布局。

GroupPanel組件使用行和列來定位在表格中的組件。它能夠自動在組件和其容器之間提 供平台特定(platform-specific)的間隔,以達到布局的目的。另外,它能夠很好地對齊 組件。GroupPanel簡化了Swing的GroupLayout,使其更加易於編寫。

下面是UI的第二個版本JFXImageSearchUI.fx:

package com.sun.demo.jfx;
import javafx.ui.Frame;
import javafx.ui.GroupPanel;
import javafx.ui.Row;
import javafx.ui.Column;
import javafx.ui.SimpleLabel;
import javafx.ui.Label;
import javafx.ui.TextField;
import javafx.ui.ProgressBar;
import javafx.ui.LineBorder;
import javafx.ui.ListBox;
Frame {
   title: "JavaFX Image Search"
   content:
     // main panel within the frame
     GroupPanel {
     // define the five rows and main column
     var searchRow = new Row
     var matchedProgressRow = new Row
     var thumbNailRow = new Row {resizable: true}
     var selectedProgressRow = new Row
     var imageRow = new Row {resizable: true}
     var mainCol = new Column {resizable: true}

     // declare the five rows and the column
     rows: [searchRow, matchedProgressRow, thumbNailRow, selectedProgressRow, imageRow]
     columns: mainCol
     // provide the array of components in the frame
     content: [
     // search text row
     GroupPanel {
       autoCreateContainerGaps: false
       row: searchRow
       column: mainCol
       var row = new Row
       var searchLabelCol = new Column
       var searchTextFieldCol = new Column {
         resizable: true
       }
       rows: row
       columns: [searchLabelCol, searchTextFieldCol]
       content: [
       SimpleLabel {
         text: "Search:"
         row: row
         column: searchLabelCol
       },
       TextField {
         row: row
         column: searchTextFieldCol
         columns: 50
       }
       ]
     },
     // matching images progress panel row
     GroupPanel {
       autoCreateContainerGaps: false
       row: matchedProgressRow
       column: mainCol
       var row = new Row
       var lblCol = new Column
       var progressBarCol = new Column {resizable: true}
       rows: row
       columns: [lblCol, progressBarCol]
       content: [
       SimpleLabel {
         text: "Matched Images"
         row: row
         column: lblCol
       },
       ProgressBar {
         row: row
         column: progressBarCol
       }
       ]
     },
     // thumbnail list row
     ListBox {
       preferredSize: {height: 200, width: 400}
       row: thumbNailRow
       column: mainCol
     },
     // selected image progress row
     GroupPanel {
       autoCreateContainerGaps: false
       row: selectedProgressRow
       column: mainCol
       var row = new Row
       var lblCol = new Column
       var progressBarCol = new Column {resizable: true}
       rows: row
       columns: [lblCol, progressBarCol]
       content: [
       SimpleLabel {
         text: "Selected Image"
         row: row
         column: lblCol
       },
       ProgressBar {
         row: row
         column: progressBarCol
       }
       ]
     },
     // selected image display row
     Label {
       opaque: true
       preferredSize: {height: 300, width: 400}
       row: imageRow
       column: mainCol
       border: LineBorder
     }
     ]
   }
   visible: true
}

這段代碼生成了更加完美的布局。框架中的組件之間進行了很好的分割,並且與框架的 左右兩側分別對齊。

圖 4. JFX圖片查詢UI

雖然在這個框架中使用的組件前一個例子中相同,但這裡使用了GroupPanel,而不是 FlowPanel和Box。當你查看原始的UI時,你會發現五個不同的行:檢索行、匹配圖片進度行 、列表行、選擇圖片進度行、被選擇圖片行。在這些組件被順序放置在一個居中的列內。

而我的第二個版本也具有五行和一列。框架的主要content是一個GroupPanel,這個組件 包含了幾個GroupPanel和其它組件。下面便讓我們看一下在GroupPanel中是如何實現這五行 和一列的:

content: GroupPanel {
   var searchRow = new Row
   var matchedProgressRow = new Row
   var thumbNailRow = new Row {resizable: true}
   var selectedProgressRow = new Row
   var imageRow = new Row {resizable: true}
   var mainCol = new Column {resizable: true}

   rows: [searchRow, matchedProgressRow, thumbNailRow, selectedProgressRow, imageRow]
   columns: mainCo

l

第一個GroupPanel包括一個組件的數組和一個用於檢索關鍵字輸入的GroupPanel(這裡 稱為第二個GroupPanel)。下面就是用於檢索關鍵字輸入的GroupPanel的行列屬性設置:

// search text row
GroupPanel {
   autoCreateContainerGaps: false
   row: searchRow
   column: mainCol

GroupPanel具有兩個非常重要的屬性:autoCreateGaps和autoCreateContainerGaps,它 們定義了如何在組件和容器之間創建間隔。這兩個屬性默認值為true,但由於這裡已經在組 件之間創建了間隔,在第一個GroupPanel和其中包含的第二個GroupPanel之間不需要額外的 間隔,因此這裡將其autoCreateContainerGaps設置為false來取掉額外的間隔。否則,檢索 文本輸入行將被插入不必要的邊緣。要使檢索文本行所在的GroupPanel組件填充父容器的相 應行、列,我們需要設置它的行和列屬性為searchRow和mainCol。

檢索文本行所在的GroupPanel定義了它自己的行和列,在其中包含了檢索標簽和文本輸 入框:

var row = new Row
       var searchLabelCol = new Column
       var searchTextFieldCol = new Column {
         resizable: true
       }
       rows: row
       columns: [searchLabelCol, searchTextFieldCol]

行、列定義被創建好後,讓我們繼續使用聲明式語法定義content的數組:

content: [
   SimpleLabel {
     text: "Search:"
     row: row
     column: searchLabelCol
   },
   TextField {
     row: row
     column: searchTextFieldCol
     columns: 50
   }
]

余下的代碼都遵循相應的模式。包含多個組件的行被封裝在一個GroupPanel中。具有單 個組件的行,例如下拉列表和圖片標簽,則使用row和column屬性與外部的GroupPanel相關 聯。

盡管我最初對使用GroupPanel很擔心,但JavaFX腳本將GroupLayout封裝後使其變得非常 易用,我在嘗試GroupPanel後便打消了擔憂。另外NetBeans IDE的編輯器插件和JavaFXPad 演示程序提供了上下文敏感的代碼自動完成功能,它可以根據輸入內容彈出相關可用的屬性 。通過使用簡單的行列布局和代碼自動完成,本人感覺使用JavaFX腳本的GroupPanel並沒有 想象中那樣困難。下面的圖片展示了在IDE或者JavaFXPad中按下CTRL+SPACE出現的彈出選項 。

圖 5. 彈出選項

總結

為了探索在創建UI過程中如何使用聲明式語法,我將現有應用的UI進行了大膽的移植。 原始應用的UI使用了Swing的GroupLayout來定位、對齊組件。盡管NetBeans IDE沒有提供用 於JavaFX腳本的圖形化設計工具,但通過編寫JavaFX代碼進行布局並非我所想象的那樣困難 。通過使用GroupPanel和其它組件,我實現了和原始應用完全相同的UI。方便的GroupPanel 組合加上NetBeans IDE插件、上下文敏感的代碼自動完成功能使工作變得輕松。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved