Java 2 圖形設計卷Ⅱ- SWING》第2章 Swing的基本知識

      網友投稿 557 2025-04-01

      本章介紹開發Swing小應用程序和應用程序時要用到的Swing的基本知識。

      雖然Swing是AWT的擴展,但是兩者的基本概念還是有許多不同之處。首先,Swing小應用程序和應用程序的實現方式與AWT小應用程序和應用程序的實現方式有所不同。而且,如果開發人員想要開發同時使用AWT組件和Swing組件的小應用程序或應用程序,則還必須注意混合使用輕量組件和重量組件所帶來的許多問題。

      Swing是線程不安全的,這就是說,在大多數情況下,只能從事件派發線程中訪問Swing組件。本章將介紹采用這種方法的原因及使用這種方法所帶來的結果,另外,本章還介紹了Swing提供的一些機制,這些機制使其他線程能從事件派發線程中執行代碼。

      2.1 小應用程序與應用程序

      使用Swing組件的小應用程序和應用程序應該分別擴展Swing的JApplet(java.applet.Applet的一個擴展)和JFrame(java.awt.Frame的一個擴展)。JApplet和JFrame除具有它們的超類所提供的功能外,還提供對Swing的支持。雖然可以分別使用Applet類和Frame類來實現Swing的小應用程序和應用程序,但是,這樣很可能出現事件處理問題和重新繪制問題。因此,應當總是使用JApplet和JFrame來實現Swing的小應用程序和應用程序。

      JApplet和JFrame都是只包含一個組件的容器,這個組件是JRootPane的一個實例,JRootPane在12.2節“JRootPane”中介紹。目前,只需知道JRootPane包含一個稱作為內容窗格的容器即可。內容窗格包含與特定的小應用程序或應用程序有關的所有內容。這里,內容指包含在小應用程序和或應用程序中的組件。實際上,這就是說小應用程序和應用程序必須把組件添加到內容窗格中而不是把它們直接添加到小應用程序或應用程序(或根窗格)中。而且,我們不應該直接為Swing小應用程序或應用程序設置布局管理器。因為組件添加到內容窗格中,所以應該為內容窗格而不是小應用程序或應用程序設置布局管理器。

      包含一個JRootPane實例的Swing容器重載用來添加組件和設置布局管理器的方法。這些方法會彈出提醒人們的異常信息:不能把組件直接添加到包含一個JRootPane實例的Swing容器中,也不能為該容器設置布局管理器。

      2.1.1 小應用程序

      圖2-1所示的小應用程序包含一個JLabel實例,該實例有一個圖標和一些文本。該小應用程序擴展JApplet并通過調用JApplet.getContentPane()方法來獲得對其內容窗格的引用。這個標簽隨后被實例化并被添加到這個內容窗格中。

      圖2-1 Swing小應用程序

      例2-1列出了圖2-1所示的小應用程序的代碼。

      例2-1 一個Swing小應用程序

      JApplet類使用BorderLayout的一個實例作為其內容窗格的布局管理器。為了強調這一點,例2-1的小應用程序指定其布局約束條件為BorderLayout.CENTER,它使標簽在內容窗格中居中顯示。用BorderLayout布局組件的缺省約束條件是BorderLayout.CENTER,所以,在該小應用程序中指定這個布局約束條件不是必須的。

      注意:當在Internet Explorer中使用例2-1的小應用程序時,必須使用BorderConstraints.CENTER。(我根本就找不到)

      JApplet的內容窗格用BorderLayout的一個實例來布局組件。記住這一點是很重要的,因為java.applet.Applet與JApplet不同,它使用FlowLayout的一個實例來布局組件。

      2.1.2 JApplet類

      Swing的JApplet類擴展java.applet.Applet并實現Accessibility接口和RootPaneContainer接口。Accessibility接口是可訪問包的一部分,而RootPaneContainer接口(如其名字所指出的)是一個包含根窗格的容器。RootPaneContainer接口被所有包含一個JRootPane實例的Swing容器所實現。

      類總結2-1中列出了JApplet提供的public和protected方法

      類總結 2-1 JApplet

      擴展:java.applet.Applet

      實現:javax.accessibility.Accessible、RootPaneContainer

      1.構造方法

      public JApplet()

      JApplet中提供了一個不帶參數的構造方法。由于小應用程序是由瀏覽器(或小應用程序閱讀器)進行實例化的,所以,正常情況下,不需要直接把JApplet的一個實例進行實例化。要了解直接實例化一個JApplet實例的情況,請參見2.1.5節“小應用程序/應用程序組合”。

      2.方法

      (1)從java.awt.Container中重載而獲得的方法

      protected void addImpl(Component,Object,int)

      public void setLayout(LayoutManager)

      public void addNotify()

      public void removeNotify()

      上面列出的四種方法都是重載java.awt.Container類中的方法而得到。

      AddImpl()是最終把組件添加到容器中的方法。如果直接把組件添加到小應用程序中,那么JApplet.addImpl()將彈出一個異常信息。這個異常中所顯示的消息是定制的(注:消息是通過JApplet的擴展的名字定制的)。例如,如果例2-1小應用程序中的標簽直接添加到該小應用程序中,那么異常信息將如下顯示:

      java.lang.Error:Do not use Test.add()use Test.getContentPane().add)instead

      at javax.swing.JApplet.createRootPaneException(JApplet.java:198)

      at javax.swing.JApplet.addImpl(JApplet.java:220)

      at java.awt.Container.add(Container.java:179)

      at Test.init(Test.java:11)

      與JApplet重載addImpl()的原因一樣,JApplet也重載setLayout()。如果設置了小應用程序的布局管理器,setLayout()將會彈出一個異常信息。如果修改例2-1的小應用程序,讓該小應用程序試圖設置它的布局管理器,則將彈出帶有下面錯誤消息的異常信息:

      java.lang.Error:Do not use Test.setLayout()use Test.getContentPane().setLayout()instead

      at javax.swing.JApplet.createRootPaneexception(JApplet.java:198)

      at javax.swing.JApplet.setLayout(JApplet.java:244)

      at Test.init(Test.java:10)

      at sun.applet.AppletPanel.run(AppletPanel.java:287)

      at java.lang.Thread.run(Thread.java:474)

      當實例化一個組件的對等組件時,將調用addNotify()方法。JApplet重載addNotify()以激發鍵盤事件并把小應用程序的可見性設置為true。

      (2)根窗格/內容窗格/玻璃窗格

      protected JRootPane createRootPane()

      protected boolean isRootPaneCheckingEnabled()

      protected void setRootPaneCheckingEnabled(boolean)

      public Container getContentPane()

      public Component getGlassPane()

      public JLayeredPane getLayeredPane()

      public JRootPane getRootPane()

      public void setContentPane(Container)

      public void setGlassPane(Component)

      public void setLayeredPane(JLayeredPane)

      public void setRootPane(JRootPane)

      Swing小應用程序通過調用protected JApplet.createRootPane方法,接著,這個方法又調用setRootPane()方法來創建根窗格。createRootPane方法可以被JApple的擴展所重載,以便替代JRootPane類的擴展作為該小應用程序的根窗格。

      如前所述,把組件直接添加到JApplet的一個實例中或顯式地設置其布局管理器都可能會信息彈出一個異常。然而,有時必須把JRootPane的一個實例直接添加到小應用程序中,并且不信息。通過調用以boolean值為為參數的setRootPaneCheckingEnabled()方法來設置一個標志,該標志跟蹤是否允許根窗格檢查。如果這個boolean值是true,則說明允許根窗格檢查,如果這個boolean值是false,則說明禁止根窗格檢查。

      isRootPaneCheckingEnabled()方法返回最后傳送給setRootPaneCheckingEnabled()方法的boolean值。

      注意:setRootPaneCheckingEnabled()和isRootPaneCheckingEnabled()都是protected方法。雖然不可能把組件直接添加到JApplet的一個實例中或顯式地設置其布局管理器,但是,實現可以控制是否允許根窗格檢查的JApplet的擴展是可能的。這種功能使JAppelt的擴展能夠在需要時直接添加組件或設置小應用程序的布局管理器。

      實際中,很少重載JApplet.createRootPane(),JApplet的擴展也很少用setRootPaneCheckingEnabled()來直接添加組件或設置小應用程序的布局管理器。

      上面列出的第二組方法是由RootPaneContainer接口定義的,這些方法能夠獲取和設置包含在JRootPane的一個實例中的容器。JRootPane和RootPaneContainer將在第12章和12.2節“JRootPane” 中介紹。

      (3)可訪問的相關內容/菜單欄/鍵盤事件/更新

      public AccessableContext getAccessableContext()

      public JMenuBar getMenuBar()

      public void setMenuBar(JMenuBar)

      proteted voidprocessKeyEvent(KeyEvent)

      public void update(Graphics)

      getAccessibleContext()返回AccessibleContext的一個實例,這個實例把小應用程序的可訪問信息提供給可訪問工具。

      --------

      JApplet實例可以有一個菜單欄,它是由setJMenuBar方法指定的。注意,Swing小應用程序能有一個菜單欄,而AWT小應用程序卻不能。參見圖2-2。

      實際上有兩種方法把菜單欄添加到Swing小應用程序中的方法。一種方法當然是調用JApplet.setJMenuBar,另一種方法是獲得對小應用程序根窗格的引用,然后把菜單欄直接添加到根窗格中。

      重載ProcessKeyEvent()來處理鍵綁定問題。有關Swing組件中鍵擊處理的更多信息,請參見4.8節“鍵出處理”。

      重載JApplet.update方法以便直接調用paint()。缺省時,AWT組件將實現它們的update方法以便擦除背景,然后調用paint()。這種技術在組件反復更新時,會導致許多閃爍。有關繪制和更新AWT組件的更多信息,請參見《Java 2 圖形設計,卷Ⅰ:AWT》。

      Swing提示

      JApplet和JFrame的內容窗格使用一個BorderLayout實例

      如果你用AWT開發過應用程序,就一定熟悉這樣一個事實:java.applet.Applet使用一個FlowLayout實例作為其布局管理器,而java.awt.Frame則使用一個BorderLayout實例作為其布局管理器。

      由于AWT小應用程序和應用程序使用不同的布局管理器,所以,當把小應用程序移植為應用程序時或把應用程序移植為小應用程序時,就可造成混亂,這里還沒有涉及到實現一個小應用程序和應用程序組合的情況。相比之下,Swing在小應用程序和應用程序的內容窗格中使用相同的布局管理器(即一個BorderLayout實例)。

      2.1.3 應用程序

      例2-2所示的應用程序與例2-1所示的小應用程序在功能上是完成相同的。它們都把JLabel的一個實例添加到它們的根窗格的內容窗格中。

      例2-3列出了圖2-3所用的應用程序的代碼。

      例2-3 一個Swing應用程序

      應用程序比小應用程序要稍微復雜些,這是因為它們不是在瀏覽器內部運行的,即瀏覽器不啟動它們也不設置它們的大小。應用程序必須提供main方法,必須把一個窗體實例化,隨后確定該窗體的大小(注:可使用JFrame.pack()顯式地給出窗體的大小)并使該窗體可見。

      例2-2中的應用程序還設置窗體的缺省關閉操作并添加一個窗口-,該-在窗體被關閉后會退出這個應用程序。有關Swing窗體的缺省關閉操作的更多信息,請參見2.1.4節“JFrame類”。

      Swing小應用程序和應用程序有許多共同點。它們都含有一個JRootPane實例,都必須把組件添加到根窗格的內容窗格中。而且,不能顯式地設置Swing小應用程序或Swing應用程序的布局管理器。

      2.1.4 JFrame類

      JFrame類擴展java.awt.Frame,與JApplet類似,它也實現Accessible接口和RootPaneCotainer接口。JFrame還實現Swing.WindowsConstants接口,該接口定義缺省關閉操作的常量。有關Swing常量的更多信息,請參見6.4節“Swing常量”。

      JFrame實惠許多在JApplet中能找到的、相同的方法。與JApplet類似,為了不顯式地設置其布局管理器或不把組件直接添加到窗體中,JFrame重載setLayout和addImpl方法。JRame實現了所有在RootPaneContainer接口中定義的方法,還實現了通話和禁止根窗格檢查的方法。JFrame還實惠了確定當前是束啟用了根窗格檢查的方法。

      類總結2-2總結了JFrame類。

      類總結2-2 JFrame

      擴展:java.applet.Frame

      實現:javax.accessibility.Accessible、RootPaneContainer

      1.構造方法

      public JFrame()

      public JFrame(String title)

      JFrame有兩個構造方法,一個構造方法不帶參數,一個構造方法以一個字符串為參數,該字符串代表窗體的標題。

      瀏覽器或小應用程序的閱讀器會調用Swing小應用程序的構造方法,因此,通常不需要開發人員編寫代碼來調用它的構造方法,但是,應用程序必須負責構造窗體并負責設置窗體的大小。通常為JFrame的實例選擇帶一個字符串的構造方法,不帶參數的構造方法將產生沒有標題的窗體。

      (1)與JApplet交疊的方法

      protected void addImpl(Component,Object,int)

      prrotected JRootPane createRootPane()

      public AccessibleContext getAccessibleContext()

      public Container getContentPane()

      public Component getGlassPane()

      public JMenuBar getMenuBar()

      public JLayeredPane getLayeredPane()

      public JRootPane getRootPane()

      protected boolean isRootPaneCheckingEnabled()

      protected void processKeyEvent(KeyEvent)

      public void setContentPane(Container)

      public void setGlassPane(Component)

      public void setMenuBar(JMenu Bar)

      public void setLayeredPanec(JlayeredPane)

      public void setLayout(LayoutManager)

      protected void setRootPane(JRootPane)

      protected void setRootPaneCheckingEnabled(boolean)

      public void update(Graphics)

      上面列出的JFrame方法與JApplet中定義的方法交疊。其中的大部分方法與JApplet中相應方法的實現方式是相同的。例如,如果允許根窗格檢查,則JFrame.setLayout和JFrame.addImpl都將彈出一個異常信息。

      有關上述方法的更多信息,請參見“類總結2-1JApplet”。

      (2)窗體初始化/缺省的關閉操作/窗口事件

      protected void frameInit()

      public int getDefaultCloseOperation()

      protected void setDefaultCloseOperation(int)

      protected void processWindowEvent(WindowEvent)

      《Java 2 圖形設計卷Ⅱ- SWING》第2章 Swing的基本知識

      JFrame構造方法調用frameInit方法來初始化窗體。JFrame的frameInit()方法允許窗體的鍵盤事件和窗口事件,設置窗體的根窗格和背景色,并允許根窗格檢查。如果缺省的設置不令人滿意的話,也可擴展JFrame以重載frameInit()。

      使用AWT窗體時,開發人要負責處理窗口關閉事件。通常,這需要重載事件處理方法,需要簡單地隱藏窗口或隱藏窗口并清除其本地資源。而Swing通過把一個缺省關閉操作與每一個JFrame實例相關聯來使窗口的關閉事件較容易處理??梢杂胹etDefaultCloseOperation方法來設置缺省的關閉操作,而且可以用getDefaultCloseOperation()來獲取缺省的關閉操作??梢詡魉徒osetDefaultCloseOperation()的integer值在WindowConstants類中定義,表2-1,表2-1列出了integer值。

      表2-1 WindowContants public常數

      ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

      方法名          實現

      ─────────────────────────────────

      DO_NOTHNG_ON_CLOSE  關閉窗口時什么也不做

      HIDE_ON_CLOSE     關閉窗口隱藏該窗口

      DISPOSE_ON_CLOSE   關閉窗口時隱藏該窗口并清除其本地資源

      ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

      如果沒有顯式地設置JFrame的缺省關閉操作,則缺省值是DO_NOTHING_ON_CLOSE。

      DISPOSE_ON_CLOSE隱藏窗體并清除與這個窗體有關的系統資源。如果該窗體是應用程序窗體,則在該窗體清除后,應用程序將繼續運行。例如,例2-2所列的應用程序把應用程序窗體的缺省關閉操作設置為DISPOSE_ON_CLOSE,但是,應用程序仍然負責處理窗體關閉事件。到應用程序得到窗體已關閉(當調用windowClosed方法時)窗體已隱藏并清除通知時,應用程序仍在運行;結果,應用程序在windowClosed方法中調用System.exit()。

      2.1.5 小應用程序/應用程序的組合

      有時需要實現這樣一個源文件,它既可作為應用程序運行又可作為小應用程序運行。例2-3示出了一種實現小應用程序/應用程序組合的方法。

      例2-3 Swing小應用程序/應用程序組合

      其思想是實現一個小應用程序,這個小應用程序包含一個main方法。這個main方法把JFrame實例化,而且還創建這個小應用程序的一個實例。在調用小應用程序的init方法后,窗體用該小應用程序的內容窗格來替代該窗體的內容窗格。這個窗體接著設置其邊界和標題。并把它的可見性設置為true。

      從本質上講,這種技術會產生共享一個內容窗格的應用程序和小應用程序。當例2-3中的代碼被編譯后,它可以既作為小應用程序運行又可以作為應用程序運行。

      應該注意的是,作為應用程序/小應用程序組合實現的應用程序,在使用main方法創建的小應用程序實例時必須非常小心。因為瀏覽器或小應用程序閱讀器不能把這種小應用程序實例化,所以這種小應用程序是不完善的(從技術上說,它沒有小應用程序的相關內容)。因此,這種小應用程序不能使用,例如,用Applet.getImage方法來獲取一幅圖像。實際應用中,也沒有那么多限制,因為應用程序除借用小應用程序的內容窗格外不需要使用小應用程序。例如,應用程序通常使用AWT工具包來獲取圖像,因此,不需要使用Applet.getImage方法。

      Swing提示

      不要直接把組件添加到Swing小應用程序或應用程序中,也不要顯式地設置其布局管理器

      Swing小應用程序和應用程序都有一個JRootPane實例,該實例又含有一個稱作內容窗格的容器。小應用程序或應用程序的內容(即組件)必須添加到內容窗格中。如果把組件直接添加到JApplet或JFrame的實例中,則會彈出一個異常信息,指出只能把組件添加到內容窗格中。

      Swing小應用程序和應用程序都使用BorderLayout布局管理器來布局它們的JRootPane實例,并且不允許顯式地設置它們的布局管理器。如果試圖顯式地設置JApplet或JFrame的布局管理器,則會彈出一個異常信息,指出不可以顯式地設置其布局管理器。

      2.2 GJApp

      本書介紹的應用程序都是在GJApp類的幫助下實現的,該類提供了一個狀態區,并能從屬性文件中讀取資源。圖2-4所示的應用程序是一個JFrame擴展,這個擴展用GJApp類來訪問一個狀態區,這個狀態區顯示從GJApp.properties文件中獲取一個字符串。

      GJApp.properties文件定義了一個屬性:

      # Simple properties file

      statusAreaText=text in the status area

      例2-4列出了圖2-4所示的應用程序的代碼。

      例2-4 使用GJApp類

      這個應用程序創建JPanel的一個實例,指定該實例為內容窗格的中心組件。用面板來突出狀態區上面的空間,并且這個面板還有一個蝕該邊框。

      應用程序通過調用static GJApp.getStatusAreas方法來獲取對GJApp狀態區的引用。狀態區指定為內容窗格南邊的組件。

      static GJApp.showStatus方法以statusAreaText資源的字符串為參數把這個狀態區初始化。資源的字符串用static GJApp.getResource方法來獲得。

      GJApp類有三個功能:

      ·初始化并顯示傳送給static launch方法的窗體。

      ·提供對小應用程序狀態區面板的訪問

      ·從GJApp.properties文件中查找資源字符串。

      例2-5列出了GJApp類。

      例2-5 GJApp類

      嚴格地說,GJApp是一個幫助類,它實現獨有的static方法。GJApp的實例不能實例化,這是GJApp private構造方法強加的規定。

      一個static代碼塊(它在main()方法之前執行)試圖獲得對GJApp.properties文件資源包的一個引用。在GJApp.getResource方法中使用這個資源包可以獲得一個與一個給定資源關鍵字相關的字符串。

      GJApp.launch方法為傳送給它的窗體設置邊界和標題,把這個窗休的可見性設置為true,并打開這個窗體。這個launch方法還配置狀態區并把窗體的缺省關閉操作設置為WindowConstants.DISPOSE_ON_CLOSE。添加到這個窗體中的窗口-在窗口關閉時會退出該應用程序。

      GJApp類用getStatusArea方法訪問其狀態區面板。與小應用程序一樣,GJApp類用showStatus方法來更新狀態區。

      注意:本書以后介紹的應用程序都是在GJApp類幫助下實現的。但是,為了簡短些,例2-5是本書中唯一列出了GJApp類的地方。

      2.3 混合使用Swing組件和AWT組件

      原來的AWT只是為重量組件設計的;在AWT1.1版本發布前,還沒有輕量組件。結果,AWT不得不重做AWT,以提供輕量組件。

      任何軟件開發人員都可證實,把一個復雜的系統和以前未預見的設計組合起來不是一個簡單的任務,把輕量組件合并到AWT中也不例外。直到現在,在一個小應用程序或應用程序中混用輕量組件和重量組件還是有許多問題,尤其是把重量組件嵌入輕量容器中時更是如此。

      2.3.1 層序

      組件的層序是同一容器中組件之間顯示的層次關系。

      如果容器是同類的(即它包含的組件都是輕量組件或都是重量組件),則按組件被添加到容器中的順序來確定其層序。第一個被添加到容器中的組件有最高的層序,即它在同一容器中所有其他組件的上面顯示。最后添加到容順中的組件的層序最低,即它在同一個容器中的所有其他組件的下面顯示。

      如果容器是異類的(即它既有輕量組件又有重量組件),則事情要稍微復雜些。從第1.2節“輕量組件與重量組件的比較”中,我們知道,輕量組件不是顯示在它們自己的窗口中,而是顯示在它們的重量容器的窗口中。所以,輕量組件的層序與重量容器的層序相同。如果多個輕量組件被添加到一個容器中,則這些輕量組件的層序是由組件被添加到容器中的順序來決定的。

      如果對此還不太明白,下面的兩個小應用程序將會有助于理解。圖2-5所示的小應用程序有七個按鈕,其中四個是重量AWT按鈕,其他三個是Swing輕量按鈕。所有的重量按鈕都顯示在輕量按鈕的上面,因為輕量按鈕的層序與它們的容器的層序相同。

      例2-6列出了圖2-5所示的少應用程序的代碼。

      例2-6 混合使用重量組件和輕量組件

      這個小應用程序把內容窗格的布局管理器設置為null,以便這些按鈕可以顯式地定位和確定大小,使這些按鈕樸素重疊。然后,這個小應用程序創建按鈕,設置按鈕的邊界并把每個按鈕添加到內容窗格中。

      即使輕量按鈕在重量按鈕之前添加到內容窗格中,輕量按鈕也仍在重量按鈕下顯示。因為輕量組件的層序與它們所在的重量容器的層序相同,所以輕量按鈕和它們的容器的層序相同。輕量按鈕的容器就是小應用程序的內容窗格。

      注意 第一個添加到內容窗格的輕量按鈕在其他輕量按鈕之上顯示。同樣,第一個添加到內容窗格的重量按鈕在其他重量按鈕之上顯示。

      圖2-6所示的小應用程序強調了這樣一個事實:輕量組件的層序與它們的重量容器的層序相同。這個小應用程序幾乎與圖2-5所示的小應用程序一樣,然而,圖2-6所示的小應用程序把三個輕量按鈕放在一個重量面板中。然后遭到把該面板添加到內容窗格中,使這個重量面板在第二個重量按鈕之后 ,在第三個重量按鈕之前。結果,輕量按鈕具有與它們所在的面板相同的層序,它們在第二個重量按鈕之下,第三個重量按鈕之上顯示。

      例2-7列出了圖2-6所示的小應用程序的代碼。

      例2-7 控制輕量按鈕的層序

      例2-7的小應用程序實現java.awt.Panel類的一個擴展(BorderedPanel),BorderedPanel在面板的外面畫了一個黑邊框,以使面板可見。

      另外還要注意,BorderedPanel類調用super.paint()。無論何時擴展了一個容口并重載了它的paint方法,都必須顯式地調用super.paint(),這樣,容器中的輕量組件才能重新繪制(注:有關輕量組件的更多信息,請參見《Graphic Java》第1卷)。如果沒有調用super.paint(),則不會重新繪制面板中的輕量Swing按鈕。

      2.3.2 Swing彈出式菜單

      缺省時,Swing彈出式菜單是輕量組件(注:這是一種簡化的說法,但適用于此處的討論。完整的介紹請參見10.8節“JPopupMenu”)。如果輕量彈出式菜單與重量組件重疊,則彈出式菜單將在該重量組件下面顯示。如圖2-7小應用程序所示。

      有些Swing組件使用彈出式菜單。Swing菜單組件就是一種使用彈出式菜單的組件,它在一個菜單被激活時,顯示一個彈出式菜單。缺省時,如果一個與某個菜單相關聯的彈出式菜單完全處在彈出式菜單所在的窗口呂,則彈出式菜單使用輕量組件。圖2-7所示的小應用程序中與File菜單相關聯的彈出式菜單是一個輕量組件,所以它在重量組件AWT按鈕的下面顯示。

      例2-8列出了圖2-7所示的小應用程序的代碼。

      例2-8 在重量組件下面顯示的輕量彈出式菜單

      這個小應用程序創建了一個菜單條、一個AWT按鈕和一個菜單。把菜單項添加到菜單中,再把菜單添加到菜單條中,按鈕則被添加到小應用程序的內容窗格中。最后,調用JApplet.setJMenuBar(),把菜單條添加到小應用程序中。

      幸運的是,Swing提供了一個機制,它迫使彈出式菜單是重量組件,這樣,它們就不會在重量組件下面彈出來。JPopupMenu類提供了一個static方法,該方法可決定彈出式菜單是重量的還是輕量的(注:某些彈出式菜單即可以指定為輕量的,也可以指定為重量的。)

      JPopupMenu.setDefaultLightWeightPopupEnabled()以一個boolean值為參數,這個值指出是把彈出式菜單實例化為輕量的還是把彈出式菜單實例化為重量的,調用setDefaultLightWeightPopupEnabled()時,如果這個boolean值為true,則創建的彈出式菜單是輕量的,如果這個boolean值為false,則創建的彈出式菜單是重量的(注:這也是簡化的說法,但同樣適用于這里的討論)。

      圖2-8所示的小應用程序除了在菜單條被實例化之前調用了JPopupMenu.setDefaultLightWeightPopupEnabled(false)以外,其余部分都與圖2-7所示的小應用程序相同。

      例2-9列出了圖2-8所示的小應用程序的代碼

      例2-9 使用重量彈出式菜單

      2.3.3 滾動

      把重量組件和輕量組件混合使用時所要關心的另一個問題是滾動。

      Swing提供了一個替代AWT重量滾動窗格的輕量組件——JScrollPane組件。由于JScrollPane是輕量的,所以任何添加到JScrollPane實例中的重量組件都將在這個滾動窗格之上顯示。如果重量組件滾動超出了JScrollPane實例的邊框,則它就不能正確地顯示了。

      圖2-9所示的小應用程序說明了把一個重量組件添加到JScrollPane實例中并滾動重量組件使其超出滾動窗格邊框的情況。

      圖2-9中上圖顯示了這個小應用程序剛啟動時的樣子,圖2-9中下圖顯示了滾動窗格滾動后,這個小應用程序的樣子。注意,在這兩種情況下,AWT按鈕都沒有能夠正確地顯示。

      例2-10列出了圖2-9所示的小應用程序的代碼。

      例2-10用JScrollPane滾動重量組件

      圖2-9所示的小應用程序把一個Swing按鈕和一個AWT按鈕添加到一個面板中,這個面板是要滾動的組件。這個小應用程序為滾動窗格設置了首選大小,并把滾動窗格添加到其內容窗格中。

      圖2-9所示的組件效果是我們不想要的。遺憾的是,與彈出式菜單不同,JScrollPane沒有能實例化為重量組件的選項。但是,幸運的是,AWT的ScrollPane組件是一個重量滾動窗格,它和Swing的JScrollPane幾乎完全相同。

      圖2-10示出了與圖2-9相同的小應用程序,但圖2-10中的小應用程序用重量AWT的ScrollPane替代了Swing的輕量JScrollPane。由于AWT滾動窗格是重量的,所以它們滾動輕量組件和重量組件都沒有問題。

      例2-11列出了圖2-10示的小應用程序的代碼

      例2-11 使用AWT的ScrollPane來滾動重量組件

      注意:在例2-11列出的小應用程序中實現了java.awt.ScrollPane的一個擴展,以便把滾動窗格的大小設置為首選尺寸。有關Swing組件與AWT組件在設置首選尺寸方面的差別的更多信息,請參見4.2.2節“最小尺寸、最大尺寸和首選尺寸?!?/p>

      2.3.4 內部窗體

      Swing的內部窗體是包含在桌面窗格中的窗體(參見第15章“內部窗體和桌面窗格”),Swing的內部窗體是輕量組件,如果把重量組件添加到一個內部窗體,則這個窗體很可能會遇到到麻煩。

      圖2-11所示的小應用程序包含兩個JInternalFrame實例。它們都包含一個重量AWT畫布。如果一個內部窗體與另一個內部窗體重疊,則下面的內部窗體的重量畫布將會使上面的內部窗體的一部分變模糊,因為重量畫布的層序比輕量內部窗體的層序高。

      例2-12 列出了圖2-11所示的小應用程序的代碼

      例2-12把重量組件添加到Swing內部窗體中

      Swing提示

      混合使用AWT組件和Swing組件的原則

      一般不提倡把Swing輕量組件與AWT重量組件混合使用。大多數情況下,這不會是一個問題,因為Swing對所有AWT組件都提供了替代的輕量組件。對已有的、使用AWT組件的小應用程序或應用程序,最好的方法是用Swing的相應組件來替代AWT組件。如果不能替代,則必須遵守如下原則:

      1)如果輕量組件必須在重量組件之上顯示,則不要在一個容器中混合使用輕量組件和重量組件。

      2)如果彈出式菜單與重量組件重疊,則必須強迫彈出式菜單成為重量組件

      3)如果把重量組件添加到一個JScrollPane實例中,而應該把重量組件添加到一個java.awt.ScrollPane實例中。

      4)不要把重量組件添加到Swing內部窗體中。

      2.4 Swing和線程

      大多數情況下,Swing是線程不安全的,即只能從單線程來訪問Swing組件。首先,我們要討論為什么Swing是線程不安全的,然后介紹在Swing開發過程中單線程設計所帶來的結果。

      讓我們面對這個事實,甚至在java中,開發多線程的應用程序也是不容易的。設計一個線程安全的工具包就更不是一個簡單的事情。例如,確定如何同步對類的訪問就是一個復雜的任務(注:參見Lea,Doug,“java中的并發編程”,Addison-Wesley,1997。)。同樣,擴展線程安全的類需要較高的技術,對非線程編程高手的開發人員(大多數開發人員都屬此范圍)是充滿危險的。Swing是線程不安全的一個主要原因是為了簡化擴展組件的任務。

      Swing是線程不安全的另一個原因是由于獲取和釋放鎖定及恢復狀態所帶來的開銷。使用線程安全GUI工具包的所有應用程序(無論它們是否是多線程的)都必須付出同樣的性能代價。

      線程的使用增加了調試、測試、維護和擴展的困難度。例如,測試和維護等通常已經很艱苦的工作對于大多數多線程應用程序就更困難了,有時甚至是不可能的。

      有些Swing組件方法確實支持多線程訪問。例如,JComponent的repaint、revalidate和invalidate等方法都對放在事件派發線程上的請求進行排隊。因此,可從任何線程中調用這些方法。另外,可以從多個線程把-添加到事件-列表(參見6.2節“事件-列表”)中或從列表中刪掉。最后,有些組件方法是同步的。例如,JCheckBoxMenuItem.setState()是同步的,因此,可以從多線程中調用它。

      2.4.1 Swing單線程設計的結果

      Swing單線程設計的主要結果是:大多數情況下,只能從事件派發線程中訪問將要在屏幕上繪制的Swing組件。

      事件派發線程是調用paint和update等回調方法的線程,而且,它還是事件-接口中定義的事件處理方法。例如,ActionListener和PropertyListener接口的實現使它們的actionPerformed方法和propertyChange方法在事件派發線程中調用。

      技術上說,在Swing組件的對等組件創建之前(指可在屏幕上繪制之前)(注:對等組件是用addNotify方法創建的),它們可以從多個線程中訪問。例如,可以有一個小應用程序的init方法中構造和操縱組件,只要在操縱它們之前,還沒有使它們成為可見的。

      2.4.2 SwingUtilties類的invokeLater和invokeAndWait方法

      由于AWT和Swing都是事件驅動工具包,所以在回調方法中更新可見的GUI就是很自然的事。例如,如果在一個按鈕激活,項目列表需要更新時,則通常在與該按鈕相關聯的事件-的actionPerformed方法中來實現該列表的更新。

      然而,有時可能需要從事件派發線程以外的線程中更新Swing組件。例如,如果上述項目列表中包含了很多來自數據庫或Internet的數據,則可能在按鈕激活后還要等一段時間才能看到更新的列表。如果信息的獲取是在actionPerformed中實現的,則按鈕仍保持按下的狀態,直到對actionPerformed的調用返回,不僅按鈕的彈起需要一段時間,而且一般來說,耗時較長的操作也不應當在事件方法中的執行,因為在事件處理方法返回之前,其他的事件不能派發。

      有時,在獨立的線程上執行耗時的操作可能更好,這將允許立即更新用戶界面和釋放事件派發線程去派發其他的事件,幸運的是,Swing提供了兩種機制,它們都支持這種想法。

      SwingUtilities類提供了兩個方法:invokdLater和invokdAndWait,它們都使事件派發線程上的可運行對象排隊。當可運行對象排在事件派隊列的隊首時,就調用基run方法。其效果是允許事件派發線程調用另一個線程中的任意一個代碼塊。

      1.SwingUtilities invokeLater

      在介紹invokeLater和invokeAndWait方法之前,我們首先來看一個小應用程序,由于是從事件派發線程以外的線程中更新Swing組件,所以該小應用程序運行不正常。圖2-12所示的小應用程序有一個按鈕和一個進度條。當激活按鈕后,就開始模仿獲取信息的長操作。當獲取了信息(即一個integer值)后,就用該信息來更新小應用程序的進度條。

      圖2-12左圖顯示的是這個小應用程序的初始狀態。圖2-12右圖顯示的則是當激活start按鈕后,這個小應用程序的樣子,此時,已獲取了信息,也更新了進度條。

      小應用程序把一個動作-添加到該按鈕中,該-創建一個新線程,這個線程不斷收到信息并更新進度條。每隔半秒獲取一次信息,而且這個線程會獲得一個對這個小應用程序進度條的引用。

      在該按鈕的-啟動上述線程后,-把按鈕的允許狀態設置為false。由于在事件派發線程上調用actionPerformed方法,所以,這是一個有效的操作。但是,在GetInfoThread中設置進度條是一個危險的做法,因為事件派發線程以外的線程將更新進度條。

      例2-13列出了圖2-12所示的小應用程序的完整的代碼。

      例2-13 從另一個線程更新組件的錯誤方法

      更新例2-13所示的小應用程序中的進度條的正確方法是使用SwingUtilities.invokeLater(或invokeAndWait)。下面列出的GetInfoThread類的構造方法被修改了以便實例化一個可運行的對象,該對象獲取對小應用程序進度條的引用并更新進度條的值。GetInfoThread類的run方法調用SwingUtilities.invokeLater并把對進度條的引用傳送給可運行對象。

      例2-14是例2-13所列的小應用程序的修改版。

      例2-14 從另一個線程中更新組件的正確方法(演示圖)

      2.SwingUtilities.InvokeAndWait

      與invokeLater一樣,SwingUtilities.InvokeAndWait也把可運行對象排入事件派發線程的隊列中。雖然,invokeLater在把可運行對象放入隊列后就返回,而InvokeAndWait一直等待直到已啟動了可運行對象的run方法才返回。如果在另一個操作能夠在另一個線程上執行之前必須從一個組件獲取信息,則InvokeAndWait方法是很有用的。

      例如,例2-14列出的小應用程序總是更新進度條的值而不管該新值是否與當前的值相同。如果只在新值與當前值不同時才更新進度條的值,則效率更高。修改這個小應用程序,使得這個小應用程序只在新值與當前值不同時才更新進度條的值。這將使我們有機會進一步介紹InvokdAndWait方法。

      首先,修改GetInfoThread類以創建兩個可運行的對象:一個對象獲取進度條當前的值,另一個對象用于設置進度條的值。

      接著,使用invokeAndWait()來修改GetInfoThread類的run方法以獲取進度條的當前值。

      SwingUtilities.invokeAndWait()獲取進度條的當前值,invokeLater()則設置進度條的值。對InvokeAndWait的調用直到getValue可運行對象的run方法返回后才返回。

      SwingUtilities.invokeAndWait可能會彈出下面兩個異常信息之一:InterruptedException或InvocationTargetException。無論何時使用invokeLater()都必須捕捉這些異常,否則,調用invokeLater()的方法中必須有一個throw子句。

      例2-15顯示了這種方法的完整代碼

      例2-15 使用SwingUtilities.InvokeAndWait()

      invokeLater()和invokeAndWait()之間一個重要的區別是:可以從事件派發線程中調用invokeLater(),卻不能從事件派發線程中調用invokeAndWait。從事件派發線程調用invokeAndWait()所帶來的問題是:invokeAndWait()鎖定調用它的線程,直到可運行對象從事件派發線程中派出去并且該可運行對象的run方法激活。如果從事件派發線程調用invokeAndWait(),則將發生線程死鎖的情況,因為invokeAndWait()正在等待事件派發,但是,由于是從事件派發線程中調用invokeAndWait()的,所以,直到invokeAndWait()返回后事件才能派發。

      Swint提示

      使用SwingUilities.invokeLater()和SwingUtilities.invokeAndWait()從事件派發線程之外的線程訪問組件

      由于Swing是線程不安全的,所以,從事件派發線程之外的線程訪問Swing組件是不安全的。SwingUtilities類提供了兩個用于執行事件派發線程中代碼的方法,這兩種方法是invokeLater和invokeAndWait。

      注意:可以從事件派發線程調用SwingUtilities.invokeLater,卻不能從事件派發線程調用SwingUtilities.invokeAndWait。如果從事件派發線程調用SwingUtilities.invokeAndWait,則將發生線程死鎖。因為invokeAndWait正在等待可運行對象被從事件派發線程中派發出去,但是調用SwingUtilities.invokeAndWait的線程返回前事件不能派發。

      2.5 本章回顧

      Swing的設計目標之一是為實現小應用程序和應用程序的完整性制定一些約定,大多數情況下,這個目標已經達到了。Swing小應用程序和應用程序含有JRootPane的一個實例,這意味著不能把組件直接添加到JApplet或JFrame的實例中,也不能顯式地為JApplet或JFrame的實例設置布局管理器。組件應該添加到根窗格的內容窗格中,同理,必須為內容窗格設置布局管理器而不是為小應用程序和應用程序布局管理器。幸運的是,無論何時組件直接添加、或是顯式地為小應用程序或窗體設置了布局管理器,JApplet和JFrame都會彈出帶錯誤的異常消息。

      把Swing實現為線程不安全的決定是肯定會遭到反對的。畢竟,Java語言本身就內置了多線程特性,因此,就會有人主張應當以線程安全的模式實現Swing。

      然而,正是因為Java內置了對多線程的支持,但這并不意味著在Java中實現安全的多線程小應用程序或應用程序是一件簡單的事情,更不用提工具包了。事實正相反,以線程安全的方式實現復雜的小應用程序和應用程序是相當困難的。另外,大多數開發人員不精通開發復雜的多線程代碼。當多線程被引入到面向對象語言中以后,人們遇到的較困難的領域之一就是如何擴展線程安全的類。相比之下,Swing開發人員使用的單線程方法使得類很容易擴展。

      總之,禁止從事件派發線程外的其他線程訪問Swing組件的決定是正確的,它產生了一個較容易擴展的、較簡單的工具包。另外,除事件派發線程外的其他線程可以調度在事件派發線程上實現的可運行對象。

      Swing是一個可靠的、工業標準的用戶界面工具包,比AWT大有改進。但是,與任何重要的軟件一樣,Swing很容易學習,但也有程序錯誤。

      Spring Java 容器

      版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。

      版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。

      上一篇:Excel批量取消行列隱藏的方法
      下一篇:請問我現在的文檔怎么無法在電腦上分享(電腦文檔分享不了怎么辦?)
      相關文章
      亚洲精品成人无码中文毛片不卡| 77777亚洲午夜久久多人| 亚洲va久久久噜噜噜久久天堂| 中文字幕亚洲一区二区va在线| 日韩亚洲国产二区| 亚洲大尺度无码无码专线一区| 亚洲欧洲专线一区| 亚洲欧美日韩中文高清www777| 亚洲色中文字幕在线播放| 亚洲国产视频久久| 亚洲综合av一区二区三区| 亚洲愉拍一区二区三区| 亚洲av无码一区二区三区人妖| 亚洲av永久无码精品秋霞电影秋| 亚洲变态另类一区二区三区 | 国产成人+综合亚洲+天堂| 国产成人亚洲精品无码AV大片| 亚洲国产精品尤物yw在线| 亚洲精品国产日韩无码AV永久免费网 | 亚洲日韩中文字幕在线播放| 亚洲色精品vr一区二区三区| 亚洲AV无码成人精品区在线观看 | 久久亚洲中文无码咪咪爱| 亚洲AⅤ视频一区二区三区| 亚洲人AV永久一区二区三区久久| 亚洲午夜爱爱香蕉片| 亚洲日产韩国一二三四区| 亚洲AV无码专区国产乱码4SE| 久久久久久久久亚洲| 亚洲美女在线观看播放| 亚洲Av高清一区二区三区| 亚洲乱色伦图片区小说| 亚洲成a人片在线播放| 国产成人精品日本亚洲专区61 | 亚洲国产一区二区三区在线观看 | 67pao强力打造67194在线午夜亚洲| 亚洲欧洲日本天天堂在线观看| 亚洲综合偷自成人网第页色| 久久亚洲中文无码咪咪爱| 中文字幕亚洲电影| 亚洲国产成人精品不卡青青草原|