Перенос текста заголовка JTable для многострочного заголовка (настраиваемый TableCellRenderer)

Как я могу получить многострочный заголовок JTable, в котором столбец заголовка правильно увеличивается, чтобы уместить некоторый текст, а затем переносится на новую строку?

Примерно так, как показано ниже:

перенос заголовка столбца

В настоящее время поиск вышеуказанных требований возвращает множество решений, ни одно из которых не решает проблему:

http://www.javarichclient.com/multiline-column-header/

Создание многострочного заголовка для JTable

Перенос слов заголовка Java JTable

Все вышеперечисленные решения предполагают использование HTML-кода, например:

String[] columnNames = {
    "<html><center>Closing<br>Date</html>",
    "<html><center>Open<br>Price</html>",
    "<html>Third<br>column</html>"
};

Это решение не изящно по нескольким причинам, в основном потому, что в случае имен столбцов переменных мне нужно передать строку функции, которая удаляет пробелы и заменяет их символами <br>, однако, если текст столбца содержит очень короткий текст, который появляется в отдельной строке.

Мне нужно будет определить минимальную и максимальную длину столбца, а затем сделать возможным центрирование текста, указанное выше решение быстро становится чрезмерно сложным и неуправляемым.

http://www.java2s.com/Code/Java/Swing-Components/MultiLineHeaderTable.htm

http://www.java2s.com/Code/Java/Swing-Components/MultiLineHeaderExample.htm

multilineheader2

Вышеупомянутые решения требуют вручную создать массив заголовков со словами, уже правильно разделенными, как в:

  public static Object[][] tableHeaders = new Object[][] {
      new String[] { "Currency" },
      new String[] { "Yesterday's", "Rate" },
      new String[] { "Today's", "Rate" },
      new String[] { "Rate", "Change" } };

-or-

DefaultTableModel dm = new DefaultTableModel();
    dm.setDataVector(
        new Object[][] { { "a", "b", "c" }, { "A", "B", "C" } },
        new Object[] { "1st\nalpha", "2nd\nbeta", "3rd\ngamma" });

Все еще не элегантно, потому что переменный текст в именах столбцов был бы невозможен.

Как изменить высоту заголовка JTable?

Ручная установка высоты заголовка, как в приведенных выше решениях, - это только половина того, что я хочу сделать, потому что тогда текст по-прежнему будет неправильно переноситься, и определение высоты по-прежнему невозможно.

В настоящее время все, что мне удалось, - это создать настраиваемый TableCellRenderer, но пока нет решения:

import java.awt.Component;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableColumn;

import java.util.*;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.StringReader;
import javax.swing.*;
import javax.swing.table.*;

/**
 * @version 1.0 11/09/98
 */
public class MultiLineHeaderExample extends JFrame
{

    MultiLineHeaderExample()
    {
        super("Multi-Line Header Example");

        DefaultTableModel dm = new DefaultTableModel();
        dm.setDataVector(new Object[][]
        {
            {
                "a", "b", "c"
            },
            {
                "A", "B", "C"
            }
        },
        new Object[]
                {
                    "My First Column, Very Long But Space Separated", "short col", "VeryLongNoSpaceSoShouldSomeHowWrap"
        });

        JTable table = new JTable(dm);
        MultiLineHeaderRenderer renderer = new MultiLineHeaderRenderer();
        Enumeration enumK = table.getColumnModel().getColumns();
        while (enumK.hasMoreElements())
        {
            ((TableColumn) enumK.nextElement()).setHeaderRenderer(renderer);
        }
        JScrollPane scroll = new JScrollPane(table);
        getContentPane().add(scroll);
        setSize(400, 110);
        setVisible(true);
    }

    public static void main(String[] args)
    {
        MultiLineHeaderExample frame = new MultiLineHeaderExample();
        frame.addWindowListener(new WindowAdapter()
        {
            public void windowClosing(WindowEvent e)
            {
                System.exit(0);
            }
        });
    }
}

class MultiLineHeaderRenderer extends JList implements TableCellRenderer
{

    public MultiLineHeaderRenderer()
    {
        ListCellRenderer renderer = getCellRenderer();
        ((JLabel) renderer).setHorizontalAlignment(JLabel.CENTER);
        setCellRenderer(renderer);
    }

    @Override
    public Component getTableCellRendererComponent(JTable table, Object value,
            boolean isSelected, boolean hasFocus, int row, int column)
    {
        setFont(table.getFont());
        String str = (value == null) ? "" : value.toString();
        BufferedReader br = new BufferedReader(new StringReader(str));
        String line;
        Vector v = new Vector();
        try
        {
            while ((line = br.readLine()) != null)
            {
                v.addElement(line);
            }
        }
        catch (IOException ex)
        {
            ex.printStackTrace();
        }
        setListData(v);
        return this;
    }
}

person sarah.ferguson    schedule 14.12.2016    source источник


Ответы (3)


Здесь также используется JTextArea, а также изменяется высота заголовка при изменении размера таблицы. Ключ к правильному вычислению высоты заголовка таблицы - setSize(width, getPreferredSize().height);

class MultiLineTableHeaderRenderer extends JTextArea implements TableCellRenderer
{
  public MultiLineTableHeaderRenderer() {
    setEditable(false);
    setLineWrap(true);
    setOpaque(false);
    setFocusable(false);
    setWrapStyleWord(true);
    LookAndFeel.installBorder(this, "TableHeader.cellBorder");
  }

  @Override
  public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
    int width = table.getColumnModel().getColumn(column).getWidth();
    setText((String)value);
    setSize(width, getPreferredSize().height);
    return this;
  }
}
person Stefan    schedule 14.12.2016
comment
Отлично работает, единственная проблема в том, что он использует JTextArea для отображения текста в заголовке, поэтому стили заголовков столбцов будут потеряны, и, например, будет использоваться белый фон. - person sarah.ferguson; 14.12.2016
comment
Я отредактировал ответ, чтобы заголовок выглядел как заголовок по умолчанию для JTable. - person Stefan; 14.12.2016
comment
@ sarah.ferguson Попробуйте установить фон JTextArea на JTable.getBackground(). - person TT.; 14.12.2016
comment
Чтобы придать заголовку тот же цвет, что и JTable, я использовал setOpaque(false). См. Отредактированный код выше. - person Stefan; 14.12.2016

вам нужен Conponent, который может переносить свой контент по словам, например JTextArea. Я изменил рендерер ячеек с вашего SSCCE, так что он изначально работает, но имеет неприятное поведение при изменении размера.

 class MultiLineHeaderRenderer extends JTextArea implements TableCellRenderer {
    public MultiLineHeaderRenderer()
    {
        setAlignmentY(JLabel.CENTER);
        setLineWrap(true);
        setWrapStyleWord(true);
        setBorder(BorderFactory.createCompoundBorder(
                BorderFactory.createLineBorder(Color.BLACK),
                BorderFactory.createEmptyBorder(3,3,3,3)
                ));

    }

    @Override
    public Component getTableCellRendererComponent(JTable table,
            Object value,
            boolean isSelected,
            boolean hasFocus,
            int row,
            int column) {
        setFont(table.getFont());
        String str = (value == null) ? "" : value.toString();
        setText(str);
        int columnWidth= getColumnWidth();
        setRows(str.length()/columnWidth);
        return this;
    }
}
person Timothy Truckle    schedule 14.12.2016

Вот еще один подход. Это решение имеет следующие преимущества:

  1. Вам не нужно вручную разбивать имена столбцов.
  2. Столбцы динамически переносятся по словам при изменении размера столбцов и / или окна.
  3. Внешний вид заголовка будет автоматически соответствовать установленному стилю.
  4. В отличие от других решений, которые я видел, это работает, даже если первый столбец не переносится (как в примере ниже).

Однако у него есть следующий недостаток: он создает неиспользуемый объект JTableHeader для каждого столбца, поэтому он немного неэлегантен и, вероятно, не подходит, если у вас много столбцов.

Основная идея состоит в том, что вы заключаете имена столбцов в теги <html>, и, что особенно важно, каждый TableColumn получает свой собственный объект TableCellRenderer.

Я пришел к этому решению после глубокой отладки, связанной с внутренней компоновкой заголовка таблицы Swing. Если не вдаваться в подробности, проблема в том, что если для TableColumns не определен headerRenderer, то для каждой ячейки заголовка столбца используется одно и то же средство визуализации по умолчанию. Код макета, используемый для JTableHeader, только мешает запросить у средства визуализации заголовка первого столбца его предпочтительный размер (см. Функцию 4. выше), и, поскольку средство визуализации используется повторно, вызов его setText() запускает создание нового View для метки, что по причинам, которые я слишком устал, чтобы даже думать об объяснении, заставляет средство визуализации заголовка всегда сообщать о своей предпочтительной развернутой высоте.

Вот быстрое и грязное доказательство концепции:

package scratch;

import java.util.*;
import javax.swing.*;
import javax.swing.table.*;

@SuppressWarnings("serial")
public class WordWrappingTableHeaderDemo extends JFrame {

    class DemoTableModel extends AbstractTableModel {

        private ArrayList<String> wrappedColumnNames = new ArrayList<String>(); 
        private int numRows;

        DemoTableModel(List<String> columnNames, int numRows) {
            for (String name: columnNames)
                wrappedColumnNames.add("<html>" + name + "</html>");
            this.numRows = numRows;
        }

        public int getRowCount() {
            return numRows;
        }

        public int getColumnCount() {
            return wrappedColumnNames.size();
        }

        public Object getValueAt(int rowIndex, int columnIndex) {
            return Integer.valueOf(10000 + (rowIndex + 1)*(columnIndex + 1));
        }

        public String getColumnName(int column) {
            return wrappedColumnNames.get(column);
        }

        public Class<?> getColumnClass(int columnIndex) {
            return Integer.class;
        }
    }

    public WordWrappingTableHeaderDemo() {

        DefaultTableColumnModel tableColumnModel = new DefaultTableColumnModel() {
            public void addColumn(TableColumn column) {
                // This works, but is a bit kludgey as it creates an unused JTableHeader object for each column:
                column.setHeaderRenderer(new JTableHeader().getDefaultRenderer());
                super.addColumn(column);
            }
        };

        JTable table = new JTable();
        table.setFillsViewportHeight(true);;
        table.setColumnModel(tableColumnModel);
        table.setModel(
                new DemoTableModel(Arrays.asList("Name", "The Second Column Name is Very Long", "Column Three"), 20));
        getContentPane().add(new JScrollPane(table));
    }

    public static void createAndShowGUI() {
        WordWrappingTableHeaderDemo app = new WordWrappingTableHeaderDemo();
        app.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        app.setLocationByPlatform(true);
        app.pack();
        app.setVisible(true);
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(() -> {createAndShowGUI();});
    }
}
person David    schedule 21.02.2017