How to get the selected node of a Tree Table in swing? - java

I have implemented a tree table in swing refrring the tutorial available here
.I have two columns in which Message Object or Fields objects under the Message will be displayed. If the node is a Field then it's Value will be displayed in the second column.I could view the required data successfully add a cell renderer to the second column where the value is a collection then addd a combo box to the cell. The issue comes when I add the cell editor. Below is my cell editor.
private class ValueCellEditor extends AbstractCellEditor implements TableCellEditor {
JTextField textEditor = new JTextField();
JComboBox comboBox = new JComboBox();
Component comp;
Field field = null;
private ValueCellEditor(){
int row=myTreeTable.getSelectedRow();
Object object= myTreeTable.getValueAt(row, 3);
TreePath path= myTreeTable.getPathForRow(row);
Object o= path.getLastPathComponent();
MyDataNode node=(MyDataNode)o;
Field f=(Field)node.getNodeDataObject();
comboBox.addItemListener(new ItemListener() {
#Override
public void itemStateChanged(ItemEvent e) {
if(field!=null){
if(e.getStateChange() == ItemEvent.SELECTED) {
field.setSelectedValue(comboBox.getSelectedItem());
field.setDefaultValue(comboBox.getSelectedItem());
}
}
}
});
textEditor.addKeyListener(new KeyAdapter() {
#Override
public void keyReleased(KeyEvent e) {
if(field!=null){
field.setDefaultValue(textEditor.getText());
}
}
});
}
#Override
public Component getTableCellEditorComponent
(JTable table, Object value, boolean isSelected, int row, int column) {
if(value instanceof List<?>) {
populateComboBox((ArrayList)value);
comp = comboBox;
} else {
textEditor.setText((String)value);
comp = textEditor;
}
return comp;
}
#Override
public Object getCellEditorValue() {
if(comp != null && comp instanceof JTextField ) {
return textEditor.getText();
} else if( comp != null && comp instanceof JCheckBox) {
return comboBox.getSelectedItem();
}
return null;
}
private void populateComboBox(List<Value> valueList){
comboBox.removeAll();
comboBox.setSelectedItem(null);
for (Value val: valueList) {
comboBox.addItem(val.getDescription());
}
if(field.getSelectedValue() != null) {
comboBox.setSelectedItem(field.getSelectedValue());
}
}
}
In my cell editor's constructor I have added a code as below through which I tried to get the current selected node of the selected row of the tree table. but the code I have entered does't work because of the 'getPathForRow' function does't support the tree table.
int row=myTreeTable.getSelectedRow();
Object object= myTreeTable.getValueAt(row, 3);
TreePath path= myTreeTable.getPathForRow(row);
Object o= path.getLastPathComponent();
MyDataNode node=(MyDataNode)o;
Field f=(Field)node.getNodeDataObject();
this is the table model I am using.
import com.milleniumit.walle.frontend.frontEndDto.Field;
public class MyDataModel extends MyAbstractTreeTableModel {
static protected String[] columnNames = { "Field", "Value" };
static protected Class<?>[] columnTypes =
{ MyTreeTableModel.class, Object.class};
public MyDataModel(MyDataNode rootNode) {
super(rootNode);
root = rootNode;
}
public Object getChild(Object parent, int index) {
return ((MyDataNode) parent).getChildren().get(index);
}
public int getChildCount(Object parent) {
return ((MyDataNode) parent).getChildren().size();
}
public int getColumnCount() {
return columnNames.length;
}
public String getColumnName(int column) {
return columnNames[column];
}
public Class<?> getColumnClass(int column) {
return columnTypes[column];
}
public Object getValueAt(Object node, int column) {
MyDataNode mNode=(MyDataNode)node;
Object obj =mNode.getNodeDataObject();
if(column==0){
return mNode.getName();
}
else if (column==1){
if(obj instanceof Field){
Field field=(Field)mNode.getNodeDataObject();
if(field.getFieldDef().getListValue().size()>0){
return field.getFieldDef().getListValue();
}
else
return mNode.getDefaultValue();
}
else
return mNode.getDefaultValue();
}
return null;
}
public boolean isCellEditable(Object node, int column) {
return true; // Important to activate TreeExpandListener
}
public void setValueAt(Object aValue, Object node, int column) {
MyDataNode mNode=(MyDataNode)node;
if (mNode.getNodeDataObject() instanceof Field && column == 1) {
Field field = (Field) mNode.getNodeDataObject();
field.setDefaultValue(aValue);
field.setSelectedValue(aValue);
}
}
}
I need to get the selected node of the tree table so that I can get the current selected field to edit it's changed value through the editor. Can someone please let me know where I am going wrong in my code. What's the correct approach to get the current selected node of this tree table.

Related

How to set a JComboBox only in specific cell in a JTable

I want to add a JComboBox only inside a cell who suppose to have a list of values. Below is my code but it adds the combo box in all the cells in the column. Let me know what's missing in my code to set the combo box only on the selected cell.
public class PropertiesTableModel extends AbstractTableModel{
//this method is called to set the value of each cell
#Override
public Object getValueAt(int rowIndex, int columnIndex) {
Field field= (Field) fieldList.get(rowIndex);
if(columnIndex==0){
String dataType=field.getFieldDef().getDataType();
return PropertiesPanel.getPpIns().getDataTypeIcon(dataType);
}
if(columnIndex==1){
return field.getFieldDef().getfName();
}
else if (columnIndex==2){
if(field.getFieldDef().getListValue().size()>0){
return createValueListCombo(field.getFieldDef().getListValue());
}
return field.getDefaultValue();
}
else{
return null;
}
}
public JComboBox createValueListCombo(List<Value> valueList){
TableColumn valColumn = table.getColumnModel().getColumn(2);
JComboBox comboBox=new JComboBox();
for (Value val: valueList) {
comboBox.addItem(val.getDescription());
}
comboBox.setSelectedIndex(0);
valColumn.setCellEditor(new DefaultCellEditor(comboBox));
return comboBox;
}
}
It's really simple and can be done using two ways
First of all your model should notify editor/table that the current cell has list of values.
public class PropertiesTableModel extends AbstractTableModel {
#Override
public Object getValueAt(int rowIndex, int columnIndex) {
// previous stuff
if (columnIndex == 2) {
// return the actually selected value,
// not the first value of the list!
// also the values changed by setValueAt() must be considered.
return null; // implement it!
}
}
public List<Object> getPossibleValues(int row, int column) {
// this method should return possible values to select.
// if cell has no possible values and should be editeb
// by a text field this methos should return null
if (column == 2) {
Field field= (Field) fieldList.get(rowIndex);
if (field.getFieldDef().getListValue().size() > 0) {
return field.getFieldDef().getListValue();
}
return null; // probably something else for non-list values
}
}
public void setValueAt(int row, int column) {
// you need to store the value chosen by the user
}
}
1) Override the method public TableCellEditor getCellEditor(int row, int column) in JTable
public TableCellEditor getCellEditor(int row, int column) {
PropertiesTableModel model = (PropertiesTableModel) getModel();
List<Object> values = model.getPossibleValues(row, column);
if (values != null) {
return new DefaultCellEditor(new JComboBox(values.toArray()));
} else {
return super.getCellEditor(row, column);
}
}
2) You can create a custom editor which delegates all calls to one of two (or more) editors depended on the currently edited cell.
public class CellEditorMulticaster implements TableCellEditor {
private DefaultTableCellEditor textEditor;
private DefaultTableCellEditor currentEditor;
public CellEditorMulticaster() {
firstEditor = new DefaultTableCellEditor(new JTextField());
}
Component getTableCellEditorComponent(JTable table, Object value,
boolean isSelected,
int row, int column) {
PropertiesTableModel model = (PropertiesTableModel) table.getModel();
List<Object> values = model.getPossibleValues(row, column);
if (values != null) {
currentEditor = new DefaultCellEditor(new JComboBox(values.toArray()));
} else {
currentEditor = textEditor;
}
return currentEditor.getTableCellEditorComponent(table, value,
isSelected, row, column);
}
public Object getCellEditorValue() {
return currentEditor.getCellEditorValue();
}
public boolean isCellEditable(EventObject anEvent) {
JTable table = (JTable) anEvent.getSource;
int row, col;
if (anEvent instanceof MouseEvent) {
MouseEvent evt = (MouseEvent) anEvent;
row = table.rowAtPoint(evt.getPoint());
col = table.columnAtPoint(evt.getPoint());
} else {
row = table.getSelectedRow();
col = table.getSelectedColumn();
}
PropertiesTableModel model = (PropertiesTableModel) table.getModel();
List<Object> values = model.getPossibleValues(row, column);
if (values != null) {
return true;
} else {
return textEditor.isCellEditable(anEvent);
}
}
public boolean shouldSelectCell(EventObject anEvent) {
return true;
}
public boolean stopCellEditing() {
return currentEditor.stopCellEditing();
}
public void cancelCellEditing() {
currentEditor.cancelCellEditing();
}
// same pattern for another methods (delegate to currentEditor)
}
i am sorry that i not valid to add comment , but i have to report a problem for sergiy's answer .
it's for the step 1 : 1) Override the method public TableCellEditor getCellEditor(int row, int column) in JTable
i found each time i click the comboBox , this code will create a new combobox as a new cell editor . and my code will be crash after i clicked 2-3 times as the Index out of bounds for length.
i am fully confuse about this , hope can get the answer here .
my code is :
#Override
public TableCellEditor getCellEditor(int row, int column) {
TableModel model = (TableModel) getModel();
String[] values = model.getPossibleValues(row, column);
if (values != null) {
JComboBox<String> comboBox = new JComboBox<String>(values);
comboBox.addActionListener((e)->{
model.setValueAt(row, column, comboBox.getSelectedItem());
});
return new DefaultCellEditor(comboBox);
} else {
return super.getCellEditor(row, column);
}
}

Implementing JTree nodes with radio/check boxes

I'm trying to achieve an elegant tree representation in which certain types of nodes are displayed as panels containing text, a radio button, and a check box. Below is a picture of what I have currently and the code that generates it. However there are a few problems that just make it feel dirty, and I'm not sure the best way to get around them.
public class DatasetTree extends JTree {
public DatasetTree(String name) {
super(new DatasetTreeModel(name));
getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
DatasetTreeCellRenderer renderer = new DatasetTreeCellRenderer();
renderer.setOpenIcon(null);
renderer.setClosedIcon(null);
renderer.setLeafIcon(null);
setCellRenderer(renderer);
setEditable(true);
PanelCellEditor editor = new PanelCellEditor(this, renderer);
setCellEditor(editor);
setShowsRootHandles(true);
setRootVisible(false);
}
public DatasetTreeModel getDatasetModel() {
return (DatasetTreeModel) treeModel;
}
public static class DatasetTreeCellRenderer extends DefaultTreeCellRenderer {
public DatasetTreeCellRenderer() {
}
#Override
public Component getTreeCellRendererComponent(JTree tree, Object value, boolean sel,
boolean expanded, boolean leaf, int row, boolean hasFocus) {
if ((value != null) && (value instanceof DatasetHandle)) {
DatasetHandle h = (DatasetHandle) value;
DatasetCellPanel line = new DatasetCellPanel(h);
if (sel) {
line.setBackground(getBackgroundSelectionColor());
line.setForeground(getTextSelectionColor());
} else {
line.setBackground(getBackgroundNonSelectionColor());
line.setForeground(getTextNonSelectionColor());
}
return line;
}
return super.getTreeCellRendererComponent(tree, value, sel, expanded, leaf, row, hasFocus);
}
}
public static class DatasetCellPanel extends JPanel {
private final JLabel lblName, lblType, lblom, lbldata, lblimages, lblspectra;
private boolean observable;
private boolean orientable;
private JRadioButton omButton;
private JCheckBox dataSelectBox;
/**
* Create the panel.
*/
public DatasetCellPanel(DatasetHandle h) {
super();
setBackground(Color.WHITE);
FileData fd = h.getFileData();
String name = fd.getFileName();
boolean observable = (fd instanceof ObservableData);
boolean orientable = (fd instanceof Orientable);
String typeName = fd.getClass().getSimpleName();
lblName = new JLabel("");
lblType = new JLabel("");
lblom = new JLabel("[om]");
lbldata = new JLabel("[data]");
lblimages = new JLabel("[images]");
lblspectra = new JLabel("[spectra]");
JRadioButton omButton = new JRadioButton("");
JCheckBox dataSelectBox = new JCheckBox("");
setLayout(new BoxLayout(this, BoxLayout.X_AXIS));
lblName.setText(name);
lblName.setMinimumSize(new Dimension(100, 8));
lblName.setPreferredSize(new Dimension(100, 16));
lblName.setMaximumSize(new Dimension(100, 64));
add(lblName);
add(Box.createRigidArea(new Dimension(5, 0)));
lblType.setText(typeName);
lblType.setMinimumSize(new Dimension(100, 8));
lblType.setPreferredSize(new Dimension(100, 16));
lblType.setMaximumSize(new Dimension(100, 64));
add(lblType);
add(Box.createRigidArea(new Dimension(5, 0)));
if (orientable) {
omButton = h.getLatticeButton();
} else {
lblom.setForeground(UIManager.getColor("Label.disabledForeground"));
omButton.setEnabled(false);
}
add(lblom);
add(omButton);
add(Box.createRigidArea(new Dimension(5, 0)));
if (observable) {
dataSelectBox = h.getDataButton();
} else {
lbldata.setForeground(UIManager.getColor("Label.disabledForeground"));
dataSelectBox.setEnabled(false);
}
add(lbldata);
add(dataSelectBox);
add(Box.createRigidArea(new Dimension(5, 0)));
add(lblimages);
add(Box.createRigidArea(new Dimension(5, 0)));
add(lblspectra);
}
public void addListeners(EventListener l) {
}
#Override
public void setForeground(Color fg) {
if (lblName != null) {
lblName.setForeground(fg);
}
if (lblType != null) {
lblType.setForeground(fg);
}
if (observable && (lbldata != null)) {
lbldata.setForeground(fg);
}
if (orientable && (lblom != null)) {
lblom.setForeground(fg);
}
if (lblimages != null) {
lblimages.setForeground(fg);
}
if (lblspectra != null) {
lblspectra.setForeground(fg);
}
super.setForeground(fg);
}
#Override
public void setBackground(Color bg) {
if (omButton != null) {
omButton.setBackground(bg);
}
if (dataSelectBox != null) {
dataSelectBox.setBackground(bg);
}
super.setBackground(bg);
}
}
public static class PanelCellEditor extends AbstractCellEditor implements TreeCellEditor {
Object value;
private JTree tree;
private DefaultTreeCellRenderer renderer;
public PanelCellEditor(JTree tree, DefaultTreeCellRenderer renderer) {
this.tree = tree;
this.renderer = renderer;
}
#Override
public Object getCellEditorValue() {
return value;
}
// FIXME: Redraw all in group when one is edited
#Override
public Component getTreeCellEditorComponent(JTree tree, Object value, boolean sel,
boolean expanded, boolean leaf, int row) {
this.value = value;
if ((value != null) && (value instanceof DatasetHandle)) {
DatasetHandle h = (DatasetHandle) value;
DatasetCellPanel line = new DatasetCellPanel(h);
if (sel) {
line.setBackground(renderer.getBackgroundSelectionColor());
line.setForeground(renderer.getTextSelectionColor());
} else {
line.setBackground(renderer.getBackgroundNonSelectionColor());
line.setForeground(renderer.getTextNonSelectionColor());
}
return line;
}
return renderer.getTreeCellRendererComponent(tree, value, sel, expanded, leaf, row, false);
}
}
}
(1) The buttons/boxes are only responsive after editing is enabled by clicking on the node once. Before that, the button/box doesn't glow on mouse over.
(2) The radio buttons for each group of nodes under a parent are in a single button group. But when I select one, the visual representation of the other does not get updated to reflect that it was deselected, until I click somewhere in it to 'edit' it.
(3) In general, this standard type of tree, where the nodes are just dummy objects, not actual components, seems improperly suited for this, but I can't think of a better alternative that allows me to group these objects, select individual nodes (either leafs or parents), and have each leaf contain check boxes/buttons that work properly.
I'm open to suggestions of alternative solutions.
EDIT:
Tried using Outline, which seems closer to what I want, but having technical issues. I followed the example here. This is what I get:
As you can see, the buttons are not displayed properly. Here is my RowModel:
public class DatasetOutlineRowModel implements RowModel {
#Override
public Class getColumnClass(int column) {
switch (column) {
case 0:
return JRadioButton.class;
case 1:
return JCheckBox.class;
case 2:
return String.class;
case 3:
return String.class;
default:
assert false;
}
return null;
}
#Override
public int getColumnCount() {
return 4;
}
#Override
public String getColumnName(int column) {
switch (column) {
case 0:
return "OM";
case 1:
return "Data";
case 2:
return "Images";
case 3:
return "Spectra";
default:
assert false;
}
return null;
}
#Override
public Object getValueFor(Object node, int column) {
if (!(node instanceof DatasetHandle))
return null;
DatasetHandle handle = (DatasetHandle) node;
switch (column) {
case 0:
return handle.getLatticeButton();
case 1:
return handle.getDataButton();
case 2:
return "";
case 3:
return "";
default:
assert false;
}
return null;
}
#Override
public boolean isCellEditable(Object arg0, int arg1) {
return false;
}
#Override
public void setValueFor(Object arg0, int arg1, Object arg2) {
// TODO Auto-generated method stub
}
}
OK, so I finally figured out how to achieve this basing it on the way JTable handles boolean cells. I created an exclusive boolean selection renderer to draw the JRadioButton and set up the tree node to make sure exclusive selection is maintained. I also overrode editingStopped to update all the cells in the column if one of the cells was edited. There are probably ways to improve this, but it works for what I need. Thanks for the guidance.
Here is my code:
DatasetOutline class
public class DatasetOutline extends Outline {
public DatasetOutline(DatasetTreeModel mdl) {
setRenderDataProvider(new DatasetRenderProvider());
setRootVisible(false);
setShowGrid(false);
setIntercellSpacing(new Dimension(0, 0));
setModel(DefaultOutlineModel.createOutlineModel(mdl, new DatasetOutlineRowModel(), true,
"Dataset"));
getColumnModel().getColumn(1).setCellRenderer(new ExclusiveBooleanRenderer());
getColumnModel().getColumn(1).setCellEditor(new ExclusiveBooleanEditor());
// [snip]
getSelectionModel().setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
}
// Update the entire column of the conditional boolean if one is changed
#Override
public void editingStopped(ChangeEvent e) {
super.editingStopped(e);
if (e.getSource() instanceof ExclusiveBooleanEditor) {
tableChanged(new TableModelEvent(getModel(), 0, getRowCount(), 1, TableModelEvent.UPDATE));
}
}
}
DatasetOutlineRowModel class
public class DatasetOutlineRowModel implements RowModel {
#Override
public Class getColumnClass(int column) {
switch (column) {
case 0:
return Boolean.class;
case 1:
return Boolean.class;
case 2:
return String.class;
case 3:
return String.class;
default:
assert false;
}
return null;
}
// [snip]
#Override
public Object getValueFor(Object node, int column) {
if (!(node instanceof DatasetHandle))
return null;
DatasetHandle handle = (DatasetHandle) node;
switch (column) {
case 0:
return handle.isLatticeSelected();
case 1:
return handle.isSelected();
case 2:
return "";
case 3:
return "";
default:
assert false;
}
return null;
}
#Override
public boolean isCellEditable(Object node, int column) {
if (column > 1)
return false;
if (node instanceof DatasetHandle)
return true;
return false;
}
#Override
public void setValueFor(Object node, int column, Object value) {
if (!(node instanceof DatasetHandle))
return;
DatasetHandle handle = (DatasetHandle) node;
if (column == 0) {
handle.setLatticeSelected((Boolean) value);
}
if (column == 1) {
handle.setSelected((Boolean) value);
}
}
}
ExclusiveBooleanEditor class (modified copy of DefaultCellRenderer)
public class ExclusiveBooleanEditor extends AbstractCellEditor implements TableCellEditor,
TreeCellEditor {
//
// Instance Variables
//
/** The Swing component being edited. */
protected JComponent editorComponent;
/**
* The delegate class which handles all methods sent from the <code>CellEditor</code>.
*/
protected EditorDelegate delegate;
/**
* An integer specifying the number of clicks needed to start editing. Even if
* <code>clickCountToStart</code> is defined as zero, it will not initiate until a click occurs.
*/
protected int clickCountToStart = 1;
//
// Constructors
//
public ExclusiveBooleanEditor() {
this(new JRadioButton());
JRadioButton radioButton = (JRadioButton) getComponent();
radioButton.setHorizontalAlignment(JRadioButton.CENTER);
}
public ExclusiveBooleanEditor(final JRadioButton radioButton) {
editorComponent = radioButton;
delegate = new EditorDelegate() {
// FIXME replace
#Override
public void setValue(Object value) {
boolean selected = false;
if (value instanceof Boolean) {
selected = ((Boolean) value).booleanValue();
} else if (value instanceof String) {
selected = value.equals("true");
}
radioButton.setSelected(selected);
}
#Override
public Object getCellEditorValue() {
return Boolean.valueOf(radioButton.isSelected());
}
};
radioButton.addActionListener(delegate);
radioButton.setRequestFocusEnabled(false);
}
/**
* Returns a reference to the editor component.
*
* #return the editor <code>Component</code>
*/
public Component getComponent() {
return editorComponent;
}
//
// Modifying
//
/**
* Specifies the number of clicks needed to start editing.
*
* #param count an int specifying the number of clicks needed to start editing
* #see #getClickCountToStart
*/
public void setClickCountToStart(int count) {
clickCountToStart = count;
}
/**
* Returns the number of clicks needed to start editing.
*
* #return the number of clicks needed to start editing
*/
public int getClickCountToStart() {
return clickCountToStart;
}
//
// Override the implementations of the superclass, forwarding all methods
// from the CellEditor interface to our delegate.
//
/**
* Forwards the message from the <code>CellEditor</code> to the <code>delegate</code>.
*
* #see EditorDelegate#getCellEditorValue
*/
#Override
public Object getCellEditorValue() {
return delegate.getCellEditorValue();
}
/**
* Forwards the message from the <code>CellEditor</code> to the <code>delegate</code>.
*
* #see EditorDelegate#isCellEditable(EventObject)
*/
#Override
public boolean isCellEditable(EventObject anEvent) {
return delegate.isCellEditable(anEvent);
}
/**
* Forwards the message from the <code>CellEditor</code> to the <code>delegate</code>.
*
* #see EditorDelegate#shouldSelectCell(EventObject)
*/
#Override
public boolean shouldSelectCell(EventObject anEvent) {
return delegate.shouldSelectCell(anEvent);
}
/**
* Forwards the message from the <code>CellEditor</code> to the <code>delegate</code>.
*
* #see EditorDelegate#stopCellEditing
*/
#Override
public boolean stopCellEditing() {
return delegate.stopCellEditing();
}
/**
* Forwards the message from the <code>CellEditor</code> to the <code>delegate</code>.
*
* #see EditorDelegate#cancelCellEditing
*/
#Override
public void cancelCellEditing() {
delegate.cancelCellEditing();
}
//
// Implementing the TreeCellEditor Interface
//
/** Implements the <code>TreeCellEditor</code> interface. */
#Override
public Component getTreeCellEditorComponent(JTree tree, Object value, boolean isSelected,
boolean expanded, boolean leaf, int row) {
String stringValue = tree.convertValueToText(value, isSelected, expanded, leaf, row, false);
delegate.setValue(stringValue);
return editorComponent;
}
//
// Implementing the CellEditor Interface
//
/** Implements the <code>TableCellEditor</code> interface. */
#Override
public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected,
int row, int column) {
delegate.setValue(value);
if ((editorComponent instanceof JCheckBox) || (editorComponent instanceof JRadioButton)) {
// in order to avoid a "flashing" effect when clicking a checkbox
// in a table, it is important for the editor to have as a border
// the same border that the renderer has, and have as the background
// the same color as the renderer has. This is primarily only
// needed for JCheckBox since this editor doesn't fill all the
// visual space of the table cell, unlike a text field.
TableCellRenderer renderer = table.getCellRenderer(row, column);
Component c =
renderer.getTableCellRendererComponent(table, value, isSelected, true, row, column);
if (c != null) {
editorComponent.setOpaque(true);
editorComponent.setBackground(c.getBackground());
if (c instanceof JComponent) {
editorComponent.setBorder(((JComponent) c).getBorder());
}
} else {
editorComponent.setOpaque(false);
}
}
return editorComponent;
}
//
// Protected EditorDelegate class
//
/**
* The protected <code>EditorDelegate</code> class.
*/
protected class EditorDelegate implements ActionListener, ItemListener, Serializable {
/** The value of this cell. */
protected Object value;
/**
* Returns the value of this cell.
*
* #return the value of this cell
*/
public Object getCellEditorValue() {
return value;
}
/**
* Sets the value of this cell.
*
* #param value the new value of this cell
*/
public void setValue(Object value) {
this.value = value;
}
/**
* Returns true if <code>anEvent</code> is <b>not</b> a <code>MouseEvent</code>. Otherwise, it
* returns true if the necessary number of clicks have occurred, and returns false otherwise.
*
* #param anEvent the event
* #return true if cell is ready for editing, false otherwise
* #see #setClickCountToStart
* #see #shouldSelectCell
*/
public boolean isCellEditable(EventObject anEvent) {
if (anEvent instanceof MouseEvent) {
return ((MouseEvent) anEvent).getClickCount() >= clickCountToStart;
}
return true;
}
/**
* Returns true to indicate that the editing cell may be selected.
*
* #param anEvent the event
* #return true
* #see #isCellEditable
*/
public boolean shouldSelectCell(EventObject anEvent) {
return true;
}
/**
* Returns true to indicate that editing has begun.
*
* #param anEvent the event
*/
public boolean startCellEditing(EventObject anEvent) {
return true;
}
/**
* Stops editing and returns true to indicate that editing has stopped. This method calls
* <code>fireEditingStopped</code>.
*
* #return true
*/
public boolean stopCellEditing() {
fireEditingStopped();
return true;
}
/**
* Cancels editing. This method calls <code>fireEditingCanceled</code>.
*/
public void cancelCellEditing() {
fireEditingCanceled();
}
/**
* When an action is performed, editing is ended.
*
* #param e the action event
* #see #stopCellEditing
*/
#Override
public void actionPerformed(ActionEvent e) {
ExclusiveBooleanEditor.this.stopCellEditing();
}
/**
* When an item's state changes, editing is ended.
*
* #param e the action event
* #see #stopCellEditing
*/
#Override
public void itemStateChanged(ItemEvent e) {
ExclusiveBooleanEditor.this.stopCellEditing();
}
}
public static class ExclusiveBooleanRenderer extends JRadioButton implements TableCellRenderer,
UIResource {
private static final Border noFocusBorder = new EmptyBorder(1, 1, 1, 1);
private static final JLabel emptyLabel = new JLabel("");
public ExclusiveBooleanRenderer() {
super();
setHorizontalAlignment(JRadioButton.CENTER);
setBorderPainted(true);
}
#Override
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected,
boolean hasFocus, int row, int column) {
// Don't draw if it is not changeable
if (value == null) {
if (isSelected) {
emptyLabel.setForeground(table.getSelectionForeground());
emptyLabel.setBackground(table.getSelectionBackground());
} else {
emptyLabel.setForeground(table.getForeground());
emptyLabel.setBackground(table.getBackground());
}
return emptyLabel;
}
if (isSelected) {
setForeground(table.getSelectionForeground());
super.setBackground(table.getSelectionBackground());
} else {
setForeground(table.getForeground());
setBackground(table.getBackground());
}
setSelected((value != null && ((Boolean) value).booleanValue()));
if (hasFocus) {
setBorder(UIManager.getBorder("Table.focusCellHighlightBorder"));
} else {
setBorder(noFocusBorder);
}
return this;
}
}
} // End of class JCellEditor

JTable: How to render Boolean as Color, and edit as text using JTextField?

I tried to make Boolean renderer and Boolean editor.
The Boolean renderer should render Boolean values as colors (two colors).
The Boolean Editor should return a JTextField and enable editing as String "T" and "F"
So if you click the cell and type "T" or "F" the color of the cell must be shifted to the corresponding color.
Based on this oracle tutorial I tried to make my renderer and editor and include it with this oracle provided example.
Below Boolean renderer and Boolean editor. I registered them to this class.
....
....
table.setDefaultRenderer(Color.class,
new ColorRenderer(true));
table.setDefaultEditor(Color.class,
new ColorEditor());
table.setDefaultRenderer(Boolean.class, new BooleanRenderer()); // My
table.setDefaultEditor(Boolean.class, new BooleanEditor()); // My
//Add the scroll pane to this panel.
add(scrollPane);
....
....
The cells aren't rendered at all, and things doesn't work as expected!.
What is the wrong with my code?
My Renderer:
import java.awt.Color;
import java.awt.Component;
import javax.swing.JLabel;
import javax.swing.JTable;
import javax.swing.JTextField;
import javax.swing.table.TableCellEditor;
import javax.swing.table.TableCellRenderer;
public class BooleanRenderer extends JLabel implements TableCellRenderer
{
#Override
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column)
{
if (hasFocus)
{
Boolean bol = (Boolean) value;
if (bol == Boolean.FALSE)
{
this.setBackground(Color.red);
this.setText("");
} else if (bol == Boolean.TRUE)
{
this.setBackground(Color.BLACK);
}
}
else
{
Boolean bol = (Boolean) value;
if (bol == Boolean.FALSE)
{
this.setBackground(Color.red);
this.setText("");
} else if (bol == Boolean.TRUE)
{
this.setBackground(Color.BLACK);
}
}
if (isSelected)
{
Boolean bol = (Boolean) value;
if (bol == Boolean.FALSE)
{
this.setBackground(Color.red);
this.setText("");
} else if (bol == Boolean.TRUE)
{
this.setBackground(Color.BLACK);
}
} else
{
Boolean bol = (Boolean) value;
if (bol == Boolean.FALSE)
{
this.setBackground(Color.red);
this.setText("");
} else if (bol == Boolean.TRUE)
{
this.setBackground(Color.BLACK);
}
}
return this;
}
}
My Editor:
import java.awt.Component;
import javax.swing.AbstractCellEditor;
import javax.swing.JTable;
import javax.swing.JTextField;
import javax.swing.table.TableCellEditor;
public class BooleanEditor extends AbstractCellEditor
implements TableCellEditor
{
Boolean bool;
JTextField tf = new JTextField();
#Override
public Object getCellEditorValue()
{
return bool;
}
#Override
public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column)
{
if (isSelected)
{
bool = (Boolean) value;
if (tf.getText().equals("T"))
{
bool = new Boolean(true);
} else
{
if (tf.getText().equals("F"))
{
bool = new Boolean(false);
}
}
}
return tf;
}
}
For your renderer:
you must give it a constructor and inside of the constructor, set it to opaque:
public BooleanRenderer() {
setOpaque(true);
}
Else the JLabel won't display any background color at all.
For your JTable cell editor:
First of all, I must recommend against your design since leaving it up to the user to type in the correct text, "T" or "F" is too prone to user entry errors. Better to give him a choice such as by using a JComboBox.
Next, get rid of the if (isSelected) block in your editor. The editor will be called when needed, and selected will likely be false making your code non-functioning.
Most importantly: get rid of your editor's bool field. In the getCellEditorValue() method, query the component for its value and return the appropriate boolean based on this value.
For example:
public class BooleanEditor extends AbstractCellEditor implements TableCellEditor {
JComboBox<Boolean> combo = new JComboBox<Boolean>(new Boolean[] {
Boolean.TRUE, Boolean.FALSE });
public BooleanEditor() {
combo.setRenderer(new DefaultListCellRenderer() {
#Override
public Component getListCellRendererComponent(JList<?> list,
Object value, int index, boolean isSelected, boolean cellHasFocus) {
Boolean boolValue = (Boolean) value;
String displayString = "";
if (boolValue == null) {
displayString = "";
} else if (boolValue) {
displayString = "T";
} else {
displayString = "F";
}
return super.getListCellRendererComponent(list, displayString,
index, isSelected, cellHasFocus);
}
});
}
#Override
public Object getCellEditorValue() {
return combo.getSelectedItem();
}
#Override
public Component getTableCellEditorComponent(JTable table, Object value,
boolean isSelected, int row, int column) {
if (value == null) {
combo.setSelectedIndex(-1);
} else {
combo.setSelectedItem((Boolean) value);
}
return combo;
}
}
My entire SSCCE:
import java.awt.*;
import javax.swing.*;
import javax.swing.table.*;
public class BoolColorTable {
#SuppressWarnings("serial")
private static void createAndShowGui() {
// null just to test how code will react to this situation
// that hopefully won't occur
Boolean[][] data = { null, { true }, { false }, { true }, { false },
{ false }, { true } };
String[] colNames = { "Data" };
DefaultTableModel model = new DefaultTableModel(data, colNames);
JTable table = new JTable(model) {
#Override
public Class<?> getColumnClass(int column) {
if (column == 0) {
return Boolean.class;
}
return super.getColumnClass(column);
}
};
table.setDefaultRenderer(Boolean.class, new BooleanRenderer());
table.setDefaultEditor(Boolean.class, new BooleanEditor());
JScrollPane mainPanel = new JScrollPane(table);
JFrame frame = new JFrame("BoolColorTable");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(mainPanel);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGui();
}
});
}
}
#SuppressWarnings("serial")
class BooleanRenderer extends JLabel implements TableCellRenderer {
public BooleanRenderer() {
setOpaque(true);
}
#Override
public Component getTableCellRendererComponent(JTable table, Object value,
boolean isSelected, boolean hasFocus, int row, int column) {
Boolean boolValue = (Boolean) value;
if (boolValue == null) {
setBackground(null);
} else {
if (!boolValue) {
this.setBackground(Color.red);
} else {
this.setBackground(Color.BLACK);
}
}
return this;
}
}
#SuppressWarnings("serial")
class BooleanEditor extends AbstractCellEditor implements TableCellEditor {
JComboBox<Boolean> combo = new JComboBox<Boolean>(new Boolean[] {
Boolean.TRUE, Boolean.FALSE });
public BooleanEditor() {
combo.setRenderer(new DefaultListCellRenderer() {
#Override
public Component getListCellRendererComponent(JList<?> list,
Object value, int index, boolean isSelected, boolean cellHasFocus) {
Boolean boolValue = (Boolean) value;
String displayString = "";
if (boolValue == null) {
displayString = "";
} else if (boolValue) {
displayString = "T";
} else {
displayString = "F";
}
return super.getListCellRendererComponent(list, displayString,
index, isSelected, cellHasFocus);
}
});
}
#Override
public Object getCellEditorValue() {
return combo.getSelectedItem();
}
#Override
public Component getTableCellEditorComponent(JTable table, Object value,
boolean isSelected, int row, int column) {
if (value == null) {
combo.setSelectedIndex(-1);
} else {
combo.setSelectedItem((Boolean) value);
}
return combo;
}
}
Edit
If you absolutely need to allow the user to be able to enter "T" or "F", then you can do this and make it mostly idiot proof by adding a DocumentFilter to the editor's JTextField that will allow only three case insensitive possible text field entries: "t", "f", or empty.
For example:
#SuppressWarnings("serial")
class BooleanStringEditor extends AbstractCellEditor implements TableCellEditor {
private JTextField textField = new JTextField();
public BooleanStringEditor() {
PlainDocument doc = (PlainDocument) textField.getDocument();
doc.setDocumentFilter(new MyDocFilter());
}
#Override
public Object getCellEditorValue() {
if (textField.getText().equalsIgnoreCase("t")) {
return Boolean.TRUE;
} else if (textField.getText().equalsIgnoreCase("f")) {
return Boolean.FALSE;
}
// default if user messes up
return null;
}
#Override
public Component getTableCellEditorComponent(JTable table, Object value,
boolean isSelected, int row, int column) {
Boolean boolValue = (Boolean) value;
if (boolValue == null) {
textField.setText("");
} else if (boolValue) {
textField.setText("T");
} else {
textField.setText("F");
}
return textField;
}
}
class MyDocFilter extends DocumentFilter {
private boolean test(String text) {
if (text.isEmpty()) {
return true;
}
if (text.equalsIgnoreCase("t") || text.equalsIgnoreCase("f")) {
return true;
}
return false;
}
#Override
public void insertString(FilterBypass fb, int offset, String string,
AttributeSet attr) throws BadLocationException {
Document doc = fb.getDocument();
StringBuilder sb = new StringBuilder();
sb.append(doc.getText(0, doc.getLength()));
sb.insert(offset, string);
if (test(sb.toString())) {
super.insertString(fb, offset, string, attr);
} else {
// warn the user and don't allow the insert
}
}
#Override
public void replace(FilterBypass fb, int offset, int length, String text,
AttributeSet attrs) throws BadLocationException {
Document doc = fb.getDocument();
StringBuilder sb = new StringBuilder();
sb.append(doc.getText(0, doc.getLength()));
sb.replace(offset, offset + length, text);
if (test(sb.toString())) {
super.replace(fb, offset, length, text, attrs);
} else {
// warn the user and don't allow the insert
}
}
#Override
public void remove(FilterBypass fb, int offset, int length)
throws BadLocationException {
Document doc = fb.getDocument();
StringBuilder sb = new StringBuilder();
sb.append(doc.getText(0, doc.getLength()));
sb.delete(offset, offset + length);
if (test(sb.toString())) {
super.remove(fb, offset, length);
} else {
// warn the user and don't allow the insert
}
}
}

How can I select a table row with a single click using a custom TableCellEditor?

I have a JTable with a custom TableCellRenderer and a custom TableCellEditor. By default, the first click on a table row switch from renderer to editor and the second click select the row.
Is there any way I can make the row selected on a single click (and swith to the editor)?
I have tried to use:
table.getSelectionModel().setSelectionInterval(row, row);
in my getTableCellEditorComponent but it doesn't work, and if I add it to my getTableCellRendererComponent it works, but only sometimes.
Here is a full example:
public class SelectRowDemo extends JFrame {
public SelectRowDemo() {
CellRendererAndEditor rendererAndEditor = new CellRendererAndEditor();
StringTableModel model = new StringTableModel();
JTable table = new JTable(model);
table.setDefaultEditor(String.class, rendererAndEditor);
table.setDefaultRenderer(String.class, rendererAndEditor);
model.addElement("");
model.addElement("");
model.addElement("");
add(new JScrollPane(table));
pack();
setDefaultCloseOperation(EXIT_ON_CLOSE);
setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel("javax.swing.plaf.nimbus.NimbusLookAndFeel");
} catch (Exception e) {
e.printStackTrace();
}
new SelectRowDemo();
}
});
}
class CellRendererAndEditor extends AbstractCellEditor implements TableCellEditor, TableCellRenderer {
private final JLabel renderer = new JLabel();
private final JLabel editor = new JLabel();
#Override
public Object getCellEditorValue() {
return editor.getText();
}
#Override
public Component getTableCellRendererComponent(JTable table, Object value,
boolean isSelected, boolean hasFocus, int row, int column) {
String str = "renderer ";
str += (isSelected) ? "selected" : "not selected";
renderer.setText(str);
return renderer;
}
#Override
public Component getTableCellEditorComponent(JTable table, Object value,
boolean isSelected, int row, int column) {
table.getSelectionModel().setSelectionInterval(row, row);
String str = "editor ";
str += (isSelected) ? "selected" : "not selected";
editor.setText(str);
return editor;
}
}
class StringTableModel extends AbstractTableModel {
private final List<String> data = new ArrayList<String>();
#Override
public int getColumnCount() {
return 1;
}
#Override
public int getRowCount() {
return data.size();
}
#Override
public Object getValueAt(int row, int column) {
return data.get(row);
}
#Override
public Class<?> getColumnClass(int column) {
return String.class;
}
#Override
public boolean isCellEditable(int row, int column) {
return true;
}
#Override
public void setValueAt(Object aValue, int row, int column) {
if(aValue instanceof String) {
data.set(row, (String)aValue);
fireTableRowsUpdated(row, column);
} else throw new IllegalStateException("aValue is not a String");
}
public void addElement(String s) {
data.add(s);
}
}
}
What's happening is that the table UI is starting to edit the cell before changing selection. If you put a println in getTableCellEditor() and shouldSelectCell(), the editor gets called first, with isSelected == false, then it calls shouldSelectCell and changes the selection.
You can see exactly where it happens in BasicTableUI.adjustSelection(MouseEvent).
boolean dragEnabled = table.getDragEnabled();
if (!dragEnabled && !isFileList && table.editCellAt(pressedRow, pressedCol, e)) {
setDispatchComponent(e);
repostEvent(e);
}
CellEditor editor = table.getCellEditor();
if (dragEnabled || editor == null || editor.shouldSelectCell(e)) {
table.changeSelection(pressedRow, pressedCol,
BasicGraphicsUtils.isMenuShortcutKeyDown(e),
e.isShiftDown());
}
As for rendering purposes, I'd just render it as if selected == true, since it will before that event is finished processing.

hyperlinks in JEditorPane in a JTable

I swear... i hope this is the last question I have to ask like this, but I'm about to go crazy.
I've got a JTable using a custom TableCellRenderer which uses a JEditorPane to display html in the individual cells of the JTable. How do I process clicking on the links displayed in the JEditorPane?
I know about HyperlinkListener but no mouse events get through the JTable to the EditorPane for any HyperlinkEvents to be processed.
How do I process Hyperlinks in a JEditorPane within a JTable?
The EditorPane isn't receiving any events because the component returned from the TableCellRenderer is only allowed to display, and not intercept events, making it pretty much the same as an image, with no behaviour allowed on it. Hence even when listeners are registered, the returned component is never 'aware' of any events. The work-around for this is to register a MouseListener on the JTable, and intercept all relevant events from there.
Here's some classes I created in the past for allowing JButton roll-over to work in a JTable, but you should be able to re-use most of this for your problem too. I had a separate JButton for every cell requiring it. With that, this ActiveJComponentTableMouseListener works out in which cell the mouse event occurs in, and dispatches an event to the corresponding component. It's the job of the ActiveJComponentTableCellRenderer to keep track of the components via a Map.
It's smart enough to know when it's already fired events, so you don't get a backlog of redundant events. Implementing this for hypertext shouldn't be that different, and you may still want roll-over too. Here are the classes
public class ActiveJComponentTableMouseListener extends MouseAdapter implements MouseMotionListener {
private JTable table;
private JComponent oldComponent = null;
private TableCell oldTableCell = new TableCell();
public ActiveJComponentTableMouseListener(JTable table) {
this.table = table;
}
#Override
public void mouseMoved(MouseEvent e) {
TableCell cell = new TableCell(getRow(e), getColumn(e));
if (alreadyVisited(cell)) {
return;
}
save(cell);
if (oldComponent != null) {
dispatchEvent(createMouseEvent(e, MouseEvent.MOUSE_EXITED), oldComponent);
oldComponent = null;
}
JComponent component = getComponent(cell);
if (component == null) {
return;
}
dispatchEvent(createMouseEvent(e, MouseEvent.MOUSE_ENTERED), component);
saveComponent(component);
save(cell);
}
#Override
public void mouseExited(MouseEvent e) {
TableCell cell = new TableCell(getRow(e), getColumn(e));
if (alreadyVisited(cell)) {
return;
}
if (oldComponent != null) {
dispatchEvent(createMouseEvent(e, MouseEvent.MOUSE_EXITED), oldComponent);
oldComponent = null;
}
}
#Override
public void mouseEntered(MouseEvent e) {
forwardEventToComponent(e);
}
private void forwardEventToComponent(MouseEvent e) {
TableCell cell = new TableCell(getRow(e), getColumn(e));
save(cell);
JComponent component = getComponent(cell);
if (component == null) {
return;
}
dispatchEvent(e, component);
saveComponent(component);
}
private void dispatchEvent(MouseEvent componentEvent, JComponent component) {
MouseEvent convertedEvent = (MouseEvent) SwingUtilities.convertMouseEvent(table, componentEvent, component);
component.dispatchEvent(convertedEvent);
// This is necessary so that when a button is pressed and released
// it gets rendered properly. Otherwise, the button may still appear
// pressed down when it has been released.
table.repaint();
}
private JComponent getComponent(TableCell cell) {
if (rowOrColumnInvalid(cell)) {
return null;
}
TableCellRenderer renderer = table.getCellRenderer(cell.row, cell.column);
if (!(renderer instanceof ActiveJComponentTableCellRenderer)) {
return null;
}
ActiveJComponentTableCellRenderer activeComponentRenderer = (ActiveJComponentTableCellRenderer) renderer;
return activeComponentRenderer.getComponent(cell);
}
private int getColumn(MouseEvent e) {
TableColumnModel columnModel = table.getColumnModel();
int column = columnModel.getColumnIndexAtX(e.getX());
return column;
}
private int getRow(MouseEvent e) {
int row = e.getY() / table.getRowHeight();
return row;
}
private boolean rowInvalid(int row) {
return row >= table.getRowCount() || row < 0;
}
private boolean rowOrColumnInvalid(TableCell cell) {
return rowInvalid(cell.row) || columnInvalid(cell.column);
}
private boolean alreadyVisited(TableCell cell) {
return oldTableCell.equals(cell);
}
private boolean columnInvalid(int column) {
return column >= table.getColumnCount() || column < 0;
}
private MouseEvent createMouseEvent(MouseEvent e, int eventID) {
return new MouseEvent((Component) e.getSource(), eventID, e.getWhen(), e.getModifiers(), e.getX(), e.getY(), e.getClickCount(), e.isPopupTrigger(), e.getButton());
}
private void save(TableCell cell) {
oldTableCell = cell;
}
private void saveComponent(JComponent component) {
oldComponent = component;
}}
public class TableCell {
public int row;
public int column;
public TableCell() {
}
public TableCell(int row, int column) {
this.row = row;
this.column = column;
}
#Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final TableCell other = (TableCell) obj;
if (this.row != other.row) {
return false;
}
if (this.column != other.column) {
return false;
}
return true;
}
#Override
public int hashCode() {
int hash = 7;
hash = 67 * hash + this.row;
hash = 67 * hash + this.column;
return hash;
}}
public class ActiveJComponentTableCellRenderer<T extends JComponent> extends AbstractCellEditor implements TableCellEditor, TableCellRenderer {
private Map<TableCell, T> components;
private JComponentFactory<T> factory;
public ActiveJComponentTableCellRenderer() {
this.components = new HashMap<TableCell, T>();
}
public ActiveJComponentTableCellRenderer(JComponentFactory<T> factory) {
this();
this.factory = factory;
}
public T getComponent(TableCell key) {
T component = components.get(key);
if (component == null && factory != null) {
// lazy-load component
component = factory.build();
initialiseComponent(component);
components.put(key, component);
}
return component;
}
/**
* Override this method to provide custom component initialisation code
* #param component passed in component from getComponent(cell)
*/
protected void initialiseComponent(T component) {
}
#Override
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
return getComponent(new TableCell(row, column));
}
#Override
public Object getCellEditorValue() {
return null;
}
#Override
public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) {
return getComponent(new TableCell(row, column));
}
public void setComponentFactory(JComponentFactory factory) {
this.factory = factory;
}}
public interface JComponentFactory<T extends JComponent> {
T build();
}
To use it, you want to register the listener to as mouse and motion listener on the table, and register the renderer on the appropriate cells. If you want to intercept actionPerformed type events, override ActiveJComponentTableCellRenderer.initialiseComponent() like so:
protected void initialiseComponent(T component) {
component.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
stopCellEditing();
}
});
}
If you register a MouseListener on the JTable, you could easily get the text at the mouse click point. This would be done by generating a Point object from the MouseEvent, using event.getX() and event.getY(). You then pass that Point into JTable's rowAtPoint(pt) and columnAtPoint(pt). From there, you can get the text via JTable.getValueAt(row, column). Now you have the value of your cell, so you can determine whether it is a link or not and do what you'd like with the result.
To solve the same problem, instead of trying have the JEditorPane produce the event, I instead processed the MouseEvent produced by the JTable, had the listener figure out when we were clicking on a link or not.
Here's code. It keeps a map of JEditorPanes, so do make sure you don't have memory leaks, and that you clear this map appropriately if the data in the table can change. It's slightly modified from the code I actually used - in the version I actually used, it only only produced JEditorPane when actually links were in the html, and didn't bother with JEditorPanes when no such links existed...
public class MessageWithPossibleHtmlLinksRenderer extends DefaultTableCellRenderer {
private final Map<Integer, JEditorPane> editorPanes = new HashMap<>();
public MessageWithPossibleHtmlLinksRenderer(JTable table) {
// register mouseAdapter to table for link-handling
table.addMouseMotionListener(this.mouseAdapter);
table.addMouseListener(this.mouseAdapter);
}
private JEditorPane getOrCreateEditorPane(int row, int col) {
final int key = combine(row, col);
JEditorPane jep = editorPanes.get(key);
if (jep == null) {
jep = new JEditorPane();
jep.putClientProperty(JEditorPane.HONOR_DISPLAY_PROPERTIES, Boolean.TRUE);
jep.setContentType("text/html");
jep.setEditable(false);
jep.setOpaque(true);
editorPanes.put(key, jep);
}
return jep;
}
private static int combine(int row, int col) {
return row * 10 + col; // works for up to 10 columns
}
#Override
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
// modify here if you want JEditorPane only when links exist
if (value instanceof String && ((String) value).startsWith("<html>")) {
final JEditorPane jep = getOrCreateEditorPane(row, column);
jep.setText((String) value);
// code to adjust row height
return jep;
} else {
return super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
}
}
private AttributeSet anchorAt(MouseEvent e) {
// figure out the JEditorPane we clicked on, or moved over, if any
final JTable table = (JTable) e.getSource();
final Point p = e.getPoint();
final int row = table.rowAtPoint(p);
final int col = table.columnAtPoint(p);
final int key = combine(row, col);
final JEditorPane pane = this.editorPanes.get(key);
if (pane == null) {
return null;
}
// figure out the exact link, if any
final Rectangle r = table.getCellRect(row, col, false);
final Point relativePoint = new Point((int) (p.getX() - r.x), (int) (p.getY() - r.y));
pane.setSize(r.getSize()); // size the component to the size of the cell
final int pos = pane.viewToModel(relativePoint);
if (pos >= 0) {
final Document doc = pane.getDocument();
if (doc instanceof HTMLDocument) {
final Element el = ((HTMLDocument) doc).getCharacterElement(pos);
return (AttributeSet) el.getAttributes().getAttribute(HTML.Tag.A);
}
}
return null;
}
private final MouseAdapter mouseAdapter = new MouseAdapter() {
#Override
public void mouseMoved(MouseEvent e) {
final AttributeSet anchor = anchorAt(e);
final Cursor cursor = anchor == null
? Cursor.getDefaultCursor()
: Cursor.getPredefinedCursor(Cursor.HAND_CURSOR);
final JTable table = (JTable) e.getSource();
if (table.getCursor() != cursor) {
table.setCursor(cursor);
}
}
#Override
public void mouseClicked(MouseEvent e) {
if (! SwingUtilities.isLeftMouseButton(e)) {
return;
}
final AttributeSet anchor = anchorAt(e);
if (anchor != null) {
try {
String href = (String) anchor.getAttribute(HTML.Attribute.HREF);
Desktop.getDesktop().browse(new URL(href).toURI());
} catch (Exception ex) {
// ignore
}
}
}
};
}

Categories