最近在為學校做一個工資發放軟件,要用JAVA SWING制作相應的工資表,這就涉及到多行表頭及表格的合并。我足足花了3天的時間去找相關的資料,然而基本上都是E文的,而且所以例子的代碼都沒有注解,所以我決定將我所收集的資料整理公布出來,希望能給大家一些幫助。由于本人只是一名小學教師,水平有限,如果有什么不正確的地方,請多包涵。
廢話少說,轉入正題吧!
一、單元格合并。
Jtable沒有提供現成的合并單元格的方法,但是使用其所提供的方法仍然能做到這一點,只是復雜了一些。為了合并單元格,我們可以使用Jtable的三個方法:getCellRect(),columnAtPoint(),and rowAtPoint()。第一個方法返回一個單元格的邊界(Rectangle類),第二、三個方法分別返回屏幕指定位置的列和行。為了實現單元格合并,我們需要重載(overwrite)這三個方法。
另外,網上的資料提到,大部分的swing components 并不是直接由paint()方法來渲染(render),而是使用ComponentUI對象來完成渲染的。所以我們需要找出渲染Jtable的ComponentUI對象,并且修改它以達到我們的目的。
由于要實現多行多列單元格合并需要多個類相互協作,直接寫出來的話可能比較復雜,所以我先講一下跨列的單元格合并的方法,然后再提供一個完整的例子。
由于swing里沒有可記錄單元格合并情況的數據模型,所以我們需要一個新的類,它要包涵一個方法來取得單元格的所跨越的列數。另外,為了使用Jtable畫(paint)起來更容易些,我們需要一個方法來確定指定單元格是否被其它單元格所覆蓋,被哪個單元格覆蓋。我們將這兩種方法都集成在接口Cmap里:
現在我們開始重載上面提及過的三個方法。由于我們目前只關注于跨列單元格的合并,方法rowAtPoint()就不用重載了。然而,方法columnAtPoint()就必須重載了,我們會使用Jtable自身的方法來取得指定單元格的列值,并且計算出覆蓋該單元格的可視單元格列值(如果該單元格本來就是可視的,則返回自身列值)。在單元格合并后,在合并區域內只有一個跨越多列的可視單元格,其它被覆蓋的單元格則不會再被渲染。當使用getCellRect()方法取得被覆蓋的單元格的大小時,都返回覆蓋該單元格的可視單元格的大小。
現在剩下的就只有創建一個表格的渲染對象了。不同的用戶接口管理器(user interface managers)使用不同的類來畫表格。我們會繼承子類 javax.swing.plaf.basic.BasicTableUI,并且重載其方法 paintComponent。
在組件(component)畫在屏幕上之前,它已經被初始化和設定好了,所以我們能使用其內部的屬性 table 和 rendererPane。屬性 table 就是將要被畫在屏幕的表格,rendererPane 是用于將單元格畫在表格中的特殊對象。使用RendererPane的目的是打破單元格和表格的直接依賴關系,并且防止當一個單元格被修改時重畫整個表。
BasicTableUI的方法getClipBounds是用于找出表格的哪一部分將會被畫出來,所以我們首先要知道那些行是可視的,我們可以使用Jtable 的rowAtPoint方法。我們可以使用Rectangle類的intersects方法來確定這些行中的所以單元格是否將會被畫在屏幕上。在我們畫任何一個單元格前,我們必須檢查一下當前單元格是否可視,如果該單元格是被其它單元格所覆蓋的,就將覆蓋它的單元格畫出來。
根據單元格是否正在被編輯,單元格將會被方法getCellEditor或getCellRenderer所返回的對象畫出來。如果你查看一下BasicTableUI的源代碼,你就會發現所以單元格會先被BasicTableUI調用table.prepareRenderer畫(drawn)出來,然后再被BasicTableUI調用rendererPane.paintComponent來渲染(paint)。我們會采用同樣的方法。
文章、源代碼來源:http://www.swingwiki.org/howto:column_spanning?
廢話少說,轉入正題吧!
一、單元格合并。
Jtable沒有提供現成的合并單元格的方法,但是使用其所提供的方法仍然能做到這一點,只是復雜了一些。為了合并單元格,我們可以使用Jtable的三個方法:getCellRect(),columnAtPoint(),and rowAtPoint()。第一個方法返回一個單元格的邊界(Rectangle類),第二、三個方法分別返回屏幕指定位置的列和行。為了實現單元格合并,我們需要重載(overwrite)這三個方法。
另外,網上的資料提到,大部分的swing components 并不是直接由paint()方法來渲染(render),而是使用ComponentUI對象來完成渲染的。所以我們需要找出渲染Jtable的ComponentUI對象,并且修改它以達到我們的目的。
由于要實現多行多列單元格合并需要多個類相互協作,直接寫出來的話可能比較復雜,所以我先講一下跨列的單元格合并的方法,然后再提供一個完整的例子。
由于swing里沒有可記錄單元格合并情況的數據模型,所以我們需要一個新的類,它要包涵一個方法來取得單元格的所跨越的列數。另外,為了使用Jtable畫(paint)起來更容易些,我們需要一個方法來確定指定單元格是否被其它單元格所覆蓋,被哪個單元格覆蓋。我們將這兩種方法都集成在接口Cmap里:
代碼: |
package com.neuri.ctable; public interface CMap { ?/** * @參數row:指定單元格所在的邏輯行 * @參數column:指定單元格所在的邏輯列 * @返回指定單元格所跨越的列數 */ int span (int row, int column); /** * @參數row:指定單元格所在的邏輯行 * @參數column:指定單元格所在的邏輯列 * @返回覆蓋指定單元格的可視單元格的列值,如果單元格本來就是可視的話,返回自身的列值 */ int visibleCell(int row, int column); } |
現在我們開始重載上面提及過的三個方法。由于我們目前只關注于跨列單元格的合并,方法rowAtPoint()就不用重載了。然而,方法columnAtPoint()就必須重載了,我們會使用Jtable自身的方法來取得指定單元格的列值,并且計算出覆蓋該單元格的可視單元格列值(如果該單元格本來就是可視的,則返回自身列值)。在單元格合并后,在合并區域內只有一個跨越多列的可視單元格,其它被覆蓋的單元格則不會再被渲染。當使用getCellRect()方法取得被覆蓋的單元格的大小時,都返回覆蓋該單元格的可視單元格的大小。
代碼: |
?package com.neuri.ctable; import javax.swing.*; import javax.swing.table.*; import java.awt.*; public class CTable extends JTable { ? public CMap map; ? public CTable(CMap cmp, TableModel tbl) { ? ? super(tbl); ? ? map=cmp; ? ? setUI(new CTUI());//設置Jtable的渲染UI ? } ? public Rectangle getCellRect(int row, int column, boolean includeSpacing){ ? ? // 該方法是Jtable構建時所必須的 ? ? if (map==null) return super.getCellRect(row,column, includeSpacing); ? ?// 指定單元格的可視單元格列值 ? ? int sk=map.visibleCell(row,column); Rectangle r1=super.getCellRect(row,sk,includeSpacing); // 如果指定單元格列寬不為1,累計出跨列單元格的寬度 ? ? if (map.span(row,sk)!=1) ? ? for (int i=1; i<map.span(row,sk); i++){ ? ? ? ? r1.width+=getColumnModel().getColumn(sk+i).getWidth(); ? ? ? } ? ? return r1; ? } ? public int columnAtPoint(Point p) { ? ? int x=super.columnAtPoint(p); ? ? // 當指定位置不在Table內時,返回-1 ? ? if (x<0) return x; int y=super.rowAtPoint(p); //獲取指定位置可視單元格的列值 ? ? return map.visibleCell(y,x); ? } } |
現在剩下的就只有創建一個表格的渲染對象了。不同的用戶接口管理器(user interface managers)使用不同的類來畫表格。我們會繼承子類 javax.swing.plaf.basic.BasicTableUI,并且重載其方法 paintComponent。
在組件(component)畫在屏幕上之前,它已經被初始化和設定好了,所以我們能使用其內部的屬性 table 和 rendererPane。屬性 table 就是將要被畫在屏幕的表格,rendererPane 是用于將單元格畫在表格中的特殊對象。使用RendererPane的目的是打破單元格和表格的直接依賴關系,并且防止當一個單元格被修改時重畫整個表。
BasicTableUI的方法getClipBounds是用于找出表格的哪一部分將會被畫出來,所以我們首先要知道那些行是可視的,我們可以使用Jtable 的rowAtPoint方法。我們可以使用Rectangle類的intersects方法來確定這些行中的所以單元格是否將會被畫在屏幕上。在我們畫任何一個單元格前,我們必須檢查一下當前單元格是否可視,如果該單元格是被其它單元格所覆蓋的,就將覆蓋它的單元格畫出來。
根據單元格是否正在被編輯,單元格將會被方法getCellEditor或getCellRenderer所返回的對象畫出來。如果你查看一下BasicTableUI的源代碼,你就會發現所以單元格會先被BasicTableUI調用table.prepareRenderer畫(drawn)出來,然后再被BasicTableUI調用rendererPane.paintComponent來渲染(paint)。我們會采用同樣的方法。
代碼: |
package com.neuri.ctable; import javax.swing.table.*; import javax.swing.plaf.basic.*; import java.awt.*; import javax.swing.*; public class CTUI extends BasicTableUI { ? public void paint(Graphics g, JComponent c) { ? ? Rectangle r=g.getClipBounds(); ? ? int firstRow=table.rowAtPoint(new Point(0,r.y)); ? ? int lastRow=table.rowAtPoint(new Point(0,r.y+r.height)); ? ? // -1 is a flag that the ending point is outside the table ? ? if (lastRow<0) ? ? ? ? ?lastRow=table.getRowCount()-1; ? ? for (int i=firstRow; i<=lastRow; i++) ? ? ? ? ? ? ? paintRow(i,g); ? } ? private void paintRow(int row, Graphics g) ? { ? ? Rectangle r=g.getClipBounds(); ? ? for (int i=0; i<table.getColumnCount();i++) ? ? { ? ? ? Rectangle r1=table.getCellRect(row,i,true); ? ? ? if (r1.intersects(r)) // at least a part is visible ? ? ? { ? ? ? ? int sk=((CTable)table).map.visibleCell(red,i); ? ? ? ? paintCell(row,sk,g,r1); ? ? ? ? // increment the column counter ? ? ? ? i+=((CTable)table).map.span(row,sk)-1; ? ? ? } ? ? } ? } ? private void paintCell(int row, int column, Graphics g,Rectangle area) ? { ? ? int verticalMargin = table.getRowMargin(); ? ? int horizontalMargin? = table.getColumnModel().getColumnMargin(); ? ? ? Color c = g.getColor(); ? ? g.setColor(table.getGridColor()); ? ? g.drawRect(area.x,area.y,area.width-1,area.height-1); ? ? g.setColor(c); ? ? ? area.setBounds(area.x + horizontalMargin/2, ? ? ? ? ? ? ? ? ? area.y + verticalMargin/2, ? ? ? ? ? ? ? ? ? ? area.width - horizontalMargin, ? ? ? ? ? ? ? ? ? area.height - verticalMargin); ? ? ? if (table.isEditing() && table.getEditingRow()==row && ? ? ? ? ?table.getEditingColumn()==column) ? ? { ? ? ? Component component = table.getEditorComponent(); ? ? ? component.setBounds(area); ? ? ? component.validate(); ? ? } ? ? else ? ? ?{ ? ? ? TableCellRenderer renderer = table.getCellRenderer(row, column); ? ? ? Component component = table.prepareRenderer(renderer, row, column); ? ? ? if (component.getParent() == null) ? ? ? ? ?rendererPane.add(component); ? ? ?rendererPane.paintComponent(g, component, table, area.x, area.y, ? ? ?area.width, area.height, true); ? ? } ? } } |
文章、源代碼來源:http://www.swingwiki.org/howto:column_spanning?
今天下載了關于java swing的一個開源項目包tame,不過由于完成年代久遠(98年),很多類在新的jdk1.4或jdk1.5上已經會報錯。例如AttributiveCellTableModel類的setDataVector方法便需要改為:
? public void setDataVector(Vector newData, Vector columnNames)
? {
? super.setDataVector(newData, columnNames);
? cellAtt = new DefaultCellAttribute(dataVector.size(),columnIdentifiers.size());
? }
有心重整tame,先記一筆。
? {
? super.setDataVector(newData, columnNames);
? cellAtt = new DefaultCellAttribute(dataVector.size(),columnIdentifiers.size());
? }
有心重整tame,先記一筆。
①java swing基于MVC架構,或者說是Model-driven結構。以jtable為例,它的特有GUI-State Model是TableColumnModel(JTable是面向列的,它基于每一列進行繪制和編輯。分別是列繪制器TableCellRenderer和列編輯器TableCellEditor);它的共有GUI-State Model是Selection Model(和jlist、jtree等共用)。除了GUI-State Model,還有決定顯示在控件中的內容的Application-data model,jtable的Application-data model是TableModel。實現方式是先定義接口TableModel,再定義抽象類AbstractTableModel實現這個接口,然后由DefaultTableModel實現抽象類。不過一般來說用戶需要自己擴展AbstractTableModel實現它的幾個方法來獲取和設定值。
?
②以jtable為例,它并未提供實現單元格合并的方法。所以我們需要重載它的三個方法(getCellRect:獲取單元格的邊界,columnAtPoint和rowAtPoint:分別返回屏幕指定位置的列和行)。
?
③現在我們需要自己繪制jtable,所以要用到Graphics類。另大部分的swing components 并不是直接由paint()方法來渲染(render),而是使用ComponentUI對象來完成渲染的。所以我們需要找出渲染Jtable的ComponentUI對象(BasicTableUI),并且修改它(重載paint()方法)以達到我們的目的。
?
④現在開始具體實現,tame先定義了4個接口(CellAttribute、ColoredCell、CellFont、CellSpan),用DefaultCellAttribute類實現了這四個接口,包含相關表格的基本屬性(顏色、字體、合并單元格的屬性等),這個類將每個cell定義為一個三維數組int[][][] span,并且都初始化為1。繼承自DefaultTableModel的AttributiveCellTableModel負責初始化table,至此并無特異之處。當需要合并單元格時。監聽按鈕將調用DefaultCellAttribute的combine方法把被覆蓋的單元格的三維數組int[][][] span設置為小于1,這樣在繪制的時候就可以判斷哪單元格可見,哪些單元格不可見了。接著通過重載jtable的三個方法得到cell的邊界以及行和列的位置,重繪表格的時候通過每個cell返回的結果(邊界、位置、是否可見)等循環繪制。