Overriding createToolTip() in a custom CellRenderer - java

I'm trying to get a custom ToolTip for a specific column of a JTable. I've already created a CellRenderer (of which I've been changing other cell-specific attributes successfully):
private class CustomCellRenderer extends DefaultTableCellRenderer
{
private static final long serialVersionUID = 1L;
#Override
public Component getTableCellRendererComponent(JTable table, Object value,
boolean isSelected, boolean hasFocus, int row, int column)
{
JComponent c = (JComponent) super.getTableCellRendererComponent(table, value,
isSelected, hasFocus, row, column);
if (value != null)
{
if(column == 1 && value instanceof Date)
{
final DateFormat df = new SimpleDateFormat("h:mm aa");
table.setValueAt(df.format(value), row, column);
}
else if(column == 2)
{
c.setToolTipText((String) value);
}
else if(column == 4)
{
final Mail m = main.selectedPage.messages.get(row);
JCheckBox checkBox;
if((Boolean) value)
{
checkBox = new JCheckBox()
{
#Override
public JToolTip createToolTip()
{
System.out.println("Passed");
return new ImageToolTip(m.getImage());
}
};
checkBox.setToolTipText(m.attachName);
}
else
checkBox = new JCheckBox();
checkBox.setSelected(((Boolean)value).booleanValue());
c = checkBox;
}
}
else
{
c.setToolTipText(null);
}
return c;
}
}
When I override any other JComponent's createTooltip() method like so, it all works fine outside of the Renderer.
checkBox = new JCheckBox()
{
#Override
public JToolTip createToolTip()
{
System.out.println("Passed");
return new ImageToolTip(m.getImage());
}
};
From what I can tell, the tooltip is created elsewhere, because "Passed" is never even printed. The checkBox.setToolTipText(m.attachName); only results in a default ToolTip with that String.
I've found someone with a similar question, but I can't say I completely understand the only resolving answer. Do I need to extend JTable and override getToolTipText(MouseEvent e)? If so, I'm not sure what with to get the correct (mine) Tooltip.
Please excuse any of my self-taught weirdness. Thanks in advance. :-)
EDIT:
Thanks to Robin, I was able to piece together something based on JTable's getToolTipText(MouseEvent e) code. I'll leave it here for anyone else with a similar problem. Again, I'm not sure it this it the best way to do it, so feel free to critique it below. :-)
messageTable = new JTable()
{
#Override
public JToolTip createToolTip()
{
Point p = getMousePosition();
// Locate the renderer under the event location
int hitColumnIndex = columnAtPoint(p);
int hitRowIndex = rowAtPoint(p);
if ((hitColumnIndex != -1) && (hitRowIndex != -1))
{
TableCellRenderer renderer = getCellRenderer(hitRowIndex, hitColumnIndex);
Component component = prepareRenderer(renderer, hitRowIndex, hitColumnIndex);
if (component instanceof JCheckBox)
{
Image img = main.selectedPage.messages.get(hitRowIndex).getImage();
if(((JCheckBox) component).isSelected())
return new ImageToolTip(img);
}
}
return super.createToolTip();
}
}

You are not able to create tooltip for checkbox inside cell renderer. Actually that component doesn't exists at the moment you are trying to move mouse over it. It is just an image. You need to create tooltip for your JTable
private void tableMouseMoved(java.awt.event.MouseEvent evt) {
String toolTipText;
int row = table.rowAtPoint(evt.getPoint());
int column = table.columnAtPoint(evt.getPoint());
if (row >= 0) {
Object o = table.getValueAt(row, column);
if (column == YourTableModel.COLUMN_INDEX_WITH_CHECKBOX) {
Boolean value = (Boolean) o;
if (value == Boolean.TRUE) {
toolTipText = "Tooltip text for true value";
} else {
toolTipText = "Tooltip text for false value";
}
}
}
}
And you need to register listener for MouseEvent of course:
javax.swing.JTable table = new JTable();
table.addMouseMotionListener(new java.awt.event.MouseMotionAdapter() {
public void mouseMoved(java.awt.event.MouseEvent evt) {
tableMouseMoved(evt);
}
});

The reason that the JTable does not use your tooltip can be seen in the implementation. The JTable will indeed use the component returned by the renderer, but it will ask it for its tooltip text. So only settings a custom tooltip text will work if you stick to the default JTable implementation. Just a quick copy-paste of the relevant part of the JTable source code to illustrate this:
if (component instanceof JComponent) {
// Convert the event to the renderer's coordinate system
Rectangle cellRect = getCellRect(hitRowIndex, hitColumnIndex, false);
p.translate(-cellRect.x, -cellRect.y);
MouseEvent newEvent = new MouseEvent(component, event.getID(),
event.getWhen(), event.getModifiers(),
p.x, p.y,
event.getXOnScreen(),
event.getYOnScreen(),
event.getClickCount(),
event.isPopupTrigger(),
MouseEvent.NOBUTTON);
tip = ((JComponent)component).getToolTipText(newEvent);
}
So yes, you will have to override the JTable method if you really want an image as tooltip for your check box.
On a side-note: your renderer code has weird behavior. The
final DateFormat df = new SimpleDateFormat("h:mm aa");
table.setValueAt(df.format(value), row, column);
seems incorrect. You should replace the setValueAt call by a
JLabel label = new JLabel();//a field in your renderer
//in the getTableCellRendererComponent method
label.setText( df.format( value ) );
return label;
or something similar. The renderer should not adjust the table values, but create an appropriate component to visualize the data. In this case a JLabel seems sufficient. And as Stanislav noticed in the comments, you should not constantly create new components. That defeats the purpose of the renderer which was introduced to avoid creating new components for each row/column combination. Note that the method is called getTableCellRendererComponent (emphasis on get) and not createTableCellRendererComponent

Related

JTable cells getting overlapped

I have implemented a JTree and populated some data. The table contains three columns and based on some values on a specific cell it should have either labels or combo boxes. All the values in the 3rd column are editable. I too have a JTree from which a node is selected and based on it the table values change accordingly. The issue exists when I edit a cell in the table and move to another node in the tree (which populates a new set of data in the table), the previously edited cell values exist in the table on top of the new cell values. Below is how I implemented TableCellRenderer and TableCellEditor. I might have used some concepts wrong since I am a beginner for swing. Please help me figure out what I have done incorrectly.
public void populateTableData(List<Field> list,JTree jTree){
fieldList = null;
tcBuilderTree = jTree;
fieldList = list;
md=new PropertiesTableModel(fieldList);
getPropertieseTable().setModel(md);
final TableCellRenderer cellRenderer = new TableCellRenderer() {
#Override
public Component getTableCellRendererComponent(JTable arg0,
Object value,
boolean isSelected,
boolean hasFocus,
int row,
int col) {
if(value instanceof List<?>) {
List<Value> valueList=(ArrayList)value;
return createComboBox(valueList);
}
else{
JLabel lbl=new JLabel();
lbl.setText((String)value);
return lbl;
}
}
};
propertiesTable.setDefaultRenderer(Object.class, cellRenderer);
final TableCellEditor cellEditor = new TableCellEditor() {
private DefaultCellEditor textEditor;
private DefaultCellEditor currentEditor;
#Override
public Component getTableCellEditorComponent(JTable table,
Object value,
boolean isSelected,
int row,
int column) {
textEditor = new DefaultCellEditor(new JTextField());
PropertiesTableModel model = (PropertiesTableModel) table.getModel();
List<Value> values = model.getPossibleValues(row, column);
if (values != null) {
List<Value> valueList=(ArrayList)value;
currentEditor = new DefaultCellEditor(createComboBox(valueList));
} else {
currentEditor = textEditor;
}
return currentEditor.getTableCellEditorComponent(table, value,
isSelected, row, column);
}
#Override
public Object getCellEditorValue() {
return currentEditor.getCellEditorValue();
}
#Override
public boolean isCellEditable(EventObject anEvent) {
JTable tbl = (JTable) anEvent.getSource();
int row, col;
if (anEvent instanceof MouseEvent) {
MouseEvent evt = (MouseEvent) anEvent;
row = tbl.rowAtPoint(evt.getPoint());
col = tbl.columnAtPoint(evt.getPoint());
} else {
row = tbl.getSelectedRow();
col = tbl.getSelectedColumn();
}
if(col<2){
return false;
}
else
{
return true;
}
}
#Override
public boolean shouldSelectCell(EventObject anEvent) {
return true;
}
#Override
public boolean stopCellEditing() {
Object obj = currentEditor.getCellEditorValue();
fieldList.get(propertiesTable.getEditingRow())
.setDefaultValue(obj);
return currentEditor.stopCellEditing();
}
#Override
public void cancelCellEditing() {
currentEditor.cancelCellEditing();
}
#Override
public void addCellEditorListener(CellEditorListener l) {
}
#Override
public void removeCellEditorListener(CellEditorListener l) {
}
};
propertiesTable.setDefaultEditor(Object.class,cellEditor);
}
The issue exists when I edit a cell in the table and move to another node in the tree (which populates a new set of data in the table), the previously edited cell values exist in the table on top of the new cell values.
I would guess you are not stopping editing of the table cell before you reload the table with new data.
You probably should add the following to your code when you create the JTable:
JTable table = new JTable(...);
table.putClientProperty("terminateEditOnFocusLost", Boolean.TRUE);
See Table Stop Editing for more information.
The table contains three columns and based on some values on a specific cell it should have either labels or combo boxes
You may not need to create custom editors. Just have the table choose the appropriate default editor based on the cell being edited. You can do this by overriding the getCellEditor(...) method of the JTable. Check out: How to add unique JComboBoxes to a column in a JTable (Java) for an example of this approach.

Putting icon in JTable cell changes it's back Color

I am putting some icons in my jtable's cell but problem is it is changing it's background color to table's background color....i want it to change it to row's background color...and also when it is selected it does't show the selection...Here is my code how i am setting icons....
Users user;
ConsoleUsersListTbl.getColumnModel().getColumn(1).setCellRenderer(new ImageRender());
DefaultTableModel userTableModel = (DefaultTableModel) ConsoleUsersListTbl.getModel();
for (int i = 0; i < userList.size()-1; i++) {
user = userList.get(i);
javax.swing.ImageIcon image_icon = new javax.swing.ImageIcon(user.getUser_image());
if (image_icon.getIconWidth() > 32 || image_icon.getIconWidth() > 32) {
InputStream in = new ByteArrayInputStream(user.getUser_image());
BufferedImage buff_image;
try {
buff_image = ImageIO.read(in);
int type = buff_image.getType() == 0 ? BufferedImage.TYPE_INT_ARGB : buff_image.getType();
BufferedImage resizedImage = resizeImage(buff_image, type);
image_icon.setImage(resizedImage);
userTableModel.setValueAt(image_icon, i, 1);
} catch (IOException ex) {
Logger.getLogger(MainFrame.class.getName()).log(Level.SEVERE, null, ex);
}
} else {
userTableModel.setValueAt(image_icon, i, 1);
}
and here is the out put......
Edit1
here is my Image Render class ;and making setOpaque true makes my icons white..
public class ImageRender extends DefaultTableCellRenderer {
JLabel lable = new JLabel();
#Override
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
lable.setIcon((ImageIcon)value);
lable.setOpaque(true);
return lable;
}
}
I think you need to implement the custom cell renderer as discussed here and also described here. The renderer has access to information if the current cell is focused or selected, so you can adjust background, foreground or even content any way you want. Mind that components may be opaque (have they own background) or not (the parent background or other content is visible through it). This is controlled through setOpaque(boolean).

Tool tip in JPanel in JTable not working

I have a JTable. One column holds a JPanel which contains some JLabels with ImageIcons. I have created a custom cell render and all works fine apart from the tool tip on the JLabel. When I mouse over any of these JLabels I need to show the Tooltip of that particular JLabel. Its not showing the tootlip of the JLabel.
Here is the CustomRenderer.
private class CustomRenderer extends
DefaultTableCellRenderer implements TableCellRenderer {
#Override
public Component getTableCellRendererComponent(JTable table,
Object value, boolean isSelected, boolean hasFocus, int row,
int column) {
if (value != null && value instanceof List) {
JPanel iconsPanel = new JPanel(new GridBagLayout());
List<ImageIcon> iconList = (List<ImageIcon>) value;
int xPos = 0;
for (ImageIcon icon : iconList) {
JLabel iconLabel = new JLabel(icon);
iconLabel.setToolTipText(icon.getDescription());
GridBagConstraints gbc = new GridBagConstraints();
gbc.gridy = 1;
gbc.gridx = xPos++;
iconsPanel.add(iconLabel, gbc);
}
iconsPanel.setBackground(isSelected ? table
.getSelectionBackground() : table.getBackground());
this.setVerticalAlignment(CENTER);
return iconsPanel;
}
return this;
}
}
The problem is that you set tooltips on subcomponents of the component returned by your CellRenderer. To perform what you want, you should consider override getToolTipText(MouseEvent e) on the JTable. From the event, you can find on which row and column the mouse is, using:
java.awt.Point p = e.getPoint();
int rowIndex = rowAtPoint(p);
int colIndex = columnAtPoint(p);
From there you could then re-prepare the cell renderer, find which component is located at the mouse position and eventually retrieve its tooltip.
Here is a snippet of how you could override JTable getToolTipText:
#Override
public String getToolTipText(MouseEvent event) {
String tip = null;
Point p = event.getPoint();
// Locate the renderer under the event location
int hitColumnIndex = columnAtPoint(p);
int hitRowIndex = rowAtPoint(p);
if (hitColumnIndex != -1 && hitRowIndex != -1) {
TableCellRenderer renderer = getCellRenderer(hitRowIndex, hitColumnIndex);
Component component = prepareRenderer(renderer, hitRowIndex, hitColumnIndex);
Rectangle cellRect = getCellRect(hitRowIndex, hitColumnIndex, false);
component.setBounds(cellRect);
component.validate();
component.doLayout();
p.translate(-cellRect.x, -cellRect.y);
Component comp = component.getComponentAt(p);
if (comp instanceof JComponent) {
return ((JComponent) comp).getToolTipText();
}
}
// No tip from the renderer get our own tip
if (tip == null) {
tip = getToolTipText();
}
return tip;
}

Scrollable Cells in JTable

I have a Jtable in which I have to show some big data. I cann't increase the size of the Cells So I need to add a scrollbar in each cell of the table through which I can scroll the text of cells.
I have tried to add a Custom Cell Renderer
private class ExtendedTableCellEditor extends AbstractCellEditor implements TableCellEditor
{
JLabel area = new JLabel();
String text;
public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int rowIndex, int vColIndex)
{
area.setText(text);
return new JScrollPane(area);
}
public Object getCellEditorValue()
{
return text;
}
}
Now I am able to see the Scroll bar on the cells but not able to click and scroll them.
Any suggestions to this issue will be great.
Thanks in Advance.
Adding a JScrollPaneand placing the JLabel in the JScrollPane solved the issue. So I would like to share it with you all.
private class ExtendedTableCellEditor extends AbstractCellEditor implements TableCellEditor
{
JLabel _component = new JLabel();
JScrollPane _pane = new JScrollPane(_component, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
/**
* Returns the cell editor component.
*
*/
public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int rowIndex, int vColIndex)
{
if (value == null) return null;
_component.setText(value != null ? value.toString() : "");
_component.setToolTipText(value != null ? value.toString() : "");
_component.setOpaque(true);
_component.setBackground((isSelected) ? Color.BLUE_DARK : Color.WHITE);
_component.setForeground((isSelected) ? Color.WHITE : Color.BLACK);
_pane.setHorizontalScrollBar(_pane.createHorizontalScrollBar());
_pane.setVerticalScrollBar(_pane.createVerticalScrollBar());
_pane.setBorder(new EmptyBorder(0,0,0,0));
_pane.setToolTipText(value != null ? value.toString() : "");
return _pane;
}
public Object getCellEditorValue()
{
return _component.getText();
}
}

Refresh JTable after look and feel update

I have a JTable with custom TableCellRenderer.
public class DateCellRenderer extends DefaultTableCellRenderer {
private static final long serialVersionUID = 58L;
public DateCellRenderer() {
super();
setHorizontalAlignment(CENTER);
setOpaque(true);
}
#Override
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
if (value instanceof Date) {
String date = new SimpleDateFormat("dd-MM-yyyy").format((Date) value);
setText(date);
}
return this;
}
}
Also in my application I have a drop down menu by which I can change the look and feel. This drop down menu is in a parent frame and the table is in a dialog. When the dialog is opened the parent frame is inaccessible. So to change the look and feel I have to close the dialog first.
Now in a particular skin if the table is populated by some data and I change the look and feel from parent frame and again open the dialog then the column, where I have added the TableCellRenderer, is keeping the old look and feel. It is not updating while the other columns render themselves in the new look and feel.
I am unable to find the problem and its solution. Any help is appreciable.
Note: The look and feel update of the application is made by the following snippet
javax.swing.UIManager.setLookAndFeel(uiProperties.getThemeModel().getThemeClass());
ComponentFactory.getLibraryFrame().getRootPane().updateUI();
for (int i = 0; i < Frame.getWindows().length; i++) {
SwingUtilities.updateComponentTreeUI(Frame.getWindows()[i]);
}
for (int i = 0; i < Frame.getFrames().length; i++) {
SwingUtilities.updateComponentTreeUI(Frame.getFrames()[i]);
}
Thanks in advance.
In HiFi theme chosen first:
Then I change the theme to Fast, and the second column "Released" not updated its ui:
The JTable is:
public class MovieSearchResultTable extends BaseTable {
private static final long serialVersionUID = 45L;
public MovieSearchResultTable(TableModel tableModel) {
super(tableModel);
LibraryLogger.initMessage(getClass().getSimpleName());
}
#Override
public void initialize() {
setFillsViewportHeight(true);
setAutoResizeMode(AUTO_RESIZE_OFF);
getColumnModel().getColumn(1).setCellRenderer(new DateCellRenderer());//if I comment out this line then no problem. but without CellRenderer how could I format a Date, if I use formatted String instead of Date, then the column will not sort!!
}
#Override
public boolean getScrollableTracksViewportWidth() {
return getPreferredSize().getWidth() < getParent().getWidth();
}
}
I think that not good L&F, JTable looks like ... ok, but other Compound JComponents aren't ...., not sure I haven't wasting my time, I leaving to test that, maybe is there something described about that on their Forum or Documentation or BugParades, but nothing from your question
there is very simple way and you can any time to check that
1) go to Insubstantial
2) download code source,
3) import all classes to the IDE (2-15 min depends of PC HardWare)
4) search for folder test, there is Check.java,
5) run that and to try everything in JMenu Look and Feel, before that required to download API's for every Custom Java Swing Look and Feels
The solution, you need to override public Component prepareRenderer(TableCellRenderer renderer, int row, int column)
Here is the class:
public class MovieSearchResultTable extends BaseTable {
private static final long serialVersionUID = 45L;
private int rolloverRowIndex = -1;
public MovieSearchResultTable(TableModel tableModel) {
super(tableModel);
LibraryLogger.initMessage(getClass().getSimpleName());
}
public Component prepareRenderer(TableCellRenderer renderer, int row, int column) {
Component component = super.prepareRenderer(renderer, row, column);
Color foreground = getForeground();
Color background = getBackground();
if (isRowSelected(row)) {
foreground = getSelectionForeground();
background = getSelectionBackground();
}
else if (row == rolloverRowIndex) {
foreground = getSelectionForeground();
background = ColorHelper.brighter(getSelectionBackground(), 40);
}
else if (row % 2 == 0) {
background = ColorHelper.brighter(getParent().getBackground(), 20);
}
component.setForeground(foreground);
component.setBackground(background);
return component;
}
private class RolloverListener extends MouseInputAdapter {
public void mouseExited(MouseEvent e) {
rolloverRowIndex = -1;
repaint();
}
public void mouseMoved(MouseEvent e) {
int row = rowAtPoint(e.getPoint());
if (row != rolloverRowIndex) {
rolloverRowIndex = row;
repaint();
}
}
}
#Override
public void initialize() {
setFillsViewportHeight(true);
setAutoResizeMode(AUTO_RESIZE_OFF);
TableColumnModel tableColumnModel = getColumnModel();
for(ComponentConstant.ColumnName columnName : ComponentConstant.Column.MOVIE_SEARCH_RESULT_TABLE) {
int order = columnName.getOrder();
TableColumn tableColumn = tableColumnModel.getColumn(order);
if(order == 0) {
continue;
}
tableColumn.setCellRenderer(RendererFactory.getMovieSearchResultTableCellRenderer());
}
RolloverListener listener = new RolloverListener();
addMouseMotionListener(listener);
addMouseListener(listener);
}
#Override
public boolean getScrollableTracksViewportWidth() {
return getPreferredSize().getWidth() < getParent().getWidth();
}
}
Thanks.

Categories

Resources