Tuesday, January 26, 2010

Таблица JTable с объедененными ячейками

Задача: нарисовать объединенные ячейки в таблице.

Для начала создадим объект, описывающий параметры объединенной ячейки:

public class CellSpan {



    private int row = 0;
    private int column = 0;
    private int rowSpan = 0;
    private int columnSpan = 0;


    public CellSpan(int row, int column, int rowSpan, int columnSpan) {
        this.column = column;
        this.columnSpan = columnSpan;
        this.row = row;
        this.rowSpan = rowSpan;
    }

    public int getColumn() {
        return column;
    }

    public void setColumn(int column) {
        this.column = column;
    }

    public int getColumnSpan() {
        return columnSpan;
    }

    public void setColumnSpan(int columnSpan) {
        this.columnSpan = columnSpan;
    }

    public int getRow() {
        return row;
    }

    public void setRow(int row) {
        this.row = row;
    }

    public int getRowSpan() {
        return rowSpan;
    }

    public void setRowSpan(int rowSpan) {
        this.rowSpan = rowSpan;
    }
}



Теперь создадим интерфейс для модели, поддерживающей объединение ячеек:

public interface SpanModel {

    public CellSpan (int rowIndex, int columnIndex);
    public boolean isCellSpanOn();

}
Метод getCellSpanAt возвращает информацию об объединении ячеек для всех ячеек, участвующих в объединении, или null, если ячейка не объединена.
Например, если объединены ячейки (1,1), (1,2), (2,1), (2,2), то для них вернется new CellSpan(1, 1, 2, 2).
Создавать новый объект CellSpan не обязательно, достаточно создать 1 объект и в методе getCellSpanAt выставлять ему свойства.

Теперь создадим модель таблицы:

public class SpanTableModel extends DefaultTableModel implements  SpanModel {

    @Override
    public int getColumnCount() {
        return 10;
    }

    @Override
    public int getRowCount() {
        return 10;
    }

    @Override
    public Object getValueAt(int row, int column) {
        return row*column;
    }

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

    public CellSpan getCellSpanAt(int rowIndex, int columnIndex) {
        if (rowIndex==0) {
            return new CellSpan(0, 0, 1, getColumnCount());
        }
        else {
            if (rowIndex>=3 && rowIndex<=5 && columnIndex>=5 && columnIndex<=6) {
                return new CellSpan(3, 5, 3, 2);
            }
        }
        return null;
    }

    public boolean isCellSpanOn() {
        return true;
    }
}




Далее займемся написанием UI делегата для таблицы. Код основан на этом примере.

public class BasicCellSpanTableUI extends BasicTableUI {

    public static ComponentUI createUI(JComponent jcomponent) {
        return new BasicCellSpanTableUI();
    }


    @Override
    public void paint(Graphics g, JComponent c) {
        Rectangle oldClipBounds = g.getClipBounds();
        Rectangle clipBounds = new Rectangle(oldClipBounds);
        int tableWidth = table.getColumnModel().getTotalColumnWidth();
        clipBounds.width = Math.min(clipBounds.width, tableWidth);
        g.setClip(clipBounds);

        int firstIndex = table.rowAtPoint(new Point(0, clipBounds.y));
        int lastIndex = table.getRowCount() - 1;

        Rectangle rowRect = new Rectangle(0, 0, tableWidth, table.getRowHeight() + table.getRowMargin());
        rowRect.y = firstIndex * rowRect.height;

        for (int index = firstIndex; index <= lastIndex; index++) {
            if (rowRect.intersects(clipBounds)) {
                paintRow(g, index);
            }
            rowRect.y += rowRect.height;
        }
        g.setClip(oldClipBounds);
    }

    private void paintRow(Graphics g, int row) {
        Rectangle rect = g.getClipBounds();

        SpanModel tableModel = (SpanModel) table.getModel();
        int numColumns = table.getColumnCount();

        for (int column = 0; column < numColumns; column++) {

            CellSpan cellSpan = tableModel.getCellSpanAt(row, column);

            Rectangle cellRect = table.getCellRect(row, column, true);
            int cellRow, cellColumn;
            if (cellSpan==null || (row==cellSpan.getRow() && column==cellSpan.getColumn())) {
                cellRow = row;
                cellColumn = column;
            } else {
                cellRow = cellSpan.getRow();
                cellColumn = cellSpan.getColumn();
            }
            if (cellRect.intersects(rect)) {
                paintCell(g, cellRect, cellRow, cellColumn);
            }
        }

    }

    private void paintCell(Graphics g, Rectangle cellRect, int row, int column) {
        int spacingHeight = table.getRowMargin();
        int spacingWidth = table.getColumnModel().getColumnMargin();

        Color c = g.getColor();
        g.setColor(table.getGridColor());
        g.drawRect(cellRect.x, cellRect.y, cellRect.width, cellRect.height);
        g.setColor(c);

        cellRect.setBounds(cellRect.x + spacingWidth / 2, cellRect.y + spacingHeight / 2,
                cellRect.width - spacingWidth, cellRect.height - spacingHeight);

        if (table.isEditing() && table.getEditingRow() == row &&
                table.getEditingColumn() == column) {
            Component component = table.getEditorComponent();
            component.setBounds(cellRect);
            component.validate();
        } else {
            TableCellRenderer renderer = table.getCellRenderer(row, column);
            Component component = table.prepareRenderer(renderer, row, column);

            if (component.getParent() == null) {
                rendererPane.add(component);
            }
            rendererPane.paintComponent(g, component, table, cellRect.x, cellRect.y,
                    cellRect.width, cellRect.height, true);
        }
    }


}

Теперь сама таблица:

public class CellSpanTable extends JTable {

    private static final Rectangle ZERO_SIZE_RECTANGLE = new Rectangle(0, 0, 0, 0);


    public Rectangle getCellRect(int row, int column, boolean includeSpacing) {

        if ((row < 0) || (column < 0) || (getRowCount() <= row) || (getColumnCount() <= column)) {
            return super.getCellRect(row, column, includeSpacing);
        }

        if (!(getModel() instanceof SpanModel)) {
            return super.getCellRect(row, column, includeSpacing);
        }

        final SpanModel model = (SpanModel) getModel();
        if (!model.isCellSpanOn()) {
            return super.getCellRect(row, column, includeSpacing);
        }

        CellSpan cellSpan = model.getCellSpanAt(row, column);

        //если запрашиваемая ячейка перекрыта, то возвращаем 0-вой прямоугольник
        if (cellSpan!=null && ((row>=cellSpan.getRow() && row
                && (column>=cellSpan.getColumn() && column
                && !(row==cellSpan.getRow() && column==cellSpan.getColumn())) {
            return ZERO_SIZE_RECTANGLE;
        }


        int rowspan = 1, columnspan = 1;

        if (cellSpan!=null) {
            columnspan = cellSpan.getColumnSpan();
            rowspan = cellSpan.getRowSpan();
        }


        int index = 0;
        Rectangle cellFrame = new Rectangle();
        //считаем высоту ячейки (все ячейки одинаковы по высоте)
        int aCellHeight = rowHeight + rowMargin;
        cellFrame.y = row * aCellHeight;
        cellFrame.height = rowspan * aCellHeight;

        Enumeration enumeration = getColumnModel().getColumns();
        //считаем позицию левой стороны ячейки
        while (enumeration.hasMoreElements()) {
            TableColumn aColumn = (TableColumn) enumeration.nextElement();
            cellFrame.width = aColumn.getWidth();
            if (index == column) break;
            cellFrame.x += cellFrame.width;
            index++;
        }
        //считаем ширину ячейки
        for (int i = 0; i < columnspan - 1; i++) {
            TableColumn aColumn = (TableColumn) enumeration.nextElement();
            cellFrame.width += aColumn.getWidth();
        }


        if (!includeSpacing) {
            Dimension spacing = getIntercellSpacing();
            cellFrame.setBounds(cellFrame.x + spacing.width / 2,
                    cellFrame.y + spacing.height / 2,
                    cellFrame.width - spacing.width,
                    cellFrame.height - spacing.height);
        }
        return cellFrame;
    }

    private int[] rowColumnAtPoint(Point point) {
        int[] retValue = {-1, -1};
        int row = point.y / (rowHeight + rowMargin);
        if ((row < 0) || (getRowCount() <= row)) return retValue;
        int column = getColumnModel().getColumnIndexAtX(point.x);

        if (!(getModel() instanceof SpanModel)) {
            return new int[] {column, row};
        }

        final SpanModel model = (SpanModel) getModel();
        if (!model.isCellSpanOn()) {
            return new int[] {column, row};
        }

        CellSpan cellSpan = model.getCellSpanAt(row, column);
        //если запрашиваемая ячейка перекрыта, то возвращаем координаты перекрывающей ячейки
        if (cellSpan!=null) {
            retValue[0] = cellSpan.getColumn();
            retValue[1] = cellSpan.getRow();
            return retValue;
        }

        retValue[0] = column;
        retValue[1] = row;
        return retValue;
    }


    public int rowAtPoint(Point point) {
        return rowColumnAtPoint(point)[1];
    }

    public int columnAtPoint(Point point) {
        return rowColumnAtPoint(point)[0];
    }


    public void columnSelectionChanged(ListSelectionEvent e) {
        repaint();
    }

    public void valueChanged(ListSelectionEvent e) {
        int firstIndex = e.getFirstIndex();
        int lastIndex = e.getLastIndex();
        if (firstIndex == -1 && lastIndex == -1) { // Selection cleared.
            repaint();
        }
        Rectangle dirtyRegion = getCellRect(firstIndex, 0, false);
        int numCoumns = getColumnCount();
        int index = firstIndex;
        for (int i = 0; i < numCoumns; i++) {
            dirtyRegion.add(getCellRect(index, i, false));
        }
        index = lastIndex;
        for (int i = 0; i < numCoumns; i++) {
            dirtyRegion.add(getCellRect(index, i, false));
        }
        repaint(dirtyRegion.x, dirtyRegion.y, dirtyRegion.width, dirtyRegion.height);
    }

    @Override
    public void updateUI() {
        if (UIManager.get("BasicCellSpanTableUI")==null) {
            UIManager.put("BasicCellSpanTableUI", "grid.BasicCellSpanTableUI");
            UIManager.put("grid.BasicCellSpanTableUI", BasicCellSpanTableUI.class);
        }
        final ComponentUI newUI = UIManager.getUI(this);
        setUI(newUI);
    }

    public String getUIClassID() {
        return "BasicCellSpanTableUI";
    }


}


 

Таблица и UI-делегат связаны неявно
Чтобы воспользоваться этим примером, нужно реализовать в вашей модели таблицы интерфейс SpanModel, и вместо JTable использовать CellSpanTable.


Итоговый внешний вид таблицы:





No comments :

Post a Comment