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
の変更されたバージョンでも同じことが可能です。つまり、これを解決するための最良のアプローチは何なのか、私には手がかりがありません。
注:この質問は この質問 の重複の可能性がありますが、その質問はまだ回答されておらず、質問はかなり短く、回答が不完全に思われるため、新しい質問を投稿することを考えました質問。これが行われない場合(FAQはこれについて明確な答えを提供していませんでした)私はその3年前の質問を更新します
この実装を見てください: http://www.Java2s.com/Code/Java/Swing-Components/InvisibleNodeTreeExample.htm
実際にTreeModelからノードを削除/追加するのではなく、「isVisible」プロパティを追加してDefaultMutableNodeのサブクラスを作成します。かなり甘いと思います。これで、フィルタリングの問題がきれいに解決されました。
ビューベースのフィルタリングは間違いなく進むべき道です。以下にコーディングした例のようなものを使用できます。ツリーをフィルタリングするときのもう1つの一般的な方法は、ツリーをフィルタリングするときにリストビューに切り替えることです。リストでは、子孫を表示する必要がある非表示のノードを表示する必要がないためです。
これは絶対に恐ろしいコードです(私は今すぐそれを作り上げるために可能な限り隅を切り取ろうとしました)が、あなたが始めるのに十分なはずです。検索ボックスにクエリを入力して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分離に従う必要があります。 「非表示」ノードは引き続きレンダリングされますが、非常に小さいため、表示されません。ただし、矢印キーを使用してツリーをナビゲートすると、それらがまだそこにあることがわかります。機能するものが必要な場合は、これで十分です。
編集
これは、Mac OSでのフィルタリングされていないバージョンとフィルタリングされたバージョンのツリーのスクリーンショットで、Mac OSで空白が表示されていることを示しています。
昔の質問、私は偶然見つけました...
私はそれがモデルのフィルタリングほどきれいではなく、可能なバックドローが付属していることを知っていますが、小さなアプリケーションの迅速な解決策が必要な場合:
DefaultTableCellRendererを拡張し、オーバーライドgetTreeCellRendererComponent-super.getTreeCellRendererComponent(...)を呼び出し、その後、非表示にするすべてのノードの優先高さをゼロに設定します。 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;
}
}
ETable
、JTable
のサブクラス、および Outline
の親クラス、説明 here =、「モデルの特定の行のみを表示できるクイックフィルター機能が含まれます( setQuickFilter()
を参照)。」これは「サードパーティライブラリ」の要件に違反しますが、Outline
JARにはJDK以外の依存関係はありません。
標準のSwingコンポーネントのみを使用する可能なソリューションは次のとおりです。
私はまだこれを使用していませんが、オンラインで見つけた他のクイックアンドダーティーソリューションよりもはるかにその実装が好きです。
拡張JXTreeTable
をフィルタリングするための回避策に取り組んでいます。簡単にするために、2モデルのアプローチに従いました。
_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;
}
_
基本的に、アイデアは、現在のfamilyノードを追跡して、元のモデルからフィルター処理されたモデルルートにノードを接続/接続解除することでした。
フィルターされたモデルには、子と親のマッピングがあり、適切な方法でこのファミリーを復元します。メソッド「restoreFamily」は、行方不明の子供を再接続します。
フィルター処理されたモデルは、ほとんどのジョブをfilter()
メソッドで実行し、abstract
メソッドshouldBeFiltered(node)
を実装に任せます。
フィルターに掛けられた子をフィルターに掛けられたルートに接続する前に、家族からすべての子の接続を解除する必要がないことを考慮する必要があります。パフォーマンスが重要な場合は、その動作をより深く分析できます。
最後に、しかし最も重要なのは、1つのメソッドを実装して別のメソッドをオーバーライドすることにより、基礎となるツリーテーブルを拡張する必要があることです。
_@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 にあります。 _Main.Java
_と、すぐに構築できるツリーが含まれています。テストGUI
には、選択したノード(存在する場合)にノードを追加するボタンがあり、フレームの上部に、書き込み中にフィルタリングするテキストフィールドがあります。
ようやく自分のニーズに合ったスーツをなんとか引き出すことができ、他の誰かが使用できるように共有したいと思いました。
私は2つのJTreeを並べて表示しようとしています。一方には他方のフィルター済みリストが含まれています。
基本的に、2つのTreeModelを作成し、両方に同じルートノードを使用します。 nodeChanged(TreeNode node)などのコードでDefaultTreeModelから呼び出されるすべてのメソッドをオーバーライドすることを確認している限り、これは今のところうまく機能しているようです。
問題は、ノード自体がchildcountなどの情報を照会されるのは、DefaultTreeModelでノード構造タイプのメソッドが呼び出されるときだけであるという事実から来ています。それとは別に、以下に示すように、ツリー構造情報のすべての呼び出しを傍受してフィルターで除外できます。
DefaultTreeModelをベースとして使用する場合は、ノード自体が照会されるたびに掘り出す必要があるため、これは厄介になる可能性があります。私のように怠惰ではなく、直接TreeModelを実装する場合、この問題は存在しない可能性があります。 NodesChangedソースは、JDKソースから直接取得されました。
幸運なことに、私が欲しかったのは、フィルターされたリストのすべてのアイテムからルートノードに戻るパスが常に存在するということです。
これは私がする必要があったすべてであり、スタックのたくさんを読むことは言うまでもなく、木の浅いコピーを再作成するなど、野生で無秩序な発明に一日中費やしたとしても!:
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);
}
}
}
}
興味深いかもしれない、これに対する提案があります。私は自分のアプリでそれを実践しましたが、うまく機能しているようです...以下は、「insertNodeInto」を示す絶対最小実装SSCCEです。
中心となる設計は、JTree-TreeModelの複数のカップリングであり、それらはすべて互いに完全に同期されています。ただし、特定のノード(およびそのサブツリー)が1つのモデルに存在しないように、いくつかのフィルタリングパターンが適用されることは明らかです。一方、ONツリーの各ノードには、OFFツリーに「対応する」ノードがあります(逆は必ずしも真ではありません)。
したがって、最も単純な設計には、このような2つのカップリングが含まれます。1つはフィルター「オフ」、もう1つはフィルター「オン」です(偶然に複数のフィルターを使用できるため、n ^ 2カップリングが必要になります(nは数値)。フィルターの...そして私はこれを働いています!).
あるカップリングから別のカップリングに(つまり、オンからオフに、またはその逆に)切り替えるには、含まれているJViewportで1つのJTreeを別のJTreeに置き換えるだけです。つまり、これは錯覚のようなものです。
ちなみに、ここで使用するフィルターは「ノードのtoString()に文字列 'nobble'が含まれているか」です。 (メソッドFilterPair.is_filtered_outを参照)
そのようなアイデアは途方もなくメモリ効率が悪いと言う人もいるかもしれませんが、実際には、異なるカップリングのノードは、異なるノードであっても同じユーザーオブジェクトを使用しています...したがって、構造はかなり軽量であることをお勧めします。
はるかに難しいのは、2つのカップリングのメカニズム(4または8はもちろん)を互いに同期させることです。以下では、insertNodeIntoのかなり包括的な実装を示しますが、DefaultTreeModel、JTree、および選択に関連する多くのメソッドには、多くの考慮が必要です。例えば。 (フィルター)OFFツリーでの選択がONツリーに対応するノードがないノード上にある場合(そのノードまたはその祖先の1つがフィルターで除外されているため)、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 );
}
}
もう1つの考え:FilterTreeModelが提供するDefaultTreeModelの独自のサブクラスを拡張するようにそれを一般化する方法? (そしてFilterJTreeが提供されたJTreeサブクラスを拡張するように、完全な実装では?)私はこのコードを最初にJythonで記述しました。クラスAをクラスBの定義のパラメーターとして渡すのは簡単です。風格のある古いJavaを使用して、リフレクションおよび静的ファクトリメソッドで、あるいは独創的なカプセル化手法で実行できる可能性があります。しかし、それは難しいスローガンになるでしょう。可能な場合は、Jythonに切り替えることをお勧めします。
私が使用した原則:DBからArrayListに入力し、ツリーにデータを入力します。ツリーノードをフィルター処理する必要がある場合は、ArrayListを反復処理し、基準に一致しないすべてのノードを削除してから、変更されたArrayListでツリーを再構築します...