最近接觸了一點Java的GUI編程,也就是由Java AWT更新而來的Java Swing。
總體上而言,Java Swing編程有兩大特點:麻煩、效果差。
麻煩是說由於設計器的使用不方便(如果您希望使用窗體設計器通過快速拖拽控件建立您的Java Swing GUI程序,請您使用MyEclipse 8.5以上版本,並且需要最高使用權限),所有代碼都得手寫,如果沒有好的編碼規范和注釋習慣。自己都會被代碼淹沒。
效果差是指運行時的界面。具體的您可以自己嘗試發現。
那么我們通過一段代碼來創建屬於我們的窗體:
1 import javax.swing.JFrame; 2 3 /** 4 * 我的第一個Java窗體 5 * 6 * @author Johness 7 * 8 */ 9 public class MyJFrame extends JFrame{ 10 11 12 13 }
然后通過main方法來測試:
運行后,窗體在屏幕左上角顯現並且是最小化的形式。
呵呵,那么關於設置窗體的顯示我就不再贅述了,值得注意的是窗體的布局必須設置。
小貼士:使用setLayout設置布局,參數傳遞null;
我們討論一下怎樣在窗體的表格中顯示復選框。
即實現如下效果:
我隨便拖了些控件(數據是老師給的……)。
好了,我們來一步步實現。
小貼士二:使用add方法向控件添加內容控件。
①首先我們需要面板(JPanel)或其他容器控件承載表格(JTable),值得一提的是:由於窗體本身就是容器型控件,您可以考慮將表格單個地放置在窗體上。
②然后我們需要將表格對象創建出來並放入該容器控件,大家可以參考手冊(如JDK_API_1_6_zh_CN.CHM)創建表格控件。值得一提的是在這七個構造方法中,設計器(如果您使用了MyEclipse)使用的是JTable(TableModel dm)
這個版本。而一般情況使用
JTable(Vector rowData, Vector columnNames)
這個版本的居多(不包括我)。如果是我,可能會選擇使用設計器的版本。
可能有細心的朋友會發現說:設計器的版本很不方便,因為需要傳遞的是接口,我們必須寫一個類實現該接口並構造實例作為參數傳遞,麻煩,不如直接使用JTable(Object[][] rowData, Object[] columnNames)這個版本。
那么在這里我向不知道“匿名內部類”(老師是這樣稱呼的,沒考證)的朋友普及一下Java的匿名內部類。
在Java方法中,如果參數需要傳遞接口,可以在調用方法時傳遞一個(匿名)對象,該對象是一個不具名的類的實例,該對象所屬類實現了方法參數的接口。
比如上面的例子JTable(TableModel dm)
,這是JTable的構造方法,需要的是一個TableModel接口類型的參數(這里只是舉例,實際運用比較復雜),我們可以使用如下寫法:JTable table = new JTable(new TableModel());
毫無疑問,這種寫法是錯誤的,但是如果這樣寫就不是了:
1 import javax.swing.*; 2 import javax.swing.event.*; 3 import javax.swing.table.*; 4 5 6 public class MyFirstJFrame extends JFrame { 7 public MyFirstJFrame() { 8 setLayout(null); 9 10 JTable table = new JTable(new TableModel(){ 11 12 @Override 13 public int getRowCount() { 14 // TODO Auto-generated method stub 15 return 0; 16 } 17 18 @Override 19 public int getColumnCount() { 20 // TODO Auto-generated method stub 21 return 0; 22 } 23 24 @Override 25 public String getColumnName(int columnIndex) { 26 // TODO Auto-generated method stub 27 return null; 28 } 29 30 @Override 31 public Class<?> getColumnClass(int columnIndex) { 32 // TODO Auto-generated method stub 33 return null; 34 } 35 36 @Override 37 public boolean isCellEditable(int rowIndex, int columnIndex) { 38 // TODO Auto-generated method stub 39 return false; 40 } 41 42 @Override 43 public Object getValueAt(int rowIndex, int columnIndex) { 44 // TODO Auto-generated method stub 45 return null; 46 } 47 48 @Override 49 public void setValueAt(Object aValue, int rowIndex, int columnIndex) { 50 // TODO Auto-generated method stub 51 52 } 53 54 @Override 55 public void addTableModelListener(TableModelListener l) { 56 // TODO Auto-generated method stub 57 58 } 59 60 @Override 61 public void removeTableModelListener(TableModelListener l) { 62 // TODO Auto-generated method stub 63 64 }}); 65 } 66 }
我可能需要解釋一下這些代碼:首先是JTable table = new JTable(new TableModel(){});可以看出來,大括號中間的部分是一些需要重寫的方法。
那么大家應該怎樣理解這一句代碼呢?我們分解一下(new TableModel(){})。我們應該怎么看待?大家回想一下我以上說過的匿名內部類的定義。我們可以這樣看,new ……()是構造方法,調用來構造一個匿名對象,其后的{}不是Java的特殊語法,但是Java中可以將方法定義在里面(這里的方法生命周期與匿名對象相 同),當然,此處是用於實現接口的方法。
清晰一點了吧?我們再來拆分:TableModel我們可以在其前面補充一個不存在的類類名,比如MyTableModel。好了,我們完整再 現一下:new MyTableModel:TableModel(){}也就是說大家可以想象成(new TableModel(){})是在聲明一個匿名對象,它屬於一個不具名的類(如MyTableModel),該類實現了TableModel接口。而由 於語法限制,不能全部寫出來所以省略了[MyTableModel:]。當然,這只是我們的推理,大家理解記憶哈。
注:這里的匿名對象只沒有引用指向(即沒有變量名)的對象。
實際上我們使用匿名內部類的地方很多,比如添加事件監聽。但是“上面創建JTable的方法是只作為示例,絕大多數是不會如此用的”,大家謹記。
我會在隨筆結尾貼出全部代碼,其中創建JTable的代碼是使用了設計器的構造方式。
③設置表格渲染。在詳細說明之前我先解釋一下JTable的顯示原理:
首先是數據來源,您使用JTable的構造方法,大部分重載中參數即包含了數據,比如JTable(Vector rowData, Vector columnNames)
中Vector保存的數據(Vector相當於數組)。
其次是表格樣式,表格將數據和如何顯示數據(比如列數量、列名稱、是否可編輯)保存在其數據模版中,該模版實現自接口TableModel。
最后,表格(每一個單元格)可以設置渲染效果。
我把完整的代碼貼出來:
1 import java.awt.Component; 2 import java.awt.event.ActionEvent; 3 import java.awt.event.ActionListener; 4 5 import javax.swing.*; 6 import javax.swing.table.*; 7 8 9 public class MyFirstJFrame extends JFrame { 10 11 // 作為測試的main方法 12 public static void main(String[] args) { 13 new MyFirstJFrame().setVisible(true); 14 } 15 16 /** 17 * 構造方法 18 */ 19 public MyFirstJFrame() { 20 InitialComponent(); 21 } 22 23 /** 24 * 初始化組件的方法 25 */ 26 private void InitialComponent(){ 27 // 設置窗體參數 28 29 // 設置布局模式 30 setLayout(null); 31 // 設置窗體大小 32 setSize(480, 360); 33 // 設置窗體居中(非常規方法) 34 setLocationRelativeTo(null); 35 // 關閉窗體退出程序 36 setDefaultCloseOperation(DISPOSE_ON_CLOSE); 37 38 // 初始化面板 39 panel = new JPanel(); 40 panel.setSize(this.getWidth(), this.getHeight()); 41 panel.setLocation(0,0); 42 panel.setLayout(null); 43 44 // 初始化表格 45 table = new JTable(new DefaultTableModel(new Object[][]{{"第一行"},{"第二行"},{"第三行"},{"第四行"}}, new String[]{"測試行1","測試行2"}){ 46 /* (non-Javadoc) 47 * 重寫方法,判斷表單元格是否可編輯 48 * 可以通過row和column索引判斷某一個單元格是否可編輯 49 * 此處設為都不可編輯 50 * @see javax.swing.table.DefaultTableModel#isCellEditable(int, int) 51 */ 52 @Override 53 public boolean isCellEditable(int row, int column) { 54 return false; 55 } 56 }); 57 58 // 開始向表格中添加復選框(注意:此示例較為簡單,缺省很多判斷,也沒有動態代碼支持) 59 // 通過設置列渲染 60 61 // 方法一:直接方式 使用TableColumn的setCellRenderer方法(推薦) 62 // 此方法可以設置某一列的渲染(即使用某一個組件--即控件來顯示單元格數據) 63 table.getColumnModel().getColumn(1).setCellRenderer(new TableCellRenderer(){ 64 65 /*(non-Javadoc) 66 * 此方法用於向方法調用者返回某一單元格的渲染器(即顯示數據的組建--或控件) 67 * 可以為JCheckBox JComboBox JTextArea 等 68 * @see javax.swing.table.TableCellRenderer#getTableCellRendererComponent(javax.swing.JTable, java.lang.Object, boolean, boolean, int, int) 69 */ 70 @Override 71 public Component getTableCellRendererComponent(JTable table, 72 Object value, boolean isSelected, boolean hasFocus, 73 int row, int column) { 74 // 創建用於返回的渲染組件 75 JCheckBox ck = new JCheckBox(); 76 // 使具有焦點的行對應的復選框選中
77 ck.setSelected(isSelected);
//設置背景顏色 這里是設置jcheckbox的背景顏色 直接設置為透明,我這里是用了一種明暗交替的顏色轉換,所以背景顏色設置了一下
ck.setOpaque(false); 78 // 設置單選box.setSelected(hasFocus); 79 // 使復選框在單元格內居中顯示 80 ck.setHorizontalAlignment((int) 0.5f); 81 return ck; 82 }}); 83 84 // 方法二:先設置列編輯器,然后設置單元格渲染 85 // 設置列編輯器 86 // 在以復選框為對象設置列編輯器時,必須保證該列能夠被編輯,否則無法更改狀態 87 // (此步驟可以省略,省略時不要忘記將列設為不可編輯) 88 // table.getColumnModel().getColumn(1).setCellEditor(new DefaultCellEditor(new JCheckBox())); 89 90 // 設置單元格渲染(這里是設置表格級別的渲染) 91 /*table.setDefaultRenderer(Object.class, new TableCellRenderer(){ 92 93 @Override 94 public Component getTableCellRendererComponent(JTable table, 95 Object value, boolean isSelected, boolean hasFocus, 96 int row, int column) { 97 // 判斷是否為需要渲染的列 98 if(column == 1){ 99 // 和方法一基本一致 100 JCheckBox box = new JCheckBox(); 101 box.setSelected(isSelected); 102 // 設置單選box.setSelected(hasFocus); 103 box.setHorizontalAlignment((int) CENTER_ALIGNMENT); // 0.5f 104 return box; 105 } 106 // 如果不是需要渲染的列,封裝文本域顯示數據 107 return new JTextArea(value.toString()); 108 }});*/ 109 110 // 在多選是需要按住Ctrl鍵或者鼠標按住拖過連續的需要選中的行,應該給用戶說明 111 // 第一種方法是被推薦的,因為它具有選中的高亮顯示,界面能更加友好 112 table.setSize(panel.getWidth(),panel.getHeight() - 90); 113 table.setLocation(0, 0); 114 115 116 btn = new JButton("Test"); 117 btn.setSize(80,40); 118 btn.setLocation((panel.getWidth()) / 2 - 40, panel.getHeight() - 80); 119 120 // 按鈕點擊時顯示當前選中項 121 btn.addActionListener(new ActionListener(){ 122 123 @Override 124 public void actionPerformed(ActionEvent e) { 125 for(int rowindex : table.getSelectedRows()){ 126 JOptionPane.showMessageDialog(null, rowindex + " " + table.getValueAt(rowindex, 0)); 127 } 128 }}); 129 130 panel.add(table); 131 panel.add(btn); 132 this.add(panel); 133 134 } 135 136 // 定義一些必要的組件 137 private JPanel panel; 138 private JTable table; 139 private JButton btn; 140 }
上面的代碼有一些缺陷,大家需要做一些修改。實際上我也不希望貼上完全無誤的perfect的代碼,對需要學習的朋友不是好事兒。
總結:充分理解Java的方法返回值作為判斷依據。
1、匿名內部類(匿名對象后{}的妙用)。
2、窗體的布局:默認布局為(最后添加?)的控件占據其窗體的全部空間。
3、編輯器、渲染。