您好,登錄后才能下訂單哦!
小編給大家分享一下Java怎么實(shí)現(xiàn)帶復(fù)選框的樹(shù),相信大部分人都還不怎么了解,因此分享這篇文章給大家參考一下,希望大家閱讀完這篇文章后大有收獲,下面讓我們一起去了解一下吧!
在使用Java Swing開(kāi)發(fā)UI程序時(shí),很有可能會(huì)遇到使用帶復(fù)選框的樹(shù)的需求,但是Java Swing并沒(méi)有提供這個(gè)組件,因此如果你有這個(gè)需求,你就得自己動(dòng)手實(shí)現(xiàn)帶復(fù)選框的樹(shù)。
CheckBoxTree與JTree在兩個(gè)層面上存在差異:
1.在模型層上,CheckBoxTree的每個(gè)結(jié)點(diǎn)需要一個(gè)成員來(lái)保存其是否被選中,但是JTree的結(jié)點(diǎn)則不需要。
2.在視圖層上,CheckBoxTree的每個(gè)結(jié)點(diǎn)比JTree的結(jié)點(diǎn)多顯示一個(gè)復(fù)選框。
既然存在兩個(gè)差異,那么只要我們把這兩個(gè)差異部分通過(guò)自己的實(shí)現(xiàn)填補(bǔ)上,那么帶復(fù)選框的樹(shù)也就實(shí)現(xiàn)了。
現(xiàn)在開(kāi)始解決第一個(gè)差異。為了解決第一個(gè)差異,需要定義一個(gè)新的結(jié)點(diǎn)類CheckBoxTreeNode,該類繼承DefaultMutableTreeNode,并增加新的成員isSelected來(lái)表示該結(jié)點(diǎn)是否被選中。對(duì)于一顆CheckBoxTree,如果某一個(gè)結(jié)點(diǎn)被選中的話,其復(fù)選框會(huì)勾選上,并且使用CheckBoxTree的動(dòng)機(jī)在于可以一次性地選中一顆子樹(shù)。那么,在選中或取消一個(gè)結(jié)點(diǎn)時(shí),其祖先結(jié)點(diǎn)和子孫結(jié)點(diǎn)應(yīng)該做出某種變化。在此,我們應(yīng)用如下遞歸規(guī)則:
1.如果某個(gè)結(jié)點(diǎn)被手動(dòng)選中,那么它的所有子孫結(jié)點(diǎn)都應(yīng)該被選中;如果選中該結(jié)點(diǎn)使其父節(jié)點(diǎn)的所有子結(jié)點(diǎn)都被選中,則選中其父結(jié)點(diǎn)。
2.如果某個(gè)結(jié)點(diǎn)被手動(dòng)取消選中,那么它的所有子孫結(jié)點(diǎn)都應(yīng)該被取消選中;如果該結(jié)點(diǎn)的父結(jié)點(diǎn)處于選中狀態(tài),則取消選中其父結(jié)點(diǎn)。
注意:上面的兩條規(guī)則是遞歸規(guī)則,當(dāng)某個(gè)結(jié)點(diǎn)發(fā)生變化,導(dǎo)致另外的結(jié)點(diǎn)發(fā)生變化時(shí),另外的結(jié)點(diǎn)也會(huì)導(dǎo)致其他的結(jié)點(diǎn)發(fā)生變化。在上面兩條規(guī)則中,強(qiáng)調(diào)手動(dòng),是因?yàn)槭謩?dòng)選中或者手動(dòng)取消選中一個(gè)結(jié)點(diǎn),會(huì)導(dǎo)致其他結(jié)點(diǎn)發(fā)生非手動(dòng)的選中或者取消選中,這種非手動(dòng)導(dǎo)致的選中或者非取消選中則不適用于上述規(guī)則。
按照上述規(guī)則實(shí)現(xiàn)的CheckBoxTreeNode源代碼如下:
package demo; import javax.swing.tree.DefaultMutableTreeNode; public class CheckBoxTreeNode extends DefaultMutableTreeNode { protected boolean isSelected; public CheckBoxTreeNode() { this(null); } public CheckBoxTreeNode(Object userObject) { this(userObject, true, false); } public CheckBoxTreeNode(Object userObject, boolean allowsChildren, boolean isSelected) { super(userObject, allowsChildren); this.isSelected = isSelected; } public boolean isSelected() { return isSelected; } public void setSelected(boolean _isSelected) { this.isSelected = _isSelected; if(_isSelected) { // 如果選中,則將其所有的子結(jié)點(diǎn)都選中 if(children != null) { for(Object obj : children) { CheckBoxTreeNode node = (CheckBoxTreeNode)obj; if(_isSelected != node.isSelected()) node.setSelected(_isSelected); } } // 向上檢查,如果父結(jié)點(diǎn)的所有子結(jié)點(diǎn)都被選中,那么將父結(jié)點(diǎn)也選中 CheckBoxTreeNode pNode = (CheckBoxTreeNode)parent; // 開(kāi)始檢查pNode的所有子節(jié)點(diǎn)是否都被選中 if(pNode != null) { int index = 0; for(; index < pNode.children.size(); ++ index) { CheckBoxTreeNode pChildNode = (CheckBoxTreeNode)pNode.children.get(index); if(!pChildNode.isSelected()) break; } /* * 表明pNode所有子結(jié)點(diǎn)都已經(jīng)選中,則選中父結(jié)點(diǎn), * 該方法是一個(gè)遞歸方法,因此在此不需要進(jìn)行迭代,因?yàn)?nbsp; * 當(dāng)選中父結(jié)點(diǎn)后,父結(jié)點(diǎn)本身會(huì)向上檢查的。 */ if(index == pNode.children.size()) { if(pNode.isSelected() != _isSelected) pNode.setSelected(_isSelected); } } } else { /* * 如果是取消父結(jié)點(diǎn)導(dǎo)致子結(jié)點(diǎn)取消,那么此時(shí)所有的子結(jié)點(diǎn)都應(yīng)該是選擇上的; * 否則就是子結(jié)點(diǎn)取消導(dǎo)致父結(jié)點(diǎn)取消,然后父結(jié)點(diǎn)取消導(dǎo)致需要取消子結(jié)點(diǎn),但 * 是這時(shí)候是不需要取消子結(jié)點(diǎn)的。 */ if(children != null) { int index = 0; for(; index < children.size(); ++ index) { CheckBoxTreeNode childNode = (CheckBoxTreeNode)children.get(index); if(!childNode.isSelected()) break; } // 從上向下取消的時(shí)候 if(index == children.size()) { for(int i = 0; i < children.size(); ++ i) { CheckBoxTreeNode node = (CheckBoxTreeNode)children.get(i); if(node.isSelected() != _isSelected) node.setSelected(_isSelected); } } } // 向上取消,只要存在一個(gè)子節(jié)點(diǎn)不是選上的,那么父節(jié)點(diǎn)就不應(yīng)該被選上。 CheckBoxTreeNode pNode = (CheckBoxTreeNode)parent; if(pNode != null && pNode.isSelected() != _isSelected) pNode.setSelected(_isSelected); } } }
第一個(gè)差異通過(guò)繼承DefaultMutableTreeNode定義CheckBoxTreeNode解決了,接下來(lái)需要解決第二個(gè)差異。第二個(gè)差異是外觀上的差異,JTree的每個(gè)結(jié)點(diǎn)是通過(guò)TreeCellRenderer進(jìn)行顯示的。為了解決第二個(gè)差異,我們定義一個(gè)新的類CheckBoxTreeCellRenderer,該類實(shí)現(xiàn)了TreeCellRenderer接口。CheckBoxTreeRenderer的源代碼如下:
package demo; import java.awt.Color; import java.awt.Component; import java.awt.Dimension; import javax.swing.JCheckBox; import javax.swing.JPanel; import javax.swing.JTree; import javax.swing.UIManager; import javax.swing.plaf.ColorUIResource; import javax.swing.tree.TreeCellRenderer; public class CheckBoxTreeCellRenderer extends JPanel implements TreeCellRenderer { protected JCheckBox check; protected CheckBoxTreeLabel label; public CheckBoxTreeCellRenderer() { setLayout(null); add(check = new JCheckBox()); add(label = new CheckBoxTreeLabel()); check.setBackground(UIManager.getColor("Tree.textBackground")); label.setForeground(UIManager.getColor("Tree.textForeground")); } /** * 返回的是一個(gè)<code>JPanel</code>對(duì)象,該對(duì)象中包含一個(gè)<code>JCheckBox</code>對(duì)象 * 和一個(gè)<code>JLabel</code>對(duì)象。并且根據(jù)每個(gè)結(jié)點(diǎn)是否被選中來(lái)決定<code>JCheckBox</code> * 是否被選中。 */ @Override public Component getTreeCellRendererComponent(JTree tree, Object value, boolean selected, boolean expanded, boolean leaf, int row, boolean hasFocus) { String stringValue = tree.convertValueToText(value, selected, expanded, leaf, row, hasFocus); setEnabled(tree.isEnabled()); check.setSelected(((CheckBoxTreeNode)value).isSelected()); label.setFont(tree.getFont()); label.setText(stringValue); label.setSelected(selected); label.setFocus(hasFocus); if(leaf) label.setIcon(UIManager.getIcon("Tree.leafIcon")); else if(expanded) label.setIcon(UIManager.getIcon("Tree.openIcon")); else label.setIcon(UIManager.getIcon("Tree.closedIcon")); return this; } @Override public Dimension getPreferredSize() { Dimension dCheck = check.getPreferredSize(); Dimension dLabel = label.getPreferredSize(); return new Dimension(dCheck.width + dLabel.width, dCheck.height < dLabel.height ? dLabel.height: dCheck.height); } @Override public void doLayout() { Dimension dCheck = check.getPreferredSize(); Dimension dLabel = label.getPreferredSize(); int yCheck = 0; int yLabel = 0; if(dCheck.height < dLabel.height) yCheck = (dLabel.height - dCheck.height) / 2; else yLabel = (dCheck.height - dLabel.height) / 2; check.setLocation(0, yCheck); check.setBounds(0, yCheck, dCheck.width, dCheck.height); label.setLocation(dCheck.width, yLabel); label.setBounds(dCheck.width, yLabel, dLabel.width, dLabel.height); } @Override public void setBackground(Color color) { if(color instanceof ColorUIResource) color = null; super.setBackground(color); } }
在CheckBoxTreeCellRenderer的實(shí)現(xiàn)中,getTreeCellRendererComponent方法返回的是JPanel,而不是像DefaultTreeCellRenderer那樣返回JLabel,因此JPanel中的JLabel無(wú)法對(duì)選中做出反應(yīng),因此我們重新實(shí)現(xiàn)了一個(gè)JLabel的子類CheckBoxTreeLabel,它可以對(duì)選中做出反應(yīng),其源代碼如下:
package demo; import java.awt.Color; import java.awt.Dimension; import java.awt.Graphics; import javax.swing.Icon; import javax.swing.JLabel; import javax.swing.UIManager; import javax.swing.plaf.ColorUIResource; public class CheckBoxTreeLabel extends JLabel { private boolean isSelected; private boolean hasFocus; public CheckBoxTreeLabel() { } @Override public void setBackground(Color color) { if(color instanceof ColorUIResource) color = null; super.setBackground(color); } @Override public void paint(Graphics g) { String str; if((str = getText()) != null) { if(0 < str.length()) { if(isSelected) g.setColor(UIManager.getColor("Tree.selectionBackground")); else g.setColor(UIManager.getColor("Tree.textBackground")); Dimension d = getPreferredSize(); int imageOffset = 0; Icon currentIcon = getIcon(); if(currentIcon != null) imageOffset = currentIcon.getIconWidth() + Math.max(0, getIconTextGap() - 1); g.fillRect(imageOffset, 0, d.width - 1 - imageOffset, d.height); if(hasFocus) { g.setColor(UIManager.getColor("Tree.selectionBorderColor")); g.drawRect(imageOffset, 0, d.width - 1 - imageOffset, d.height - 1); } } } super.paint(g); } @Override public Dimension getPreferredSize() { Dimension retDimension = super.getPreferredSize(); if(retDimension != null) retDimension = new Dimension(retDimension.width + 3, retDimension.height); return retDimension; } public void setSelected(boolean isSelected) { this.isSelected = isSelected; } public void setFocus(boolean hasFocus) { this.hasFocus = hasFocus; } }
通過(guò)定義CheckBoxTreeNode和CheckBoxTreeCellRenderer。我們解決了CheckBoxTree和JTree的兩個(gè)根本差異,但是還有一個(gè)細(xì)節(jié)問(wèn)題需要解決,就是CheckBoxTree可以響應(yīng)用戶事件決定是否選中某個(gè)結(jié)點(diǎn)。為此,我們?yōu)镃heckBoxTree添加一個(gè)響應(yīng)用戶鼠標(biāo)事件的監(jiān)聽(tīng)器CheckBoxTreeNodeSelectionListener,該類的源代碼如下:
package demo; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import javax.swing.JTree; import javax.swing.tree.TreePath; import javax.swing.tree.DefaultTreeModel; public class CheckBoxTreeNodeSelectionListener extends MouseAdapter { @Override public void mouseClicked(MouseEvent event) { JTree tree = (JTree)event.getSource(); int x = event.getX(); int y = event.getY(); int row = tree.getRowForLocation(x, y); TreePath path = tree.getPathForRow(row); if(path != null) { CheckBoxTreeNode node = (CheckBoxTreeNode)path.getLastPathComponent(); if(node != null) { boolean isSelected = !node.isSelected(); node.setSelected(isSelected); ((DefaultTreeModel)tree.getModel()).nodeStructureChanged(node); } } } }
到此為止,CheckBoxTree所需要的所有組件都已經(jīng)完成了,接下來(lái)就是如何使用這些組件。下面給出了使用這些組件的源代碼:
package demo; import javax.swing.JFrame; import javax.swing.JScrollPane; import javax.swing.JTree; import javax.swing.tree.DefaultTreeModel; public class DemoMain { public static void main(String[] args) { JFrame frame = new JFrame("CheckBoxTreeDemo"); frame.setBounds(200, 200, 400, 400); JTree tree = new JTree(); CheckBoxTreeNode rootNode = new CheckBoxTreeNode("root"); CheckBoxTreeNode node1 = new CheckBoxTreeNode("node_1"); CheckBoxTreeNode node1_1 = new CheckBoxTreeNode("node_1_1"); CheckBoxTreeNode node1_2 = new CheckBoxTreeNode("node_1_2"); CheckBoxTreeNode node1_3 = new CheckBoxTreeNode("node_1_3"); node1.add(node1_1); node1.add(node1_2); node1.add(node1_3); CheckBoxTreeNode node2 = new CheckBoxTreeNode("node_2"); CheckBoxTreeNode node2_1 = new CheckBoxTreeNode("node_2_1"); CheckBoxTreeNode node2_2 = new CheckBoxTreeNode("node_2_2"); node2.add(node2_1); node2.add(node2_2); rootNode.add(node1); rootNode.add(node2); DefaultTreeModel model = new DefaultTreeModel(rootNode); tree.addMouseListener(new CheckBoxTreeNodeSelectionListener()); tree.setModel(model); tree.setCellRenderer(new CheckBoxTreeCellRenderer()); JScrollPane scroll = new JScrollPane(tree); scroll.setBounds(0, 0, 300, 320); frame.getContentPane().add(scroll); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setVisible(true); } }
其執(zhí)行結(jié)果如下圖所示:
以上是“Java怎么實(shí)現(xiàn)帶復(fù)選框的樹(shù)”這篇文章的所有內(nèi)容,感謝各位的閱讀!相信大家都有了一定的了解,希望分享的內(nèi)容對(duì)大家有所幫助,如果還想學(xué)習(xí)更多知識(shí),歡迎關(guān)注億速云行業(yè)資訊頻道!
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。