Bom noite a todos.
Estou precisando de uma JTree customizada onde seus nós são substituídos por controles checkbox e label's com ícone.
Vasculhando a net por ai, consegui obter alguns resultados interessantes, e até consegui implementar alguma coisa bem bacana.
Tenho um problema e uma dúvida:
Problema: quando a JTree é alimentada pelo modelo que crio, a JLabel com o ícone é truncada pelo JCheckBox e a String de dentro da JLabel também fica truncada, procurei substituir os layouts por outros, mas não tive sucesso.
A dúvida é referente a seleção dos nós, pois na ideia, pretendo ler os nós e exibir quais estão selecionados.
Segue os fontes do que tenho até agora:
Program para o fluxo principal
package br.com.misael;
import br.com.misael.controller.CtrJTree;
public class Program {
public static void main(String[] args) {
new CtrJTree();
}
}
O Formulário.
package br.com.misael.view;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.ScrollPaneConstants;
import javax.swing.border.EmptyBorder;
import br.com.misael.view.uc.JCheckBoxTree;
import javax.swing.JButton;
import javax.swing.JList;
public class FrmJTree extends JFrame {
private static final long serialVersionUID = -3780261717671688258L;
private JPanel pnl;
private JScrollPane scrollPane1;
private JScrollPane scrollPane2;
private JCheckBoxTree tree;
private JList<String> listCheckedNode;
private JButton btnShowCheckedNodes;
/**
* Create the frame.
*/
public FrmJTree() {
setResizable(false);
setTitle("Prototype JTree + JCheckBox + JLabel with icon");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setBounds(100, 100, 837, 557);
pnl = new JPanel();
pnl.setBorder(new EmptyBorder(5, 5, 5, 5));
setContentPane(pnl);
pnl.setLayout(null);
scrollPane1 = new JScrollPane();
scrollPane1
.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);
scrollPane1.setBounds(10, 11, 405, 497);
pnl.add(scrollPane1);
tree = new JCheckBoxTree();
scrollPane1.setViewportView(tree);
btnShowCheckedNodes = new JButton("Show checked nodes");
btnShowCheckedNodes.setBounds(425, 9, 396, 23);
pnl.add(btnShowCheckedNodes);
scrollPane2 = new JScrollPane();
scrollPane2
.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);
scrollPane2.setBounds(425, 43, 396, 465);
pnl.add(scrollPane2);
listCheckedNode = new JList<String>();
scrollPane2.setViewportView(listCheckedNode);
setVisible(true);
}
public JPanel getPnl() {
return pnl;
}
public void setPnl(JPanel pnl) {
this.pnl = pnl;
}
public JScrollPane getScrollPane1() {
return scrollPane1;
}
public void setScrollPane1(JScrollPane scrollPane1) {
this.scrollPane1 = scrollPane1;
}
public JScrollPane getScrollPane2() {
return scrollPane2;
}
public void setScrollPane2(JScrollPane scrollPane2) {
this.scrollPane2 = scrollPane2;
}
public JCheckBoxTree getTree() {
return tree;
}
public void setTree(JCheckBoxTree tree) {
this.tree = tree;
}
public JList<String> getListCheckedNode() {
return listCheckedNode;
}
public void setListCheckedNode(JList<String> listCheckedNode) {
this.listCheckedNode = listCheckedNode;
}
public JButton getBtnShowCheckedNodes() {
return btnShowCheckedNodes;
}
public void setBtnShowCheckedNodes(JButton btnShowCheckedNodes) {
this.btnShowCheckedNodes = btnShowCheckedNodes;
}
}
Um controlador.
package br.com.misael.controller;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowEvent;
import java.awt.event.WindowListener;
import java.util.Enumeration;
import javax.swing.DefaultListModel;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;
import br.com.misael.view.FrmJTree;
public class CtrJTree implements ActionListener, WindowListener {
private FrmJTree frmJTree;
public CtrJTree() {
this.frmJTree = new FrmJTree();
this.frmJTree.addWindowListener(this);
this.frmJTree.getBtnShowCheckedNodes().addActionListener(this);
}
private void loadJTree() {
DefaultMutableTreeNode _root = null;
DefaultMutableTreeNode _node = null;
DefaultMutableTreeNode _leaf = null;
_root = new DefaultMutableTreeNode("DATA BASE NAME ");
for(int n = 0; n < 3; n++) {
_node = new DefaultMutableTreeNode("TABLE NAME " + (n + 1) );
for(int f = 0; f < 5; f++) {
_leaf = new DefaultMutableTreeNode("COLUMN NAME " + (f + 1));
_node.add(_leaf);
}
_root.add(_node);
}
DefaultTreeModel defaultTreeModel = new DefaultTreeModel(_root);
this.frmJTree.getTree().setModel(defaultTreeModel);
this.frmJTree.getScrollPane1().setViewportView(this.frmJTree.getTree());
this.frmJTree.getTree().repaint();
}
private void showCheckedNode() {
DefaultListModel<String> listContent = new DefaultListModel<String>();
this.frmJTree.getListCheckedNode().setModel(listContent);
// Recupera o nome do nó raiz do modelo da JTree.
listContent.addElement( this.frmJTree.getTree().getModel().getRoot().toString() );
// O nome da classe
// conteudoLista.addElement( this.frmJTree.getTree().getModel().getRoot().getClass().getName() );
DefaultMutableTreeNode no = (DefaultMutableTreeNode)this.frmJTree.getTree().getModel().getRoot();
for(Enumeration<?> e = no.children(); e.hasMoreElements(); ) {
// Navega para o próximo
DefaultMutableTreeNode noChild = (DefaultMutableTreeNode) e.nextElement();
// Imprime o nome na JList
listContent.addElement( " " + noChild.getUserObject().toString() );
// Itera sobre os nós filhos dos filhos do nó raiz.
for(Enumeration<?> en = noChild.children(); en.hasMoreElements(); ) {
DefaultMutableTreeNode noChildChild = (DefaultMutableTreeNode) en.nextElement();
listContent.addElement( " " + noChildChild.getUserObject().toString() );
}
}
}
@Override
public void actionPerformed(ActionEvent e) {
if(e.getSource() == this.frmJTree.getBtnShowCheckedNodes()) {
this.showCheckedNode();
}
}
@Override
public void windowActivated(WindowEvent e) {
}
@Override
public void windowClosed(WindowEvent e) {
}
@Override
public void windowClosing(WindowEvent e) {
}
@Override
public void windowDeactivated(WindowEvent e) {
}
@Override
public void windowDeiconified(WindowEvent e) {
}
@Override
public void windowIconified(WindowEvent e) {
}
@Override
public void windowOpened(WindowEvent e) {
this.loadJTree();
}
}
E o principal: o modelo de componente JTree customizado!
Eu pegue esse fonte http://stackoverflow.com/questions/21847411/java-swing-need-a-good-quality-developed-jtree-with-checkboxes, e fiz pequenas modificações, inserindo a JLabel com o ícone, dei uma estudada no fonte mas não consegui resolver ainda a questão do truncamento das Label’s.
package br.com.misael.view.uc;
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.io.InputStream;
import java.util.EventListener;
import java.util.EventObject;
import java.util.HashMap;
import java.util.HashSet;
import javax.imageio.ImageIO;
import javax.swing.ImageIcon;
import javax.swing.JCheckBox;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTree;
import javax.swing.event.EventListenerList;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeSelectionModel;
import javax.swing.tree.TreeCellRenderer;
import javax.swing.tree.TreeModel;
import javax.swing.tree.TreeNode;
import javax.swing.tree.TreePath;
/**
* Original implementation from http://stackoverflow.com/questions/21847411/java-swing-need-a-good-quality-developed-jtree-with-checkboxes
* @author SomethingSomething
*
*/
public class JCheckBoxTree extends JTree {
private static final long serialVersionUID = -4194122328392241790L;
JCheckBoxTree selfPointer = this;
// Defining data structure that will enable to fast check-indicate the state of each node
// It totally replaces the "selection" mechanism of the JTree
private class CheckedNode {
boolean isSelected;
boolean hasChildren;
boolean allChildrenSelected;
public CheckedNode(boolean isSelected_, boolean hasChildren_, boolean allChildrenSelected_) {
isSelected = isSelected_;
hasChildren = hasChildren_;
allChildrenSelected = allChildrenSelected_;
}
}
HashMap<TreePath, CheckedNode> nodesCheckingState;
HashSet<TreePath> checkedPaths = new HashSet<TreePath>();
// Defining a new event type for the checking mechanism and preparing event-handling mechanism
protected EventListenerList listenerList = new EventListenerList();
public class CheckChangeEvent extends EventObject {
private static final long serialVersionUID = -8100230309044193368L;
public CheckChangeEvent(Object source) {
super(source);
}
}
public interface CheckChangeEventListener extends EventListener {
public void checkStateChanged(CheckChangeEvent event);
}
public void addCheckChangeEventListener(CheckChangeEventListener listener) {
listenerList.add(CheckChangeEventListener.class, listener);
}
public void removeCheckChangeEventListener(CheckChangeEventListener listener) {
listenerList.remove(CheckChangeEventListener.class, listener);
}
void fireCheckChangeEvent(CheckChangeEvent evt) {
Object[] listeners = listenerList.getListenerList();
for (int i = 0; i < listeners.length; i++) {
if (listeners[i] == CheckChangeEventListener.class) {
((CheckChangeEventListener) listeners[i + 1]).checkStateChanged(evt);
}
}
}
// Override
public void setModel(TreeModel newModel) {
super.setModel(newModel);
resetCheckingState();
}
// New method that returns only the checked paths (totally ignores original "selection" mechanism)
public TreePath[] getCheckedPaths() {
return checkedPaths.toArray(new TreePath[checkedPaths.size()]);
}
// Returns true in case that the node is selected, has children but not all of them are selected
public boolean isSelectedPartially(TreePath path) {
CheckedNode cn = nodesCheckingState.get(path);
return cn.isSelected && cn.hasChildren && !cn.allChildrenSelected;
}
private void resetCheckingState() {
nodesCheckingState = new HashMap<TreePath, CheckedNode>();
checkedPaths = new HashSet<TreePath>();
DefaultMutableTreeNode node = (DefaultMutableTreeNode)getModel().getRoot();
if (node == null) {
return;
}
addSubtreeToCheckingStateTracking(node);
}
// Creating data structure of the current model for the checking mechanism
private void addSubtreeToCheckingStateTracking(DefaultMutableTreeNode node) {
TreeNode[] path = node.getPath();
TreePath tp = new TreePath(path);
CheckedNode cn = new CheckedNode(false, node.getChildCount() > 0, false);
nodesCheckingState.put(tp, cn);
for (int i = 0 ; i < node.getChildCount() ; i++) {
addSubtreeToCheckingStateTracking((DefaultMutableTreeNode) tp.pathByAddingChild(node.getChildAt(i)).getLastPathComponent());
}
}
// Overriding cell renderer by a class that ignores the original "selection" mechanism
// It decides how to show the nodes due to the checking-mechanism
private class CheckBoxCellRenderer extends JPanel implements TreeCellRenderer {
private static final long serialVersionUID = -7341833835878991719L;
JCheckBox checkBox;
JLabel label;
public CheckBoxCellRenderer() {
super();
this.setLayout(new BorderLayout());
checkBox = new JCheckBox();
// MISAEL: Insert a JLabel statement.
label = new JLabel();
add(checkBox, BorderLayout.WEST);
// MISAEL: Add on JPanel.
add(label, BorderLayout.EAST);
setOpaque(false);
}
@Override
public Component getTreeCellRendererComponent(JTree tree, Object value, boolean selected, boolean expanded, boolean leaf, int row, boolean hasFocus) {
DefaultMutableTreeNode node = (DefaultMutableTreeNode) value;
Object obj = node.getUserObject();
TreePath tp = new TreePath(node.getPath());
CheckedNode cn = nodesCheckingState.get(tp);
if (cn == null) {
return this;
}
// MISAEL - my change here, insert labels wiht icons!
// Insert this code block.
try {
if(node.getLevel() == 0) {
InputStream inputStream = JCheckBoxTree.class.getResourceAsStream("/br/com/misael/view/uc/icons/database.png");
label.setIcon(new ImageIcon( ImageIO.read(inputStream) ));
}
if(node.getLevel() == 1) {
InputStream inputStream = JCheckBoxTree.class.getResourceAsStream("/br/com/misael/view/uc/icons/application_view_columns.png");
label.setIcon(new ImageIcon( ImageIO.read(inputStream) ));
}
if(node.getLevel() == 2) {
InputStream inputStream = JCheckBoxTree.class.getResourceAsStream("/br/com/misael/view/uc/icons/bullet_green.png");
label.setIcon(new ImageIcon( ImageIO.read(inputStream) ));
}
} catch(Exception e) {
System.err.println(e.getMessage());
}
// MISAEL: End code block.
checkBox.setSelected(cn.isSelected);
// MISAEL: Remove from original.
// checkBox.setText(obj.toString());
// MISAEL: Set text of label.
label.setText(obj.toString());
checkBox.setOpaque(cn.isSelected && cn.hasChildren && ! cn.allChildrenSelected);
return this;
}
}
public JCheckBoxTree() {
super();
// Disabling toggling by double-click
this.setToggleClickCount(0);
// Overriding cell renderer by new one defined above
CheckBoxCellRenderer cellRenderer = new CheckBoxCellRenderer();
this.setCellRenderer(cellRenderer);
// Overriding selection model by an empty one
DefaultTreeSelectionModel dtsm = new DefaultTreeSelectionModel() {
private static final long serialVersionUID = -8190634240451667286L;
// Totally disabling the selection mechanism
public void setSelectionPath(TreePath path) {}
public void addSelectionPath(TreePath path) {}
public void removeSelectionPath(TreePath path) {}
public void setSelectionPaths(TreePath[] pPaths) {}
};
// Calling checking mechanism on mouse click
this.addMouseListener(new MouseListener() {
public void mouseClicked(MouseEvent e) {
TreePath tp = selfPointer.getPathForLocation(e.getX(), e.getY());
if (tp == null) {
return;
}
boolean checkMode = ! nodesCheckingState.get(tp).isSelected;
checkSubTree(tp, checkMode);
updatePredecessorsWithCheckMode(tp, checkMode);
// Firing the check change event
fireCheckChangeEvent(new CheckChangeEvent(new Object()));
// Repainting tree after the data structures were updated
selfPointer.repaint();
}
public void mouseEntered(MouseEvent e) {}
public void mouseExited(MouseEvent e) {}
public void mousePressed(MouseEvent e) {}
public void mouseReleased(MouseEvent e) {}
});
this.setSelectionModel(dtsm);
}
// When a node is checked/unchecked, updating the states of the predecessors
protected void updatePredecessorsWithCheckMode(TreePath tp, boolean check) {
TreePath parentPath = tp.getParentPath();
// If it is the root, stop the recursive calls and return
if (parentPath == null) {
return;
}
CheckedNode parentCheckedNode = nodesCheckingState.get(parentPath);
DefaultMutableTreeNode parentNode = (DefaultMutableTreeNode) parentPath.getLastPathComponent();
parentCheckedNode.allChildrenSelected = true;
parentCheckedNode.isSelected = false;
for (int i = 0 ; i < parentNode.getChildCount() ; i++) {
TreePath childPath = parentPath.pathByAddingChild(parentNode.getChildAt(i));
CheckedNode childCheckedNode = nodesCheckingState.get(childPath);
// It is enough that even one subtree is not fully selected
// to determine that the parent is not fully selected
if (! childCheckedNode.allChildrenSelected) {
parentCheckedNode.allChildrenSelected = false;
}
// If at least one child is selected, selecting also the parent
if (childCheckedNode.isSelected) {
parentCheckedNode.isSelected = true;
}
}
if (parentCheckedNode.isSelected) {
checkedPaths.add(parentPath);
} else {
checkedPaths.remove(parentPath);
}
// Go to upper predecessor
updatePredecessorsWithCheckMode(parentPath, check);
}
// Recursively checks/unchecks a subtree
protected void checkSubTree(TreePath tp, boolean check) {
CheckedNode cn = nodesCheckingState.get(tp);
cn.isSelected = check;
DefaultMutableTreeNode node = (DefaultMutableTreeNode) tp.getLastPathComponent();
for (int i = 0 ; i < node.getChildCount() ; i++) {
checkSubTree(tp.pathByAddingChild(node.getChildAt(i)), check);
}
cn.allChildrenSelected = check;
if (check) {
checkedPaths.add(tp);
} else {
checkedPaths.remove(tp);
}
}
}
Att,
Misael C. HOmem