对JTree
应用过滤,以避免某些节点/离开显示在JTree
的呈现版本中。理想情况下,我正在寻找一个允许使用动态过滤器的解决方案,但如果我能让静态过滤器工作,我会很高兴。
为了使它更容易一些,我们假设JTree
仅支持渲染,而不支持编辑。应该可以移动,添加,删除节点。
示例是JTree
上方的搜索字段,输入JTree
时只会显示带匹配的子树。
一些限制:它将用于可以访问JDK和SwingX的项目中。我想避免包含其他第三方库。
我已经想到了一些可能的解决方案,但这些解决方案似乎都不理想
基于模型的过滤
TreeModel
以过滤掉一些值。快速而简单的版本很容易编写。过滤掉节点,并且在过滤器或委托TreeModel
的每次更改时,装饰器都可以触发整个树发生变化的事件(treeStructureChanged
,其中根节点为节点)。将此与恢复JTree
的选择状态和扩展状态的侦听器相结合,您将得到一个或多或少有效的版本,但源自TreeModel
的事件会搞乱。这或多或少是this question TreeModel
,但尝试触发正确的事件。我(还)没有设法提出这个的工作版本。它似乎需要委托TreeModel
的副本,以便能够在从委托模型中删除节点时使用正确的子索引触发事件。我认为有更多的时间我可以让它工作,但它只是感觉不对(过滤感觉就像视图应该做的,而不是模型)TreeModel
的任何数据结构。但是,这完全是不可重用的,可能和为TreeModel
基于观看的过滤
这似乎是要走的路。过滤不应影响模型,只影响视图。
我看了RowFilter
课。虽然javadoc似乎建议您可以将它与JTree
组合使用:
当与JTree关联时,条目对应于节点。
我在RowFilter
(或RowSorter
)和JTree
类之间找不到任何链接。 RowFilter
和Swing教程的标准实现似乎表明RowFilter
只能与JTable
直接一起使用(请参阅JTable#setRowSorter
)。 JTree
JXTree
javadoc。它有ComponentAdapter
可用,ComponentAdapter
的javadoc表示RowFilter
可以与目标组件进行交互,但我看不出如何在RowFilter
和JTree
之间建立链接JTable
RowFilter
如何使用JTree
来处理过滤,也许可以对{{1}}的修改版本进行相同的处理。简而言之:我不知道解决这个问题的最佳方法是什么
注意:这个问题可能与this question重复,但这个问题仍然没有答案,问题相当简短,答案似乎不完整,所以我想发布一个新问题。如果没有这样做(常见问题解答没有提供明确的答案)我会更新这个3岁的问题
答案 0 :(得分:8)
基于视图的过滤绝对是最佳选择。您可以使用类似我在下面编写的示例。过滤树时的另一种常见做法是在过滤树时切换到列表视图,因为列表不会要求您显示需要显示其后代的隐藏节点。</ p>
这是绝对可怕的代码(我试图在刚刚鞭打它的过程中削减每一个角落),但它应该足以让你开始。只需在搜索框中键入您的查询,然后按Enter,它就会过滤JTree的默认模型。 (仅供参考,前90行只是生成的样板和布局代码。)
package com.example.tree;
import java.awt.BorderLayout;
public class FilteredJTreeExample extends JFrame {
private JPanel contentPane;
private JTextField textField;
/**
* Launch the application.
*/
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
public void run() {
try {
FilteredJTreeExample frame = new FilteredJTreeExample();
frame.setVisible(true);
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
/**
* Create the frame.
*/
public FilteredJTreeExample() {
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setBounds(100, 100, 450, 300);
contentPane = new JPanel();
contentPane.setBorder(new EmptyBorder(5, 5, 5, 5));
contentPane.setLayout(new BorderLayout(0, 0));
setContentPane(contentPane);
JPanel panel = new JPanel();
contentPane.add(panel, BorderLayout.NORTH);
GridBagLayout gbl_panel = new GridBagLayout();
gbl_panel.columnWidths = new int[]{34, 116, 0};
gbl_panel.rowHeights = new int[]{22, 0};
gbl_panel.columnWeights = new double[]{0.0, 1.0, Double.MIN_VALUE};
gbl_panel.rowWeights = new double[]{0.0, Double.MIN_VALUE};
panel.setLayout(gbl_panel);
JLabel lblFilter = new JLabel("Filter:");
GridBagConstraints gbc_lblFilter = new GridBagConstraints();
gbc_lblFilter.anchor = GridBagConstraints.WEST;
gbc_lblFilter.insets = new Insets(0, 0, 0, 5);
gbc_lblFilter.gridx = 0;
gbc_lblFilter.gridy = 0;
panel.add(lblFilter, gbc_lblFilter);
JScrollPane scrollPane = new JScrollPane();
contentPane.add(scrollPane, BorderLayout.CENTER);
final JTree tree = new JTree();
scrollPane.setViewportView(tree);
textField = new JTextField();
GridBagConstraints gbc_textField = new GridBagConstraints();
gbc_textField.fill = GridBagConstraints.HORIZONTAL;
gbc_textField.anchor = GridBagConstraints.NORTH;
gbc_textField.gridx = 1;
gbc_textField.gridy = 0;
panel.add(textField, gbc_textField);
textField.setColumns(10);
textField.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent evt) {
TreeModel model = tree.getModel();
tree.setModel(null);
tree.setModel(model);
}
});
tree.setCellRenderer(new DefaultTreeCellRenderer() {
private JLabel lblNull = new JLabel();
@Override
public Component getTreeCellRendererComponent(JTree tree, Object value,
boolean arg2, boolean arg3, boolean arg4, int arg5, boolean arg6) {
Component c = super.getTreeCellRendererComponent(tree, value, arg2, arg3, arg4, arg5, arg6);
DefaultMutableTreeNode node = (DefaultMutableTreeNode) value;
if (matchesFilter(node)) {
c.setForeground(Color.BLACK);
return c;
}
else if (containsMatchingChild(node)) {
c.setForeground(Color.GRAY);
return c;
}
else {
return lblNull;
}
}
private boolean matchesFilter(DefaultMutableTreeNode node) {
return node.toString().contains(textField.getText());
}
private boolean containsMatchingChild(DefaultMutableTreeNode node) {
Enumeration<DefaultMutableTreeNode> e = node.breadthFirstEnumeration();
while (e.hasMoreElements()) {
if (matchesFilter(e.nextElement())) {
return true;
}
}
return false;
}
});
}
}
当你真实地实现它时,你可能想要创建自己的TreeNode和TreeCellRenderer实现,使用一个不那么愚蠢的方法来触发更新,并遵循MVC分离。请注意&#34;隐藏&#34;节点仍然呈现,但它们非常小,以至于您无法看到它们。但是,如果您使用箭头键导航树,您会注意到它们仍在那里。如果你只是需要一些有用的东西,这可能就足够了。
修改强>
以下是Mac OS中未经过滤和过滤的树版本的屏幕截图,显示在Mac OS中可以看到空格:
答案 1 :(得分:6)
看看这个实现:http://www.java2s.com/Code/Java/Swing-Components/InvisibleNodeTreeExample.htm
它创建DefaultMutableNode的子类,添加“isVisible”属性,而不是实际从TreeModel中删除/添加节点。我觉得很甜蜜,它整齐地解决了我的过滤问题。
答案 2 :(得分:4)
旧问题,我偶然发现......对于那些想要快速简便的
解决方案的人我知道它不像过滤模型那样干净,并且可能有可能的退款,但如果您只是想要一个小型应用程序的快速解决方案:
扩展DefaultTableCellRenderer,覆盖 getTreeCellRendererComponent - 调用super.getTreeCellRendererComponent(...),之后只需为要隐藏的所有节点将首选高度设置为ZERO。 构建你的JTree时一定要设置 setRowHeight(0); - 所以它会尊重每一行的首选高度......
瞧 - 所有过滤的行都是看不见的!import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JTree;
import javax.swing.UIManager;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeCellRenderer;
import javax.swing.tree.DefaultTreeModel;
public class JTreeExample
{
public static void main( final String[] args ) throws Exception
{
UIManager.setLookAndFeel( UIManager.getSystemLookAndFeelClassName() );
// The only correct way to create a SWING Frame...
EventQueue.invokeAndWait( new Runnable()
{
@Override
public void run()
{
swingMain();
}
} );
}
protected static void swingMain()
{
final JFrame f = new JFrame( "JTree Test" );
f.setLocationByPlatform( true );
f.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
final int items = 5;
final DefaultMutableTreeNode rootNode = new DefaultMutableTreeNode( "JTree", true );
final DefaultTreeModel myModel = new DefaultTreeModel( rootNode );
final Box buttonBox = new Box( BoxLayout.X_AXIS );
for( int i = 0; i < items; i++ )
{
final String name = "Node " + i;
final DefaultMutableTreeNode newChild = new DefaultMutableTreeNode( name );
rootNode.add( newChild );
final JButton b = new JButton( "Show/Hide " + i );
buttonBox.add( b );
b.addActionListener( new ActionListener()
{
@Override
public void actionPerformed( final ActionEvent e )
{
// If the node has a Text, set it to null, otherwise reset it
newChild.setUserObject( newChild.getUserObject() == null ? name : null );
myModel.nodeStructureChanged( newChild.getParent() );
}
} );
}
final JTree tree = new JTree( myModel );
tree.setRowHeight( 0 );
tree.setCellRenderer( new JTreeExample.TreeRenderer() );
f.add( tree, BorderLayout.CENTER );
f.add( buttonBox, BorderLayout.SOUTH );
f.setSize( 600, 500 );
f.setVisible( true );
}
public static class TreeRenderer extends DefaultTreeCellRenderer
{
@Override
public Component getTreeCellRendererComponent( final JTree tree, final Object value, final boolean selected,
final boolean expanded, final boolean leaf, final int row, final boolean hasFocus )
{
// Invoke default Implementation, setting all values of this
super.getTreeCellRendererComponent( tree, value, selected, expanded, leaf, row, hasFocus );
if( !isNodeVisible( (DefaultMutableTreeNode)value ) )
{
setPreferredSize( new Dimension( 0, 0 ) );
}
else
{
setPreferredSize( new Dimension( 200, 15 ) );
}
return this;
}
}
public static boolean isNodeVisible( final DefaultMutableTreeNode value )
{
// In this example all Nodes without a UserObject are invisible
return value.getUserObject() != null;
}
}
答案 3 :(得分:2)
ETable
,JTable
的子类和Outline
的父类,描述为here,包括“快速过滤功能,只允许显示模型中的某些行(见setQuickFilter()
)。“虽然这违反了没有“第三方库”的要求,但Outline
JAR除了JDK之外没有依赖关系。
答案 4 :(得分:1)
答案 5 :(得分:1)
我一直致力于过滤扩展的JXTreeTable
。为简单起见,我遵循双模式方法。
public abstract class TellapicModelFilter extends DefaultTreeTableModel {
protected Map<AbstractMutableTreeTableNode,
AbstractMutableTreeTableNode> family;
protected Map<AbstractMutableTreeTableNode,
AbstractMutableTreeTableNode> filter;
protected MyTreeTable treeTable;
private boolean withChildren;
private boolean withParents;
/**
*
* @param model
*/
public TellapicModelFilter(MyTreeTable treeTable) {
this(treeTable, false, false);
}
/**
*
* @param treeTable
* @param wp
* @param wc
*/
public TellapicModelFilter(MyTreeTable treeTable, boolean wp, boolean wc) {
super(new DefaultMutableTreeTableNode("filteredRoot"));
this.treeTable = treeTable;
setIncludeChildren(wc);
setIncludeParents(wp);
}
/**
*
*/
public void filter() {
filter = new HashMap<AbstractMutableTreeTableNode, AbstractMutableTreeTableNode>();
family = new HashMap<AbstractMutableTreeTableNode, AbstractMutableTreeTableNode>();
AbstractMutableTreeTableNode filteredRoot = (AbstractMutableTreeTableNode) getRoot();
AbstractMutableTreeTableNode root = (AbstractMutableTreeTableNode) treeTable.getTreeTableModel().getRoot();
filterChildren(root, filteredRoot);
for(AbstractMutableTreeTableNode node : family.keySet())
node.setParent(null);
for(AbstractMutableTreeTableNode node : filter.keySet())
node.setParent(filter.get(node));
}
/**
*
* @param node
* @param filteredNode
*/
private void filterChildren(AbstractMutableTreeTableNode node, AbstractMutableTreeTableNode filteredNode) {
int count = node.getChildCount();
for(int i = 0; i < count; i++) {
AbstractMutableTreeTableNode child = (AbstractMutableTreeTableNode) node.getChildAt(i);
family.put(child, node);
if (shouldBeFiltered(child)) {
filter.put(child, filteredNode);
if (includeChildren())
filterChildren(child, child);
} else {
filterChildren(child, filteredNode);
}
}
}
/**
*
*/
public void restoreFamily() {
for(AbstractMutableTreeTableNode child : family.keySet()) {
AbstractMutableTreeTableNode parent = family.get(child);
child.setParent(parent);
}
}
/**
*
* @param node
* @return
*/
public abstract boolean shouldBeFiltered(AbstractMutableTreeTableNode node);
/**
* Determines if parents will be included in the filtered result. This DOES NOT means that parent will be filtered
* with the filter criteria. Instead, if a node {@code}shouldBeFiltered{@code} no matter what the parent node is,
* include it in the filter result. The use of this feature is to provide contextual data about the filtered node,
* in the terms of: "where was this node that belongs to?"
*
* @return True is parents should be included anyhow.
*/
public boolean includeParents() {
return withParents;
}
/**
* Determines if children should be filtered. When a node {@code}shouldBeFiltered{@code} you can stop the filtering
* process in that node by setting: {@code}setIncludeChildren(false){@code}. In other words, if you want to filter
* all the tree, {@code}includeChildren{@code} should return true.
*
* By letting this method return {@code}false{@code} all children of the node filtered will be automatically added
* to the resulting filter. That is, children aren't filtered with the filter criteria and they will be shown with
* their parent in the filter result.
*
* @return True if you want to filter all the tree.
*/
public boolean includeChildren() {
return withChildren;
}
/**
*
* @param include
*/
public void setIncludeParents(boolean include) {
withParents = include;
}
/**
*
* @param include
*/
public void setIncludeChildren(boolean include) {
withChildren = include;
}
基本上,我们的想法是将原始模型中的节点连接/取消连接到过滤后的模型根,跟踪当前系列节点。
过滤后的模型将在子项和父项之间建立映射,并采用适当的方法来恢复此系列。 “therestoreFamily”方法将重新连接那些失踪的孩子。
过滤后的模型将使用filter()
方法执行大部分工作,将abstract
方法shouldBeFiltered(node)
保留给实现:
应该考虑到,在将过滤后的孩子连接到过滤后的根之前,不需要将所有孩子与家人断开连接。如果绩效是关键,那么可以更深入地分析这种行为。
最后,但最重要的是,需要通过实现一种方法并覆盖另一种方法来扩展底层treetable:
@Override
public void setTreeTableModel(TreeTableModel treeModel) {
if (!(treeModel instanceof TellapicModelFilter))
model = treeModel;
super.setTreeTableModel(treeModel);
}
public void setModelFilter(TellapicModelFilter mf) {
if (modelFilter != null) {
modelFilter.restoreFamily();
setTreeTableModel(getUnfilteredModel());
}
// Is this necessary?
if (mf == null) {
setTreeTableModel(getUnfilteredModel());
} else {
modelFilter = mf;
modelFilter.filter();
setTreeTableModel(modelFilter);
}
}
可以在此link找到一个带有treetable的完整且有效的示例。它包含一个Main.java
,其中包含一个可以构建的树。测试GUI
有一个按钮,可以在所选节点中添加节点(如果有的话),在框架的顶部有一个文本字段,可以在写入时进行过滤。
答案 6 :(得分:1)
我终于设法完全满足了我的需求,并且认为我会分享以防其他人可以使用它。
我一直试图并排显示两个JTree - 一个包含另一个的过滤列表。
基本上我已经制作了两个TreeModel,并且两者都使用相同的根节点。到目前为止,这似乎工作正常,只要我确保覆盖从我的代码中的DefaultTreeModel调用的每个方法,例如nodeChanged(TreeNode节点),否则会有痛苦。
痛苦来自于这样一个事实,就是在nodecountModel上调用节点结构类型方法时,唯一一次查询节点本身以获取像childcount这样的信息。除此之外,所有对树结构信息的调用都可以被拦截并过滤掉,如下所示。
如果您像我一样使用DefaultTreeModel作为基础,那么每次查询节点本身时都必须确保挖掘出来,这可能会变得很糟糕。如果您直接实现TreeModel而不是像我一样懒惰,则可能不存在此问题。 NodesChanged源代码来自JDK源代码。
我很幸运,我想要的意思是总是有一条路径从过滤列表中的每个项目返回到根节点。
这就是我需要做的全部,即使我花了一整天时间尝试疯狂和混乱的发明,比如重新创建树的浅层副本等等,更不用说在Stack上阅读!:
public class FilteredSceneModel extends DefaultTreeModel {
public static boolean isSceneItem(Object child) {
return !(child instanceof DataItem);
}
public FilteredSceneModel(RootSceneNode root, SelectionModel sm) {
super(root, sm);
}
private boolean isSceneFolder(Object node) {
return node instanceof RootSceneNode || node instanceof Floor;
}
@Override
public AbstractSceneItem getChild(Object parent, int index) {
AbstractSceneItem asi = (AbstractSceneItem) parent;
if (isSceneItem(parent)) {
int dex = 0;
for (AbstractSceneItem child : asi.children) {
if (isSceneItem(child)) {
if (dex == index) {
return child;
}
dex++;
}
}
}
System.out.println("illegal state for: " + parent + " at index: " + index);
return asi.getChildAt(index);
}
@Override
public int getChildCount(Object parent) {
if (isSceneItem(parent)) {
AbstractSceneItem asi = (AbstractSceneItem) parent;
int count = 0;
for (AbstractSceneItem child : asi.children) {
if (isSceneItem(child)) {
count++;
}
}
return count;
}
return -1;
}
@Override
public int getIndexOfChild(Object parent, Object childItem) {
if (isSceneItem(parent)) {
AbstractSceneItem asi = (AbstractSceneItem) parent;
int count = 0;
for (AbstractSceneItem child : asi.children) {
if (isSceneItem(child)) {
if (child == childItem) {
return count;
}
count++;
}
}
}
return -1;
}
@Override
public boolean isLeaf(Object node) {
if (isSceneItem(node)) {
if (isSceneFolder(node)) {
return false;
}
}
return true;
}
@Override
public void activeFloorChanged(Floor floor) {
for (AbstractSceneItem asi : floor) {
if (isSceneItem(asi)) {
nodeChanged(asi);
}
}
}
@Override
protected void renamed(AbstractSceneItem asi) {
if (isSceneItem(asi)) {
nodeChanged(asi);
System.out.println("scene only model renamed: " + asi.fullPathToString());
}
}
@Override
public void nodeChanged(TreeNode tn) {
if (isSceneItem(tn)) {
filteredNodeChanged(tn);
}
}
@Override
public void nodeStructureChanged(TreeNode tn) {
if (isSceneItem(tn)) {
super.nodeStructureChanged(tn);
}
}
private void filteredNodeChanged(TreeNode node) {
if (listenerList != null && node != null) {
TreeNode parent = node.getParent();
if (parent != null) {
int anIndex = getIndexOfChild(parent, node);
if (anIndex != -1) {
int[] cIndexs = new int[1];
cIndexs[0] = anIndex;
nodesChanged(parent, cIndexs);
}
} else if (node == getRoot()) {
nodesChanged(node, null);
}
}
}
@Override
public void nodesChanged(TreeNode node, int[] childIndices) {
if (node != null) {
if (childIndices != null) {
int cCount = childIndices.length;
if (cCount > 0) {
Object[] cChildren = new Object[cCount];
for (int counter = 0; counter < cCount; counter++) {
cChildren[counter] = getChild(node, childIndices[counter]);
}
fireTreeNodesChanged(this, getPathToRoot(node),
childIndices, cChildren);
}
} else if (node == getRoot()) {
fireTreeNodesChanged(this, getPathToRoot(node), null, null);
}
}
}
}
答案 7 :(得分:0)
我使用的原则: 从DB填充ArrayList,然后填充树。当我必须过滤树节点时,我只是遍历ArrayList,删除所有不匹配条件的节点,然后使用修改后的ArrayList重建树...
答案 8 :(得分:0)
我对此有一个可能感兴趣的建议。我已经在我自己的应用程序中付诸实践,它似乎工作得很好......下面是一个绝对最小的实现SSCCE演示“insertNodeInto”。
中心设计是JTree-TreeModel的多个耦合,它们彼此保持完美同步......显然,除了应用某些过滤模式以便某些节点(及其子树)不存在于一个中模型。同时,ON树中的每个节点在OFF树中都有一个“对应”节点(尽管反过来不一定正确)。
最简单的设计因此涉及2个这样的耦合:一个带过滤器“OFF”,另一个带过滤器“ON”(顺便提一下,你可以有多个过滤器,这样你就需要n ^ 2个接头,其中n是过滤器的数量......我已经有了这个工作!)。
要从一个耦合切换到另一个耦合(即从ON切换到OFF或反之亦然),您只需将一个JTree替换为包含JViewport的另一个JTree ......这种情况发生在眨眼之间,完全无法解决。所以它有点像视错觉。
顺便说一句,这里使用的过滤器是“节点的toString()是否包含字符串'nobble'”? (参见方法FilterPair.is_filtered_out)
有些人可能会说这样的想法可能是内存效率低得离谱......但实际上不同耦合中的节点虽然是不同的节点,却使用相同的用户对象......所以我建议结构相当轻巧。 / p> 很难,更难的是让两个耦合的机制(更不用说4或8)相互同步。下面我演示了一个相当全面的insertNodeInto实现...但是,许多DefaultTreeModel,JTree的方法,以及与选择有关的方法都需要经过深思熟虑。例如。如果(过滤器)OFF树中的选择是在ON树中没有对应物的节点上(因为它或它的一个祖先已被过滤掉),那么ON树中的选择应该去哪里?我找到了所有这些问题的答案,但这里没有空间来展示它们......
import java.awt.*;
import java.awt.event.*;
import java.io.*;
import javax.swing.*;
import javax.swing.tree.*;
public class FilterTreeDemo {
public static void main(String[] args) throws FileNotFoundException {
EventQueue.invokeLater(new ShowIt());
}
}
class FiltNode extends DefaultMutableTreeNode {
FiltNode( Object user_obj ){
super( user_obj );
}
FiltNode m_counterpart_node;
// public String toString(){
// // hash code demonstrates (as you toggle) that these are not the same nodes...
// return super.toString() + " (" + hashCode() + ")";
// }
}
class FilterPair {
TreeCoupling m_on_coupling, m_off_coupling;
boolean m_filter_on = true;
JFrame m_main_frame;
FiltNode m_on_root = new FiltNode( "root" );
FiltNode m_off_root = new FiltNode( "root" );
// needed to prevent infinite calling between models...
boolean m_is_propagated_call = false;
FilterPair( JFrame main_frame ){
m_on_root.m_counterpart_node = m_off_root;
m_off_root.m_counterpart_node = m_on_root;
m_on_coupling = new TreeCoupling( true );
m_off_coupling = new TreeCoupling( false );
m_main_frame = main_frame;
// starts by toggling to OFF (i.e. before display)
toggle_filter();
}
// this is the filter method for this particular FilterPair...
boolean is_filtered_out( MutableTreeNode node ){
return node.toString().contains( "nobble");
}
class TreeCoupling {
class FilterTreeModel extends DefaultTreeModel {
FilterTreeModel( TreeNode root ){
super( root );
}
public void insertNodeInto(MutableTreeNode new_child, MutableTreeNode parent, int index){
// aliases for convenience
FiltNode new_filt_node = (FiltNode)new_child;
FiltNode parent_filt_node = (FiltNode)parent;
FiltNode new_counterpart_filt_node = null;
FiltNode counterpart_parent_filt_node = null;
// here and below the propagation depth test is used to skip code which is leading to another call to
// insertNodeInto on the counterpart TreeModel...
if( ! m_is_propagated_call ){
// NB the counterpart new FiltNode is given exactly the same user object as its counterpart: no duplication
// of the user object...
new_counterpart_filt_node = new FiltNode( new_filt_node.getUserObject() );
counterpart_parent_filt_node = parent_filt_node.m_counterpart_node;
// set up the 2 counterpart relationships between the node in the ON tree and the node in the OFF tree
new_counterpart_filt_node.m_counterpart_node = new_filt_node;
new_filt_node.m_counterpart_node = new_counterpart_filt_node;
}
if( TreeCoupling.this == m_on_coupling ){
// ... we are in the ON coupling
// if first call and the parent has no counterpart (i.e. in the OFF coupling) sthg has gone wrong
if( ! m_is_propagated_call && counterpart_parent_filt_node == null ){
throw new NullPointerException();
}
if( ! is_filtered_out( new_filt_node ) ){
// only insert here (ON coupling) if the node is NOT filtered out...
super.insertNodeInto( new_filt_node, parent_filt_node, index);
}
else {
// enable the originally submitted new node (now rejected) to be unlinked and garbage-collected...
// (NB if you suspect the first line here is superfluous, try commenting out and see what happens)
new_filt_node.m_counterpart_node.m_counterpart_node = null;
new_filt_node.m_counterpart_node = null;
}
if( ! m_is_propagated_call ){
// as we are in the ON coupling we can't assume that the index value should be passed on unchanged to the
// OFF coupling: some siblings (of lower index) may be missing here... but we **do** know that the previous
// sibling (if there is one) of the new node has a counterpart in the OFF tree... so get the index of its
// OFF counterpart and add 1...
int off_index = 0;
if( index > 0 ){
FiltNode prev_sib = (FiltNode)parent_filt_node.getChildAt( index - 1 );
off_index = counterpart_parent_filt_node.getIndex( prev_sib.m_counterpart_node ) + 1;
}
m_is_propagated_call = true;
m_off_coupling.m_tree_model.insertNodeInto( new_counterpart_filt_node, counterpart_parent_filt_node, off_index);
}
}
else {
// ... we are in the OFF coupling
super.insertNodeInto( new_filt_node, parent_filt_node, index);
if( ! m_is_propagated_call ){
// we are in the OFF coupling: it is perfectly legitimate for the parent to have no counterpart (i.e. in the
// ON coupling: indicates that it, or an ancestor of it, has been filtered out)
if( counterpart_parent_filt_node != null ){
// OTOH, if the parent **is** available, we can't assume that the index value should be passed on unchanged:
// some siblings of the new incoming node (of lower index) may have been filtered out... to find the
// correct index value we track down the index value until we reach a node which has a counterpart in the
// ON coupling... or if not found the index must be 0
int on_index = 0;
if( index > 0 ){
for( int i = index - 1; i >= 0; i-- ){
FiltNode counterpart_sib = ((FiltNode)parent_filt_node.getChildAt( i )).m_counterpart_node;
if( counterpart_sib != null ){
on_index = counterpart_parent_filt_node.getIndex( counterpart_sib ) + 1;
break;
}
}
}
m_is_propagated_call = true;
m_on_coupling.m_tree_model.insertNodeInto( new_counterpart_filt_node, counterpart_parent_filt_node, on_index);
}
else {
// ... no ON-coupling parent node "counterpart": the new ON node must be discarded
new_filt_node.m_counterpart_node = null;
}
}
}
m_is_propagated_call = false;
}
}
JTree m_tree;
FilterTreeModel m_tree_model;
TreeCoupling( boolean on ){
m_tree = new JTree();
m_tree_model = on ? new FilterTreeModel( m_on_root ) : new FilterTreeModel( m_off_root );
m_tree.setModel( m_tree_model );
}
}
void toggle_filter(){
m_filter_on = ! m_filter_on;
m_main_frame.setTitle( m_filter_on? "FilterTree - ON (Ctrl-F6 to toggle)" : "FilterTree - OFF (Ctrl-F6 to toggle)" );
}
TreeCoupling getCurrCoupling(){
return m_filter_on? m_on_coupling : m_off_coupling;
}
}
class ShowIt implements Runnable {
@Override
public void run() {
JFrame frame = new JFrame("FilterTree");
final FilterPair pair = new FilterPair( frame );
final JScrollPane jsp = new JScrollPane( pair.getCurrCoupling().m_tree );
Action toggle_between_views = new AbstractAction( "toggle filter" ){
@Override
public void actionPerformed(ActionEvent e) {
pair.toggle_filter();
jsp.getViewport().setView( pair.getCurrCoupling().m_tree );
jsp.requestFocus();
}};
JPanel cpane = (JPanel)frame.getContentPane();
cpane.getActionMap().put("toggle between views", toggle_between_views );
InputMap new_im = new InputMap();
new_im.put(KeyStroke.getKeyStroke(KeyEvent.VK_F6, InputEvent.CTRL_DOWN_MASK), "toggle between views");
cpane.setInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT, new_im);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(jsp);
frame.pack();
frame.setBounds(50, 50, 800, 500);
frame.setVisible(true);
// populate the tree(s) NB we are currently viewing the OFF tree
FilterPair.TreeCoupling curr_coupling = pair.getCurrCoupling();
curr_coupling.m_tree_model.insertNodeInto( new FiltNode( "scrags 1" ), (FiltNode)curr_coupling.m_tree_model.getRoot(), 0 );
FiltNode d2 = new FiltNode( "scrags 2" );
curr_coupling.m_tree_model.insertNodeInto( d2, (FiltNode)curr_coupling.m_tree_model.getRoot(), 1 );
curr_coupling.m_tree_model.insertNodeInto( new FiltNode( "scrags 3" ), (FiltNode)curr_coupling.m_tree_model.getRoot(), 2 );
curr_coupling.m_tree_model.insertNodeInto( new FiltNode( "scrags 2.1" ), d2, 0 );
// this will be filtered out of the ON tree
FiltNode nobble = new FiltNode( "nobble" );
curr_coupling.m_tree_model.insertNodeInto( nobble, d2, 1 );
// this will also be filtered out of the ON tree
FiltNode son_of_nobble = new FiltNode( "son of nobble");
curr_coupling.m_tree_model.insertNodeInto( son_of_nobble, nobble, 0 );
curr_coupling.m_tree_model.insertNodeInto( new FiltNode( "peewit (granddaughter of n****e)"), son_of_nobble, 0 );
// expand the OFF tree
curr_coupling.m_tree.expandPath( new TreePath( curr_coupling.m_tree_model.getRoot() ) );
curr_coupling.m_tree.expandPath( new TreePath( d2.getPath() ) );
curr_coupling.m_tree.expandPath( new TreePath( nobble.getPath() ) );
curr_coupling.m_tree.expandPath( new TreePath( son_of_nobble.getPath() ) );
// switch view (programmatically) to the ON tree
toggle_between_views.actionPerformed( null );
// expand the ON tree
curr_coupling = pair.getCurrCoupling();
curr_coupling.m_tree.expandPath( new TreePath( curr_coupling.m_tree_model.getRoot() ) );
curr_coupling.m_tree.expandPath( new TreePath( d2.m_counterpart_node.getPath() ) );
// try to expand the counterpart of "nobble"... there shouldn't be one...
FiltNode nobble_counterpart = nobble.m_counterpart_node;
if( nobble_counterpart != null ){
curr_coupling.m_tree.expandPath( new TreePath( nobble_counterpart.getPath() ) );
System.err.println( "oops..." );
}
else {
System.out.println( "As expected, node \"nobble\" has no counterpart in the ON coupling" );
}
// try inserting a node into the ON tree which will immediately be "rejected" by the ON tree (due to being
// filtered out before the superclass insertNodeInto is called), but will nonetheless appear in the
// OFF tree as it should...
curr_coupling.m_tree_model.insertNodeInto( new FiltNode( "yet another nobble"), d2.m_counterpart_node, 0 );
}
}
还有一个想法:如何推广它以便FilterTreeModel扩展你自己提供的DefaultTreeModel的子类? (并在完整的implmentation中,以便FilterJTree扩展您提供的JTree子类?)。我最初在Jython中编写了这段代码,其中将类A作为B类定义的参数传递是微不足道的!使用庄严的旧Java,可以使用反射和静态工厂方法,或者可能通过一些巧妙的封装技术。尽管如此,这将是一个艰难的过程。如果可能的话,最好切换到Jython!
答案 9 :(得分:-1)
给出了解决方案http://forums.sun.com/thread.jspa?forumID=57&threadID=5378510
我们已经在这里实现了它,并且它可以作为一种魅力。
您只需要实现TreeModel,并且在JTree上使用FilterTreeModel和TreeFilter。
实现很简单,可能在侦听器上有一些事情要做,因为实际的代码会调用两次,这根本就不好。我的想法是将监听器传递给委托模型,我没有看到在过滤器模型上添加监听器的意义......但这是另一个问题。