简体   繁体   English

TreeCellRenderer 中的 label 宽度不正确

[英]Incorrect label width in TreeCellRenderer

I got a JTree with a custom TreeCellRenderer .我得到了一个带有自定义TreeCellRendererJTree
This renderer is a panel containing a checkbox and a label.此渲染器是一个面板,其中包含一个复选框和一个 label。
While the text of the label is fixed for each node (specified in the UserObject of the DefaultMutableTreeNode ), this text may or may not be bold.虽然 label 的文本对于每个节点都是固定的(在DefaultMutableTreeNode的 UserObject 中指定),但该文本可能是粗体,也可能不是粗体。 This depends on the status of the checkbox.这取决于复选框的状态。
When the checkbox is de-selected, the label text is no longer bold but its width remains unchanged (too broad).取消选中该复选框时,label 文本不再是粗体,但其宽度保持不变(太宽)。
Similar, when selecting the checkbox, the text is reported bold but the label is not enlarged.类似地,选中复选框时,文本报告为粗体,但 label 未放大。
This causes the text to be truncated.这会导致文本被截断。

The real-life situation is a little more complicated but here below is a full example.现实生活中的情况要复杂一些,但下面是一个完整的例子。
In order to reproduce the problem:为了重现问题:

  • Select the first node by clicking on the text Select 第一个节点通过点击文字
  • Press the space bar to de-select the checkbox.按空格键取消选中复选框。 The text is not bold but the label is too large.文字不是粗体,但 label 太大。
  • Click the expand button on the first node.单击第一个节点上的展开按钮。 The label width is adjusted label宽度调整
  • Press the space bar to select the combobox.按空格键到 select combobox。 The text becomes bold but is truncated.文本变为粗体但被截断。

I tried to insert several calls to invalidate , repaint , etc. but nothing solves the problem.我尝试插入几个调用以invalidaterepaint等,但没有解决问题。
The problem occurs both in the default look-and-feel and the system (Windows) look-and-feel.默认外观和系统 (Windows) 外观中都会出现此问题。

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

@SuppressWarnings("serial")
public class TestFrame extends JFrame
{
  public TestFrame()
  {
    getContentPane().setLayout(new GridBagLayout());
    setDefaultCloseOperation(EXIT_ON_CLOSE);
    setTitle("Test TreeCellRenderer");

    JScrollPane tree_pane;
    tree_pane = new JScrollPane();
    tree_pane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
    tree_pane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);
    tree_pane.setPreferredSize(new Dimension(300, 200));

    TestTree tree;
    tree = new TestTree();
    tree_pane.getViewport().add(tree, null);

    GridBagConstraints constraints;
    constraints = new GridBagConstraints(0, 0, 1, 1, 1.0, 1.0, GridBagConstraints.NORTH,
                                         GridBagConstraints.BOTH, new Insets(8, 8, 8, 8), 0, 0);
    getContentPane().add(tree_pane, constraints);

    pack();
    setMinimumSize(getPreferredSize());

  }

  public static void main(String[] args)
  {
    try
    {
      UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
      TestFrame frame;
      frame = new TestFrame();
      frame.setVisible(true);
    }
    catch (Exception exception)
    {
      exception.printStackTrace();
    }

  } // main

} // class TestFrame

This class implements my tree:这个 class 实现了我的树:

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.border.EmptyBorder;
import javax.swing.plaf.basic.BasicTreeUI;
import javax.swing.tree.*;

@SuppressWarnings("serial")
public class TestTree extends JTree
{
  // The width of the checkbox within the renderer
  // We need it when a node is clicked in order to check what part is exactly underneath the mouse
  public int checkboxWidth;

  public TestTree()
  {
    // Initialize object
    super(getNodes());
    setRootVisible(false);
    setShowsRootHandles(true);
    setCellRenderer(new MyCellRenderer());
    addMouseListener(new TreeMouseManager());
    addKeyListener(new TreeKeyManager());

  } // constructor

  private void toggleCheckBox(TreePath treePath)
  {
    // Determine node being toggled
    Object[]               path;
    DefaultMutableTreeNode node;
    NodeInfo            info;
    path = treePath.getPath();
    node = (DefaultMutableTreeNode)path[path.length - 1];
    info = (NodeInfo)node.getUserObject();

    // Toggle selection
    info.checked = !info.checked;
    repaint();

  } // toggleCheckBox

  private class TreeMouseManager extends MouseAdapter
  {
    @Override
    public void mouseClicked(MouseEvent event)
    {
      // Determine node corresponding to location
      TreePath treePath;
      treePath = getPathForLocation(event.getX(), event.getY());
      if (treePath == null)
        return;

      // Manage only single click with left button
      if ((event.getClickCount() != 1) || (event.getButton() != MouseEvent.BUTTON1))
        return;

      // Determine horizontal position of checkbox
      BasicTreeUI ui;
      int         depth;
      int         leftIndent;
      int         rightIndent;
      int         checkboxLeft;
      int         checkboxRight;
      ui = (BasicTreeUI)getUI();
      depth = treePath.getPathCount();
      leftIndent = ui.getLeftChildIndent();
      rightIndent = ui.getRightChildIndent();
      checkboxLeft = (depth - 1) * (leftIndent + rightIndent);
      checkboxRight = checkboxLeft + checkboxWidth - 1;

      // Ignore if not clicked on checkbox
      int x;
      x = event.getX();
      if ((x < checkboxLeft) || (x > checkboxRight))
        return;

      // Toggle checkbox
      toggleCheckBox(treePath);

    } // mouseClicked

  } // class TreeMouseManager

  private class TreeKeyManager extends KeyAdapter
  {
    @Override
    public void keyPressed(KeyEvent event)
    {
      // Determine selected element
      TreePath treePath;
      treePath = getSelectionPath();
      if (treePath == null)
        return;

      // Manage event for this element
      if (event.getKeyCode() == KeyEvent.VK_SPACE)
        toggleCheckBox(treePath);

    } // keyPressed

  } // class TreeKeyManager

  private class MyCellRenderer extends JPanel implements TreeCellRenderer
  {
    public MyCellRenderer()
    {
      // Create components
      checkbox = new JCheckBox();
      checkbox.setBorder(null);
      checkbox.setOpaque(false);
      label = new JLabel();
      label.setBorder(new EmptyBorder(new Insets(0, 2, 0, 2)));

      // Initialize panel
      GridBagConstraints constraints;
      setLayout(new GridBagLayout());
      setOpaque(false);
      constraints = new GridBagConstraints(0, 0, 1, 1, 0.0, 0.0, GridBagConstraints.WEST,
                                           GridBagConstraints.NONE, new Insets(0, 0, 0, 0), 0, 0);
      add(checkbox, constraints);
      constraints = new GridBagConstraints(1, 0, 1, 1, 1.0, 0.0, GridBagConstraints.WEST,
                                           GridBagConstraints.HORIZONTAL, new Insets(0, 0, 0, 0), 0, 0);
      add(label, constraints);

      // Save the width of the checkbox
      // We need it when the mouse is clicked on a node
      checkboxWidth = (int)checkbox.getPreferredSize().getWidth();

    } // constructor

    @Override
    public Component getTreeCellRendererComponent(JTree   tree,
                                                  Object  value,
                                                  boolean selected,
                                                  boolean expanded,
                                                  boolean leaf,
                                                  int     row,
                                                  boolean hasFocus)
    {
      // Make data accessible
      // Ignore if it's the root node
      DefaultMutableTreeNode node;
      NodeInfo            info;
      node = (DefaultMutableTreeNode)value;
      if (node.getUserObject() instanceof NodeInfo)
        info = (NodeInfo)node.getUserObject();
      else
        return (this);

      // Determine font
      Font font;
      font = label.getFont();
      if (info.checked)
        font = font.deriveFont(font.getStyle() | Font.BOLD);
      else
        font = font.deriveFont(font.getStyle() & ~Font.BOLD);

      // Configure components
      checkbox.setSelected(info.checked);
      label.setText(info.name);
      label.setOpaque(selected);
      label.setFont(font);
      if (selected)
      {
        label.setBackground(SystemColor.textHighlight);
        label.setForeground(SystemColor.textHighlightText);
      }
      else
      {
        label.setBackground(SystemColor.text);
        label.setForeground(SystemColor.textText);
      }

      // Make sure everything is painted correctly
      label.invalidate();
      checkbox.invalidate();
      invalidate();

      // Done
      return (this);

    } // getTreeCellRendererComponent

    private JCheckBox checkbox;
    private JLabel label;

  } // class MyCellRenderer

  private static DefaultMutableTreeNode getNodes()
  {
    // Create root
    DefaultMutableTreeNode root;
    root = new DefaultMutableTreeNode("root");

    // Create first level children
    DefaultMutableTreeNode first;
    DefaultMutableTreeNode second;
    DefaultMutableTreeNode third;
    NodeInfo               info;
    info = new NodeInfo();
    info.name = "This is the first node";
    info.checked = true;
    first = new DefaultMutableTreeNode(info);
    info = new NodeInfo();
    info.name = "And this is the second";
    info.checked = false;
    second = new DefaultMutableTreeNode(info);
    info = new NodeInfo();
    info.name = "Finally, the third";
    info.checked = false;
    third = new DefaultMutableTreeNode(info);
    root.add(first);
    root.add(second);
    root.add(third);

    // Add second level children
    info = new NodeInfo();
    info.name = "Second level node";
    info.checked = true;
    first.add(new DefaultMutableTreeNode(info));
    info = new NodeInfo();
    info.name = "This is another one";
    info.checked = false;
    first.add(new DefaultMutableTreeNode(info));
    info = new NodeInfo();
    info.name = "And this is the last one";
    info.checked = true;
    first.add(new DefaultMutableTreeNode(info));

    // Done
    return (root);

  } // getNodes

  private static class NodeInfo
  {
    public String name;
    public boolean checked;
  }

} // class TestTree

UPDATE更新
Within getTreeCellRendererComponent , I tried getting the preferred size.getTreeCellRendererComponent中,我尝试获取首选大小。
They seem OK.他们看起来不错。 When selecting the checkbox, the preferred size of both the label and the panel itself increase.选中复选框时,label 和面板本身的首选尺寸都会增加。 When de-selecting the checkbox, they decrease.取消选中复选框时,它们会减少。

Thanks to the answer to this question Change JTree row height resizing behavior when rendering , I managed to resolve the problem myself:感谢这个问题的答案Change JTree row height resizing behavior when rendering ,我自己设法解决了这个问题:

  private void toggleCheckBox(TreePath treePath)
  {
    // Determine node being toggled
    Object[]               path;
    DefaultMutableTreeNode node;
    NodeInfo               info;
    path = treePath.getPath();
    node = (DefaultMutableTreeNode)path[path.length - 1];
    info = (NodeInfo)node.getUserObject();

    // Toggle selection
    info.checked = !info.checked;

    // Make sure tree recalculates width of the nodes
    BasicTreeUI ui = (BasicTreeUI)getUI();
    try
    {
      Method method = BasicTreeUI.class.getDeclaredMethod("configureLayoutCache");
      method.setAccessible(true);
      method.invoke(ui);
    }
    catch (Exception e1)
    {
     e1.printStackTrace();
    }

  } // toggleCheckBox

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM