先是春游然后又開始忙了,都沒時間寫了,不爽.
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和事件處理不是我們的重點,我在這里寫的可能大多數項目都不會用到,(至少我很少用).因此對樹的處理大多還是邏輯,主要是生成樹、更新和刪除節點.