views:

960

answers:

3

Right now I'm using a JTable for what I'm doing. After looking through the Java API and various web resources, I don't think a JTable is going to cut it anymore. What I'm looking for is a table for which I can specify very strict selection procedures. I want to be able to not only select rows and columns, but also select cells in a diagonal direction. More than that, I need the overall ability to be able to specify which cells can be selected when in another cell.

For example, if I have a 10x10 table and I'm in cell (4, 3) [(row, column)], I want to be able to say, okay, you can select the following intervals from here:

  • (4, 3) to (4, 10)
  • (4, 3) to (4, 1)
  • (4, 3) to (10, 4)
  • (4, 3) to (1, 4)
  • (4, 3) to (10, 10) [diagonally]
  • (4, 3) to (1, 1) [diagonally]
  • (4, 3) to (1, 6) [diagonally]
  • (4, 3) to (6, 1) [diagonally]

Any ideas for how I could do this?

A: 

Doesn't sound like you are really modeling a 'table'. (JTable assumes table semantics and uses a List selection model.) However, I don't think it's that far removed from a matrix, if you are willing to hack the JTable code.

An alternative is your own (yep) component: A JPanel that contains the matrix cells. All keyboard/mouse event processing needs to be delegated to the parent JPanel. I would certainly recommend cloning the relevant subsets and design from JTable (data model, selection model, etc.).

So, basically, you will need 3 classes:

JMatrix, JMatrixModel, JMatrixSelectionModel.

The JMatrix is the extended JPanel with its child components. The JMatrixSelectionModel is the class that will implement the selection rules. The JMatrix should call the selection model on selection events (registered on matrix cells, delegated to handler on the parent JMatrix). The data model is fairly straightforward -- you may even be able to use existing JTableModel.

A: 

I am in a similar situation. My solution (sorry, don't want to write a huge class) was to add a Cell Renderer for all the columns which was a mouse listener for the table. Since the Renderer knows which buttons have been selected, it can render them differently.

public class MultipleSelectionRenderer extends DefaultTableCellRenderer implements MouseListener { private JTable table; private Map selectedMap = new LinkedHashMap(); TableUpdateIfc updater;

public MultipleSelectionRenderer(TableUpdateIfc updater, JTable table, Map<String, Boolean> selectedMap) {
    this.table = table;
    this.selectedMap = selectedMap;
    this.updater = updater;
}

@Override
public void mouseReleased(MouseEvent e) {
    if(e.getSource() == table){
        try {
            if(!e.isControlDown())
                selectedMap.clear();
            selectedMap.put(table.getSelectedRow()+":"+table.getSelectedColumn(), true);
        } catch (Exception ex) {
            selectedMap.clear();
        }
    }
    updater.updateMultipleSelectionTable(table);
}
public void mouseEntered(MouseEvent e) {}
public void mouseExited(MouseEvent e) { }
public void mouseClicked(MouseEvent e) {}
public void mousePressed(MouseEvent e) {}

/**
 *
 * @param table
 * @param value
 * @param isSelected
 * @param hasFocus
 * @param row
 * @param column
 * @return
 */
@Override
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
    Component result =super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
    if(selectedMap.get(row+":"+column) != null && selectedMap.get(row+":"+column) == true) {
        setText(getHTMLString(value));
    }
    return result;
}

private String getHTMLString(Object value){
    String html = "<html><body><table cellpadding=0><tr>";
    html = html + "<td bgcolor=#bf65a5>";
    html = html + value.toString();
    html = html + "</td><td>&nbsp;" + value+"</td>";
    html = html + "</tr></table></body></html>";
    return html;
}

}

Scott Izu
A: 

import java.awt.Component; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.util.LinkedHashMap; import java.util.Map; import javax.swing.JTable; import javax.swing.table.DefaultTableCellRenderer;

/** * The purpose of this class is to update a map which states which cells are selected * * This class may be set up as a listener for a Table to keep track of selected * cells. * * This renderer updates the selection map based on the TableUpdateIfc's unique Object. * * The renderer holds two maps. The first is a selection map. The renderer will ask the TableUpdateIfc * for the unique object for a given row and a unique object for a given column. * * In many of the tables I use, I put a unique object in the first cell in the row and * use the column name as a unique object for the column. * * The reason the columns and rows aren't stored is because the cells may change location. * To track down the objects, the unique identifiers are used. The first map, holds a Boolean object. * A Boolean may be true, false or null as opposed to a boolean which may only be true or false. * * To indicate selection, null means not selected (by default all things are null) and not null * means selected (a Boolean with false value is non null, so this may be confusing). * * The last RenderedTextMap may be used to grab the last rendered string given the current row and column. * * * @author scott.izu */ public class MultipleSelectionRenderer extends DefaultTableCellRenderer implements MouseListener { private JTable table;

private Map<Object, Map<Object, Boolean>> selectedMap = new LinkedHashMap<Object, Map<Object, Boolean>>();
private Map<Object, Map<Object, String>> lastRenderedTextMap = new LinkedHashMap<Object, Map<Object, String>>();
TableUpdateIfc updater;
public static final String SELECTION_RGB = "bf65a5";

public MultipleSelectionRenderer(TableUpdateIfc updater, JTable table) {
    this.table = table;
    this.updater = updater;
}

@Override
public void mouseReleased(MouseEvent e) {
    if(e.getSource() == table){
        if(e.getButton() == MouseEvent.BUTTON1) {
            boolean selectionChanged = false;
            if(!e.isControlDown()) {
                selectedMap.clear();
                selectionChanged = true;
            }

// for(int row: table.getSelectedRows()) // for(int col: table.getSelectedColumns()) // System.err.println(row+""+col);

            // Store selections in terms of model, since table view may change
            Object rowObject = updater.getUniqueRowObject(table, table.getSelectedRow());
            Object colObject = updater.getUniqueColumnObject(table, table.getSelectedColumn());
            if(updater.canSelectColumn(colObject)){
                if(selectedMap.get(rowObject) == null)
                    selectedMap.put(rowObject, new LinkedHashMap<Object, Boolean>());

                if(selectedMap.get(rowObject).get(colObject) != null)
                    selectedMap.get(rowObject).remove(colObject); // Deselect
                else
                    selectedMap.get(rowObject).put(colObject, true); // Select
                selectionChanged = true;
            }
            if(selectionChanged)
                updater.updateMultipleSelection(table);
        }
    }
}
public void mouseEntered(MouseEvent e) {}
public void mouseExited(MouseEvent e) { }
public void mouseClicked(MouseEvent e) {}
public void mousePressed(MouseEvent e) {}

/**
 *
 * @param table
 * @param value
 * @param isSelected
 * @param hasFocus
 * @param row
 * @param column
 * @return
 */
@Override
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int tableRow, int tableColumn) {
    Component result = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, tableRow, tableColumn);

    Object rowObject = updater.getUniqueRowObject(table, tableRow);
    Object colObject = updater.getUniqueColumnObject(table, tableColumn);
    if(isCellSelected(rowObject, colObject))
        this.setText(getHTMLString(value));

    if(lastRenderedTextMap.get(rowObject) == null)
        lastRenderedTextMap.put(rowObject, new LinkedHashMap<Object, String>());
    lastRenderedTextMap.get(rowObject).put(colObject, this.getText());
    return result;
}

private String getHTMLString(Object value){
    String valueString = value.toString();
    if(valueString.equals(""))
        valueString = "&nbsp;&nbsp;&nbsp;";
    String html = "<html><body><table cellpadding=0><tr>";
    html = html + "<td bgcolor=#" + SELECTION_RGB + ">";
    html = html + valueString;
    html = html + "</td>";
    html = html + "</tr></table></body></html>";
    return html;
}

public String getLastRenderedText(JTable table, int tableRow, int tableColumn){
    Object rowObject = updater.getUniqueRowObject(table, tableRow);
    Object colObject = updater.getUniqueColumnObject(table, tableColumn);
    return lastRenderedTextMap.get(rowObject).get(colObject);
}

public void clearSelectedCells(){
    selectedMap.clear();
}

public boolean isCellSelected(Object rowObject, Object colObject){
    return (selectedMap.get(rowObject) != null && selectedMap.get(rowObject).get(colObject) != null);
}

public void selectCell(Object rowObject, Object colObject){
    if(selectedMap.get(rowObject) == null)
        selectedMap.put(rowObject, new LinkedHashMap<Object, Boolean>());
    selectedMap.get(rowObject).put(colObject, true);
}

public static interface TableUpdateIfc {
    public void updateMultipleSelection(JTable table);
    public Object getUniqueRowObject(JTable table, int tableRow);
    public Object getUniqueColumnObject(JTable table, int tableColumn);
    public boolean canSelectColumn(Object colObject); // Determines if this column is used for multiple selection
}

}

Scott Izu