Java Swing写的支持合并单元格的JTable

N年前在网上参加了一个JavaSwing的招聘上机考试。招聘方要求开发一个类似EXCEL支持单元格合并的JTable。差不多用了5天的时间提交代码,最后被告知测试通过,我提出是否可做兼职,对方回复需要到上海做全职开发,最后也就放弃了。最近公司的一个项目中需要用到以前的代码,偶又重构了一次,设计思想来源于ListSelectionModel。

 



GridBagModel:抽象模型接口。该接口用于描述表格中单元格的合并状态。
DefaultGridBagTableModel:GridBagModel的默认实现。
GridBagTable:继承自JTable的控制器。通过该类中的方法控制表格单元的合并和拆分。
GridBagTableUI:GridBagTable对应的UI。

TODO:(已合并)行、列的插入,删除操作对应的GridBagModel的修改,不过已留接口。

package org.dxj.guitools.gridbagtable;
import java.awt.Component;
import java.awt.Point;
import java.awt.Rectangle;
import java.util.Enumeration;
import java.util.EventObject;

import javax.swing.DefaultCellEditor;
import javax.swing.JTable;
import javax.swing.SwingUtilities;
import javax.swing.event.TableModelEvent;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.TableColumn;
import javax.swing.table.TableColumnModel;

/**
 * @author 15860102@qq.com
 */
public class GridBagTable extends JTable{

	GridBagModel gridBagModel;
	
	public GridBagModel getGridBagModel() {
		return gridBagModel;
	}
	
	public void setGridBagModel(GridBagModel gridBagModel){
		if( gridBagModel != null && gridBagModel != this.gridBagModel )
			this.gridBagModel = gridBagModel;
	}

	public GridBagTable(AbstractTableModel dm){
		super(dm);		
		getTableHeader().setReorderingAllowed(false);
		gridBagModel = new DefaultGridBagTableModel(dm);		
		getColumnModel().setColumnSelectionAllowed(true);
	}
	
	 private void updateSubComponentUI(Object componentShell) {
        if (componentShell == null) {
            return;
        }
        Component component = null;
        if (componentShell instanceof Component) {
            component = (Component)componentShell;
        }
        if (componentShell instanceof DefaultCellEditor) {
            component = ((DefaultCellEditor)componentShell).getComponent();
        }

        if (component != null) {
            SwingUtilities.updateComponentTreeUI(component);
        }
    }
	
	public void updateUI() {	
        // Update the UIs of the cell renderers, cell editors and header renderers.
        TableColumnModel cm = getColumnModel();
        for(int column = 0; column < cm.getColumnCount(); column++) {
            TableColumn aColumn = cm.getColumn(column);
	    updateSubComponentUI(aColumn.getCellRenderer());
            updateSubComponentUI(aColumn.getCellEditor());
	    updateSubComponentUI(aColumn.getHeaderRenderer());
        }

        // Update the UIs of all the default renderers.
        Enumeration defaultRenderers = defaultRenderersByColumnClass.elements();
        while (defaultRenderers.hasMoreElements()) {
            updateSubComponentUI(defaultRenderers.nextElement());
        }

        // Update the UIs of all the default editors.
        Enumeration defaultEditors = defaultEditorsByColumnClass.elements();
        while (defaultEditors.hasMoreElements()) {
            updateSubComponentUI(defaultEditors.nextElement());
        }

        // Update the UI of the table header
        if (tableHeader != null && tableHeader.getParent() == null) {
            tableHeader.updateUI();
        }
        setUI(new GridBagTableUI());
    }
	
	public Rectangle getGridCellRect(int row, int column, boolean includeSpacing){
		return super.getCellRect(row, column, includeSpacing);
	}
	
	public Rectangle getCellRect(int row, int column, boolean includeSpacing) {		
		Rectangle cellRect = super.getCellRect(row, column, includeSpacing);
		int cols = gridBagModel.getColumnGrid(row, column);
		TableColumnModel cm = getColumnModel();
		for( int n=1; n<cols; n++)
			cellRect.width += cm.getColumn(column+n).getWidth();
    	int rows = gridBagModel.getRowGrid(row, column);
    	for( int n=1; n<rows; n++)
			cellRect.height += getRowHeight(row+n);
    	return cellRect;		 
	}
	
	public void tableChanged(TableModelEvent e){
		super.tableChanged(e);
		//TODO
	}
	
	public boolean mergeCells(int startRow, int endRow, int startColumn, int endColumn){
		if( gridBagModel.mergeCells(startRow, endRow, startColumn, endColumn)){
			repaint();
			return true;
		}	
		return false;
	}
	 
	public boolean mergeCells(int[] rows, int[] columns){
		if( gridBagModel.mergeCells(rows, columns)){
			repaint();
			return true;
		}	
		return false;
	}
	
	public boolean spliteCellAt(int row, int column){
		if( gridBagModel.spliteCellAt(row, column)){
			repaint();
			return true;
		}
		return false;
	}
	
    public void changeSelection(int rowIndex, int columnIndex, boolean toggle, boolean extend) {
    	if( gridBagModel.getCellState( rowIndex , columnIndex ) != GridBagModel.COVERED  )
    		super.changeSelection(rowIndex, columnIndex, toggle, extend);
		Point p;
		for( int row = rowIndex; row >= 0; row-- ){
			for( int col = columnIndex; col >= 0; col-- ){
				p = gridBagModel.getGrid(row, col);
				//p = ((Point)((Vector)rowVector.get(row)).get(col));
				if( col + p.x > columnIndex && row + p.y > rowIndex){
					rowIndex = row;
					columnIndex = col;
					break;
				}
    		}
		}  	
    	super.changeSelection(rowIndex, columnIndex, toggle, extend);
    	repaint();
    }
    
    public boolean editCellAt(int rowIndex, int columnIndex, EventObject e){
    	if( gridBagModel.getCellState( rowIndex , columnIndex ) != GridBagModel.COVERED  )
    		return super.editCellAt(rowIndex, columnIndex, e);   
		Point p;
		for( int row = rowIndex; row >= 0; row-- ){
			for( int col = columnIndex; col >= 0; col-- ){
				p = gridBagModel.getGrid(row, col);
				if( col + p.x > columnIndex && row + p.y > rowIndex){
					rowIndex = row;
					columnIndex = col;
					break;
				}
    		}
		}   	
    	return super.editCellAt(rowIndex, columnIndex, e);        
    }
}
 
package org.dxj.guitools.gridbagtable;

import java.awt.Point;

public interface GridBagModel {
	//格子处于正常状态
	int DEFAULT = 0;
	//格子合并了其他的格子
	int MERGE = 1;
	//格子被其他格子合并
	int COVERED = -1;
	
	/**
	 * @param row 行
	 * @param column 列
	 * @return 该单元格在行、列的跨度
	 */
	Point getGrid(int row, int column);
	
	/**
	 * 在Y轴方向的跨度
	 * @param row
	 * @param column
	 * @return
	 */
	int getRowGrid(int row, int column);
	
	/**
	 * 在X轴方向的跨度
	 * @param row
	 * @param column
	 * @return
	 */
	int getColumnGrid(int row, int column);

	/**
	 * @param rows 行集合
	 * @param columns 列集合
	 * @return 单元格集合是否可以合并在一起
	 */
	boolean canMergeCells(int[] rows, int[] columns);
	
	/**
	 * 判断该单元格状态
	 * @param row
	 * @param column
	 * @return MERGE|DEFAULT|COVERED
	 */
	int getCellState(int row, int column);
	
	/**
	 * 将单元格集合合并
	 * @param startRow 开始行
	 * @param endRow 结束行
	 * @param startColumn 开始列
	 * @param endColumn 结束列
	 * @return 是否合并成功
	 */
	boolean mergeCells(int startRow, int endRow, int startColumn, int endColumn);
	
	/**
	 * 将单元格集合合并
	 * @param rows 行集合
	 * @param columns 列集合
	 * @return 是否合并成功
	 */
	boolean mergeCells(int[] rows, int[] columns);
	
	/**
	 * 拆分单元格
	 * @param row 行
	 * @param column 列
	 * @return 是否拆分成功
	 */
	boolean spliteCellAt(int row, int column);
	
	/**
	 * 清除 所有合并
	 */
	void clearMergence();
}
 
package org.dxj.guitools.gridbagtable;

import java.awt.Point;
import java.util.Arrays;
import java.util.List;
import java.util.Vector;

import javax.swing.event.TableModelEvent;
import javax.swing.event.TableModelListener;
import javax.swing.table.AbstractTableModel;

public class DefaultGridBagTableModel implements GridBagModel, TableModelListener{
	protected AbstractTableModel model;
	protected List<List<Point>> gridInfo;
	
	DefaultGridBagTableModel(AbstractTableModel model){
		gridInfo = new Vector<List<Point>>();
		setTableModel(model);
	}
	
	public void setTableModel(AbstractTableModel model){
		if( model != null && model != this.model ){
			if( this.model != null )
				this.model.removeTableModelListener(this);
			//防止多次添加监听器
			model.removeTableModelListener(this);
			model.addTableModelListener(this);
			this.model = model;
			clearMergence();
		}
	}
	
	public void clearMergence(){
		if( gridInfo == null  )
			gridInfo = new Vector<List<Point>>();
		else
			gridInfo.clear();
		
		if( model == null )
			return;
		
		//初始化,每个格子占的格子数为(1,1);
		for(int row=model.getRowCount(); --row>=0;){
			List<Point> infos = new Vector<Point>();
			gridInfo.add(infos);
			for(int col=model.getColumnCount(); --col>=0;){
				infos.add(getDefaultPoint());
			}
		}
	}
	
	public Point getDefaultPoint(){
		return new Point(1,1);
	}
	
	@Override
	public boolean canMergeCells(int[] rows, int[] columns) {
		if( rows == null || columns == null ) return false;
		Arrays.sort(rows);
		for(int index=0; index<rows.length-1; index++){
			if( rows[index+1] - rows[index] > 1 )
				return false;
		}
		Arrays.sort(columns);
		for(int index=0; index<columns.length-1; index++){
			if( columns[index+1] - columns[index] > 1 )
				return false;
		}
		return true;
	}
	
	@Override
	public int getCellState(int row, int column) {
		Point grid = getGrid(row, column);
		if( grid == null ) return DEFAULT;
		if( grid.x>1 || grid.y>1 )
			return MERGE;
		if( grid.x<=0 || grid.y<=0 )
			return COVERED;
		return DEFAULT;
	}

	@Override
	public int getColumnGrid(int row, int column) {
		if( gridInfo != null && row >=0 && row < gridInfo.size() ){
			List<Point> gridRow = gridInfo.get(row);
			if( gridRow != null && column >=0 && column < gridRow.size() ){
				Point point = gridRow.get(column);
				if( point != null )
					return point.x;
			}	
		}
		return 1;
	}

	@Override
	public Point getGrid(int row, int column) {
		if( gridInfo != null && row >=0 && row < gridInfo.size() ){
			List<Point> gridRow = gridInfo.get(row);
			if( gridRow != null && column >=0 && column < gridRow.size() ){
				return gridRow.get(column);
			}	
		}
		return getDefaultPoint();
	}

	@Override
	public int getRowGrid(int row, int column) {
		if( gridInfo != null && row >=0 && row < gridInfo.size() ){
			List<Point> gridRow = gridInfo.get(row);
			if( gridRow != null && column >=0 && column < gridRow.size() ){
				Point point = gridRow.get(column);
				if( point != null )
					return point.y;
			}	
		}
		return 1;
	}

	protected boolean setGrid(int row, int column, Point grid) {
		if( gridInfo != null && row >=0 && row < gridInfo.size() ){
			List<Point> gridRow = gridInfo.get(row);
			if( gridRow != null && column >=0 && column < gridRow.size() ){
				Point point = gridRow.get(column);
				if( point != null ){
					point.setLocation(grid);
				}
				else{
					gridRow.set(column, grid.getLocation());
				}
				return true;
			}	
		}
		return false;
	}

	@Override
	public boolean spliteCellAt(int row, int column) {
		if( gridInfo != null && row >=0 && row < gridInfo.size() ){
			List<Point> gridRow = gridInfo.get(row);
			if( gridRow != null && column >=0 && column < gridRow.size() ){
				Point point = gridRow.get(column);
				if( point != null ){
					point = point.getLocation();
					for(int a=0; a<point.y; a++){
						for(int b=0; b<point.x; b++){
							setGrid(row+a, column+b, getDefaultPoint());
						}
					}
				}
				else{
					gridRow.set(column, getDefaultPoint());
				}
				return true;
			}	
		}
		return false;
	}
	
	@Override
	/**
	 * table中发生行的添加和删除的时候需要修改该模型
	 */
	public void tableChanged(TableModelEvent e) {
		//TODO
	}
	
	@Override
	public boolean mergeCells(int[] rows, int[] columns) {
		if( !canMergeCells(rows, columns) )
			return false;
		Arrays.sort(rows);
		Arrays.sort(columns);
		return mergeCells(rows[0],rows[rows.length-1],columns[0],columns[columns.length-1]);
	}

	@Override
	public boolean mergeCells(int startRow, int endRow, int startColumn, int endColumn) {
		setGrid(startRow, startColumn, new Point(endColumn-startColumn+1, endRow-startRow+1)); 
		for(int row=startRow; row<=endRow; row++){
			for(int col=startColumn; col<=endColumn; col++){
				if(row==startRow&&col==startColumn)
					continue;
				else
					setGrid(row, col, new Point(COVERED,COVERED)); 
			}
		}
		return true;
	}
	
	public String toString(){
		if( gridInfo == null )
			return "";
		StringBuffer sb = new StringBuffer();
		for(List<Point> rowInfo : gridInfo ){
			for(Point grid : rowInfo){
				sb.append("["+grid.x+","+grid.y+"], ");
			}
			sb.append("\n");
		}
		return sb.toString();
	}
}
 
package org.dxj.guitools.gridbagtable;

import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Point;
import java.awt.Rectangle;
import java.util.Enumeration;

import javax.swing.BorderFactory;
import javax.swing.JComponent;
import javax.swing.JTable;
import javax.swing.UIManager;
import javax.swing.plaf.basic.BasicTableUI;
import javax.swing.table.JTableHeader;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableColumn;
import javax.swing.table.TableColumnModel;

public class GridBagTableUI extends BasicTableUI
{
    public Dimension getPreferredSize(JComponent c) {
        long width = 0;
        Enumeration<TableColumn> enumeration = table.getColumnModel().getColumns();
        while (enumeration.hasMoreElements()) {
            TableColumn aColumn = (TableColumn)enumeration.nextElement();
            width = width + aColumn.getPreferredWidth();
        }
        return createTableSize(width);
    }
    
    private Dimension createTableSize(long width) {
    	int height = 0;
    	int rowCount = table.getRowCount();
    	if (rowCount > 0 && table.getColumnCount() > 0) {
    	    Rectangle r = table.getCellRect(rowCount-1, 0, true);
    	    height = r.y + r.height;
    	}
    	// Width is always positive. The call to abs() is a workaround for
    	// a bug in the 1.1.6 JIT on Windows.
    	long tmp = Math.abs(width);
            if (tmp > Integer.MAX_VALUE) {
                tmp = Integer.MAX_VALUE;
            }
    	return new Dimension((int)tmp, height);
        }
    
    public void paint(Graphics g, JComponent c) {
        Rectangle clip = g.getClipBounds();

        Rectangle bounds = table.getBounds();
        // account for the fact that the graphics has already been translated
        // into the table's bounds
        bounds.x = bounds.y = 0;

	if (table.getRowCount() <= 0 || table.getColumnCount() <= 0 ||
                // this check prevents us from painting the entire table
                // when the clip doesn't intersect our bounds at all
                !bounds.intersects(clip)) {

            paintDropLines(g);
	    return;
	}

        boolean ltr = table.getComponentOrientation().isLeftToRight();

	Point upperLeft = clip.getLocation();
        if (!ltr) {
            upperLeft.x++;
        }

	Point lowerRight = new Point(clip.x + clip.width - (ltr ? 1 : 0),
                                     clip.y + clip.height);

        int rMin = table.rowAtPoint(upperLeft);
        int rMax = table.rowAtPoint(lowerRight);
        // This should never happen (as long as our bounds intersect the clip,
        // which is why we bail above if that is the case).
        if (rMin == -1) {
	    rMin = 0;
        }
        // If the table does not have enough rows to fill the view we'll get -1.
        // (We could also get -1 if our bounds don't intersect the clip,
        // which is why we bail above if that is the case).
        // Replace this with the index of the last row.
        if (rMax == -1) {
	    rMax = table.getRowCount()-1;
        }

        int cMin = table.columnAtPoint(ltr ? upperLeft : lowerRight); 
        int cMax = table.columnAtPoint(ltr ? lowerRight : upperLeft);        
        // This should never happen.
        if (cMin == -1) {
	    cMin = 0;
        }
	// If the table does not have enough columns to fill the view we'll get -1.
        // Replace this with the index of the last column.
        if (cMax == -1) {
	    cMax = table.getColumnCount()-1;
        }

        // Paint the grid.
        //paintGrid(g, rMin, rMax, cMin, cMax);

        // Paint the cells.
	paintCells(g, rMin, rMax, cMin, cMax);

        paintDropLines(g);
    }
    
    private void paintDropLines(Graphics g) {
        JTable.DropLocation loc = table.getDropLocation();
        if (loc == null) {
            return;
        }

        Color color = UIManager.getColor("Table.dropLineColor");
        Color shortColor = UIManager.getColor("Table.dropLineShortColor");
        if (color == null && shortColor == null) {
            return;
        }

        Rectangle rect;

        rect = getHDropLineRect(loc);
        if (rect != null) {
            int x = rect.x;
            int w = rect.width;
            if (color != null) {
                extendRect(rect, true);
                g.setColor(color);
                g.fillRect(rect.x, rect.y, rect.width, rect.height);
            }
            if (!loc.isInsertColumn() && shortColor != null) {
                g.setColor(shortColor);
                g.fillRect(x, rect.y, w, rect.height);
            }
        }

        rect = getVDropLineRect(loc);
        if (rect != null) {
            int y = rect.y;
            int h = rect.height;
            if (color != null) {
                extendRect(rect, false);
                g.setColor(color);
                g.fillRect(rect.x, rect.y, rect.width, rect.height);
            }
            if (!loc.isInsertRow() && shortColor != null) {
                g.setColor(shortColor);
                g.fillRect(rect.x, y, rect.width, h);
            }
        }
    }
    
    /*
     * Paints the grid lines within <I>aRect</I>, using the grid
     * color set with <I>setGridColor</I>. Paints vertical lines
     * if <code>getShowVerticalLines()</code> returns true and paints
     * horizontal lines if <code>getShowHorizontalLines()</code>
     * returns true.
     */
    private void paintGrid(Graphics g, int rMin, int rMax, int cMin, int cMax) {
        g.setColor(table.getGridColor());

        Rectangle minCell = table.getCellRect(rMin, cMin, true);
        Rectangle maxCell = table.getCellRect(rMax, cMax, true);
        Rectangle damagedArea = minCell.union( maxCell );

		if (table.getShowHorizontalLines()) {
			int tableWidth = damagedArea.x + damagedArea.width;
			int y = damagedArea.y;
			for (int row = rMin; row <= rMax; row++) {
				y += table.getRowHeight(row);
				g.drawLine(damagedArea.x, y - 1, tableWidth - 1, y - 1);
			}
		}
		if (table.getShowVerticalLines()) {
			TableColumnModel cm = table.getColumnModel();
			int tableHeight = damagedArea.y + damagedArea.height;
			int x;
			if (table.getComponentOrientation().isLeftToRight()) {
				x = damagedArea.x;
				for (int column = cMin; column <= cMax; column++) {
					int w = cm.getColumn(column).getWidth();
					x += w;
					g.drawLine(x - 1, 0, x - 1, tableHeight - 1);
				}
			} else {
				x = damagedArea.x;
				for (int column = cMax; column >= cMin; column--) {
					int w = cm.getColumn(column).getWidth();
					x += w;
					g.drawLine(x - 1, 0, x - 1, tableHeight - 1);
				}
			}
		}
    }
    
    private void paintCells(Graphics g, int rMin, int rMax, int cMin, int cMax) {
    	JTableHeader header = table.getTableHeader();
    	TableColumn draggedColumn = (header == null) ? null : header.getDraggedColumn();

    	TableColumnModel cm = table.getColumnModel();
    	int columnMargin = cm.getColumnMargin();
            Rectangle cellRect;
    	TableColumn aColumn;
    	int columnWidth;
    	if (table.getComponentOrientation().isLeftToRight()) {
    	    for(int row = rMin; row <= rMax; row++) {
    	    	if( table instanceof GridBagTable )
    	    		cellRect = ((GridBagTable)table).getGridCellRect(row, cMin, false);
    	    	else
    	    		cellRect = table.getCellRect(row, cMin, false);
                for(int column = cMin; column <= cMax; column++) {
                    aColumn = cm.getColumn(column);
                    columnWidth = aColumn.getWidth();                                 
                    //TODO
                    cellRect.width = columnWidth - columnMargin;
                    int oldHeight = cellRect.height;
                    if( table instanceof GridBagTable ){
                    	if(((GridBagTable)table).getGridBagModel().getCellState( row, column) == GridBagModel.COVERED ) {
                        	cellRect.width = 0;
                        	cellRect.height = 0;
                        }
                    	else{
                    		int h = ((GridBagTable)table).getGridBagModel().getColumnGrid(row, column);
                        	if( h >1 ){
                        		for( int n=1; n<h; n++)
                        			cellRect.width += cm.getColumn(column+n).getWidth();
                        	}
                        	int v = ((GridBagTable)table).getGridBagModel().getRowGrid(row, column);
                        	if( v >1 ){
                        		for( int n=1; n<v; n++)
                        			cellRect.height += table.getRowHeight(row+n);
                        	}
                    	}          	
                    }                                                                       
                    if (aColumn != draggedColumn) {
                        paintCell(g, cellRect, row, column);
                    }                       
                	cellRect.height = oldHeight;                   	
                    cellRect.x += columnWidth;
            	}
    	    }
    	} else {
    	    for(int row = rMin; row <= rMax; row++) {
                    cellRect = table.getCellRect(row, cMin, false);
                    aColumn = cm.getColumn(cMin);
                    if (aColumn != draggedColumn) {
                        columnWidth = aColumn.getWidth();
                        cellRect.width = columnWidth - columnMargin;
                        paintCell(g, cellRect, row, cMin);
                    }
                    for(int column = cMin+1; column <= cMax; column++) {
                        aColumn = cm.getColumn(column);
                        columnWidth = aColumn.getWidth();
//                      TODO
                        cellRect.width = columnWidth - columnMargin;
                        cellRect.x -= columnWidth;
                        if (aColumn != draggedColumn) {
                            paintCell(g, cellRect, row, column);
                        }
            	}
    	    }
    	}

            // Paint the dragged column if we are dragging.
            if (draggedColumn != null) {
    	    paintDraggedArea(g, rMin, rMax, draggedColumn, header.getDraggedDistance());
    	}

    	// Remove any renderers that may be left in the rendererPane.
    	rendererPane.removeAll();
        }
    
    private void paintCell(Graphics g, Rectangle cellRect, int row, int column) {
        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 instanceof JComponent ){
            	((JComponent)component).setBorder(BorderFactory.createLineBorder(Color.gray));
            }
            rendererPane.paintComponent(g, component, table, cellRect.x, cellRect.y,
                                        cellRect.width, cellRect.height, true);           
        }
    }
    
    private Rectangle getHDropLineRect(JTable.DropLocation loc) {
        if (!loc.isInsertRow()) {
            return null;
        }

        int row = loc.getRow();
        int col = loc.getColumn();
        if (col >= table.getColumnCount()) {
            col--;
        }

        Rectangle rect = table.getCellRect(row, col, true);

        if (row >= table.getRowCount()) {
            row--;
            Rectangle prevRect = table.getCellRect(row, col, true);
            rect.y = prevRect.y + prevRect.height;
        }

        if (rect.y == 0) {
            rect.y = -1;
        } else {
            rect.y -= 2;
        }

        rect.height = 3;

        return rect;
    }
    
    private void paintDraggedArea(Graphics g, int rMin, int rMax, TableColumn draggedColumn, int distance) {
        int draggedColumnIndex = viewIndexForColumn(draggedColumn);

        Rectangle minCell = table.getCellRect(rMin, draggedColumnIndex, true);
	Rectangle maxCell = table.getCellRect(rMax, draggedColumnIndex, true);

	Rectangle vacatedColumnRect = minCell.union(maxCell);

	// Paint a gray well in place of the moving column.
	g.setColor(table.getParent().getBackground());
	g.fillRect(vacatedColumnRect.x, vacatedColumnRect.y,
		   vacatedColumnRect.width, vacatedColumnRect.height);

	// Move to the where the cell has been dragged.
	vacatedColumnRect.x += distance;

	// Fill the background.
	g.setColor(table.getBackground());
	g.fillRect(vacatedColumnRect.x, vacatedColumnRect.y,
		   vacatedColumnRect.width, vacatedColumnRect.height);

	// Paint the vertical grid lines if necessary.
	if (table.getShowVerticalLines()) {
	    g.setColor(table.getGridColor());
	    int x1 = vacatedColumnRect.x;
	    int y1 = vacatedColumnRect.y;
	    int x2 = x1 + vacatedColumnRect.width - 1;
	    int y2 = y1 + vacatedColumnRect.height - 1;
	    // Left
	    g.drawLine(x1-1, y1, x1-1, y2);
	    // Right
	    g.drawLine(x2, y1, x2, y2);
	}

	for(int row = rMin; row <= rMax; row++) {
	    // Render the cell value
	    Rectangle r = table.getCellRect(row, draggedColumnIndex, false);
	    r.x += distance;
	    paintCell(g, r, row, draggedColumnIndex);

	    // Paint the (lower) horizontal grid line if necessary.
	    if (table.getShowHorizontalLines()) {
		g.setColor(table.getGridColor());
		Rectangle rcr = table.getCellRect(row, draggedColumnIndex, true);
		rcr.x += distance;
		int x1 = rcr.x;
		int y1 = rcr.y;
		int x2 = x1 + rcr.width - 1;
		int y2 = y1 + rcr.height - 1;
		g.drawLine(x1, y2, x2, y2);
	    }
	}
    }
    
    private int viewIndexForColumn(TableColumn aColumn) {
        TableColumnModel cm = table.getColumnModel();
        for (int column = 0; column < cm.getColumnCount(); column++) {
            if (cm.getColumn(column) == aColumn) {
                return column;
            }
        }
        return -1;
    }
    
    private Rectangle extendRect(Rectangle rect, boolean horizontal) {
        if (rect == null) {
            return rect;
        }

        if (horizontal) {
            rect.x = 0;
            rect.width = table.getWidth();
        } else {
            rect.y = 0;

            if (table.getRowCount() != 0) {
                Rectangle lastRect = table.getCellRect(table.getRowCount() - 1, 0, true);
                rect.height = lastRect.y + lastRect.height;
            } else {
                rect.height = table.getHeight();
            }
        }

        return rect;
    }
    
    private Rectangle getVDropLineRect(JTable.DropLocation loc) {
        if (!loc.isInsertColumn()) {
            return null;
        }

        boolean ltr = table.getComponentOrientation().isLeftToRight();
        int col = loc.getColumn();
        Rectangle rect = table.getCellRect(loc.getRow(), col, true);

        if (col >= table.getColumnCount()) {
            col--;
            rect = table.getCellRect(loc.getRow(), col, true);
            if (ltr) {
                rect.x = rect.x + rect.width;
            }
        } else if (!ltr) {
            rect.x = rect.x + rect.width;
        }

        if (rect.x == 0) {
            rect.x = -1;
        } else {
            rect.x -= 2;
        }
        
        rect.width = 3;

        return rect;
    }

}  // End of Class BasicTableUI

  测试代码:

import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.table.DefaultTableModel;

import com.jrf.jgrid.guitools.gridbagtable.GridBagTable;


public class Test implements ActionListener{
	
	GridBagTable table;
	public Test()
	{
		JFrame d = new JFrame();
		DefaultTableModel model = new DefaultTableModel(5,5);
		
		table = new GridBagTable(model);
		table.setRowHeight(20);
		
		JScrollPane pane = new JScrollPane(table);
		d.getContentPane().add(pane, BorderLayout.CENTER);
		JButton btn = new JButton("合并/拆分");
		d.getContentPane().add(btn, BorderLayout.NORTH);
		btn.addActionListener(this);
		d.setBounds(0, 0, 400, 400);
		d.setVisible(true);
	}
	
	public static void main(String[] fsd){
		new Test();
	}
	
	public void actionPerformed(ActionEvent e) {
		table.mergeCells(table.getSelectedRows(), table.getSelectedColumns());
	}
}
 

 

Java Swing中,可以使用JTable实现表格的展示和操作。JTable提供了一些方法可以用来合并单元格。 下面是一个简单的示例代码,演示如何使用JTable合并单元格: ```java import java.awt.BorderLayout; import java.awt.Color; import java.awt.Component; import java.awt.Dimension; import java.awt.EventQueue; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import javax.swing.JButton; import javax.swing.JFrame; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JTable; import javax.swing.ListSelectionModel; import javax.swing.UIManager; import javax.swing.table.DefaultTableCellRenderer; import javax.swing.table.DefaultTableModel; import javax.swing.table.JTableHeader; public class JTableMergeCellDemo extends JFrame { private static final long serialVersionUID = 1L; private JPanel contentPane; private JTable table; public static void main(String[] args) { EventQueue.invokeLater(new Runnable() { public void run() { try { JTableMergeCellDemo frame = new JTableMergeCellDemo(); frame.setVisible(true); } catch (Exception e) { e.printStackTrace(); } } }); } public JTableMergeCellDemo() { setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); setBounds(100, 100, 450, 300); contentPane = new JPanel(); contentPane.setBorder(UIManager.getBorder("ScrollPane.border")); contentPane.setLayout(new BorderLayout(0, 0)); setContentPane(contentPane); JScrollPane scrollPane = new JScrollPane(); contentPane.add(scrollPane, BorderLayout.CENTER); String[] columnNames = {"姓名", "性别", "年龄", "爱好", "备注"}; Object[][] rowData = {{"张三", "男", 18, "篮球", "小张"}, {"李四", "女", 20, "足球", "小李"}, {"王五", "男", 22, "乒乓球", "小王"}, {"赵六", "女", 24, "羽毛球", "小赵"}, {"钱七", "男", 26, "游泳", "小钱"}, {"孙八", "女", 28, "跑步", "小孙"}, {"周九", "男", 30, "健身", "小周"}, {"吴十", "女", 32, "瑜伽", "小吴"}}; DefaultTableModel model = new DefaultTableModel(rowData, columnNames); table = new JTable(model); table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); table.setRowHeight(30); table.setPreferredScrollableViewportSize(new Dimension(400, 180)); table.setAutoResizeMode(JTable.AUTO_RESIZE_OFF); JTableHeader header = table.getTableHeader(); header.setPreferredSize(new Dimension(header.getWidth(), 30)); header.setDefaultRenderer(new MergeHeaderCellRenderer()); // 合并单元格 MergeCellUtil.mergeCells(table, new int[]{0, 1}, new int[]{2, 3, 4}); scrollPane.setViewportView(table); JPanel panel = new JPanel(); contentPane.add(panel, BorderLayout.SOUTH); JButton btnNewButton = new JButton("获取选中行"); btnNewButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { int row = table.getSelectedRow(); if (row != -1) { String name = (String) table.getValueAt(row, 0); JOptionPane.showMessageDialog(null, "选中的行为:" + name); } } }); panel.add(btnNewButton); } /** * 表头合并单元格的渲染器 */ private class MergeHeaderCellRenderer extends DefaultTableCellRenderer { private static final long serialVersionUID = 1L; @Override public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column); setHorizontalAlignment(CENTER); setBackground(Color.LIGHT_GRAY); return this; } } } ``` 在这个示例中,我们使用了一个名为MergeCellUtil的工具类,其中包含了一个mergeCells方法,可以用来合并单元格。这个方法的实现如下: ```java public class MergeCellUtil { /** * 合并单元格 * * @param table JTable对象 * @param rows 要合并的行数组 * @param cols 要合并的列数组 */ public static void mergeCells(JTable table, int[] rows, int[] cols) { DefaultTableModel model = (DefaultTableModel) table.getModel(); for (int i = 0; i < rows.length; i++) { int row = rows[i]; for (int j = 1; j < cols.length; j++) { int col = cols[j]; model.setValueAt("", row, col); table.getCellRenderer(row, col); } int width = 0; for (int j = 0; j < cols.length; j++) { int col = cols[j]; TableColumn column = table.getColumnModel().getColumn(col); width += column.getPreferredWidth(); } TableColumn column = table.getColumnModel().getColumn(cols[0]); column.setPreferredWidth(width); } } } ``` 通过这个工具类,我们可以传入要合并的行和列的数组,然后在表格中实现单元格的合并。 需要注意的是,表格中的单元格合并只是外观上的合并,并不会改变单元格的数据。如果需要获取合并后的单元格数据,需要自行编相关的代码来实现。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值