Java中JTable头上方的合并头

Java中JTable头上方的合并头,java,swing,jtable,jtableheader,Java,Swing,Jtable,Jtableheader,我想把一些额外的合并单元格放在我的表格标题的顶部,以便于分组 我发现了一些类似的东西,在这个论坛上得到了回答,但我需要的东西似乎在这里没有得到回答(.NET)。 因此,如果我不清楚,我会附上一张我想出现的图片 我有这些列,我需要让这些超级列出现在我画的图片中 有任何帮助吗?请尝试给出的分组表标题示例,或者以下是完美的工作代码: import java.awt.*; import javax.swing.*; import javax.swing.table.*; import javax.sw

我想把一些额外的合并单元格放在我的表格标题的顶部,以便于分组

我发现了一些类似的东西,在这个论坛上得到了回答,但我需要的东西似乎在这里没有得到回答(.NET)。 因此,如果我不清楚,我会附上一张我想出现的图片

我有这些列,我需要让这些超级列出现在我画的图片中


有任何帮助吗?

请尝试给出的分组表标题示例,或者以下是完美的工作代码:

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

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;

import java.util.Enumeration;

import java.io.ObjectOutputStream;
import java.io.ObjectInputStream;
import java.io.IOException;


/**
    Example of a table header that allows cells to span multiple columns.

    Copyright (C) 2001 Christian Kaufhold <ch-ka...@gmx.de>

    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Library General Public
    License as published by the Free Software Foundation; either
    version 2 of the License, or (at your option) any later version.

    This library is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    Library General Public License for more details.

    You should have received a copy of the GNU Library General Public
    License along with this library; if not, write to the Free
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/

/** Just a default renderer. To work with TableHeader, the corresponding
    TableHeader must be read from the JTable client property (see code),
    since JTable.getTableHeader() cannot be used.
*/
class DefaultHeaderRenderer
    extends DefaultTableCellRenderer
{
    {
        setBorder(UIManager.getBorder("TableHeader.cellBorder"));
    }

    public void updateUI()
    {
        super.updateUI();

        setHorizontalAlignment(CENTER);

        setBorder(UIManager.getBorder("TableHeader.cellBorder"));
    }


    public Component getTableCellRendererComponent
        (JTable table, Object value, boolean selected, boolean focused,
        int row, int column)
    {
        TableHeader header;

        if (table != null && (header = (TableHeader)table.getClientProperty(TableHeader.KEY)) != null)
        {
            setForeground(header.getForeground());
            setBackground(header.getBackground());

            setFont(header.getFont());

            setComponentOrientation(header.getComponentOrientation());

            setEnabled(header.isEnabled());
        }
        else
        {
            setForeground(UIManager.getColor("TableHeader.foreground"));
            setBackground(UIManager.getColor("TableHeader.background"));

            setFont(UIManager.getFont("TableHeader.font"));

            setComponentOrientation(ComponentOrientation.UNKNOWN);

            setEnabled(true);
        }

        setText(value != null ? value.toString() : "");

        return this;
    }
}

/** An alternative TableHeader that allows header cells to span multiple
    columns. This is just a bare-bones prototype, things to do are:
    - Optimize paint code to paint only concerned columns
    - Area conversion (columnAtPoint, headerRect or similar)
    - getToolTipText(MouseEvent)
    - resizing of columns (which of the columns should be resized?)
    - moving of columns (which cannot be done in real time since JTable
      doesn't know that it has to paint multiple dragged columns.)
    - how to serialize if the renderer isn't serializable?
    - Accessibility

    Note: JTable aggressively installs its JTableHeader in the column header
    view of JScrollPane, even if it is set to 'null'. To avoid confusion,
    override its configureEnclosingScrollPane() method to do nothing, for
    example.

    Typically, a TableHeader is set up like this:

    TableColumnModel columns = ...;

    // columns contains some XTableColumns that have spans set.

    JTable table = new FixedJTable(data, columns); // see above which fix

    table.setTableHeader(null);

    TableHeader header = new TableHeader(table);

    JScrollPane pane = new JScrollPane(table);

    pane.setColumnHeaderView(header);
*/

public class TableHeader
    extends JComponent
{
    /** TableColumn that also has a headerSpan property. */
    public static class XTableColumn
        extends TableColumn
    {
        private int headerSpan;


        public XTableColumn()
        {
            headerSpan = 1;
        }

        /** number of columns that the header cell spans. */
        public final int headerSpan()
        {
            return headerSpan;
        }

        /** set 'headerSpan'. requires newHeaderSpan > 0. During the
            manipulation of TableColumnModels, there may be times where
            a column spans more columns than there are at the right of it.
            This state is only allowed during such modifications. Once they
            are finished, all spans must not be too large. 
            XTableColumns that are hidden by the spans of their predecessors
            are ignored (in the TableHeader, of course not by the JTable).

            Due to the broken notification way of TableColumnModel/Table-
            Column there is no way to notify the header that it must 
            repaint().
            If firePropertyChange were not private, we could send the
            following fake event
            firePropertyChange("width", getWidth(), getWidth());
            which would handle that (only width and preferred width
            changes can reach the header).
            The moral is: don't change spans later, or if, repaint the
            header manually.
        */
        public void setHeaderSpan(int newHeaderSpan)
        {
            headerSpan = newHeaderSpan;   
        }
    }


    private static TableCellRenderer staticDefaultRenderer
        = new DefaultHeaderRenderer();

    /** Under this key, the table header is stored in the JTable, so that
        the renderer can access it. See demo renderer above.
    */
    public static Object KEY = TableHeader.class;

    private JTable table;

    private transient TableColumnModel columns;

    private TableCellRenderer defaultRenderer
        = staticDefaultRenderer;


    private transient Listener listener;


   public TableHeader(JTable table)
    {
        this.table = table;

        table.putClientProperty(KEY, this);

        this.columns = table.getColumnModel();

        this.listener = createListener();

        table.addPropertyChangeListener(listener);

        columns.addColumnModelListener(listener);

        add(new CellRendererPane());

        updateUI();
    }


    public void updateUI()
    {
        LookAndFeel.installColorsAndFont
            (this, "TableHeader.background", "TableHeader.foreground",
            "TableHeader.font");

        LookAndFeel.installBorder(this, "TableHeader.border");

        if (defaultRenderer instanceof JComponent)
            ((JComponent)defaultRenderer).updateUI();

        revalidate(); repaint();
    }


    public final JTable table()
    {
        return table;
    }

    public void setTable(JTable t)
    {
        JTable oldTable = table;
        TableColumnModel oldColumns = columns;

        table.putClientProperty(KEY, null);

        table.removePropertyChangeListener(listener);

        columns.removeColumnModelListener(listener);

        table = t;

        table.putClientProperty(KEY, this);

        columns = t.getColumnModel();

        table.addPropertyChangeListener(listener);

        columns.addColumnModelListener(listener);

        revalidate(); repaint();

        firePropertyChange("table", oldTable, table);
        firePropertyChange("columns", oldColumns, columns);
    }

    public final TableColumnModel columns()
    {
        return columns;
    }

    /** For serialization, the TableCellRenderer is needed to be serializable.
     */
    public void setDefaultRenderer(TableCellRenderer r)
    {
        TableCellRenderer oldRenderer = defaultRenderer;

        defaultRenderer = r;

        revalidate(); repaint();

        firePropertyChange("defaultRenderer", oldRenderer, defaultRenderer);
    }

    public final TableCellRenderer defaultRenderer()
    {
        return defaultRenderer;
    }


    private TableCellRenderer renderer(TableColumn c)
    {
        TableCellRenderer result = c.getHeaderRenderer();

        if (result != null)
            return result;

        return defaultRenderer;
    }


    private Component component(TableCellRenderer r, TableColumn c, int column)
    {
        return r.getTableCellRendererComponent
            (table, c.getHeaderValue(), false, false, -1, column);
    }


    private Dimension size(long innerWidth)
    {
        Insets i = getInsets();

        return new Dimension((int)Math.min(innerWidth + i.left + i.bottom, Integer.MAX_VALUE), innerHeight() + i.top + i.bottom);
    }


    /** Alas, this cannot be cached. */
    private int innerHeight()
    {
        int result = 0;

        int count = columns.getColumnCount();

        for (int j = 0; j < count; )
        {
            TableColumn c = columns.getColumn(j);

            int span;

            if (c instanceof XTableColumn)
                span = ((XTableColumn)c).headerSpan();
            else
                span = 1;

            Component d = component(renderer(c), c, j);

            result = Math.max(result, d.getPreferredSize().height);

            j += span;
        }

        return result;
    }



    public Dimension getMinimumSize()
    {
        if (isMinimumSizeSet())
            return super.getMinimumSize();

        return size(minWidth(columns));
    }

    public Dimension getPreferredSize()
    {
        if (isPreferredSizeSet())
            return super.getPreferredSize();

        return size(preferredWidth(columns));
    }

    public Dimension getMaximumSize()
    {
        if (isMaximumSizeSet())
            return super.getMaximumSize();

        return size(maxWidth(columns));
    }


    public void paintComponent(Graphics g)
    {
        Insets i = getInsets();

        Rectangle clip = g.getClipBounds();

        CellRendererPane pane = (CellRendererPane)getComponent(0);

        Rectangle r = new Rectangle();

        r.x = i.left;
        r.y = i.top;
        r.height = getHeight() - i.top - i.bottom;

        if (r.height <= 0)
            return;


        int count = columns.getColumnCount();

        for (int j = 0; j < count; )
        {
            TableColumn c = columns.getColumn(j);

            r.width = c.getWidth();

            int span;

            if (c instanceof XTableColumn)
            {
                span = ((XTableColumn)c).headerSpan();

                if (j + span > count)
                {
                    System.err.println("column: "+j+" span: "+span+" > "+count);
                    System.err.println("This state of TableColumnModel is forbidden!");
                    span = count - j;
                }

                for (int k = 1; k < span; k++)
                    r.width += columns.getColumn(j + k).getWidth();
            }
            else
            {
                span = 1;
            }

            Component d = component(renderer(c), c, j);

            pane.paintComponent(g, d, this, 
                r.x, r.y, r.width, r.height, true);

            r.x += r.width;

            j += span;
        }

        pane.removeAll();
    }


    private void readObject(ObjectInputStream in)
        throws IOException, ClassNotFoundException
    {
        in.defaultReadObject();

        listener = createListener();

        table.addPropertyChangeListener(listener);

        columns = table.getColumnModel();

        columns.addColumnModelListener(listener);
    }


    protected Object clone()
        throws CloneNotSupportedException
    {
        throw new CloneNotSupportedException();
    }


    private Listener createListener()
    {
        return new Listener();
    }


    private class Listener
        implements TableColumnModelListener, PropertyChangeListener
    {
        public void propertyChange(PropertyChangeEvent e)
        {
            if (e.getPropertyName().equals("columnModel"))
            {
                TableColumnModel oldColumns = columns;

                columns.removeColumnModelListener(this);

                columns = table.getColumnModel();

                columns.addColumnModelListener(this);

                revalidate(); repaint();

                firePropertyChange("columns", oldColumns, columns);
            }
        }


        public void columnAdded(TableColumnModelEvent e)
        {
            revalidate(); repaint();
        }

        public void columnRemoved(TableColumnModelEvent e)
        {
            revalidate(); repaint();
        }

        public void columnSelectionChanged(ListSelectionEvent e)
        {
        }

        public void columnMoved(TableColumnModelEvent e)
        {
            repaint();
        }

        public void columnMarginChanged(ChangeEvent e)
        {
            revalidate(); repaint();
        }
    }  

    /* Utility methods. Copied here from TableColumnModels. */

    public static long minWidth(TableColumnModel columns)
    {
        long result = 0;

        for (Enumeration e = columns.getColumns(); e.hasMoreElements();)
            result += ((TableColumn)e.nextElement()).getMinWidth();

        return result;
    }

    public static long preferredWidth(TableColumnModel columns)
    {
        long result = 0;

        for (Enumeration e = columns.getColumns(); e.hasMoreElements();)
            result += ((TableColumn)e.nextElement()).getPreferredWidth();

        return result;
    }

    public static long maxWidth(TableColumnModel columns)
    {
        long result = 0;

        for (Enumeration e = columns.getColumns(); e.hasMoreElements();)
            result += ((TableColumn)e.nextElement()).getMaxWidth();

        return result;
    }
}


class TableHeaderExample
{
    public static void main(String[] args)
    {
        DefaultTableModel data = new DefaultTableModel(10, 0);

        data.addColumn("ABC");
        data.addColumn("DEF");
        data.addColumn("GHI");
        data.addColumn("JKL");
        data.addColumn("MNO");
        data.addColumn("PQR");

        TableColumnModel columns = new DefaultTableColumnModel();

        TableHeader.XTableColumn abc = new TableHeader.XTableColumn();
        abc.setHeaderValue("ABC");
        abc.setHeaderSpan(2);

        TableColumn ghi = new TableColumn(2);
        ghi.setHeaderValue("GHI");

        TableHeader.XTableColumn jkl = new TableHeader.XTableColumn();
        jkl.setHeaderValue("JKL");
        jkl.setHeaderSpan(3);
        jkl.setModelIndex(3);

        columns.addColumn(abc);
        columns.addColumn(new TableColumn(1));
        columns.addColumn(ghi);
        columns.addColumn(jkl);
        columns.addColumn(new TableColumn(4));
        columns.addColumn(new TableColumn(5));

        JTable table = new JTable(data, columns)
        {
            protected void configureEnclosingScrollPane()
            {
            }
        };

        table.setTableHeader(null);

        TableHeader header = new TableHeader(table);

        header.setForeground(Color.blue);
        header.setFont(header.getFont().deriveFont(18.0f));

        JScrollPane pane = new JScrollPane(table);

        pane.setColumnHeaderView(header);

        JFrame f = new JFrame();

        f.setContentPane(pane);

        f.pack();

        f.setVisible(true);
    }
}
import java.awt.*;
导入javax.swing.*;
导入javax.swing.table.*;
导入javax.swing.event.*;
导入java.beans.PropertyChangeEvent;
导入java.beans.PropertyChangeListener;
导入java.util.Enumeration;
导入java.io.ObjectOutputStream;
导入java.io.ObjectInputStream;
导入java.io.IOException;
/**
允许单元格跨越多列的表格标题示例。
版权所有(C)2001克里斯蒂安·考夫霍尔德
这个图书馆是免费软件;您可以重新分发它和/或
根据GNU公共图书馆的条款对其进行修改
自由软件基金会发布的许可证;任何一个
许可证的版本2,或(由您选择)任何更高版本。
这个图书馆的发行是希望它会有用,
但无任何保证;甚至没有任何关于
适销性或适合某一特定目的。见GNU
有关更多详细信息,请参阅图书馆通用公共许可证。
您应该已经收到一份GNU公共图书馆的副本
与此库一起使用的许可证;如果没有,写信给免费的
基金会,59寺庙广场,套房330,波士顿,MA 02111-1307美国
*/
/**只是一个默认的渲染器。要使用TableHeader,请使用相应的
TableHeader必须从JTable客户端属性中读取(请参见代码),
因为无法使用JTable.getTableHeader()。
*/
类DefaultHeaderRenderer
扩展DefaultTableCellRenderer
{
{
setboorder(UIManager.getBorder(“TableHeader.cellBorder”);
}
公共void updateUI()
{
super.updateUI();
设置水平对齐(中心);
setboorder(UIManager.getBorder(“TableHeader.cellBorder”);
}
公共组件GetTableCellRenderComponent
(JTable表格、对象值、布尔选择、布尔聚焦、,
整数行,整数列)
{
表格标题;
if(table!=null&(header=(TableHeader)table.getClientProperty(TableHeader.KEY))!=null)
{
set前台(header.get前台());
setBackground(header.getBackground());
setFont(header.getFont());
setComponentOrientation(header.getComponentOrientation());
setEnabled(header.isEnabled());
}
其他的
{
setForeground(UIManager.getColor(“TableHeader.foreground”);
setBackground(UIManager.getColor(“TableHeader.background”);
setFont(UIManager.getFont(“TableHeader.font”);
setComponentOrientation(ComponentOrientation.UNKNOWN);
setEnabled(真);
}
setText(value!=null?value.toString():“”);
归还这个;
}
}
/**另一种TableHeader,允许标头单元格跨越多个
柱。这只是一个简单的原型,需要做的事情有:
-优化绘制代码以仅绘制相关列
-面积转换(columnAtPoint、headerRect或类似)
-GetToolTiptText(MouseeEvent)
-调整列的大小(应调整哪些列的大小?)
-列的移动(由于JTable,无法实时完成)
不知道它必须绘制多个拖动的列。)
-如果渲染器不可序列化,如何序列化?
-可达性
注意:JTable在列标题中积极安装其JTableHeader
JScrollPane视图,即使设置为“null”。为避免混淆,,
例如,将其configureEnclosuringScrollPane()方法重写为不执行任何操作
例子。
通常,TableHeader的设置如下所示:
TableColumnModelColumns=。。。;
//列包含一些设置了跨距的XTableColumns。
JTable table=新的固定JTable(数据、列);//请参见上面的哪个补丁
table.setTableHeader(空);
TableHeader=新的TableHeader(表);
JScrollPane=新的JScrollPane(表);
pane.setColumnHeaderView(标题);
*/
公共类表头
扩展JComponent
{
/**也具有headerSpan属性的TableColumn*/
公共静态类XTableColumn
扩展表列
{
私人国际校长班;
公共XTableColumn()
{
headerSpan=1;
}
/**标题单元格跨越的列数*/
公共最终int headerSpan()
{
返回头盘;
}
/**设置“headerSpan”。需要newHeaderSpan>0。在
操作TableColumnModels时,可能会出现以下情况:
一根柱子跨越的柱子比它右边的要多。
此状态仅在此类修改期间允许。一旦
完工后,所有跨度不得太大。
XTableColumns被其前辈的跨距隐藏
被忽略(在TableHeader中,当然不是JTable)。
由于TableColumnModel/Table的通知方式中断-
列没有方法通知标头它必须
重新绘制()。
如果firePropertyChange不是私人的,我们可以发送
假事件之后
firePropertyChange(“宽度”,getWidth(),getWidth());
哪一个可以处理(仅宽度和首选宽度)
更改可以到达页眉)。
寓意是:不要在以后更改跨度,或者如果需要,请重新绘制
头手动。
*/
公共空间