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
Related
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.
Recently I have created two very simple, yet very useful components called AttributesEditableView and AttributesEditor. AttributesEditableView is designed to just show arbitrary number of attributes, while the AttributesEditor makes it possible to edit attributes.
So, say we have three attributes that can take values 'Y', 'N' and '?' (where by '?' we mean "unspecified"). The view looks something like [[Y][N][?]]:
In the editor, user can use arrow-keys to jump left-right between attributes, and by pressing the SPACE key toggle the value of particular (currently selected) attribute.
(I guess those of you who are familiar with JTable and its peculiarities already understand when am I getting to right now).
It all works well in a form, but when I want to make a TableCellEditor out of this AttributesEditor, I get stuck, because JTable either "eats" all events, or there is something that I am doing wrong so my AttributesEditor simply does not get events... :(
My logic is the following: how come DefaultCellEditor when the editor component is JTextField gets those events (left-key or right-key presses for example), while my cell-editor does not?
I know I am not giving all the classes here, but I believe the two classes below should give those who have already encountered this problem enough information to be able to tell me how to make this cell editor work as expected?
UPDATE: I managed to fix the problem by refactoring the AttributesEditor class. Now it uses key-bindings instead of the KeyListener and everything works as expected. Lesson learned...
You can see the AttributesEditableView, AttributesEditor and AttributesCellEditor in action on the following screen shot (I also have the renderer, but could not bother to put it on, it looks like AttributesEditableView anyway):
In the screenshot above, first attribute may have 'Y', 'N' and '?' values, second one 'A', 'B', 'C', and 'D', and the third attribute may have 'T' and 'F' as values.
Here are the classes:
AttributesCellEditor
/**
* Authors: Dejan Lekic , http://dejan.lekic.org
* License: MIT
*/
public class AttributesCellEditor extends AbstractCellEditor
implements TableCellEditor, TreeCellEditor {
private AttributesEditor attributesEditor;
private AttributesColumnModel attrColModel;
private AttributesModel model;
private int clickCountToStart = 2;
DefaultCellEditor dfe;
public AttributesCellEditor(AttributesColumnModel argModel) {
super();
attrColModel = argModel;
model = new AttributesModel(attrColModel.getAllowedValues());
model.setDefaultValues(attrColModel.getDefaultValues());
attributesEditor = new AttributesEditor(model);
attributesEditor.setBorder(new BevelBorder(BevelBorder.LOWERED));
}
// ::::: CellEditor method implementations :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
#Override
public Object getCellEditorValue() {
System.out.println("getCellEditorValue()");
// before we return the value, we should also modify the attributes column model and set the
// selected attribute index to the index of what we last edited.
attrColModel.setSelectedAttributeIndex(model.getSelectedAttributeIndex());
return model.getAttributes();
}
// ::::: TableCellEditor method implementations ::::::::::::::::::::::::::::::::::::::::::::::::::::::::
#Override
public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row,
int column) {
String val = (value == null) ? "" : value.toString();
model.setAttributes(val);
model.setSelectedAttributeIndex(attrColModel.getSelectedAttributeIndex());
attributesEditor.setModel(model);
return attributesEditor;
}
// ::::: TreeCellEditor method implementations ::::::::::::::::::::::::::::::::::::::::::::::::::::::::
// TODO: implement these
#Override
public Component getTreeCellEditorComponent(JTree tree, Object value, boolean isSelected,
boolean expanded, boolean leaf, int row) {
//To change body of generated methods, choose Tools | Templates.
throw new UnsupportedOperationException("Not supported yet.");
}
// ::::: AbstractCellEditor method overrides ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
#Override
public boolean isCellEditable(EventObject e) {
if (e instanceof MouseEvent) {
return ((MouseEvent) e).getClickCount() >= clickCountToStart;
}
return true;
}
/**
* {#inheritDoc}
* #return
*/
#Override
public boolean stopCellEditing() {
boolean ret = super.stopCellEditing();
return ret;
}
} // AttributesCellEditor class
AttributesEditor
/**
* Authors: Dejan Lekic , http://dejan.lekic.org
* License: MIT
*/
public class AttributesEditor
extends AttributesEditableView
implements PropertyChangeListener, FocusListener, KeyListener {
public AttributesEditor() {
super();
editorComponent = true;
setFocusable(true);
setBackground(Color.white);
// Let's copy the border from the TextField
Border b = (Border) UIManager.getLookAndFeelDefaults().get("TextField.border");
setBorder(b);
// Let's copy the insets from the TextField
Insets insets = (Insets) UIManager.getLookAndFeelDefaults().get("TextField.margin");
getInsets().set(insets.top, insets.left, insets.bottom, insets.right);
addKeyListener(this);
// listeners...
addFocusListener(this);
}
public AttributesEditor(AttributesModel argModel) {
this();
setOpaque(true);
setFocusable(true);
setBackground(Color.white);
repaint();
setModel(argModel);
}
// :::: PropertyChangeListener method implementations :::::::::::::::::::::::::::::::::::::::::::::::::
#Override
public void propertyChange(PropertyChangeEvent evt) {
String str = (String) evt.getNewValue();
System.out.println("CALL: AttributesEditor.propertyChange() : " + str);
updateView();
}
// :::: FocusListener method implementations ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
#Override
public void focusGained(FocusEvent e) {
int sel = model.getSelectedAttributeIndex();
if (sel < 0) {
model.setSelectedAttributeIndex(0);
}
sel = model.getSelectedAttributeIndex();
labels[sel].setBackground(model.getSelectedBackgroundColor());
}
#Override
public void focusLost(FocusEvent e) {
model.setSelectedAttributeIndex(-1);
updateView();
}
// :::: KeyListener method implementations ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
#Override
public void keyTyped(KeyEvent e) {
// Weird thing is, arrow keys do not trigger keyTyped() to be called,
// so we have to use keyPressed here, or keyReleased
}
/**
* Weird thing is, arrow keys do not trigger keyTyped() to be called, so we have to use keyPressed/keyReleased here.
* #param e
*/
#Override
public void keyPressed(KeyEvent e) {
int key = e.getKeyCode();
switch (key) {
case KeyEvent.VK_UP:
model.previous(model.getSelectedAttributeIndex());
break;
case KeyEvent.VK_DOWN:
case KeyEvent.VK_SPACE:
model.next(model.getSelectedAttributeIndex());
break;
case KeyEvent.VK_LEFT:
model.previousAttribute();
updateView();
break;
case KeyEvent.VK_RIGHT:
model.nextAttribute();
updateView();
break;
default:
int idx = model.getSelectedAttributeIndex();
char chr = Character.toUpperCase(e.getKeyChar());
if (model.isValid(idx, chr)) {
model.setValue(idx, chr);
}
} // switch
} // keyPressed() method
#Override
public void keyReleased(KeyEvent e) {
// nothing
}
} // AttributesEditor class
AttributesEditableView
/**
* Authors: Dejan Lekic , http://dejan.lekic.org
* License: MIT
*/
public class AttributesEditableView
extends JComponent
implements PropertyChangeListener, MouseListener {
protected AttributesModel model;
protected JLabel[] labels;
protected boolean editorComponent;
protected boolean inTable;
public AttributesEditableView() {
super();
FlowLayout fl = new FlowLayout(FlowLayout.LEFT);
fl.setVgap(0);
setLayout(fl);
editorComponent = false;
inTable = false;
}
public AttributesEditableView(AttributesModel argModel) {
this();
setModel(argModel);
}
// :::: JComponent method overrides :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
/**
* I had to do this because it is a common problem with JComponent subclasses.
* More about it: http://docs.oracle.com/javase/tutorial/uiswing/painting/problems.html
*
* #param g
*/
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(getBackground());
g.fillRect(0, 0, getWidth(), getHeight());
g.setColor(getForeground());
}
// :::: PropertyChangeListener mthod implementations ::::::::::::::::::::::::::::::::::::::::::::::::::
#Override
public void propertyChange(PropertyChangeEvent evt) {
String str = (String) evt.getNewValue();
updateView();
}
// :::: <Interface> method implementations ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
#Override
public void mouseClicked(MouseEvent e) {
// labels have names in form of "attributelbl-3", we need to extract the ID from the name
String num = e.getComponent().getName().split("-")[1];
int idx = Integer.parseInt(num);
if (editorComponent) {
model.setSelectedAttributeIndex(idx);
}
model.next(idx);
} // mouseClicked() method
#Override
public void mousePressed(MouseEvent e) {
// do nothing
}
#Override
public void mouseReleased(MouseEvent e) {
// do nothing
}
#Override
public void mouseEntered(MouseEvent e) {
// labels have names in form of "attributelbl-3", we need to extract the ID from the name
String num = e.getComponent().getName().split("-")[1];
int idx = Integer.parseInt(num);
model.setSelectedAttributeIndex(idx);
updateView();
}
#Override
public void mouseExited(MouseEvent e) {
// labels have names in form of "attributelbl-3", we need to extract the ID from the name
String num = e.getComponent().getName().split("-")[1];
int idx = Integer.parseInt(num);
if (!editorComponent) {
model.setSelectedAttributeIndex(-1);
}
updateView();
}
/**
* Use this method to forcefully highlight specific attribute.
* #param argIndex
*/
public final void highlight(int argIndex) {
for (int i = 0; i < model.getNumberOfAttributes(); i++) {
if (i == argIndex) {
labels[i].setBackground(model.getSelectedBackgroundColor());
} else {
labels[i].setBackground(model.getBackgroundColor());
}
} // for
} // highlight() method
/**
* Extremely important method. Here we set the model, and generate JLabel objects for the attributes.
* We also set the values, and initialise the listeners.
* #param argModel
*/
public final void setModel(AttributesModel argModel) {
if (model != null) {
model.removePropertyChangeListener(this);
}
labels = null;
removeAll();
labels = new JLabel[argModel.getNumberOfAttributes()];
int w = 0;
int h = 0;
for (int i = 0; i < argModel.getNumberOfAttributes(); i++) {
String txt = "" + argModel.getValue(i);
System.out.println(txt);
JLabel lbl = new JLabel(txt);
labels[i] = lbl;
lbl.setName("attributelbl-" + i); // very important, because we will use the name to get the idx
lbl.setHorizontalAlignment(SwingConstants.CENTER);
lbl.setOpaque(true);
lbl.setBackground(argModel.getBackgroundColor());
if (isInTable()) {
lbl.setBorder(BorderFactory.createMatteBorder(0, 0, 0, 1, Color.GRAY));
}
int nh = lbl.getPreferredSize().height;
int nw = nh; // make the width to be equal to the height
lbl.setPreferredSize(new Dimension(nw, nh));
lbl.addMouseListener(this);
h = Math.max(h, lbl.getPreferredSize().height);
w = w + lbl.getPreferredSize().width;
add(lbl);
} // for
Dimension ps = new Dimension(w, h);
model = argModel;
model.addPropertyChangeListener(this);
} // setModel() method
public final AttributesModel getModel() {
return model;
}
public boolean isInTable() {
return inTable;
}
public void setInTable(boolean inTable) {
this.inTable = inTable;
}
/**
* Updates the view
*/
protected void updateView() {
String attrs = model.getAttributes();
for (int i = 0; i < model.getNumberOfAttributes(); i++) {
labels[i].setText("" + model.getValue(i));
if (model.getSelectedAttributeIndex() == i) {
labels[i].setBackground(model.getSelectedBackgroundColor());
} else {
labels[i].setBackground(model.getBackgroundColor());
}
}
}
} // AttributesEditableView class
I faced the same problem as mentioned in the following SO question Wrap multiple lines in JTable. and I found Multile cell rendered to do that job. Now my problem is after implementing the cell renderer my cell is not showing wrapped data. I have custom tableModel and I am not sure how to call datavalidator on that model. Could anyone please suggest me.
My Table model:
public class KeywordTableModel extends AbstractTableModel {
private static final long serialVersionUID = 1L;
Logger logger = Logger.getLogger(KeywordTableModel.class.getName());
KeywordList keywordList ;
public KeywordTableModel(KeywordList keywordList){
this.keywordList = keywordList;
}
#Override
public int getRowCount() {
// TODO Auto-generated method stub
return keywordList.getKeywords().size();
}
#Override
public int getColumnCount() {
// TODO Auto-generated method stub
return keywordList.getTitles().length;
}
#Override
public Object getValueAt(int rowIndex, int columnIndex) {
// TODO Auto-generated method stub
TypeRec objectRec = (TypeRec) keywordList.getKeywords().elementAt(rowIndex);
return objectRec.getColumnData(columnIndex);
}
#Override
public String getColumnName(int column){
return keywordList.getTitles()[column];
}
public void setDataValidator(){
}
}
My cell renderer is :
/**
* Multiline Table Cell Renderer.
*/
public class MultiLineTableCellRenderer extends JTextArea
implements TableCellRenderer {
/**
*
*/
Logger logger = Logger.getLogger(MultiLineTableCellRenderer.class.getName());
private static final long serialVersionUID = 1L;
private List<List<Integer>> rowColHeight = new ArrayList<List<Integer>>();
public MultiLineTableCellRenderer() {
setLineWrap(true);
setWrapStyleWord(true);
setOpaque(true);
logger.debug("inside multilinetablecellrenderer constructor");
}
public Component getTableCellRendererComponent(
JTable table, Object value, boolean isSelected, boolean hasFocus,
int row, int column) {
logger.debug("inside multilinetablecellrenderer renderer");
if (isSelected) {
setForeground(table.getSelectionForeground());
setBackground(table.getSelectionBackground());
} else {
setForeground(table.getForeground());
setBackground(table.getBackground());
}
setFont(table.getFont());
if (hasFocus) {
setBorder(UIManager.getBorder("Table.focusCellHighlightBorder"));
if (table.isCellEditable(row, column)) {
setForeground(UIManager.getColor("Table.focusCellForeground"));
setBackground(UIManager.getColor("Table.focusCellBackground"));
}
} else {
setBorder(new EmptyBorder(1, 2, 1, 2));
}
if (value != null) {
setText(value.toString());
} else {
setText("");
}
adjustRowHeight(table, row, column);
return this;
}
/**
* Calculate the new preferred height for a given row, and sets the height on the table.
*/
private void adjustRowHeight(JTable table, int row, int column) {
//The trick to get this to work properly is to set the width of the column to the
//textarea. The reason for this is that getPreferredSize(), without a width tries
//to place all the text in one line. By setting the size with the with of the column,
//getPreferredSize() returnes the proper height which the row should have in
//order to make room for the text.
logger.debug("inside adjustRowheight method for adjusting the row height");
int cWidth = table.getTableHeader().getColumnModel().getColumn(column).getWidth();
setSize(new Dimension(cWidth, 1000));
int prefH = getPreferredSize().height;
while (rowColHeight.size() <= row) {
rowColHeight.add(new ArrayList<Integer>(column));
}
List<Integer> colHeights = rowColHeight.get(row);
while (colHeights.size() <= column) {
colHeights.add(0);
}
colHeights.set(column, prefH);
int maxH = prefH;
for (Integer colHeight : colHeights) {
if (colHeight > maxH) {
maxH = colHeight;
}
}
if (table.getRowHeight(row) != maxH) {
table.setRowHeight(row, maxH);
}
}
}
and I am setting my cell renderer as
cnr_DATA.setDefaultRenderer(String.class, new MultiLineTableCellRenderer());
The program is still not wrapping data in multiple line.
Absent a complete example, I'm guessing you need to override getColumnClass() in your TableModel to return the same type token that you specified in setDefaultRenderer(), i.e. String.class. Note that the AbstractTableModel implementation returns Object.class unconditionally.
I have a problem with the following code. My task is, I have to have radio buttons in the first column and when a user selects that radio button that row is selected and sent for processing. But my problem is, I am able to select the radio button which are in the first column, but afterwards when user clicks in any part of the table then my clicked radio button is being unchecked. I am not able to figure, why it is happeneing. I am really stuck with this problem. Help required. The following code shows my problem.
import java.awt.Component;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.util.ArrayList;
import java.util.List;
import javax.swing.ButtonGroup;
import javax.swing.DefaultCellEditor;
import javax.swing.JCheckBox;
import javax.swing.JDialog;
import javax.swing.JPanel;
import javax.swing.JRadioButton;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.SwingUtilities;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.TableCellRenderer;
public class DisplayTable extends JDialog {
public void initialize() {
SourceTableModel stm = new SourceTableModel();
JTable sourceTable = new JTable(stm);
sourceTable.getColumnModel().getColumn(0).setCellRenderer(new RadioButtonRenderer());
sourceTable.getColumnModel().getColumn(0).setCellEditor(new RadioButtonEditor(new JCheckBox ()));
JPanel panel = new JPanel();
panel.add(new JScrollPane(sourceTable));
add(panel);
setModal(true);
pack();
setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
new DisplayTable().initialize();
}
});
}
}
class SourceTableModel extends AbstractTableModel{
private static final long serialVersionUID = 1L;
private List<SourceModel> sourceList = new ArrayList<SourceModel>();
private String[] columnNamesList = {"Select", "Group", "Work"};
public SourceTableModel() {
this.sourceList = getSourceDOList();
}
public String getColumnName(int column) {
return columnNamesList[column];
}
public int getRowCount() {
return sourceList.size();
}
public int getColumnCount() {
return columnNamesList.length;
}
public Class<?> getColumnClass(int columnIndex) {
return (columnIndex == 0 ? Boolean.class : String.class);
}
public boolean isCellEditable(int rowIndex, int columnIndex) {
return (columnIndex == 0 ? true : false);
}
public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
SourceModel model = (SourceModel) sourceList.get(rowIndex);
switch (columnIndex) {
case 0:
model.setSelect((Boolean)aValue);
break;
case 1:
model.setFactory((String) aValue);
break;
case 2:
model.setSupplier((String) aValue);
break;
}
fireTableCellUpdated(rowIndex, columnIndex);
}
public Object getValueAt(int rowIndex, int columnIndex) {
SourceModel source = sourceList.get(rowIndex);
switch(columnIndex){
case 0:
return source.isSelect();
case 1:
return source.getFactory();
case 2:
return source.getSupplier();
default:
return null;
}
}
private List<SourceModel> getSourceDOList() {
List<SourceModel> tempSourceList=new ArrayList<SourceModel>();
for (int index = 0; index < 5; index++) {
SourceModel source = new SourceModel();
source.setSelect(false);
source.setFactory("group");
source.setSupplier("Work");
tempSourceList.add(source);
}
return tempSourceList;
}
}
class SourceModel {
private boolean select;
private String factory;
private String supplier;
public SourceModel() {
// No Code;
}
public SourceModel(boolean select, String factory, String supplier) {
super();
this.select = select;
this.factory = factory;
this.supplier = supplier;
}
public boolean isSelect() {
return select;
}
public void setSelect(boolean select) {
this.select = select;
}
public String getFactory() {
return factory;
}
public void setFactory(String factory) {
this.factory = factory;
}
public String getSupplier() {
return supplier;
}
public void setSupplier(String supplier) {
this.supplier = supplier;
}
}
class RadioButtonEditor extends DefaultCellEditor implements ItemListener {
public JRadioButton btn = new JRadioButton();
public RadioButtonEditor(JCheckBox checkBox) {
super(checkBox);
}
public Component getTableCellEditorComponent(JTable table, Object
value, boolean isSelected, int row, int column) {
if (value==null)
return null;
btn.addItemListener(this);
if (( (Boolean) value).booleanValue())
btn.setSelected(true);
else
btn.setSelected(false);
return btn;
}
public Object getCellEditorValue() {
if(btn.isSelected() == true)
return new Boolean(true);
else
return new Boolean(false);
}
public void itemStateChanged(ItemEvent e) {
super.fireEditingStopped();
}
}
class RadioButtonRenderer implements TableCellRenderer {
public JRadioButton btn = new JRadioButton();
public Component getTableCellRendererComponent(JTable table, Object value,
boolean isSelected, boolean hasFocus, int row, int column) {
if (value==null) return null;
if(((Boolean)value).booleanValue())
btn.setSelected(true);
else
btn.setSelected(false);
if (isSelected) {
btn.setForeground(table.getSelectionForeground());
btn.setBackground(table.getSelectionBackground());
} else {
btn.setForeground(table.getForeground());
btn.setBackground(table.getBackground());
}
return btn;
}
}
EDIT:
I have updated my code and I have used Boolean class for the first column. The problem which I am facing is, if I remove super.fireEditingStopped(); from RadioButtonEditor class then I am able to check and then if I click at any part of the table then checked one I being unchecked. If I keep the super.fireEditingStopped(); then I am not even able to check the Radio button.
I know that super.fireEditingStopped(); will stop editing. But my question is how to check it?
P.S: Sorry I have posted my entire code. I thought it will be easy for some one to look at the problem.
This is the screen shot of the program.
From your illustration, it appears that you want to enforce mutual exclusion among the rows of a JTable, where each row has a single JRadioButton. As a ButtonGroup is unsuitable, this example due to #Guillaume Polet uses a custom manager.
I have a problem with the following code. My task is, I have to have
radio buttons in the first column and when a user selects that radio
button that row is selected and sent for processing. But my problem
is, I am able to select the radio button which are in the first
column, but afterwards when user clicks in any part of the table then
my clicked radio button is being unchecked. I am not able to figure,
why it is happeneing. I am really stuck with this problem. Help
required. The following code shows my problem.
don't to use JRadioButton, use built_in support for Boolean value in JTable == JCheckBox,
then you can sorting and filtering based on Boolean value
otherwise you have to override to String ("true" / "false")
there are a few good JRadioButtons as Renderer and Editor in JTable, including usage of JComboBox as Editor for RadioButtonGroup
If you need to dynamically change the Look and Feel, your CellEditor is recommended to extend Component.
//#see javax/swing/SwingUtilities.java
static void updateRendererOrEditorUI(Object rendererOrEditor) {
if (rendererOrEditor == null) {
return;
}
Component component = null;
if (rendererOrEditor instanceof Component) {
component = (Component)rendererOrEditor;
}
if (rendererOrEditor instanceof DefaultCellEditor) {
//Ahh, AbstractCellEditor ...
component = ((DefaultCellEditor)rendererOrEditor).getComponent();
}
if (component != null) {
SwingUtilities.updateComponentTreeUI(component);
}
}
Here is a "CellEditor extends JRadioButton ..." example:
import java.awt.*;
import java.awt.event.*;
import java.util.EventObject;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.table.*;
public class DisplayTable2 extends JDialog {
public void initialize() {
Object[][] data = {
{ true, "Group1", "Work1" }, { false, "Group2", "Work2" },
{ false, "Group3", "Work3" }, { false, "Group4", "Work4" }
};
JTable sourceTable = new JTable(new SourceTableModel(data));
sourceTable.getColumnModel().getColumn(0).setCellRenderer(new RadioButtonRenderer());
sourceTable.getColumnModel().getColumn(0).setCellEditor(new RadioButtonEditor());
JPanel panel = new JPanel();
panel.add(new JScrollPane(sourceTable));
add(panel);
setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
setModal(true);
pack();
setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override public void run() {
new DisplayTable2().initialize();
}
});
}
}
class SourceTableModel extends DefaultTableModel {
private static final String[] columnNamesList = {"Select", "Group", "Work"};
public SourceTableModel(Object[][] data) {
super(data, columnNamesList);
}
#Override public Class<?> getColumnClass(int columnIndex) {
return (columnIndex == 0 ? Boolean.class : String.class);
}
#Override public boolean isCellEditable(int rowIndex, int columnIndex) {
return (columnIndex == 0 ? true : false);
}
#Override public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
if(columnIndex==0 && aValue instanceof Boolean) {
//lazy development
for(int i=0; i<getRowCount(); i++) {
super.setValueAt(i==rowIndex, i, columnIndex);
}
} else {
super.setValueAt(aValue, rowIndex, columnIndex);
}
}
}
class RadioButtonRenderer extends JRadioButton implements TableCellRenderer {
#Override public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
if(value instanceof Boolean) {
setSelected((Boolean)value);
}
return this;
}
}
class RadioButtonEditor extends JRadioButton implements TableCellEditor {
public RadioButtonEditor() {
super();
addActionListener(new ActionListener() {
#Override public void actionPerformed(ActionEvent e) {
fireEditingStopped();
}
});
}
#Override public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) {
if(value instanceof Boolean) {
setSelected((Boolean)value);
}
return this;
}
#Override public Object getCellEditorValue() {
return isSelected();
}
//Copid from AbstractCellEditor
//protected EventListenerList listenerList = new EventListenerList();
//transient protected ChangeEvent changeEvent = null;
#Override public boolean isCellEditable(EventObject e) {
return true;
}
#Override public boolean shouldSelectCell(EventObject anEvent) {
return true;
}
#Override public boolean stopCellEditing() {
fireEditingStopped();
return true;
}
#Override public void cancelCellEditing() {
fireEditingCanceled();
}
#Override public void addCellEditorListener(CellEditorListener l) {
listenerList.add(CellEditorListener.class, l);
}
#Override public void removeCellEditorListener(CellEditorListener l) {
listenerList.remove(CellEditorListener.class, l);
}
public CellEditorListener[] getCellEditorListeners() {
return listenerList.getListeners(CellEditorListener.class);
}
protected void fireEditingStopped() {
// Guaranteed to return a non-null array
Object[] listeners = listenerList.getListenerList();
// Process the listeners last to first, notifying
// those that are interested in this event
for(int i = listeners.length-2; i>=0; i-=2) {
if(listeners[i]==CellEditorListener.class) {
// Lazily create the event:
if(changeEvent == null) changeEvent = new ChangeEvent(this);
((CellEditorListener)listeners[i+1]).editingStopped(changeEvent);
}
}
}
protected void fireEditingCanceled() {
// Guaranteed to return a non-null array
Object[] listeners = listenerList.getListenerList();
// Process the listeners last to first, notifying
// those that are interested in this event
for(int i = listeners.length-2; i>=0; i-=2) {
if(listeners[i]==CellEditorListener.class) {
// Lazily create the event:
if(changeEvent == null) changeEvent = new ChangeEvent(this);
((CellEditorListener)listeners[i+1]).editingCanceled(changeEvent);
}
}
}
}
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
}
}
}
};
}