【问题标题】:Deselect node from JTree when click any place outside the tree单击树外的任何位置时从 JTree 中取消选择节点
【发布时间】:2019-08-17 22:06:56
【问题描述】:

我使用的是JTree,它使用的是DefaultTreeModel。这个树模型里面有一些节点,当我点击一个节点时,我得到了节点的信息,我改变了背景颜色以显示这个节点被选中。

当我点击树外的任何地方时,是否可以调用树来清除选择?通过清除选择,我将能够再次更改背景颜色,但是当我单击树外时,我不知道如何或在何处使用树的 clearSelection() 方法。

这是我正在使用的代码:

例子:

import javax.swing.*;
import javax.swing.tree.*;
import java.awt.*;

public class JTreeSelectDeselect {

    public static void main(String[] args) {
        JFrame frame = new JFrame();
        frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);

        JPanel panel = new JPanel(new BorderLayout());
        JTree tree = new JTree();
        tree.setCellRenderer(new DeselectTreeCellRenderer());

        panel.add(tree, BorderLayout.LINE_START);
        panel.add(new JScrollPane(new JTextArea(10, 30)));
        frame.add(panel);

        frame.pack();
        frame.setVisible(true);
    }
}

class DeselectTreeCellRenderer extends DefaultTreeCellRenderer {

    @Override
    public Color getBackgroundSelectionColor() {
        return new Color(86, 92, 160);
    }

    @Override
    public Color getBackground() {
        return (null);
    }

    @Override
    public Color getBackgroundNonSelectionColor() {
        return new Color(23, 27, 36);
    }

    @Override
    public Component getTreeCellRendererComponent(JTree tree, Object value,
            boolean sel, boolean exp, boolean leaf, int row, boolean hasFocus) {
        super.getTreeCellRendererComponent(tree, value, sel, exp, leaf, row, hasFocus);

        setForeground(new Color(225, 225, 221, 255));
        setOpaque(false);

        return this;
    }
}

我在这里展示了如何使用树模型创建节点并将其添加到树中,以及如何设置我的自定义 TreeCellRenderer

在单元格渲染器中,我使用特定颜色绘制选定的节点,如果取消选择该节点,我使用另一种颜色绘制它。当我更改节点的选择时,它们的背景正在正确绘制,但是当我在树外单击时,选定的节点没有被取消选择,因此它没有使用单元格渲染器中建立的特定颜色进行绘制。

当我在树外单击时,有一种方法可以取消选择节点吗?

如果有人知道,有一种方法可以通过TreeCellRenderer 中的复选框更改一些叶子?将一些子元素作为标签,将其他一些子元素作为复选框。因为当我尝试添加复选框时,它说(如我所料)复选框不是 DefaultMutableTreeNode 对象,我无法将它们添加到树模型中。

【问题讨论】:

  • 根据您的建议,我编辑了问题以提供一个完整且可验证的示例。非常感谢!
  • 谢谢@AndrewThompson。巨大的变化!或许这样更容易理解。非常感谢。

标签: java swing jtree


【解决方案1】:

首先,如果您只想更改一些颜色,则不需要继承 DefaultTreeCellRenderer。您可以创建一个新的,根据需要设置颜色并将其设置为树。在下面的代码示例中,我在 getDefaultTreeCellRenderer() 中完成了此操作。

您的面板包含两个元素,即树和文本区域。 为了实现您所需要的,我在树中添加了一个鼠标侦听器和一个焦点侦听器:

  • 鼠标侦听器 - mouseClicked() 在您单击树内部或外部时都会触发(但不是在 TextArea 中,因为我们有焦点侦听器)。 要检查您是否点击了单元格的边界,我们使用 tree.getRowForLocation(e.getX(),e.getY()) 如果它返回 -1 这意味着我们点击了任何单元格之外,因此我们可以清除选择
  • 焦点侦听器 - 当您从 JTree 失去焦点并单击文本区域时,这将被触发,我们只需清除选择

我们需要两个侦听器,因为第一个侦听器仅在您单击树及其周围而不是文本区域时触发,而第二个侦听器在您将注意力移出树区域并专注于文本区域时触发。

import javax.swing.*;
import javax.swing.tree.DefaultTreeCellRenderer;
import java.awt.*;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;

public class JTreeSelectDeselect {

    public static void main(String[] args) {
        JFrame frame = new JFrame();
        frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);

        JPanel panel = new JPanel(new BorderLayout());
        JTree tree = new JTree();

        tree.setCellRenderer(getDefaultTreeCellRenderer());
        tree.addMouseListener(getMouseListener(tree));
        tree.addFocusListener(getFocusListener(tree));

        panel.add(tree, BorderLayout.LINE_START);
        panel.add(new JScrollPane(new JTextArea(10, 30)));
        frame.add(panel);

        frame.pack();
        frame.setVisible(true);
    }

    private static DefaultTreeCellRenderer getDefaultTreeCellRenderer() {
        DefaultTreeCellRenderer defaultTreeCellRenderer = new DefaultTreeCellRenderer();
        defaultTreeCellRenderer.setBackgroundSelectionColor(new Color(86, 92, 160));
        defaultTreeCellRenderer.setBackgroundNonSelectionColor(new Color(135, 151, 53));
        defaultTreeCellRenderer.setBackground(new Color(225, 225, 221, 255));
        defaultTreeCellRenderer.setForeground(new Color(225, 225, 221, 255));
        return defaultTreeCellRenderer;
    }

    private static FocusListener getFocusListener(final JTree tree) {
        return new FocusListener() {
            @Override
            public void focusGained(FocusEvent e) {

            }

            @Override
            public void focusLost(FocusEvent e) {
                System.out.println("focus lost");
                tree.clearSelection();
            }
        };
    }

    private static MouseListener getMouseListener(final JTree tree) {
        return new MouseListener() {
            @Override
            public void mouseClicked(MouseEvent e) {
                System.out.println("mouse clicked");
                if(tree.getRowForLocation(e.getX(),e.getY()) == -1) {
                    System.out.println("clicked outside a specific cell");
                    tree.clearSelection();
                }
            }

            @Override
            public void mousePressed(MouseEvent e) {

            }

            @Override
            public void mouseReleased(MouseEvent e) {

            }

            @Override
            public void mouseEntered(MouseEvent e) {

            }

            @Override
            public void mouseExited(MouseEvent e) {

            }
        };
    }
}

【讨论】:

  • 感谢您的回复!我收到的所有回复都非常有用,可以帮助我理解这一点。我已经尝试过您的解决方案和@Sergiy Medvynskyy 的解决方案,两者都非常好。最后我选择了你的,因为它非常接近我想要实现的解决方案,所以非常感谢!
【解决方案2】:

如果您想在鼠标点击其他地方时取消选择树中的所有选定节点,您需要在用户点击其他地方时收到通知。要获取它,您可以使用全局事件侦听器 (Toolkit.getDefaultToolkit().addAWTEventListener())。

如果你想在你的树中显示一些复选框,你需要一个自定义的单元格渲染器,它对于一个条件返回一个复选框。您还需要一个数据容器来保存节点是否被选中,最后您需要一个例程来更新您的数据模型,当单击节点时。最后一件事通常由单元格编辑器提供(更多关于editors and renderes 的文章),但在您的情况下,使用我们之前安装的全局鼠标侦听器更容易。

当然,我的示例包含一点“摇摆魔法”,所以当您有不明白的地方,请随时向我提问;)

import java.awt.AWTEvent;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Toolkit;
import java.awt.event.AWTEventListener;
import java.awt.event.MouseEvent;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

import javax.swing.JCheckBox;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JTree;
import javax.swing.SwingUtilities;
import javax.swing.WindowConstants;
import javax.swing.tree.DefaultTreeCellRenderer;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreePath;

/**
 * <code>JTreeDeselected</code>.
 */
public class JTreeSelectDeselect {

    private final JTree tree = new JTree();

    // model to hold nodes that must be presented as check boxes 
    // and whether the check boxes are selected
    private final Map<Object, Boolean> checkMap = new HashMap<>();

    // listener as method reference
    private AWTEventListener awtListener = this::mouseClicked;

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new JTreeSelectDeselect()::start);
    }

    private void start() {
        checkMap.put("football", true);
        checkMap.put("soccer", false);
        checkMap.put("violet", true);
        checkMap.put("yellow", false);
        checkMap.put("pizza", true);
        checkMap.put("ravioli", false);
        JFrame frame = new JFrame();
        frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);

        JPanel panel = new JPanel(new BorderLayout());
        // JTree tree = new JTree();
        tree.setCellRenderer(new DeselectTreeCellRenderer(checkMap));
        panel.add(new JScrollPane(tree), BorderLayout.LINE_START);
        panel.add(new JScrollPane(new JTextArea(10, 30)));
        frame.add(panel);
        // register global listener
        Toolkit.getDefaultToolkit().addAWTEventListener(awtListener, AWTEvent.MOUSE_EVENT_MASK);
        frame.pack();
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);
    }

    private void mouseClicked(AWTEvent evt) {
        if (evt instanceof MouseEvent) {
            MouseEvent me = (MouseEvent) evt;
            if (me.getID() == MouseEvent.MOUSE_PRESSED) {
                if (me.getComponent().equals(tree)) {
                    TreePath path = tree.getPathForLocation(me.getX(), me.getY());
                    if (path == null) {
                        tree.clearSelection();
                    } else {
                        // update check box value
                        String pathString = Objects.toString(path.getLastPathComponent(), "");
                        Boolean val = checkMap.get(pathString);
                        if (val != null) {
                            checkMap.put(pathString, !val);
                            ((DefaultTreeModel) tree.getModel()).valueForPathChanged(path, pathString);
                        }
                    }
                } else {
                    tree.clearSelection();
                }
            }
        }
    }
}

class DeselectTreeCellRenderer extends DefaultTreeCellRenderer {

    private final JCheckBox checkBox = new JCheckBox();

    private final Map<Object, Boolean> checkMap;

    public DeselectTreeCellRenderer(Map<Object, Boolean> checkMap) {
        this.checkMap = checkMap;
    }

    @Override
    public Color getBackgroundSelectionColor() {
        return new Color(86, 92, 160);
    }

    @Override
    public Color getBackground() {
        return (null);
    }

    @Override
    public Color getBackgroundNonSelectionColor() {
        return new Color(23, 27, 36);
    }

    @Override
    public Component getTreeCellRendererComponent(JTree tree, Object value, boolean sel, boolean exp, boolean leaf, int row,
            boolean hasFocus) {
        super.getTreeCellRendererComponent(tree, value, sel, exp, leaf, row, hasFocus);

        setForeground(new Color(225, 225, 221, 255));
        setOpaque(false);

        // if our "check model" contains an entry for the node,
        // present the node as check box
        if (checkMap.containsKey(Objects.toString(value))) {
            checkBox.setOpaque(true);
            checkBox.setSelected(Boolean.TRUE == checkMap.get(Objects.toString(value)));
            checkBox.setBackground(sel ? getBackgroundSelectionColor() : getBackgroundNonSelectionColor());
            checkBox.setForeground(getForeground());
            checkBox.setFont(getFont());
            checkBox.setBorder(getBorder());
            checkBox.setText(getText());
            return checkBox;
        }
        return this;
    }
}

【讨论】:

    【解决方案3】:

    Java AWT 包中的 Robot 类用于生成本地系统输入事件,用于测试自动化、自运行演示以及其他需要控制鼠标和键盘的应用程序。 Robot 的主要目的是促进 Java 平台实现的自动化测试。简单来说,该类提供对鼠标和键盘设备的控制。在下面的 sn-p 中,“Robot”已用于捕获事件并从树中清除选择。

    import java.awt.AWTException;
    import java.awt.Robot;
    import java.awt.event.*;
    import javax.swing.*;
    import javax.swing.tree.TreePath;
    
    public class TreeDeselectionTest {
    
      JTree createdTreeInstance = new JTree();
    
      TreePath pathSelectionInstance;
    
      Robot robotInstance;
    
      public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
          public void run() {
            new TreeDeselectionTest().createTreeAndCaptureEvents();
          }
        });
      }
    
      public void createTreeAndCaptureEvents() {
        try {
          robotInstance = new Robot();
        } catch (AWTException exceptionInstance) {
          exceptionInstance.printStackTrace();
        }
        createdTreeInstance.setShowsRootHandles(false);
        createdTreeInstance.addMouseListener(new MouseAdapter() {
          @Override
          public void mousePressed(MouseEvent eventMousePressInstance) {
            if (robotInstance != null && pathSelectionInstance != null && eventMousePressInstance.getButton() == MouseEvent.BUTTON1) {
              createdTreeInstance.clearSelection();
              robotInstance.mousePress(InputEvent.BUTTON1_MASK);
              robotInstance.mouseRelease(InputEvent.BUTTON1_MASK);
            }
            pathSelectionInstance = createdTreeInstance.getSelectionPath();
          }
        });
        JFrame frameConetnds = new JFrame();
        frameConetnds.setContentPane(new JScrollPane(createdTreeInstance));
        frameConetnds.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frameConetnds.setSize(400, 400);
        frameConetnds.setLocationRelativeTo(null);
        frameConetnds.setVisible(true);
      }
    }
    

    【讨论】:

      猜你喜欢
      • 2012-06-10
      • 1970-01-01
      • 2012-08-02
      • 1970-01-01
      • 1970-01-01
      • 2018-08-24
      • 2013-01-01
      • 1970-01-01
      • 2019-01-28
      相关资源
      最近更新 更多