树(选择框)
先是春游然后又开始忙了,都没时间写了,不爽.
JTree的选择框其实也是Renderer的一种表现,单纯实现效果的话很简单,只需要设置Renderer就可以了,但是如果你想实现一个好的JTree选择框就比较难了,因为这里有选择问题、监听问题、选中后的父子关系等,这里主要是参考别人的实现写的.
先看一个简单的例子,从网上看到的,如图:
它只是单纯的实现了树的选择框效果,写的很简单.
首先是TreeNode,我们扩展Java的TreeNode,添加了我们自己的选择属性:
publicclass CheckBoxTreeNode extends DefaultMutableTreeNode {
属性:
/**
* is node check
*/
privatebooleanisChecked = false;
然后就是Renderer了,这里实现TreeCellRenderer,并继承了JCheckBox
publicclass CheckBoxTreeCellRenderer extends JCheckBox implements
TreeCellRenderer {
然后实现接口的方法:
@Override
public Component getTreeCellRendererComponent(JTree tree, Object value, boolean selected, boolean expanded, boolean leaf, int row,
boolean hasFocus) {
取得TreeNode:
// get tree node
CheckBoxTreeNode node = ((CheckBoxTreeNode) value);
// set check box text
setText(node.toString());
设置选择状态后返回:
setSelected(false);
returnthis;
然后是一个我们自己的JTree,它增加了鼠标监听,实现选择框的勾选效果:
publicclass CheckBoxTree extends JTree {
在构造函数里设置它的Renderer和监听:
setCellRenderer(new CheckBoxTreeCellRenderer());
addCheckingListener();
然后是处理监听:
addMouseListener(new MouseAdapter() {
@Override
publicvoid mousePressed(MouseEvent e) {
在监听里先取得选择的节点:
int row = getRowForLocation(e.getX(), e.getY());
TreePath treePath = getPathForRow(row);
CheckBoxTreeNode node = ((CheckBoxTreeNode) treePath
.getLastPathComponent());
然后设置选择状态:
// if check , will uncheck.
boolean checking = !node.isChecked();
node.setChecked(checking);
当然这里可以做额外处理,例如选中节点时同时选择子节点或者父节点:
最后刷新:
// repaint
repaint();
然后就是使用了,和一般的JTree基本一致,只是节点是我们自己定义的Node.
CheckBoxTree tree = new CheckBoxTree();
之后就和一个普通的JTree一样了.
到这里,简单的选择框树就完成了,它基本可以用,但是还是有一些问题的.
因为我们使用JCheckBox作为树的节点,导致我们只能呈现一个选择框和一个文本框的效果,其它复杂效果很难再实现了,简单说就是JCheckBox很难做效果
解决办法就是我们在做Renderer时,使用JPanel继承,这样就可以实现更复杂的Node了.
要通过鼠标监听和Repaint才能使树选择效果刷新.
通过解决问题一,我们可以在Renderer设置JCheckBox,这样就避免了刷新;同时我们可以额外实现一个单选效果.
选择模式简单(需要鼠标事件),验证数据单一(关联关系不好),封装性不好(使用达不到完全封闭)
这个问题就需要定义接口和数据结构了,本来想自己写呢,后来发现一个老外写了一个,很强大,比我写的好多了,就用它了.
先看我们简单解决1和2的例子,如图:
TreeNode和前一个例子差不多,我们额外添加了一个选择模式的属性:
publicclass MyTreeNode extends DefaultMutableTreeNode {
两个属性,表示选择和选择模式:
/** is select or not. */
privatebooleanisSelected = false;
/** select model. */
privateintselectionMode = 0;
在设置选择时,如果只允许单选的选择模式,我们设置其它不选择:
publicvoid setSelected(boolean isSelected) {
this.isSelected = isSelected;
if ((selectionMode == TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION)
&& (children != null)) {
Enumeration<?> enumTemp = children.elements();
while (enumTemp.hasMoreElements()) {
MyTreeNode node = (MyTreeNode) enumTemp.nextElement();
node.setSelected(isSelected);
}
}
}
然后就是Renderer了,这里我们不继承JCheckBox,继承JPanel:
publicclass MyCheckRenderer extends JPanel implements TreeCellRenderer {
在JPanel上我们放置了两个组件,当然也可以放置更复杂的:
/** check box in tree node. */
private JCheckBox checkBox = null;
/** label text in tree node. */
private TreeLabel labelText = null;
其中TreeLabel是我们自己写的:
privateclass TreeLabel extends JLabel {
我们为它添加了焦点状态和选择状态:
/** is select. */
privatebooleanisSelected = false;
/** is have focus. */
privatebooleanhasFocus = false;
然后复写它的方法和方法,使它的呈现和JTree一致:
@Override
public Dimension getPreferredSize() {
@Override
publicvoid paint(Graphics g) {
设置颜色和大小:
g.setColor(UIManager
.getColor("Tree.selectionBorderColor"));
g.drawRect(imageOffset, 0, d.width - 1 - imageOffset,
d.height - 1);
在类里我们实现TreeCellRenderer接口的方法:
@Override
public Component getTreeCellRendererComponent(JTree tree, Object value, boolean isSelected, boolean expanded, boolean leaf, int row,
boolean hasFocus) {
设置JCheckBox的状态:
checkBox.setSelected(((MyTreeNode) value).isSelected());
设置树节点显示:
labelText.setFont(tree.getFont());
labelText.setText(stringValue);
labelText.setSelected(isSelected);
labelText.setFocus(hasFocus);
然后复写JPanel的getPreferredSize方法和doLayout方法,使显示合理:
/**
* set select node's prefer size.
*/
@Override
public Dimension getPreferredSize() {
Dimension d_check = checkBox.getPreferredSize();
Dimension d_label = labelText.getPreferredSize();
returnnew Dimension(d_check.width + d_label.width,
(d_check.height < d_label.height ? d_label.height
: d_check.height));
}
/**
* set tree select node layout.
*/
@Override
publicvoid doLayout() {
Dimension d_check = checkBox.getPreferredSize();
Dimension d_label = labelText.getPreferredSize();
int y_check = 0;
int y_label = 0;
if (d_check.height < d_label.height) {
y_check = (d_label.height - d_check.height) / 2;
} else {
y_label = (d_check.height - d_label.height) / 2;
}
checkBox.setLocation(0, y_check);
checkBox.setBounds(0, y_check, d_check.width, d_check.height);
labelText.setLocation(d_check.width, y_label);
labelText.setBounds(d_check.width, y_label, d_label.width,
d_label.height);
}
最后是使用,和前面得差不多,先取得JTree,再设置Renderer,监听鼠标.
JTree tree = new JTree();
tree.setCellRenderer(new MyCheckRenderer());
tree.addMouseListener(new NodeSelectionListener(tree));
处理鼠标监听:
@Override
publicvoid mouseClicked(MouseEvent e) {
设置选择:
MyTreeNode node = (MyTreeNode) path.getLastPathComponent();
boolean isSelected = !(node.isSelected());
node.setSelected(isSelected);
((DefaultTreeModel) tree.getModel()).nodeChanged(node);
然后和普通的JTree一样使用了.
最后是问题三的解决,这个是一个老外写的,很不错,但是很复杂,如图:
它实现了无关的选中、父选择子全选择、子选择父选择和子单选择父选择四种选择状态.
代码别人写的就不写了,写下它的大概思路:
首先它定义了一个事件: TreeCheckingEvent,这个事件是描绘选择关系和选择路径的,这样就简化了鼠标事件处理;然后是事件的监听器: TreeCheckingListener,它提供监听.
然后定义了一个数据模型:TreeCheckingMode,在模型里它提供了checkPath、uncheckPath、updateCheckAfterChildrenInserted、updateCheckAfterChildrenRemoved和updateCheckAfterStructureChanged的虚方法,供子类实现,这些实现就是树的选择状态的表示,当一个树的节点选择、取消选择、插入、删除和更新之后,节点选择状态的变化.
TreeCheckingMode有SimpleTreeCheckingMode、PropagateTreeCheckingMode、PropagatePreservingCheckTreeCheckingMode、PropagatePreservingUncheckTreeCheckingMode四个实现类,代表了四种选择关联状态,通过实现父类的虚方法,当树选择变化或内容变化时,选择节点变化,这样我们的树就可以了四种选择逻辑了,当然你也可以继承TreeCheckingMode实现自己的选择逻辑.
然后还有一个Renderer类DefaultCheckboxTreeCellRenderer,继承JPanel,实现TreeCellRenderer,来渲染树的节点,这个和我上面写的基本一致.所以大家可以看到,做UI最后还是做逻辑,UI呈现也就那么多,还是逻辑多而复杂.
然后是树的CheckModel类TreeCheckingModel和DefaultTreeCheckingModel,主要是处理树的数据变化和增删改;以及监听和选择状态的记录(树的Model我们还用,在它的基础上添加了新的选择Model,这样就分离了数据和选择状态).
最后是CheckboxTree,它继承JTree,设置Model是TreeModel,设置CheckModel是TreeCheckingModel,设置Mode是TreeCheckingMode,增加TreeCheckingListener监听,并提供了展开树等事件.
使用很简单,new出来直接使用就可以了,可以设置选择状态:
tree.getCheckingModel().setCheckingMode(CheckingMode.PROPAGATE);
总之,这个树写的还是不错了,它自己实现了事件和数据模型,这样可以很自由的进行树数据的处理,建议如果大家做大项目的时候,比较常用的组件还是自己实现写,使用自己的事件处理和数据模型甚至UI,这样虽然麻烦,但可以做出更利于自己的效果(当然是很大很复杂的项目,小项目还不够费时间呢).
到此为止,关于树的就写完了,除了一个DND拖拽应该没有漏什么东西了,树的组件并不复杂, 方法不算太多,UI可以重写的也很少,Mdoel因为数据集简单也不复杂,数据处理最多也就是个递归,监听也就是鼠标和树选择、展开事件,因此一般树的UI和事件处理不是我们的重点,我在这里写的可能大多数项目都不会用到,(至少我很少用).因此对树的处理大多还是逻辑,主要是生成树、更新和删除节点.