JXTreeTable,行条纹和适当的重新绘制

时间:2014-05-23 14:36:24

标签: java swing look-and-feel swingx jxtreetable

我使用的是一种默认情况下会对表进行行条带化的外观。当我输入JXTreeTable时,我注意到由于某种原因它没有自动排条。

所以我使用荧光笔进行了解决方法,但看起来我有一个重新绘制的故障:

screenshot showing the painting glitch

似乎JXTreeTable只是专门重新绘制文本的边界而不是整个单元格。我一直试图在调试器中找到这个以找出原因,但每次在程序之间切换时,整个窗口都会重新绘制,所以几乎不可能捕获到这种东西。

JTableJTree都表现得很清醒。这种外观和感觉是描绘JTree(如Quaqua和Synth)的整行,所以也许这也与它有关。或许JXTreeTable有某种假设,即外观和感觉不会绘制树的行?如果是这样,有办法解决这个问题吗?这不仅仅是具有问题的外观和感觉。

代码:

import org.jdesktop.swingx.JXTreeTable;
import org.jdesktop.swingx.decorator.AbstractHighlighter;
import org.jdesktop.swingx.decorator.ComponentAdapter;
import org.jdesktop.swingx.decorator.Highlighter;
import org.jdesktop.swingx.treetable.DefaultMutableTreeTableNode;
import org.jdesktop.swingx.treetable.DefaultTreeTableModel;
import org.jdesktop.swingx.treetable.TreeTableModel;
import org.trypticon.haqua.HaquaLookAndFeel;

import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.WindowConstants;
import java.awt.BorderLayout;
import java.awt.Component;
import java.util.Arrays;

public class TreeTableDemo2 implements Runnable {
    public static void main(String[] args) {
        SwingUtilities.invokeLater(new TreeTableDemo2());
    }

    @Override
    public void run() {
        try {
            UIManager.setLookAndFeel(new HaquaLookAndFeel());
        } catch (Exception e) {
            throw new RuntimeException(e);
        }

        JFrame frame = new JFrame("Tree Table Demo");
        frame.setLayout(new BorderLayout());
        frame.add(createPanel(), BorderLayout.CENTER);
        frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
        frame.pack();
        frame.setVisible(true);
    }

    public JPanel createPanel() {
        JPanel panel = new JPanel(new BorderLayout());

        TreeTableModel treeTableModel = new DummyTreeTableModel();
        JXTreeTable treeTable = new FixedTreeTable(treeTableModel);
        JScrollPane treeTableScroll = new JScrollPane(treeTable);

        panel.add(treeTableScroll, BorderLayout.CENTER);
        return panel;
    }

    private static class FixedTreeTable extends JXTreeTable {
        private static final Highlighter oddRowHighlighter = new AbstractHighlighter() {
            @Override
            protected Component doHighlight(Component component, ComponentAdapter componentAdapter) {
                if (componentAdapter.row % 2 != 0 &&
                        !componentAdapter.isSelected()) {
                    component.setBackground(UIManager.getColor("Table.alternateRowColor"));
                }
                return component;
            }
        };

        public FixedTreeTable(TreeTableModel treeModel) {
            super(treeModel);

            // This hack makes it paint correctly after releasing the mouse, which is not quite good enough.
//            getSelectionModel().addListSelectionListener(new ListSelectionListener() {
//                @Override
//                public void valueChanged(ListSelectionEvent e) {
//                    Rectangle repaintRange = getCellRect(e.getFirstIndex(), 0, true);
//                    repaintRange.add(getCellRect(e.getLastIndex(), 0, true));
//                    repaint(repaintRange);
//                }
//            });
        }

        @Override
        public void updateUI() {
            removeHighlighter(oddRowHighlighter);

            super.updateUI();

            // JTable does this striping automatically but JXTable's default renderer
            // seems to ignore it, so JXTreeTable inherits this broken behaviour.
            if (UIManager.get("Table.alternateRowColor") != null) {
                addHighlighter(oddRowHighlighter);
            }
        }
    }

    private static class DummyTreeTableNode extends DefaultMutableTreeTableNode {
        private final Object[] values;

        private DummyTreeTableNode(String name) {
            super(name);
            values = new Object[5];
            values[0] = name;
        }

        private DummyTreeTableNode(Object... values) {
            super(values[0]);
            this.values = values;
        }

        @Override
        public Object getValueAt(int column) {
            return values[column];
        }
    }

    private static class DummyTreeTableModel extends DefaultTreeTableModel {
        private static DefaultMutableTreeTableNode rootNode = new DefaultMutableTreeTableNode();
        static {
            DefaultMutableTreeTableNode blue = new DefaultMutableTreeTableNode("Blue");
            blue.add(new DummyTreeTableNode("Orionis C",          33000,  30000.0,    18.0,   5.90));
            rootNode.add(blue);

            DefaultMutableTreeTableNode bluish = new DefaultMutableTreeTableNode("Bluish");
            bluish.add(new DummyTreeTableNode("Becrux",             30000,  16000.0,    16.0,   5.70));
            bluish.add(new DummyTreeTableNode("Spica",              22000,  8300.0,     10.5,   5.10));
            bluish.add(new DummyTreeTableNode("Achernar",           15000,  750.0,      5.40,   3.70));
            bluish.add(new DummyTreeTableNode("Rigel",              12500,  130.0,      3.50,   2.70));
            rootNode.add(bluish);

            DefaultMutableTreeTableNode blueWhite = new DefaultMutableTreeTableNode("Blue-White");
            blueWhite.add(new DummyTreeTableNode("Sirius A",           9500,   63.0,       2.60,   2.30));
            blueWhite.add(new DummyTreeTableNode("Fomalhaut",          9000,   40.0,       2.20,   2.00));
            blueWhite.add(new DummyTreeTableNode("Altair",             8700,   24.0,       1.90,   1.80));
            rootNode.add(blueWhite);

            DefaultMutableTreeTableNode white = new DefaultMutableTreeTableNode("White");
            white.add(new DummyTreeTableNode("Polaris A",          7400,   9.0,        1.60,   1.50));
            white.add(new DummyTreeTableNode("Eta Scorpii",        7100,   6.3,        1.50,   1.30));
            white.add(new DummyTreeTableNode("Procyon A",          6400,   4.0,        1.35,   1.20));
            rootNode.add(white);

            DefaultMutableTreeTableNode yellowWhite = new DefaultMutableTreeTableNode("Yellow-White");
            yellowWhite.add(new DummyTreeTableNode("Alpha Centauri A",   5900,   1.45,       1.08,   1.05));
            yellowWhite.add(new DummyTreeTableNode("The Sun",            5800,   100.0,      1.00,   1.00));
            yellowWhite.add(new DummyTreeTableNode("Mu Cassiopeiae",     5600,   0.70,       0.95,   0.91));
            yellowWhite.add(new DummyTreeTableNode("Tau Ceti",           5300,   0.44,       0.85,   0.87));
            rootNode.add(yellowWhite);

            DefaultMutableTreeTableNode orange = new DefaultMutableTreeTableNode("Orange");
            orange.add(new DummyTreeTableNode("Pollux",             5100,   0.36,       0.83,   0.83));
            orange.add(new DummyTreeTableNode("Epsilon Eridani",    4830,   0.28,       0.78,   0.79));
            orange.add(new DummyTreeTableNode("Alpha Centauri B",   4370,   0.18,       0.68,   0.74));
            rootNode.add(orange);

            DefaultMutableTreeTableNode red = new DefaultMutableTreeTableNode("Red");
            red.add(new DummyTreeTableNode("Lalande 21185",      3400,   0.03,       0.33,   0.36));
            red.add(new DummyTreeTableNode("Ross 128",           3200,   0.0005,     0.20,   0.21));
            red.add(new DummyTreeTableNode("Wolf 359",           3000,   0.0002,     0.10,   0.12));
            rootNode.add(red);
        }

        private static final Object[] columnNames = {
                "Star", "Temperature (K)", "Luminosity", "Mass", "Radius"
        };

        public DummyTreeTableModel() {
            super(rootNode, Arrays.asList(columnNames));
        }

        @Override
        public Class<?> getColumnClass(int columnIndex) {
            if (columnIndex == 0) {
                return String.class;
            } else {
                return Double.class;
            }
        }

        @Override
        public boolean isCellEditable(Object node, int column) {
            return false;
        }
    }
}

进一步调查第1轮:

我终于设法通过在JXTreeTable中观察绘制方法来捕获调试器中的条件。我所看到的是它有一个名为ClippedTreeCellRenderer的东西,它有一些看起来与可疑行为相对应的字段:

iconRect = {java.awt.Rectangle@2946}"java.awt.Rectangle[x=20,y=92,width=16,height=16]"
textRect = {java.awt.Rectangle@2947}"java.awt.Rectangle[x=20,y=-17,width=62,height=15]"
itemRect = {java.awt.Rectangle@2948}"java.awt.Rectangle[x=20,y=36,width=103,height=18]"

我还没有确认它肯定是使用这个值来绘制矩形,但是textRect正好是它重绘的小窗口的大小。所以现在的问题是,JXTreeTable在哪里提取这些值以及为什么要使用它们?

我的直觉告诉我,JXTreeTable的渲染器以某种方式使用树单元格渲染器直接渲染单元格而不是仅仅告诉树本身进行绘制。绘制行背景和展开/折叠图标的逻辑是在树中,而不是在单元格中,所以如果它正在这样做,那么它就不会一直在绘制树。

进一步调查第2轮:

我认为我完全走错了路。看起来整个行都是被绘制,但是tree.isPathSelected(path)为新选择的蓝色行返回false,并为刚刚取消选择的行返回true

我可以通过DefaultTreeSelectionModel中的断点确认树选择仅在您放开鼠标后更新,这就是它最终返回正确渲染的原因。

我将不得不深入研究JXTreeTable,看看它是如何让它们保持同步的。

1 个答案:

答案 0 :(得分:0)

我发现了这个错误。它看起来像JXTreeTable.java中的一些善意代码,它试图减少更新:

    /**
     * Class responsible for calling updateSelectedPathsFromSelectedRows
     * when the selection of the list changse.
     */
    class ListSelectionHandler implements ListSelectionListener {
        @Override
        public void valueChanged(ListSelectionEvent e) {
            if (!e.getValueIsAdjusting()) {
                updateSelectedPathsFromSelectedRows();
            }
        }
    }

如果删除if检查,一切正常。我想我必须对SwingX做一些局部更改,因为该项目基本上已经死了。 :(