How can I set the tooltip location for a JTable header? I have no issue displaying the tooltip, but I would like to place it directly over the column title rather than the default location (under mouse cursor).
I can do this successfully for JTable cells by overriding the getToolTipLocation method in the JTable class, but it doesn't seem to work for the table header.
As well, the JTableHeader class doesn't support the getToolTipLocation method. I've been toiling with this for a while now and any samples or insight would be greatly appreciated.
I was wrong about JTableHeader not supporting getToolTipLocation. It's buried in the JComponent class which JTableHeader is subclassed from. The code example I provided solves my problem. I implemented the code in a class that is subclassed from the JTable class. I'm still getting use to this site and my apologies if the example doesn't seem complete.
protected JTableHeader createDefaultTableHeader() {
return new JTableHeader(this.columnModel) {
Point p = null;
#Override
public String getToolTipText(MouseEvent e) {
int colIdx = this.columnAtPoint(e.getPoint());
Rectangle rect = this.getHeaderRect(colIdx);
p = rect.getLocation();
return this.columnModel.getColumn(colIdx).getHeaderValue().toString();
}
#Override
public Point getToolTipLocation(MouseEvent e) {
return p;
}
};
}
Related
I want to use GWT-Bootstrap Tooltip for cells of a column in the CellTable. Each Tooltip will show description of a cell which is a field in the "MyCustomObject" class used for generating the table.
One of the quick solutions I found in one of the answers on this question is the following.
CellTable<MyCustomObject> table = new CellTable<MyCustomObject>();
table.addCellPreviewHandler(new Handler<MyCustomObject>() {
#Override
public void onCellPreview(CellPreviewEvent<MyCustomObject> event) {
if (event.getNativeEvent().getType().equals("mouseover")){
table.getRowElement(event.getIndex()).getCells()
.getItem(event.getColumn()).setTitle(event.getValue().getDescription());
}
}
}
);
It makes a little sense but I don't see anything when I hover the cursor over the table cells and nothing is attached to the DOM. Can anyone explain how this even works? I want to attach the Tooltip to table cell similar to the following.
Tooltip tt = new Tooltip("Here goes the description");
tt.setAnimation(true);
tt.setWidget(tableColumn);
tt.reconfigure();
The problem is that a CellTable cell is not a widget so it can't be attached in that way.
So is there any workaround for this?
I've faced this issue once and I created my own cell that displays a tooltip, here's how I did it: Note: My cell was displaying an image with a tooltip so I quickly changed the code to display String text... So I did not test it with text.
private class TooltipCell extends AbstractCell<String> {
private String tooltipText = "";
#Override
public void render(Context context, String value, SafeHtmlBuilder sb) {
if (value != null) {
sb.appendHtmlConstant("<div title=\"" + tooltipText + "\">");
sb.append(SafeHtmlUtils.fromString(value));
}
}
public void setTooltip(String tootltipTextToSet){
tooltipText = tootltipTextToSet;
}
}
And in your table use method setTooltip("tooltip text") in method getValue just before returning the cell text.
Hope this helps
I know that by using JTable the column is sorted when we click on the column heading, but what I want is that, when I right-click on the column name a function name 'sort' should be displayed. Any suggestion in doing it?
Start by adding a MouseListener to the table. See How to write mouse listeners
You will need to translate the click point to a column, see JTable#columnAtPoint.
You will then need to update the SortKey for the table. Check out Sorting and Filtering for an example
If I understand you correctly, you want to sort by some explicit action (triggered f.i. in a popup) instead of by the normal left-click.
If so, the tricky part is to force the ui-delegate to do nothing. There are two options:
hook into the default mouse listener installed by the ui delegate, as described in a recent QA
let the ui do its stuff, but fool it by a sorter implementation that doesn't follow the rules (beware: that's as dirty as the first approach!)
The mis-behaving sorter:
public class MyTableRowSorter extends TableRowSorter {
public MyTableRowSorter(TableModel model) {
super(model);
}
/**
* Implemented to do nothing to fool tableHeader internals.
*/
#Override
public void toggleSortOrder(int column) {
}
/**
* The method that really toggles, called from custom code.
*
* #param column
*/
public void realToggleSortOrder(int column) {
super.toggleSortOrder(column);
}
}
// usage
final JTable table = new JXTable(new AncientSwingTeam());
table.setRowSorter(new MyTableRowSorter(table.getModel()));
Action toggle = new AbstractAction("toggleSort") {
#Override
public void actionPerformed(ActionEvent e) {
JXTableHeader header = SwingXUtilities.getAncestor(
JXTableHeader.class, (Component) e.getSource());
Point trigger = header.getPopupTriggerLocation();
int column = trigger != null ? header.columnAtPoint(trigger) : -1;
if (column < 0) return;
int modelColumn = header.getTable().convertColumnIndexToModel(column);
((MyTableRowSorter) header.getTable().getRowSorter())
.realToggleSortOrder(modelColumn);
}
};
JPopupMenu menu = new JPopupMenu();
menu.add(toggle);
table.getTableHeader().setComponentPopupMenu(menu);
Yeah, couldn't resist throwing in some SwingX api, lazy me :-) With plain Swing, you'll have to write some lines more but the basics are the same: install the tricksy sorter and use its custom toggle sort to really sort whereever needed, f.i. in a mouseListener.
I have a JTable where the user should be able to select only a single row, but whenever a row is selected by the user, some other rows (that are related according to some logic) should also be selected programmatically. The problem is that if I set the selection mode of the table to ListSelectionModel.SINGLE_SELECTION, addRowSelectionInterval will also select only one row. Any ideas?
EDIT: I think all ideas (custom selection model, clearing all but last user selections, custom renderer for highlighting) were good, but the best is to use SwingX, because it doesn't require much infrastructure-code, only a clever usage of the library. (and it's easy to be clever when a SwingX-guru is helping :)
Biased me would say: certainly much easier in SwingX :-)
All you need is
a custom HighlightPredicate which decides about what is related
a ColorHighlighter configured with the selectionColors
set the custom predicate on receiving change notification from the selection model
Some code:
// the custom predicate
public static class RelatedHighlightPredicate implements HighlightPredicate {
List<Integer> related;
public RelatedHighlightPredicate(Integer... related) {
this.related = Arrays.asList(related);
}
#Override
public boolean isHighlighted(Component renderer,
ComponentAdapter adapter) {
int modelIndex = adapter.convertRowIndexToModel(adapter.row);
return related.contains(modelIndex);
}
}
// its usage
JXTable table = new JXTable(someModel);
table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
final ColorHighlighter hl = new ColorHighlighter(HighlightPredicate.NEVER,
table.getSelectionBackground(), table.getSelectionForeground());
table.addHighlighter(hl);
ListSelectionListener l = new ListSelectionListener() {
#Override
public void valueChanged(ListSelectionEvent e) {
if (e.getValueIsAdjusting()) return;
invokeUpdate((ListSelectionModel) e.getSource());
}
private void invokeUpdate(final ListSelectionModel source) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
int singleSelection = source.getMinSelectionIndex();
if (singleSelection >= 0) {
int first = Math.max(0, singleSelection - 2);
int last = singleSelection + 2;
hl.setHighlightPredicate(new RelatedHighlightPredicate(first, last));
} else {
hl.setHighlightPredicate(HighlightPredicate.NEVER);
}
}
});
}
};
table.getSelectionModel().addListSelectionListener(l);
You might set multiselection possible for the table, but with each selection change - take only 1 (last selected) row, clear other selections and add your own computed selection.
The problem is that if I set the selection mode of the table
use ListSelectionModel.SINGLE_SELECTION for events came from mouse and keyborad
some other rows (that are related according to some logic) should also be selected programmatically
have look at Renderer for JTable, then required row(s), columns or whatever could be highlighted until programmatic rules stay unchanged
... maybe will help you
Im using a JPanel with propertyChangeListener and want it to rerender itself based on whenever a particular variable model changes. My code for the same is as follows --
public class LabelMacroEditor extends JPanel implements PropertyChangeListener {
private static final long serialVersionUID = 1L;
private LabelMacroModel model;
public LabelMacroEditor(LabelMacroModel bean) {
this.model = bean;
model.addPropertyChangeListener(this);
setupComponents();
validate();
setVisible(true);
}
public void setupComponents()
{
Box allButtons = Box.createVerticalBox();
JScrollPane macroModelScroller = new JScrollPane(allButtons);
macroModelScroller.setPreferredSize(new Dimension(300, 200));
for(MacroModel macroModel : model.getMacroModelList())
{
LabelMacroEditorEditableEntity macroEditorEntity = new LabelMacroEditorEditableEntity(macroModel);
Box entityBox = Box.createHorizontalBox();
entityBox.add(macroEditorEntity.getUpButton());
entityBox.add(Box.createHorizontalStrut(15));
entityBox.add(macroEditorEntity.getMacroDetailsButton());
entityBox.add(Box.createHorizontalStrut(15));
entityBox.add(macroEditorEntity.getDownButton());
allButtons.add(entityBox);
}
add(macroModelScroller);
}
#Override
public void propertyChange(PropertyChangeEvent arg0) {
revalidate();
repaint();
}
}
When i use the debug mode in eclipse i can see that whenever there is a change to model it triggers off the call propertyChange and it also runs over revalidate and repaint but only the JPanel display remains the same. It does not seem to be rerendering itself.
Anything fundamental that I'm missing here ?
EDIT :
An example snippet of a property im changing is as follows --
labelMacroModel.addMacroModel(addedMacroModel);
where labelMacroModel is of the type LabelMacroModel and addedMacroModel is of the type Macro
Now the relevant part of LabelMacroModel class that fires off the property change is as follows --
private List<MacroModel> macroModelList;// this is the list of all MacroModels
public void addMacroModel(MacroModel macroModel) {
macroModelList.add(macroModel);
pcs.fireIndexedPropertyChange("LabelMacroModel", macroModelList.size(), null, macroModel);
}
Its not clear how you are changing the components in the panel. If panel is not updated then repaint/revalidate will have no effect. I think you should not need revalidate/repaint to be called explicitly if you are not modifying the way components are laid out. JButton.setText should for example change the label of the button without need of calling repaint.
To expand on the answer by AKJ above, I think you should be reconstructing your components on property change. So doing a remove all then readding is one way to do this. Once you get this working you could be more selective about pushing the model update into the GUI eg if a new entry has been added then just add a new component to reflect this. The remove all / readd is fine for a lot of cases though. HTH.
OK, this problem is out of my league. I'm trying to implement a GUI widget in swing that allows files to be dropped onto a JTable, and allows the rows of the JTable to be dragged for re-sorting. Think VLC's playlists or the one in iTunes.
I got dropping files from the OS (Explorer, Finder, etc.) working just fine, but I'm having an impossible time with re-arranging the rows of the table, once the files are in. The problem is that when I add a custom TransferHandler to the table, dragging from the table is instantly killed. Here is some example code:
import javax.swing.*;
public class TableTest
{
public static void main (String [] argv)
{
// setup table data
String [] columns = new String [] {"Foo", "Bar", "Baz", "Quux"};
String [][] data = new String [][] {{"A", "B", "C", "D"},
{"1", "2", "3", "4"},
{"i", "ii", "iii", "iv"}};
// create table
JTable table = new JTable(data, columns);
// set up drag and drop
table.setDragEnabled(true);
table.setDropMode(DropMode.INSERT_ROWS);
table.setFillsViewportHeight(true);
TransferHandler dnd = new TransferHandler() {
// here be code to handle drops, and one would
// presume drag exporting, too
};
table.setTransferHandler(dnd);
JScrollPane scroll = new JScrollPane(table);
// create and show window
JFrame window = new JFrame();
window.getContentPane().add(scroll);
window.pack();
window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
window.setVisible(true);
}
}
Run this code as-is and you'll see that you cannot initiate a drag on the table.If you comment out the call to setTransferHandler() on the table, dragging works (i.e., when I start dragging a table row, I get the X'd out circle cursor saying I can't drop there). But as soon as a TransferHandler is set for the table, I can't drag any rows. The problem has to be in the TransferHandler, but I've thoroughly troubleshot and debugged it, and have determined that dragging is never started once there is a TransferHandler on the table. What am I doing wrong?
I had the same problem, it's got nothing to do with your custom implementation of the TransferHandler. When you replace the TransferHandler you also need to get a hold of the default DragSource and tell it to recognize drag gesture. You may also need to implement your own Transferable because you'll need to pass it to the DragGestureEvent.startDrag() method.
table.setTransferHandler(new MyTransferHandler());
table.setDragEnabled(true);
DragSource source = DragSource.getDefaultDragSource();
source.createDefaultDragGestureRecognizer(table, DnDConstants.ACTION_COPY, new DragGestureListener() {
#Override
public void dragGestureRecognized(DragGestureEvent dge) {
//grab the selected files from the table model
ArrayList<File> files = new ArrayList<File>();
for (int row : table.getSelectedRows()) {
files.add((File) dm.getValueAt(row, 1));
}
//FileTransferable is a custom Transferable implementation
Transferable transferable = new FileTransferable(files);
//and this is the magic right here
dge.startDrag(null,transferable);
}
});
It doesn't look like you are using the TransferHandler properly. Try to read through the tutorial here.
See the TransferHandler doc here. The empty constructor doesn't look like it's meant for usage outside a subclass of TransferHandler.
And you don't implement any of the functionality provided in the standard TransferHandler provided on Swing components. See exerpt from the DnD tutorial here (my bold):
Note: If you install a custom TransferHandler onto a Swing component, the default support is replaced. For example, if you replace JTextField's TransferHandler with one that handles colors only, you will disable its ability to support import and export of text.
If you must replace a default TransferHandler — for example, one that handles text — you will need to re-implement the text import and export ability. This does not need to be as extensive as what Swing provides — it could be as simple as supporting the StringFlavor data flavor, depending on your application's needs.
I think the problem is that the empty TransferHandler actually prevents DnD events from occurring. There is a sample here which may be relevant.
http://www.java2s.com/Code/Java/Swing-JFC/ExtendedDnDDragandDropDemo.htm
I did not want to get into the nuts and bolts of what was going on so I just delegated the methods I wasn't interested to the old TransferHandler.
tree.setDragEnabled(true);
tree.setDropMode(DropMode.XXXX);
tree.setTransferHandler(new MyTransferHandler(tree.getTransferHandler());
Start with a standard setup but pass the old TransferHandler into your custom TransferHandler.
private class MyTransferHandler extends TransferHandler {
private TransferHandler delegate;
public MyTransferHandler(TransferHandler delegate) {
this.delegate = delegate;
}
public boolean canImport(JComponent comp, DataFlavor[] transferFlavors) {
return delegate.canImport(comp, transferFlavors);
}
public boolean canImport(TransferSupport support) {
return true;
}
protected Transferable createTransferable(JComponent c) {
try {
Method method = delegate.getClass().getDeclaredMethod("createTransferable", JComponent.class);
method.setAccessible(true);
return (Transferable) method.invoke(delegate, c);
} catch (Exception e) {
return super.createTransferable(c);
}
}
public void exportAsDrag(JComponent comp, InputEvent event, int action) {
delegate.exportAsDrag(comp, event, action);
}
protected void exportDone(JComponent source, Transferable data, int action) {
try {
Method method = delegate.getClass().getDeclaredMethod("exportDone", JComponent.class, Transferable.class,
int.class);
method.setAccessible(true);
method.invoke(delegate, source, data, action);
} catch (Exception e) {
super.exportDone(source, data, action);
}
}
public int getSourceActions(JComponent c) {
return delegate.getSourceActions(c);
}
public Icon getVisualRepresentation(Transferable t) {
return delegate.getVisualRepresentation(t);
}
public boolean importData(JComponent comp, Transferable t) {
return delegate.importData(comp, t);
}
public boolean importData(TransferHandler.TransferSupport support) {
return delegate.importData(support);
}
}
One gotcha is that createTransferable(JComponent) and exportDone(JComponent, Transferable, int) methods are protected so you need to do reflection in order to delegate to those methods. When I didn't do this reflection delegation the strategy did not work. Once I did this delegation drag and drop worked as expected without changing the DragSource or having to write a new Transferable.