过滤JTree [英] Filtering on a JTree
问题描述
问题
在 JTree
中应用过滤,以避免某些节点/叶片在呈现版本中显示的 JTree
。理想情况下,我正在寻找一个允许使用动态过滤器的解决方案,但如果我能够获得静态过滤器,我将很高兴。
有点简单,让我们假设 JTree
只支持渲染,而不是编辑。移动,添加,删除节点应该是可能的。
一个示例是 JTree
上方的搜索字段,并输入 JTree
只会显示具有匹配项的子树。
一些限制:用于可以访问JDK和SwingX的项目。我想避免包括其他第三方库。
我已经想到了一些可能的解决方案,但这两个似乎都不理想。
方法
基于模型的过滤
- 装饰
TreeModel
以过滤掉一些值。一个快速和脏的版本很容易写。过滤出节点,并且在过滤器或委托的每个更改TreeModel
上,装饰器可以触发整个树有更改的事件(treeStructureChanged
,以根节点为节点)。将这与将恢复
JTree
的选择状态和扩展状态的监听器组合起来,您可以获得更多或更少的工作版本,但来自TreeModel
被搞砸了。这或多或少是此问题中使用的方法 - 装饰
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
s,也可以在修改版本的JTree
上完成。
所以简而言之:我不知道解决这个问题的最佳方法是什么。
注意:这个问题可能是这个问题的重复,但是这个问题仍然没有得到答复这个问题比较简短,答案似乎是不完整,所以我想提出一个新的问题。如果没有这样做(常见问题解答没有提供一个明确的答案),我会更新3年前的问题
查看的过滤绝对是要走的路。你可以使用像下面编码的例子。过滤树的另一个常见做法是在过滤树时切换到列表视图,因为列表不需要您显示需要显示后代的隐藏节点。</ p>
这是绝对可怕的代码(我试图削减每一个角落可能在打搅它刚刚),但它应该足以让你开始。只需在搜索框中输入查询,然后按Enter键即可过滤JTree的默认模型。 (FYI,前90行只是生成样板和布局代码。)
package com.example.tree;
import java.awt.BorderLayout;
public class FilteredJTreeExample extends JFrame {
private JPanel contentPane;
private JTextField textField;
/ **
*启动应用程序。
* /
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();
}
}
});
}
/ **
*创建框架。
* /
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 ,对象值,
boolean arg2,boolean arg3,boolean arg4,int arg5,boolean arg6){
组件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){
枚举< DefaultMutableTreeNode> e = node.breadthFirstEnumeration();
while(e.hasMoreElements()){
if(matchesFilter(e.nextElement())){
return true;
}
}
return false;
}
});
}
}
您可能需要创建自己的TreeNode和TreeCellRenderer实现,使用较少的愚蠢方法触发更新,并遵循MVC分离。请注意,隐藏节点仍然呈现,但它们太小,无法看到它们。如果您使用箭头键导航树,那么您会注意到它们仍然存在。如果你只需要一些有用的东西,这可能就够了。
修改
在Mac OS中未过滤和过滤版本的树的屏幕截图,显示空白在Mac OS中可见:
Problem
Applying filtering on a JTree
to avoid certain nodes/leaves to show up in the rendered version of the JTree
. Ideally I am looking for a solution which allows to have a dynamic filter, but I would already be glad if I can get a static filter to work.
To make it a bit easier, let us suppose the JTree
only supports rendering, and not editing. Moving, adding, removing of nodes should be possible.
An example is a search field above a JTree
, and on typing the JTree
would only show the subtree with matches.
A few restrictions: it is to be used in a project which has access to JDK and SwingX. I would like to avoid to include other third party libs.
I already thought of a few possible solutions, but neither of those seemed ideal
Approaches
Model based filtering
- decorate the
TreeModel
to filter out some of the values. A quick-and-dirt version is easy to write. Filter out nodes, and on every change of the filter or the delegateTreeModel
the decorator can fire an event that the whole tree has changes (treeStructureChanged
with the root node as node). Combine this with listeners which restore the selection state and the expansion state of theJTree
and you get a version which works more or less, but the events originating from theTreeModel
are messed up. This is more or less the approach used in this question - decorate the
TreeModel
but try fire the correct events. I did not (yet) managed to come up with a working version of this. It seems to require a copy of the delegateTreeModel
in order to be able to fire an event with the correct child indices when nodes are removed from the delegate model. I think with some more time I could get this to work, but it just feels wrong (filtering feels like something the view should do, and not the model) - decorate whatever data structure was used to create the initial
TreeModel
. However, this is completely non-reusable, and probably as hard as to write a decorator for aTreeModel
View based filtering
This seems like the way to go. Filtering should not affect the model but only the view.
I took a look at
RowFilter
class. Although the javadoc seems to suggest you can use it in combination with aJTree
:when associated with a JTree, an entry corresponds to a node.
I could not find any link between
RowFilter
(orRowSorter
) and theJTree
class. The standard implementations ofRowFilter
and the Swing tutorials seems to suggest thatRowFilter
can only be used directly with aJTable
(seeJTable#setRowSorter
). No similar methods are available on aJTree
- I also looked at the
JXTree
javadoc. It has aComponentAdapter
available and the javadoc ofComponentAdapter
indicates aRowFilter
could interact with the target component, but I fail to see how I make the link between theRowFilter
and theJTree
- I did not yet look at how a
JTable
handles the filtering withRowFilter
s, and perhaps the same can be done on a modified version of aJTree
.
So in short: I have no clue on what's the best approach to solve this
Note: this question is a possible duplicate of this question, but that question is still unanswered, the question rather short and the answers seems incomplete, so I thought to post a new question. If this is not done (the FAQ did not provide a clear answer on this) I will update that 3year old question
View-based filtering is definitely the way to go. You can use something like the example I've coded below. Another common practice when filtering trees is to switch to a list view when filtering a tree, since the list won't require you to show hidden nodes whose descendants need to be shown.
This is absolutely horrendous code (I tried to cut every corner possible in whipping it up just now), but it should be enough to get you started. Just type your query in the search box and press Enter, and it'll filter the JTree's default model. (FYI, the first 90 lines are just generated boilerplate and layout code.)
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;
}
});
}
}
When you implement it for real, you'll probably want to create your own TreeNode and TreeCellRenderer implementations, use a less stupid method for triggering an update, and follow MVC separation. Note that the "hidden" nodes are still rendered, but they're so small that you can't see them. If you use the arrow keys to navigate the tree, though, you'll notice that they're still there. If you just need something that works, this might be good enough.
Edit
Here are screenshots of the unfiltered and filtered version of the tree in Mac OS, showing that the whitespace is visible in Mac OS:
这篇关于过滤JTree的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!