Is there any way to get a cell renderer to respond to mouse events, such as mouseovers?
Never tried it but I guess you would need to:
a) create a custom renderer to paint the cell in two states
b) you need to keep track of which cell should currently be painted in the "mouse over" state
c) add a mouse listener to track mouse entered/exited and mouseMoved. With each event you would need to update a variable that tracks which cell the mouse is positioned over. You
can use the columnAtPoint() and rowAtPoint() methods of JTable
d) when the mouse leaves a cell you need to invoke repaint() on the cell. You can use the getCellRect() method to determine which cell to repaint
e) when the mouse enters a cell you need to reset the cell value for the "mouse over" state and then repaint the cell.
Ok, so I've tried implement camickr's approach, but I've encountered some very nasty problem in the process. I've added a MouseMotionListener to JTable to track the current and previous cell and added some methods that instruct a renderer about which component to return and then repaint the appropriate cell. However, for some odd reason each cell is repainted twice even though there was only one repaint request. Basically, I was able to highlight a cell on a mouse rollover, but it was impossible to remove highlighting from a cell once the mouse cursor leaved it. After initial confusion I've decided to do this in a different way. I've added a method which invokes the editor whe the mouse is over the cell and then added some code that stops editing as soon as the state of JToggleButton (my rendering and editing component) is changed. My current code looks like this :
package guipkg;
import java.awt.event.*;
import javax.swing.*;
import java.awt.*;
public class Grid extends JTable implements MouseListener {
int currentCellColumn = -1;
int currentCellRow = -1;
int previousCellColumn = -1;
int previousCellRow = -1;
public void detectCellAtCursor (MouseEvent e) {
Point hit = e.getPoint();
int hitColumn = columnAtPoint(hit);
int hitRow = rowAtPoint(hit);
if (currentCellRow != hitRow || currentCellColumn != hitColumn) {
this.editCellAt(hitRow, hitColumn);
currentCellRow = hitRow;
currentCellColumn = hitColumn;
}
}
}
package guipkg;
import javax.swing.table.*;
import java.awt.*;
import javax.swing.*;
import java.awt.event.*;
public class TCEditor extends AbstractCellEditor implements TableCellEditor {
/**
* A toggle button which will serve as a cell editing component
*/
JToggleButton togglebutton = new JToggleButton();
public Component getTableCellEditorComponent (JTable Table, Object value, boolean isSelected, int rindex, int cindex) {
/**
* We're adding an action listener here to stop editing as soon as the state of JToggleButton is switched.
* This way data model is updated immediately. Otherwise updating will only occur after we've started to
* edit another cell.
*/
togglebutton.addActionListener(new ActionListener() {
public void actionPerformed (ActionEvent e) {
stopCellEditing();
}
});
if (value.toString().equals("true")) {
togglebutton.setSelected(true);
}
else {
togglebutton.setSelected(false);
}
togglebutton.setBorderPainted(false);
return togglebutton;
}
public Object getCellEditorValue () {
return togglebutton.isSelected();
}
}
I hope that will help someone
Related
Is there a way to adjust the drop-down window size of a JCombobox?
let's say I have:
comArmor.setBounds(81, 102, 194, 26);
But when the user selects the box and the drop-down list pops up, i'd like for the drop-down window to expand so that a long line of text will be displayed entirely (say size x of 300).
Is this possible?
Small hack to get pop up menu size bigger enough to show items even though the combo box size could be smaller
Source: http://www.jroller.com/santhosh/entry/make_jcombobox_popup_wide_enough
import java.awt.Dimension;
import java.util.Vector;
import javax.swing.ComboBoxModel;
import javax.swing.JComboBox;
public class ComboBoxFullMenu<E> extends JComboBox<E> {
public ComboBoxFullMenu(E[] items) {
super(items);
addActionListener(this);
}
public ComboBoxFullMenu(Vector<E> items) {
super(items);
addActionListener(this);
}
public ComboBoxFullMenu(ComboBoxModel<E> aModel) {
super(aModel);
addActionListener(this);
}
/**
* Small hack to get pop up menu size bigger enough to show items even though
* the combo box size could be smaller
* */
private boolean layingOut = false;
#Override
public void doLayout(){
try{
layingOut = true;
super.doLayout();
}finally{
layingOut = false;
}
}
#Override
public Dimension getSize(){
Dimension dim = super.getSize();
if ( !layingOut ) {
dim.width = Math.max(dim.width, getPreferredSize().width);
}
return dim;
}
}
Not sure if there is built in functionality for this already, but you could always have a ActionListener which listens for selection changes and then programmatically set the width of the JComboBox to the length of the selected content (getSelectedItem()) when it changes.
box.addActionListener (new ActionListener () {
public void actionPerformed(ActionEvent e) {
String item = comboBox.getSelectedItem().toString();
comboBox.setBounds(81, 102, item.length * CONSTANT, 26);
}
});
It seems a bit hacky to me and you might have to play around with this idea to get it to work.
I hope this helps you!
Update:
JComboBox appears to have a setPrototypeDisplayValue(Object) method that is used to calculate component's preferred width based on the length of the parameter.
Sets the prototype display value used to calculate the size of the display for the UI portion.
I might consider using this instead.
I have a functionality in a JTable, that when user clicks the cell, it removes certain character in it (like when there's content - Hello, when user clicks it, it shows Hello). When it's no longer edited, it shows - Hello again.
My problem is that when some cell is selected (but not being edited yet) and I start typing Hi, it doesn't remove the character, so the editable cell looks like - Hello Hi.
Same problem is when some cell is selected and user presses space key.
I want to add the functionality to the JTable, so that when the content of the cell starts to be edited (by any way - clicking/typing when selected/space key/and maybe there are more options I don't know about), I want to programatically change the content first. Another option would be removing it when the cell is selected (but then I have to remember position of the last selected cell, so that the character could be readded to it).
I've tried in propertyChange in class TableChangeListener:
table.setValueAt(removeCharacter(table.getValueAt(row,column)), row, column);
but it doesn't work as the cell is already being edited and I cant change it.
You will have to use your own implementation of Cell Editor to meet your own set of requirements.
So create a custom CellEditor implementing FocusLisetener and ActionListener and implement the FocusGained and FocusLost function
Implement the actionPerformed function too to update value on enter click.
Handling the Focus event is a little bit tricky. As it tends to update the cell wrongly. That is why i had to pass the reference table to the CellEditor as a constructor parameter and read the cell row, col on Focus gain.
To reflect the - xxxx: placing - before the cell value, try using a custom CellRenderer. Check out the official tutorial page for details with example. And the part of the credit goes to #mKobel.
An implemented custom cell editor for direction: assign it to your target table column and test.
Giff of my test result:
Code:
class CustomRenderer extends DefaultTableCellRenderer {
public void setValue(Object value)
{
setText("- "+value);
}
}
class MyCellEditor extends AbstractCellEditor
implements TableCellEditor,
FocusListener,
ActionListener
{
JTextField textFeild;
String currentValue;
JTable table;
int row, col;
public MyCellEditor(JTable table) {
this.table = table;
textFeild = new JTextField();
textFeild.addActionListener(this);
textFeild.addFocusListener(this);
}
#Override
public Object getCellEditorValue() {
return currentValue;
}
#Override
public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) {
currentValue = (String)value;
return textFeild;
}
#Override
public void focusGained(FocusEvent e) {
textFeild.setText("");
row = table.getSelectedRow();
col = table.getSelectedColumn();
}
#Override
public void focusLost(FocusEvent e) {
if(!textFeild.getText().equals(""))
//currentValue = textFeild.getText();
table.setValueAt(textFeild.getText(), row, col);
fireEditingStopped();
}
#Override
public void actionPerformed(ActionEvent e) {
if(!textFeild.getText().trim().equals(""))
currentValue = textFeild.getText();
fireEditingStopped();
}
}
I think you should not change the content of the cell at all.
What you need is to set on the table a TableCellRenderer that renders the cell values. Implement the cell renderer so that it shows the value "- Hello" (although your actual data could contain just "Hello"). The renderer just shows any component you want in the table. When user starts editing the cell, the renderer component is not shown. Actually you could also manipulate the editing component using a TableCellEditor.
Extracted from this comment:
The table shows prices like "$5" "20€" and when user clicks the cell
to change the price, I'd like the sign to disappear. When user
finishes editing (clicks enter or by other way), I want the symbol to
appear again.
Although #Sage post is a really great and general solution (+1 for you :), in this particular case I'd implement a TableCellRenderer and TableCellEditor using JFormattedTextField which can manage the currency format matter, as follows:
Set a generic number format to the renderer component: NumberFormat.getNumberInstance()
Set a currency number format to the editor component: NumberFormat.getCurrencyInstance()
This way when the cell is displayed the currency sign will be shown but when the cell is being edited the currency sign will "disappear".
Take a look to this example of implementation:
import java.awt.Color;
import java.awt.Component;
import java.awt.event.MouseEvent;
import java.text.NumberFormat;
import java.util.EventObject;
import java.util.Locale;
import javax.swing.AbstractCellEditor;
import javax.swing.BorderFactory;
import javax.swing.JFormattedTextField;
import javax.swing.JTable;
import javax.swing.UIManager;
import javax.swing.border.Border;
import javax.swing.table.TableCellEditor;
import javax.swing.table.TableCellRenderer;
import javax.swing.text.NumberFormatter;
public class CurrencyEditor extends AbstractCellEditor implements TableCellEditor, TableCellRenderer {
JFormattedTextField editor;
JFormattedTextField renderer;
Integer clickCountToStart = 2;
public CurrencyEditor(Locale locale) {
initEditor(locale);
initRenderer(locale);
}
private void initRenderer(Locale locale) {
NumberFormat format = locale != null ?
NumberFormat.getCurrencyInstance(locale) : NumberFormat.getCurrencyInstance();
NumberFormatter formatter = new NumberFormatter(format);
formatter.setMinimum(Double.MIN_VALUE);
formatter.setMaximum(Double.MAX_VALUE);
formatter.setAllowsInvalid(false);
renderer = new JFormattedTextField(formatter);
}
private void initEditor(Locale locale) {
NumberFormat format = locale != null ?
NumberFormat.getNumberInstance(locale) : NumberFormat.getNumberInstance();
NumberFormatter formatter = new NumberFormatter(format);
formatter.setMinimum(Double.MIN_VALUE);
formatter.setMaximum(Double.MAX_VALUE);
formatter.setAllowsInvalid(false);
editor = new JFormattedTextField(formatter);
editor.setBorder(UIManager.getBorder("Tree.editorBorder"));
}
#Override
public Object getCellEditorValue() {
return editor.getValue();
}
#Override
public boolean isCellEditable(EventObject anEvent) {
if (anEvent instanceof MouseEvent) {
return ((MouseEvent)anEvent).getClickCount() >= clickCountToStart;
}
return true;
}
#Override
public boolean shouldSelectCell(EventObject anEvent) {
return true;
}
#Override
public boolean stopCellEditing() {
fireEditingStopped();
return true;
}
#Override
public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) {
if(value instanceof Double){
editor.setValue(value);
}
return editor;
}
#Override
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
if(value instanceof Double) {
Color background = isSelected ? UIManager.getColor("Table.selectionBackground") : UIManager.getColor("Table.background");
Color foreground = isSelected ? UIManager.getColor("Table.selectionForeground") : UIManager.getColor("Table.foreground");
Border border = hasFocus ? UIManager.getBorder("Table.focusCellHighlightBorder") : BorderFactory.createEmptyBorder();
renderer.setBackground(background);
renderer.setForeground(foreground);
renderer.setBorder(border);
renderer.setValue(value);
return renderer;
} else {
String message = String.format("Not supported for %1$1s class!", value.getClass());
throw new IllegalArgumentException(message);
}
}
}
Disclaimer: it may not work properly with Nimbus look and feel as UIManager properties are named different. I've tested it using Metal, Windows, Windows Classic and Motif.
Here is the code I've used to test it:
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.util.Locale;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.SwingUtilities;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableColumn;
public class Demo {
private void initGUI(){
DefaultTableModel model = new DefaultTableModel(new Object[]{"Item", "Price USD", "Price EUR"}, 0);
model.addRow(new Object[]{"Fender stratocaster", 1599.99d, 1176.46d});
model.addRow(new Object[]{"Gibson Les Paul", 1299.99d, 955.87d});
model.addRow(new Object[]{"Pual Reed Smith Standard 24", 1999.99d, 1470.58d});
JTable table = new JTable(model);
table.setPreferredScrollableViewportSize(new Dimension(500, 300));
TableColumn priceUSD = table.getColumn("Price USD");
priceUSD.setCellRenderer(new CurrencyEditor(Locale.US));
priceUSD.setCellEditor(new CurrencyEditor(Locale.US));
TableColumn priceEUR = table.getColumn("Price EUR");
priceEUR.setCellRenderer(new CurrencyEditor(Locale.GERMANY));
priceEUR.setCellEditor(new CurrencyEditor(Locale.GERMANY));
JPanel content = new JPanel(new BorderLayout());
content.add(new JScrollPane(table));
JFrame frame = new JFrame("Demo");
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
frame.getContentPane().add(content);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
new Demo().initGUI();
}
});
}
}
Screenshot
I want to display an info box on mousing over a JTable cell using Java Swing, so there are multi-parts
How can I capture the mouse-over event in a table cell? I have to be able to set the cell content, then get data on it.
How can I display a panel/box with dynamic server data on mousing over that cell?
How can I cache the info panel/box so I don't have to query the server on every mouse over?
Example:
In table cell I enter: 94903. After tabbing or entering, the cell is set to the number. On mouse-over, it displays a box with Name, Address, Phone number, email, etc.
Thanks!
You could format the tooltip text using HTML, this would allow you to provide a complex structure of information to the tooltip without the need or expense of writing your own solution. The only problem is that the tooltip will be automatically discarded.
If this still doesn't suit, you could try:
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.*;
import javax.swing.JPopupMenu;
import javax.swing.JTable;
import javax.swing.Timer;
public class TestTable {
private Timer showTimer;
private Timer disposeTimer;
private JTable table;
private Point hintCell;
private MyPopup popup; // Inherites from JPopupMenu
public TestTable() {
showTimer = new Timer(1500, new ShowPopupActionHandler());
showTimer.setRepeats(false);
showTimer.setCoalesce(true);
disposeTimer = new Timer(5000, new DisposePopupActionHandler());
disposeTimer.setRepeats(false);
disposeTimer.setCoalesce(true);
table.addMouseMotionListener(new MouseMotionAdapter() {
#Override
public void mouseMoved(MouseEvent e) {
Point p = e.getPoint();
int row = table.rowAtPoint(p);
int col = table.columnAtPoint(p);
if ((row > -1 && row < table.getRowCount()) && (col > -1 && col < table.getColumnCount())) {
if (hintCell == null || (hintCell.x != col || hintCell.y != row)) {
hintCell = new Point(col, row);
Object value = table.getValueAt(row, col);
// Depending on how the data is stored, you may need to load more data
// here...
// You will probably want to maintain a reference to the object hint data
showTimer.restart();
}
}
}
});
}
protected MyPopup getHintPopup() {
if (popup == null) {
// Construct the popup...
}
return popup;
}
public class ShowPopupActionHandler implements ActionListener {
#Override
public void actionPerformed(ActionEvent e) {
if (hintCell != null) {
disposeTimer.stop(); // don't want it going off while we're setting up
MyPopup popup = getHintPopup();
popup.setVisible(false);
// You might want to check that the object hint data is update and valid...
Rectangle bounds = table.getCellRect(hintCell.y, hintCell.x, true);
int x = bounds.x;
int y = bounds.y + bounds.height;
popup.show(table, x, y);
disposeTimer.start();
}
}
}
public class DisposePopupActionHandler implements ActionListener {
#Override
public void actionPerformed(ActionEvent e) {
MyPopup popup = getHintPopup();
popup.setVisible(false);
}
}
}
Now, I've not constructed the popup, I'd use the popup menu from Bob Sinclar's answer as well
Heres a good way to get the mouse over working I woud do this first then just output some text when your over it to check.
This shows a way to get a pop up menu to appear
And in regards to the cacheing i might store the last 10 values in memory and do a request from the server each time a new entry point is pinged ie you dont have it locally. And then maybe every minute update the last 10 hit incase their info changes.
another useful mouseover guide
I am using a JColorchooser at various places in an application. There can be multiple instances of the panel that can invoke a JColorChooser.
The "Swatches" panel in the chooser has an area of "recent" colors, which only persists within each instance of JColorChooser. I would like to (a) have the same "recent" colors in all my choosers in my application, and (b) to save the colors to disk so that these colors survive close and restart of the application.
(At least (a) could be solved by using the same single chooser instance all over the whole app, but that apears cumbersome because I would need to be very careful with attached changelisteners, and adding/removing the chooser panel to/from various dialogs.)
I did not find any method that lets me set (restore) these "recent" colors in the chooser panel. So to me, it appears that the only ways of achieving this would be:
serialize and save / restore the whole chooser (chooser panel?)
or
create my own chooser panel from scratch
Is this correct, or am I missing something?
BTW: I would also like to detect a double click in the chooser, but it seems hard to find the right place to attach my mouse listener to. Do I really need to dig into the internal structure of the chooser panel to do this? (No, it does not work to detect a second click on the same color, because the change listener only fires if a different color is clicked.)
As you noticed, there is no public api to access the recent colors in the DefaultSwatchChooserPanel, even the panel itself isn't accessible.
As you'll need some logic/bean which holds and resets the recent colors anyway (plus the extended mouse interaction), rolling your own is the way to go. For some guidance, have a look at the implementation of the swatch panel (cough ... c&p what you need and modify what you don't). Basically, something like
// a bean that keeps track of the colors
public static class ColorTracker extends AbstractBean {
private List<Color> colors = new ArrayList<>();
public void addColor(Color color) {
List<Color> old = getColors();
colors.add(0, color);
firePropertyChange("colors", old, getColors());
}
public void setColors(List<Color> colors) {
List<Color> old = getColors();
this.colors = new ArrayList<>(colors);
firePropertyChange("colors", old, getColors());
}
public List<Color> getColors() {
return new ArrayList<>(colors);
}
}
// a custom SwatchChooserPanel which takes and listens to the tracker changes
public class MySwatchChooserPanel ... {
ColorTracker tracker;
public void setColorTracker(....) {
// uninstall old tracker
....
// install new tracker
this.tracker = tracker;
if (tracker != null)
tracker.addPropertyChangeListener(.... );
updateRecentSwatchPanel()
}
/**
* A method updating the recent colors in the swatchPanel
* This is called whenever necessary, specifically after building the panel,
* on changes of the tracker, from the mouseListener
*/
protected void updateRecentSwatchPanel() {
if (recentSwatchPanel == null) return;
recentSwatchPanel.setMostRecentColors(tracker != null ? tracker.getColors() : null);
}
// the mouseListener which updates the tracker and triggers the doubleClickAction
// if available
class MainSwatchListener extends MouseAdapter implements Serializable {
#Override
public void mousePressed(MouseEvent e) {
if (!isEnabled())
return;
if (e.getClickCount() == 2) {
handleDoubleClick(e);
return;
}
Color color = swatchPanel.getColorForLocation(e.getX(), e.getY());
setSelectedColor(color);
if (tracker != null) {
tracker.addColor(color);
} else {
recentSwatchPanel.setMostRecentColor(color);
}
}
/**
* #param e
*/
private void handleDoubleClick(MouseEvent e) {
if (action != null) {
action.actionPerformed(null);
}
}
}
}
// client code can install the custom panel on a JFileChooser, passing in a tracker
private JColorChooser createChooser(ColorTracker tracker) {
JColorChooser chooser = new JColorChooser();
List<AbstractColorChooserPanel> choosers =
new ArrayList<>(Arrays.asList(chooser.getChooserPanels()));
choosers.remove(0);
MySwatchChooserPanel swatch = new MySwatchChooserPanel();
swatch.setColorTracker(tracker);
swatch.setAction(doubleClickAction);
choosers.add(0, swatch);
chooser.setChooserPanels(choosers.toArray(new AbstractColorChooserPanel[0]));
return chooser;
}
As to doubleClick handling: enhance the swatchChooser to take an action and invoke that action from the mouseListener as appropriate.
You can use the JColorChooser.createDialog method - one of the parameters is a JColorChooser. Use a static instance of the JColorChooser and make it the Dialog modal - that way, only one color chooser is displayed at a time.
The createDialog method also takes ActionListeners as parameters for the OK and Cancel button. Thus, don't really have to manage listeners. Of course, this doesn't persist the recent colors across invocations of the app, just persists recent colors in the current app.
Here's a workaround using reflection - it will work provided the underlying implementation doesn't change. Assuming you have a JColorChooser, add your recent colors to it like this:
final JColorChooser chooser = new JColorChooser(Color.white);
for (AbstractColorChooserPanel p : chooser.getChooserPanels()) {
if (p.getClass().getSimpleName().equals("DefaultSwatchChooserPanel")) {
Field recentPanelField = p.getClass().getDeclaredField("recentSwatchPanel");
recentPanelField.setAccessible(true);
Object recentPanel = recentPanelField.get(p);
Method recentColorMethod = recentPanel.getClass().getMethod("setMostRecentColor", Color.class);
recentColorMethod.setAccessible(true);
recentColorMethod.invoke(recentPanel, Color.BLACK);
recentColorMethod.invoke(recentPanel, Color.RED);
//add more colors as desired
break;
}
}
As the question states, I'd like to set a mouse listener to my JTree so that I can change the cursor to a HAND_CURSOR when the user places their mouse over a node.
I already have a MouseAdapter registered on my JTree to handle click events, but I can't seem to get a MouseMoved or MouseEntered/MouseExited to work with what I'm trying to do.
Any suggestions?
You need to add a MouseMotionListener/Adapter:
tree.addMouseMotionListener(new MouseMotionAdapter() {
#Override
public void mouseMoved(MouseEvent e) {
int x = (int) e.getPoint().getX();
int y = (int) e.getPoint().getY();
TreePath path = tree.getPathForLocation(x, y);
if (path == null) {
tree.setCursor(Cursor.getDefaultCursor());
} else {
tree.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
}
}
});
In a JTree, each of tree node is showed by a label generated by the TreeCellRenderer associated to this tree. The usually used class is DefaultTreeCellRenderer which renders this (the DefaultTreeCellRenderer). As a consequence, you can try adding this DefaultTreeCellRenderer a MouseMotionListener to toggle mouse cursor.
Notice adding MouseMotionListener to the tree will simply toggle mouse rendering when on Tree component, not when mouse is on a label.