先是春游然后又開始忙了,都沒時間寫了,不爽.
JTree的選擇框其實也是Renderer的一種表現(xiàn),單純實現(xiàn)效果的話很簡單,只需要設(shè)置Renderer就可以了,但是如果你想實現(xiàn)一個好的JTree選擇框就比較難了,因為這里有選擇問題、監(jiān)聽問題、選中后的父子關(guān)系等,這里主要是參考別人的實現(xiàn)寫的.
先看一個簡單的例子,從網(wǎng)上看到的,如圖:
它只是單純的實現(xiàn)了樹的選擇框效果,寫的很簡單.
首先是TreeNode,我們擴(kuò)展Java的TreeNode,添加了我們自己的選擇屬性:
publicclass CheckBoxTreeNode extends DefaultMutableTreeNode {
屬性:
/**
* is node check
*/
privatebooleanisChecked = false;
然后就是Renderer了,這里實現(xiàn)TreeCellRenderer,并繼承了JCheckBox
publicclass CheckBoxTreeCellRenderer extends JCheckBox implements
TreeCellRenderer
{
然后實現(xiàn)接口的方法:
@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());
設(shè)置選擇狀態(tài)后返回:
setSelected(false);
returnthis;
然后是一個我們自己的JTree,它增加了鼠標(biāo)監(jiān)聽,實現(xiàn)選擇框的勾選效果:
publicclass CheckBoxTree extends JTree {
在構(gòu)造函數(shù)里設(shè)置它的Renderer和監(jiān)聽:
setCellRenderer(new CheckBoxTreeCellRenderer());
addCheckingListener();
然后是處理監(jiān)聽:
addMouseListener(new MouseAdapter() {
@Override
publicvoid
mousePressed(MouseEvent e) {
在監(jiān)聽里先取得選擇的節(jié)點:
int row =
getRowForLocation(e.getX(), e.getY());
TreePath
treePath = getPathForRow(row);
CheckBoxTreeNode node = ((CheckBoxTreeNode) treePath
.getLastPathComponent());
然后設(shè)置選擇狀態(tài):
// if check , will uncheck.
boolean checking = !node.isChecked();
node.setChecked(checking);
當(dāng)然這里可以做額外處理,例如選中節(jié)點時同時選擇子節(jié)點或者父節(jié)點:
最后刷新:
// repaint
repaint();
然后就是使用了,和一般的JTree基本一致,只是節(jié)點是我們自己定義的Node.
CheckBoxTree
tree = new CheckBoxTree();
之后就和一個普通的JTree一樣了.
到這里,簡單的選擇框樹就完成了,它基本可以用,但是還是有一些問題的.
因為我們使用JCheckBox作為樹的節(jié)點,導(dǎo)致我們只能呈現(xiàn)一個選擇框和一個文本框的效果,其它復(fù)雜效果很難再實現(xiàn)了,簡單說就是JCheckBox很難做效果
解決辦法就是我們在做Renderer時,使用JPanel繼承,這樣就可以實現(xiàn)更復(fù)雜的Node了.
要通過鼠標(biāo)監(jiān)聽和Repaint才能使樹選擇效果刷新.
通過解決問題一,我們可以在Renderer設(shè)置JCheckBox,這樣就避免了刷新;同時我們可以額外實現(xiàn)一個單選效果.
選擇模式簡單(需要鼠標(biāo)事件),驗證數(shù)據(jù)單一(關(guān)聯(lián)關(guān)系不好),封裝性不好(使用達(dá)不到完全封閉)
這個問題就需要定義接口和數(shù)據(jù)結(jié)構(gòu)了,本來想自己寫呢,后來發(fā)現(xiàn)一個老外寫了一個,很強(qiáng)大,比我寫的好多了,就用它了.
先看我們簡單解決1和2的例子,如圖:
TreeNode和前一個例子差不多,我們額外添加了一個選擇模式的屬性:
publicclass MyTreeNode extends DefaultMutableTreeNode {
兩個屬性,表示選擇和選擇模式:
/** is
select or not. */
privatebooleanisSelected = false;
/** select
model. */
privateintselectionMode = 0;
在設(shè)置選擇時,如果只允許單選的選擇模式,我們設(shè)置其它不選擇:
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上我們放置了兩個組件,當(dāng)然也可以放置更復(fù)雜的:
/** check box in tree node. */
private JCheckBox checkBox = null;
/** label text in tree node. */
private TreeLabel labelText = null;
其中TreeLabel是我們自己寫的:
privateclass TreeLabel extends JLabel {
我們?yōu)樗砑恿私裹c狀態(tài)和選擇狀態(tài):
/** is select. */
privatebooleanisSelected = false;
/** is have focus. */
privatebooleanhasFocus = false;
然后復(fù)寫它的方法和方法,使它的呈現(xiàn)和JTree一致:
@Override
public Dimension
getPreferredSize() {
@Override
publicvoid paint(Graphics g)
{
設(shè)置顏色和大小:
g.setColor(UIManager
.getColor("Tree.selectionBorderColor"));
g.drawRect(imageOffset, 0, d.width - 1 - imageOffset,
d.height - 1);
在類里我們實現(xiàn)TreeCellRenderer接口的方法:
@Override
public Component
getTreeCellRendererComponent(JTree tree, Object value, boolean isSelected,
boolean expanded, boolean leaf, int row,
boolean hasFocus) {
設(shè)置JCheckBox的狀態(tài):
checkBox.setSelected(((MyTreeNode)
value).isSelected());
設(shè)置樹節(jié)點顯示:
labelText.setFont(tree.getFont());
labelText.setText(stringValue);
labelText.setSelected(isSelected);
labelText.setFocus(hasFocus);
然后復(fù)寫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,再設(shè)置Renderer,監(jiān)聽鼠標(biāo).
JTree tree = new JTree();
tree.setCellRenderer(new MyCheckRenderer());
tree.addMouseListener(new NodeSelectionListener(tree));
處理鼠標(biāo)監(jiān)聽:
@Override
publicvoid mouseClicked(MouseEvent e) {
設(shè)置選擇:
MyTreeNode node = (MyTreeNode)
path.getLastPathComponent();
boolean isSelected =
!(node.isSelected());
node.setSelected(isSelected);
((DefaultTreeModel) tree.getModel()).nodeChanged(node);
然后和普通的JTree一樣使用了.
最后是問題三的解決,這個是一個老外寫的,很不錯,但是很復(fù)雜,如圖:
它實現(xiàn)了無關(guān)的選中、父選擇子全選擇、子選擇父選擇和子單選擇父選擇四種選擇狀態(tài).
代碼別人寫的就不寫了,寫下它的大概思路:
首先它定義了一個事件: TreeCheckingEvent,這個事件是描繪選擇關(guān)系和選擇路徑的,這樣就簡化了鼠標(biāo)事件處理;然后是事件的監(jiān)聽器: TreeCheckingListener,它提供監(jiān)聽.
然后定義了一個數(shù)據(jù)模型:TreeCheckingMode,在模型里它提供了checkPath、uncheckPath、updateCheckAfterChildrenInserted、updateCheckAfterChildrenRemoved和updateCheckAfterStructureChanged的虛方法,供子類實現(xiàn),這些實現(xiàn)就是樹的選擇狀態(tài)的表示,當(dāng)一個樹的節(jié)點選擇、取消選擇、插入、刪除和更新之后,節(jié)點選擇狀態(tài)的變化.
TreeCheckingMode有SimpleTreeCheckingMode、PropagateTreeCheckingMode、PropagatePreservingCheckTreeCheckingMode、PropagatePreservingUncheckTreeCheckingMode四個實現(xiàn)類,代表了四種選擇關(guān)聯(lián)狀態(tài),通過實現(xiàn)父類的虛方法,當(dāng)樹選擇變化或內(nèi)容變化時,選擇節(jié)點變化,這樣我們的樹就可以了四種選擇邏輯了,當(dāng)然你也可以繼承TreeCheckingMode實現(xiàn)自己的選擇邏輯.
然后還有一個Renderer類DefaultCheckboxTreeCellRenderer,繼承JPanel,實現(xiàn)TreeCellRenderer,來渲染樹的節(jié)點,這個和我上面寫的基本一致.所以大家可以看到,做UI最后還是做邏輯,UI呈現(xiàn)也就那么多,還是邏輯多而復(fù)雜.
然后是樹的CheckModel類TreeCheckingModel和DefaultTreeCheckingModel,主要是處理樹的數(shù)據(jù)變化和增刪改;以及監(jiān)聽和選擇狀態(tài)的記錄(樹的Model我們還用,在它的基礎(chǔ)上添加了新的選擇Model,這樣就分離了數(shù)據(jù)和選擇狀態(tài)).
最后是CheckboxTree,它繼承JTree,設(shè)置Model是TreeModel,設(shè)置CheckModel是TreeCheckingModel,設(shè)置Mode是TreeCheckingMode,增加TreeCheckingListener監(jiān)聽,并提供了展開樹等事件.
使用很簡單,new出來直接使用就可以了,可以設(shè)置選擇狀態(tài):
tree.getCheckingModel().setCheckingMode(CheckingMode.PROPAGATE);
總之,這個樹寫的還是不錯了,它自己實現(xiàn)了事件和數(shù)據(jù)模型,這樣可以很自由的進(jìn)行樹數(shù)據(jù)的處理,建議如果大家做大項目的時候,比較常用的組件還是自己實現(xiàn)寫,使用自己的事件處理和數(shù)據(jù)模型甚至UI,這樣雖然麻煩,但可以做出更利于自己的效果(當(dāng)然是很大很復(fù)雜的項目,小項目還不夠費時間呢).
到此為止,關(guān)于樹的就寫完了,除了一個DND拖拽應(yīng)該沒有漏什么東西了,樹的組件并不復(fù)雜, 方法不算太多,UI可以重寫的也很少,Mdoel因為數(shù)據(jù)集簡單也不復(fù)雜,數(shù)據(jù)處理最多也就是個遞歸,監(jiān)聽也就是鼠標(biāo)和樹選擇、展開事件,因此一般樹的UI和事件處理不是我們的重點,我在這里寫的可能大多數(shù)項目都不會用到,(至少我很少用).因此對樹的處理大多還是邏輯,主要是生成樹、更新和刪除節(jié)點.