I am implementing a MVC and I want to have a JToolTip on a JLabel in my view. I succeeded but now I want to set the dismiss and initial timer.
I read the Javadoc for JToolTip and TollTipManager. I found what I want in ToolTipManager but now I can't understand how to use it.
JToolTip doesn't have a setter for ToolTipManager and in the doc they said:
Manages all the ToolTips in the system.
I don't really understand how I should use ToolTipManager. Should my View extend ToolTipManager or am I mislead?
ToolTipManager is a singleton. To use it, you need to call a static sharedInstance method that returns the instance of this class.
ToolTipManager manages all tooltips - it is a global configuration class, changes made to the sharedInstance will be applied to every tooltip in your application.
Here is an example showing how to use the ToolTipManager:
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.SwingUtilities;
import javax.swing.ToolTipManager;
public class TestTooltips {
protected static void initUI() {
ToolTipManager.sharedInstance().setInitialDelay(0);
ToolTipManager.sharedInstance().setDismissDelay(500);
JFrame frame = new JFrame("test");
JLabel label = new JLabel("Label text");
label.setToolTipText("My cool nice and fun tooltip");
frame.add(label);
frame.pack();
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
initUI();
}
});
}
}
No idea what you tried and what you want to do... do you have success with the following code?
import java.awt.*;
import java.awt.event.MouseWheelEvent;
import java.awt.event.MouseWheelListener;
import javax.swing.*;
import javax.swing.table.DefaultTableCellRenderer;
public class JScrollableToolTip extends JToolTip {
private static final long serialVersionUID = 1L;
private final MouseWheelListener mouseWheelListener;
private final JTextArea tipArea;
public JScrollableToolTip(final int rows, final int columns) {
setLayout(new BorderLayout());
mouseWheelListener = createMouseWheelListener();
tipArea = new JTextArea(rows, columns);
tipArea.setLineWrap(true);
tipArea.setWrapStyleWord(true);
tipArea.setEditable(false);
LookAndFeel.installColorsAndFont(tipArea, "ToolTip.background",
"ToolTip.foreground", "ToolTip.font");
JScrollPane scrollpane = new JScrollPane(tipArea);
scrollpane.setBorder(null);
scrollpane.getViewport().setOpaque(false);
add(scrollpane);
}
private MouseWheelListener createMouseWheelListener() {
return new MouseWheelListener() {
#Override
public void mouseWheelMoved(final MouseWheelEvent e) {
JComponent component = getComponent();
if (component != null) {
tipArea.dispatchEvent(new MouseWheelEvent(tipArea, e.getID(),
e.getWhen(), e.getModifiers(), 0, 0, e.getClickCount(),
e.isPopupTrigger(), e.getScrollType(), e.getScrollAmount(),
e.getWheelRotation()));
}
}
};
}
#Override
public void addNotify() {
super.addNotify();
JComponent component = getComponent();
if (component != null) {
component.addMouseWheelListener(mouseWheelListener);
}
}
#Override
public void removeNotify() {
JComponent component = getComponent();
if (component != null) {
component.removeMouseWheelListener(mouseWheelListener);
}
super.removeNotify();
}
#Override
public void setComponent(JComponent c) {
JComponent component = getComponent();
if (component != null) {
component.removeMouseWheelListener(mouseWheelListener);
}
super.setComponent(c);
}
#Override
public void setTipText(final String tipText) {
String oldValue = this.tipArea.getText();
tipArea.setText(tipText);
tipArea.setCaretPosition(0);
firePropertyChange("tiptext", oldValue, tipText);
}
#Override
public Dimension getPreferredSize() {
return getComponent(0).getPreferredSize();
}
#Override
public String getTipText() {
return tipArea == null ? "" : tipArea.getText();
}
#Override
protected String paramString() {
String tipTextString = (tipArea.getText() != null ? tipArea.getText() : "");
return super.paramString() + ",tipText=" + tipTextString;
}
//for testing only:
public static void main(final String args[]) {
EventQueue.invokeLater(new Runnable() {
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (Exception e) {
e.printStackTrace();
}
JFrame f = new JFrame("JScrollableToolTip");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setSize(300, 200);
f.setLocationRelativeTo(null);
ToolTipManager.sharedInstance().setInitialDelay(500);
ToolTipManager.sharedInstance().setDismissDelay(10000);
ToolTipManager.sharedInstance().mousePressed(null);
JTable table = new JTable(50, 4) {
private static final long serialVersionUID = 1L;
#Override
public JToolTip createToolTip() {
JScrollableToolTip tip = new JScrollableToolTip(3, 20);
tip.setComponent(this);
return tip;
}
};
table.setDefaultRenderer(Object.class, new DefaultTableCellRenderer() {
private static final long serialVersionUID = 1L;
#Override
public Component getTableCellRendererComponent(JTable table, Object value,
boolean isSelected, boolean hasFocus, int row, int column) {
super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
setToolTipText("Row " + row + " Column " + column
+ "\nUsed to display a 'Tip' for a Component. "
+ "Typically components provide api to automate the process of "
+ "using ToolTips. For example, any Swing component can use the "
+ "JComponent setToolTipText method to specify the text for a standard tooltip.");
return this;
}
});
f.add(new JScrollPane(table));
f.setVisible(true);
}
});
}
}
Related
Imagine I'm building an IRC client with Java and I'd like rich text in the chat view to show IRC colors and colored nicks. I'd like to build this with a JTable. I can do that, but the text is then not selectable. Making the table editable doesn't make sense.
I've also investigated:
TextArea - no rich text formatting
JEditPane - can't append, only replace which is bad performance wise
JList - can't select text
So I got a table working I just need the text to be selectable without making it editable. I'd also would only like the text contents, and none of the HTML to be copied into the clipboard upon copying the text selection.
I have tried various iterations of setRowSelectionAllowed(), setColumnSelectionEnabled() and setCellSelectionEnabled() and setSelectionMode the table model returns false for isCellEditable(). Nothing has made the text selectable.
EDIT: as per answer 1 I was wrong about text editor panes so I'm trying those solutions.
I don't know why you don't want to use a JTextPane or JEditorPane. You insert text by its document. Examples here --> How to use Editor Panes and Text Panes.
But for your purpose you can for example do something like this. I override changeSelection to selectAll text when is clicking, the cells are editable but its cellEditors are not editable.
public class JTableTest {
private final DefaultCellEditor cellEditor;
private final JTextField textfield;
private JPanel panel;
private MyTableModel tableModel = new MyTableModel();
private JTable table = new JTable() {
#Override
public TableCellEditor getCellEditor(int row, int column) {
return JTableTest.this.cellEditor;
}
#Override
public void changeSelection(
final int row, final int column, final boolean toggle, final boolean extend) {
super.changeSelection(row, column, toggle, extend);
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
if ((getCellEditor(row, column) != null && !editCellAt(row, column))) {
JTextField textfield=(JTextField)JTableTest.this.cellEditor.getComponent();
textfield.selectAll();
}
}
});
}
};
public JTableTest() {
JScrollPane scroll = new JScrollPane(table);
table.setModel(tableModel);
panel = new JPanel(new BorderLayout());
panel.add(scroll, BorderLayout.CENTER);
textfield = new JTextField();
textfield.setEditable(Boolean.FALSE);
textfield.setBorder(null);
cellEditor = new DefaultCellEditor(textfield);
tableModel.insertValue(new ItemRow("nonEditable", "Editable"));
}
private class ItemRow {
private String column1;
private String column2;
public ItemRow(String column1, String column2) {
this.column1 = column1;
this.column2 = column2;
}
public String getColumn1() {
return column1;
}
public void setColumn1(String column1) {
this.column1 = column1;
}
public String getColumn2() {
return column2;
}
public void setColumn2(String column2) {
this.column2 = column2;
}
}
private class MyTableModel extends AbstractTableModel {
public static final int COLUMN1_INDEX = 0;
public static final int COLUMN2_INDEX = 1;
private final List<ItemRow> data = new ArrayList<>();
private final String[] columnsNames = {
"Column1",
"Column2",};
private final Class<?>[] columnsTypes = {
String.class,
String.class
};
public MyTableModel() {
super();
}
#Override
public Object getValueAt(int inRow, int inCol) {
ItemRow row = data.get(inRow);
Object outReturn = null;
switch (inCol) {
case COLUMN1_INDEX:
outReturn = row.getColumn1();
break;
case COLUMN2_INDEX:
outReturn = row.getColumn2();
break;
default:
throw new RuntimeException("invalid column");
}
return outReturn;
}
#Override
public void setValueAt(Object inValue, int inRow, int inCol) {
System.out.println("Gets called ");
if (inRow < 0 || inCol < 0 || inRow >= data.size()) {
return;
}
ItemRow row = data.get(inRow);
switch (inCol) {
case COLUMN1_INDEX:
row.setColumn1(inValue.toString());
break;
case COLUMN2_INDEX:
row.setColumn2(inValue.toString());
break;
}
fireTableCellUpdated(inRow, inCol);
}
#Override
public int getRowCount() {
return data.size();
}
#Override
public int getColumnCount() {
return columnsTypes.length;
}
#Override
public String getColumnName(int inCol) {
return this.columnsNames[inCol];
}
#Override
public Class<?> getColumnClass(int columnIndex) {
return this.columnsTypes[columnIndex];
}
/**
*
* #param row
*/
public void insertValue(ItemRow row) {
data.add(row);
fireTableRowsInserted(data.size() - 1, data.size() - 1);
}
#Override
public boolean isCellEditable(int rowIndex, int columnIndex) {
return true;
}
}
private static void createAndShowGUI(final Container container, final String title) {
//Create and set up the window.
JFrame frame = new JFrame(title);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLocationByPlatform(Boolean.TRUE);
frame.add(container);
//Display the window.
frame.pack();
frame.setVisible(true);
}
public static void main(String args[]) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
createAndShowGUI(new JTableTest().panel, "Test");
}
});
}
}
I accomplished this by enabling the editing and then making the component responsible for the edition ignore any changes. For this I created a TableCellEditor and intercepted the key types to the JTextField, the component used for editing.
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import javax.swing.AbstractCellEditor;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableCellEditor;
public class TableCellSelectionTest
{
public static void main(String[] args)
{
SwingUtilities.invokeLater(new Runnable() {
public void run()
{
new TableCellSelectionTest().initUI();
}
});
}
public void initUI()
{
JFrame frame = new JFrame();
int N = 5;
int M = 3;
Object[][] data = new Object[N][M];
for (int i = 0; i < N; ++i)
{
for (int j = 0; j < M; ++j)
{
data[i][j] = "This is the cell (" + i + ", " + j +")";
}
}
String[] columnNames = { "Column 1", "Column 2", "Column 3" };
DefaultTableModel model = new DefaultTableModel(data, columnNames);
final MyTableCellEditor editor = new MyTableCellEditor();
JTable table = new JTable(model) {
#Override
public TableCellEditor getCellEditor(int row, int column)
{
return editor;
}
};
frame.add(new JScrollPane(table), BorderLayout.CENTER);
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
class MyTableCellEditor extends AbstractCellEditor implements
TableCellEditor
{
Object _value;
#Override
public Object getCellEditorValue()
{
return _value;
}
#Override
public Component getTableCellEditorComponent(JTable table,
Object value, boolean isSelected, int row, int column)
{
_value = value;
JTextField textField = new JTextField(_value.toString());
textField.addKeyListener(new KeyAdapter()
{
public void keyTyped(KeyEvent e) {
e.consume(); //ignores the key
}
#Override
public void keyPressed(KeyEvent e)
{
e.consume();
}});
textField.setEditable(false); //this is functionally irrelevent, makes slight visual changes
return textField;
}
}
}
I tried both the answers here... but one problem at least is that you can tell when you've entered the "editing" mode.
This might be of interest... uses a combination of Editor magic and cheeky rendering to make it look like no editing is going on: editor's click-count-to-start is set to 1, and the component (JTextPane) delivered by the editor's method does setEditable( false ).
If this tickles your fancy, you might be interested at looking at my implementation of a JTable which adjusts (perfectly, harnessing the JTextPane's powerful wrapping power) the row height to the text, for individual rows, including when you change the columns: How to wrap lines in a jtable cell?
public class SelectableNonEditableTableTest {
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
JFrame main_frame = new JFrame();
main_frame.setPreferredSize(new Dimension(1200, 300));
main_frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
ArrayList<String> nonsense = new ArrayList<String>(
Arrays.asList(
"Lorem ipsum dolor sit amet, sed dolore vivendum ut",
"pri an soleat causae doctus.",
"Alienum abhorreant mea ea",
"cum malorum diceret ei. Pri oratio invidunt consequat ne.",
"Ius tritani detraxit scribentur et",
"has detraxit legendos intellegat at",
"quo oporteat constituam ex"));
JTable example_table = new JTable(10, 4);
example_table.setRowHeight( example_table.getRowHeight() * 2 );
DefaultCellEditor cell_editor = new SelectableNonEditableCellEditor(
new JTextField());
cell_editor.setClickCountToStart(1);
example_table.setDefaultEditor(Object.class, cell_editor);
TableCellRenderer renderer = new SelectableNonEditableTableRenderer();
example_table.setDefaultRenderer(Object.class, renderer);
for (int i = 0; i < 10; i++) {
example_table.setValueAt(nonsense.get(i % nonsense.size()),
i, i % 4);
}
main_frame.getContentPane().add(new JScrollPane(example_table));
main_frame.pack();
main_frame.setVisible(true);
}
});
}
}
class SelectableNonEditableCellEditor extends DefaultCellEditor {
public SelectableNonEditableCellEditor(JTextField textField) {
super(textField);
}
public Component getTableCellEditorComponent(JTable table, Object value,
boolean isSelected, int row, int col) {
Component comp = super.getTableCellEditorComponent(table, value,
isSelected, row, col);
if (value instanceof java.lang.String) {
DefaultStyledDocument sty_doc = new DefaultStyledDocument();
try {
sty_doc.insertString(0, (String) value, null);
} catch (BadLocationException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
JTextPane jtp_comp = new JTextPane(sty_doc);
jtp_comp.setEditable(false);
return jtp_comp;
}
return comp;
}
}
class SelectableNonEditableTableRenderer extends JTextPane implements
TableCellRenderer {
#Override
public Component getTableCellRendererComponent(JTable table, Object value,
boolean isSelected, boolean hasFocus, int row, int column) {
if (value instanceof DefaultStyledDocument) {
setDocument((DefaultStyledDocument) value);
} else {
setText((String) value);
}
return this;
}
}
Maybe you can implement your own TableCellRenderer that extends JTextField in your table.
Recently I solved a "mysterious" IOException that I got while DnD items from a JList to the JTable objects. Apparently objects that I transfer must be serializable. Is this "a must", or there is a way to avoid serialization?
One thing I must note - the type I am transferring is in a different package.
You can write a custom TransferHandler. For example I believe a TranferHandler for a JTabel will export a String that is comma delimited and then the import will parse the string to add each token as a different column.
So in your case you could export you data the same way. Then on you import you would need to be able to recreate your custom Object using the parsed tokens.
Take a look at the Swing tutorial on Drag and Drop and Data Transfer for more information and examples.
Or maybe easier if the DnD is only between your Java application than you can pass the actual reference to the object. Here is an example of my attempt to do something like this by dragging a Swing component between panels:
import java.awt.*;
import java.awt.datatransfer.*;
import java.awt.event.*;
import java.beans.*;
import javax.swing.*;
import javax.swing.border.*;
import javax.swing.plaf.*;
import javax.swing.text.*;
import java.io.*;
public class DragComponent extends JPanel
{
// public final static DataFlavor COMPONENT_FLAVOR = new DataFlavor(Component[].class, "Component Array");
public static DataFlavor COMPONENT_FLAVOR;
public DragComponent()
{
try
{
COMPONENT_FLAVOR = new DataFlavor(DataFlavor.javaJVMLocalObjectMimeType + ";class=\"" + Component[].class.getName() + "\"");
}
catch(Exception e)
{
System.out.println(e);
}
setLayout(null);
setTransferHandler( new PanelHandler() );
MouseListener listener = new MouseAdapter()
{
#Override
public void mousePressed(MouseEvent e)
{
JComponent c = (JComponent) e.getSource();
TransferHandler handler = c.getTransferHandler();
handler.exportAsDrag(c, e, TransferHandler.MOVE);
}
};
TransferHandler handler = new ComponentHandler();
for (int i = 0; i < 5; i++)
{
JLabel label = new JLabel("Label " + i);
label.setSize( label.getPreferredSize() );
label.setLocation(30 * (i+1), 30 * (i+1));
label.addMouseListener( listener );
label.setTransferHandler( handler );
add( label );
}
}
private static void createAndShowUI()
{
DragComponent north = new DragComponent();
north.setBackground(Color.RED);
north.setPreferredSize( new Dimension(200, 200) );
DragComponent south = new DragComponent();
south.setBackground(Color.YELLOW);
south.setPreferredSize( new Dimension(200, 200) );
JFrame frame = new JFrame("DragComponent");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(north, BorderLayout.NORTH);
frame.add(south, BorderLayout.SOUTH);
frame.pack();
frame.setLocationByPlatform( true );
frame.setVisible( true );
}
public static void main(String[] args)
{
EventQueue.invokeLater(new Runnable()
{
public void run()
{
createAndShowUI();
}
});
}
}
class ComponentHandler extends TransferHandler
{
#Override
public int getSourceActions(JComponent c)
{
return MOVE;
}
#Override
public Transferable createTransferable(final JComponent c)
{
return new Transferable()
{
#Override
public Object getTransferData(DataFlavor flavor)
{
Component[] components = new Component[1];
components[0] = c;
return components;
}
#Override
public DataFlavor[] getTransferDataFlavors()
{
DataFlavor[] flavors = new DataFlavor[1];
flavors[0] = DragComponent.COMPONENT_FLAVOR;
return flavors;
}
#Override
public boolean isDataFlavorSupported(DataFlavor flavor)
{
return flavor.equals(DragComponent.COMPONENT_FLAVOR);
}
};
}
#Override
public void exportDone(JComponent c, Transferable t, int action)
{
System.out.println(c.getBounds());
}
}
class PanelHandler extends TransferHandler
{
#Override
public boolean canImport(TransferSupport support)
{
if (!support.isDrop())
{
return false;
}
boolean canImport = support.isDataFlavorSupported(DragComponent.COMPONENT_FLAVOR);
return canImport;
}
#Override
public boolean importData(TransferSupport support)
{
if (!canImport(support))
{
return false;
}
Component[] components;
try
{
components = (Component[])support.getTransferable().getTransferData(DragComponent.COMPONENT_FLAVOR);
}
catch (Exception e)
{
e.printStackTrace();
return false;
}
Component component = components[0];
Container container = (Container)support.getComponent();
container.add(component);
// container.revalidate();
// container.repaint();
container.getParent().revalidate();
container.getParent().repaint();
JLabel label = (JLabel)component;
DropLocation location = support.getDropLocation();
System.out.println(label.getText() + " + " + location.getDropPoint());
label.setLocation( location.getDropPoint() );
return true;
}
}
i got stuck at adding a button to a JComboBox editor, I succeeded to add a button but I got some issues like when I first enter to the editor an action perform event gets fired which is unacceptable and the other is I can't get the text typed.
Result:
Problems:
#Override
public Component getEditorComponent() {
return panel;
}
This is the problem, if I return panel.jtexfield I only get a text field without a button, so what's the trick here?
Here is my code
import Store.util.DatabaseHelper;
import java.awt.*;
import java.awt.event.*;
import java.util.ArrayList;
import javax.swing.*;
import javax.swing.border.Border;
import javax.swing.plaf.basic.BasicComboBoxEditor;
import org.hibernate.HibernateException;
import org.netbeans.lib.awtextra.AbsoluteLayout;
public class NewComboTest extends JFrame {
private ArrayList<Object> shopCart = new ArrayList<Object>();
private JComboBox cb;
private static final Object[] comboContents = {
"First", "Second", "Third", "Fourth", "Fifth"
};
public NewComboTest() {
super("New Combo Test");
setLayout(null);
cb = new JComboBox();
cb.setRenderer(new NewComboRenderer());
cb.setEditor(new NewComboEditor());
cb.setEditable(true);
cb.setSize(new Dimension(350, 100));
for (int i = 0; i < comboContents.length; i++) {
cb.addItem(comboContents[ i]);
}
cb.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
System.out.println("_____________" + cb.getSelectedItem());
shopCart.add(cb.getSelectedItem());
System.out.println("items added" + shopCart);
}
});
cb.getEditor().getEditorComponent().addKeyListener(new KeyAdapter() {
#Override
public void keyReleased(KeyEvent e) {
System.out.println("KeyReleased" + cb.getEditor().getItem().toString());
populateModel(cb.getEditor().getItem().toString());
}
});
getContentPane().add(cb, new org.netbeans.lib.awtextra.AbsoluteConstraints(320, 200, 480, 50));
setSize(1200, 450);
setDefaultCloseOperation(EXIT_ON_CLOSE);
setVisible(true);
}
public static void main(String[] arg) {
new NewComboTest();
}
private class NewComboEditor extends JPanel implements ComboBoxEditor {
JTextField tf;
JButton eraseButton;
textPanel panel = new textPanel();
public NewComboEditor() {
}
#Override
public void addActionListener(ActionListener l) {
tf.addActionListener(l);
}
#Override
public Component getEditorComponent() {
return panel;
}
public Component getEditorComponent2() {
return panel;
}
#Override
public Object getItem() {
return tf.getText();
}
#Override
public void removeActionListener(ActionListener l) {
tf.removeActionListener(l);
}
#Override
public void selectAll() {
tf.selectAll();
}
#Override
public void setItem(Object o) {
if (o != null) {
tf.setText(tf.getText());
} else {
tf.setText("");
}
}
private class textPanel extends JPanel {
JTextField jTextField1 = new JTextField();
JButton jButton1 = new JButton();
public textPanel() {
setLayout(new BorderLayout());
jButton1.setBackground(new java.awt.Color(255, 255, 255));
jButton1.setForeground(new java.awt.Color(0, 51, 51));
jButton1.setText("X");
jButton1.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
jTextField1.setText("");
}
});
add(jTextField1, BorderLayout.CENTER);
add(jButton1, BorderLayout.EAST);
}
public String getText(){
return jTextField1.getText();
}
}
}
private class NewComboRenderer extends JLabel implements ListCellRenderer {
public NewComboRenderer() {
setOpaque(true);
}
public Component getListCellRendererComponent(
JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
setText(value.toString());
setBackground(isSelected ? Color.BLUE : Color.white);
setForeground(isSelected ? Color.white : Color.red);
return this;
}
}
/* public void populateModel(String text) throws HibernateException {
java.util.List l = DatabaseHelper.GetProductsBy(text);
for (Object object : l) {
cb.addItem(object);
}
ignore this its unnecessary.
*/
}
}
I also wish to set the text font and size to the same as the set up at the combo box.
The first set of problems I can see is, you define a JTextField and JButton in the NewComboEditor, but also define a textPanel, which contains all these things any way. But instead of using the components on the textPane, you use the newly created components (in the NewComboEditor) instead...In fact, I'm not even sure how that could work, because you never initilise these components (in the NewComboEditor), so there should be a NullPointerException...
If that wasn't enough problems, the JTextField and JButton aren't added to anything anyway...
Instead...
NewComboEditor shouldn't need to extend from anything (or it could extend from textPane instead if you really wanted to).
All references to the field should be made to the text field in the textPane
As an example...
import java.awt.Component;
import java.awt.EventQueue;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.event.ActionListener;
import javax.swing.ComboBoxEditor;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class CustomComboBoxEditor {
public static void main(String[] args) {
new CustomComboBoxEditor();
}
public CustomComboBoxEditor() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
}
JComboBox cb = new JComboBox();
cb.addItem("Apple");
cb.addItem("Banana");
cb.addItem("Orange");
cb.setEditable(true);
cb.setEditor(new MyComboBoxEditor());
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new GridBagLayout());
frame.add(cb);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class MyComboBoxEditor implements ComboBoxEditor {
private EditorPane editorPane;
public MyComboBoxEditor() {
editorPane = new EditorPane();
}
#Override
public Component getEditorComponent() {
return editorPane;
}
#Override
public void setItem(Object anObject) {
editorPane.setText(anObject == null ? null : anObject.toString());
}
#Override
public Object getItem() {
return editorPane.getText();
}
#Override
public void selectAll() {
editorPane.selectAll();
}
#Override
public void addActionListener(ActionListener l) {
editorPane.addActionListener(l);
}
#Override
public void removeActionListener(ActionListener l) {
editorPane.removeActionListener(l);
}
}
public class EditorPane extends JPanel {
private JTextField field;
private JButton button;
public EditorPane() {
field = new JTextField(10);
button = new JButton("X");
setLayout(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
gbc.weightx = 1;
gbc.fill = GridBagConstraints.HORIZONTAL;
gbc.gridx = 0;
add(field, gbc);
gbc.weightx = 0;
gbc.fill = GridBagConstraints.NONE;
gbc.gridx++;
add(button, gbc);
}
#Override
public void addNotify() {
super.addNotify();
field.requestFocusInWindow();
}
public void selectAll() {
field.selectAll();
}
public void setText(String text) {
field.setText(text);
}
public String getText() {
return field.getText();
}
public void addActionListener(ActionListener listener) {
field.addActionListener(listener);
}
public void removeActionListener(ActionListener listener) {
field.removeActionListener(listener);
}
}
}
Now, if you want to set the field's properties to be the same as the combo box, I would simply pass a reference of the combo box to the editor and allow it to extract the properties you need (i.e. font, color, etc.)
In my main application, the JTable is losing focus when a dialog is shown from a cell editor component.
Below is a simple SSCCE I made for you to see the problem.
Do these simples experiments:
Press F2 in the first table column to start editing. Then change to column contents to the number 2 and press ENTER key. The table will lose focus and the first field in the form with get focus.
Press F2 in the first table column to start editing. Then change to column contents to the number 2 and press TAB key. The table will lose focus and the first field in the form with get focus.
The first field in the form is also a SearchField component. Because it is not in the JTable, it behaves properly when you change its contente to the number 2 and commit the edit (with ENTER or TAB).
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.text.NumberFormat;
import java.text.ParseException;
import java.util.Objects;
import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.DefaultCellEditor;
import javax.swing.JFormattedTextField;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.JTextField;
import javax.swing.ListSelectionModel;
import javax.swing.SwingUtilities;
import javax.swing.table.AbstractTableModel;
import javax.swing.text.DefaultFormatterFactory;
import javax.swing.text.NumberFormatter;
public class SSCCE extends JPanel
{
private SSCCE()
{
setLayout(new BorderLayout());
setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
JPanel pnlFields = new JPanel();
pnlFields.setLayout(new BoxLayout(pnlFields, BoxLayout.PAGE_AXIS));
pnlFields.setBorder(BorderFactory.createEmptyBorder(0, 0, 10, 0));
SearchField field1 = new SearchField();
configureField(field1);
pnlFields.add(field1);
pnlFields.add(Box.createRigidArea(new Dimension(0, 3)));
JTextField field2 = new JTextField();
configureField(field2);
pnlFields.add(field2);
add(pnlFields, BorderLayout.PAGE_START);
add(new JScrollPane(createTable()), BorderLayout.CENTER);
}
private void configureField(JTextField field)
{
field.setPreferredSize(new Dimension(150, field.getPreferredSize().height));
field.setMaximumSize(field.getPreferredSize());
field.setAlignmentX(LEFT_ALIGNMENT);
}
private JTable createTable()
{
JTable table = new JTable(new CustomTableModel());
table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
table.setCellSelectionEnabled(true);
table.getTableHeader().setReorderingAllowed(false);
table.setPreferredScrollableViewportSize(new Dimension(500, 170));
table.setDefaultEditor(Integer.class, new SearchFieldCellEditor(new SearchField()));
return table;
}
private static void createAndShowGUI()
{
JFrame frame = new JFrame("SSCCE (JTable Loses Focus)");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new SSCCE());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public static void main(String[] args)
{
SwingUtilities.invokeLater(
new Runnable()
{
#Override
public void run()
{
createAndShowGUI();
}
}
);
}
}
class CustomTableModel extends AbstractTableModel
{
private String[] columnNames = {"Column1 (Search Field)", "Column 2"};
private Class<?>[] columnTypes = {Integer.class, String.class};
private Object[][] data = {{1, ""}, {3, ""}, {4, ""}, {5, ""}, {6, ""}};
#Override
public int getColumnCount()
{
return columnNames.length;
}
#Override
public int getRowCount()
{
return data.length;
}
#Override
public String getColumnName(int col)
{
return columnNames[col];
}
#Override
public Object getValueAt(int row, int col)
{
return data[row][col];
}
#Override
public Class<?> getColumnClass(int c)
{
return columnTypes[c];
}
#Override
public boolean isCellEditable(int rowIndex, int columnIndex)
{
return true;
}
#Override
public void setValueAt(Object value, int row, int col)
{
data[row][col] = value;
fireTableCellUpdated(row, col);
}
}
class SearchFieldCellEditor extends DefaultCellEditor
{
SearchFieldCellEditor(final SearchField searchField)
{
super(searchField);
searchField.removeActionListener(delegate);
delegate = new EditorDelegate()
{
#Override
public void setValue(Object value)
{
searchField.setValue(value);
}
#Override
public Object getCellEditorValue()
{
return searchField.getValue();
}
};
searchField.addActionListener(delegate);
}
#Override
public boolean stopCellEditing()
{
try
{
((SearchField) getComponent()).commitEdit();
}
catch (ParseException ex)
{
ex.printStackTrace();
}
return super.stopCellEditing();
}
}
class SearchField extends JFormattedTextField implements PropertyChangeListener
{
private Object _oldValue;
SearchField()
{
setupFormatter();
addPropertyChangeListener("value", this);
}
private void setupFormatter()
{
NumberFormat integerFormat = NumberFormat.getIntegerInstance();
integerFormat.setGroupingUsed(false);
NumberFormatter integerFormatter =
new NumberFormatter(integerFormat)
{
#Override
public Object stringToValue(String text) throws ParseException
{
return text.isEmpty() ? null : super.stringToValue(text);
}
};
integerFormatter.setValueClass(Integer.class);
integerFormatter.setMinimum(Integer.MIN_VALUE);
integerFormatter.setMaximum(Integer.MAX_VALUE);
setFormatterFactory(new DefaultFormatterFactory(integerFormatter));
}
#Override
public void propertyChange(PropertyChangeEvent evt)
{
Object newValue = evt.getNewValue();
if (!Objects.equals(newValue, _oldValue))
{
_oldValue = newValue;
// Suppose that a value of 2 means that the data wasn't found.
// So we display a message to the user.
if (new Integer(2).equals(newValue))
{
JOptionPane.showMessageDialog(
null, "Not found: " + newValue + ".", "Warning",
JOptionPane.WARNING_MESSAGE);
}
}
}
}
So, is there a way to solve this problem? The solution of this issue is very important to me.
Thank you.
Marcos
* UPDATE *
I think I've found a solution, but I would like to have your opinion if it is really a trustworthy solution.
Change the stopCellEditing method to this and test the SSCCE again:
#Override
public boolean stopCellEditing()
{
SearchField searchField = (SearchField) getComponent();
try
{
searchField.commitEdit();
}
catch (ParseException ex)
{
ex.printStackTrace();
}
Component table = searchField.getParent();
table.requestFocusInWindow();
return super.stopCellEditing();
}
So, do you think this really solves the problem or is there any flaw?
Marcos
UPDATE 2
I've found a little flaw. It is corrected with these changes:
class SearchFieldCellEditor extends DefaultCellEditor
{
SearchFieldCellEditor(final SearchField searchField)
{
super(searchField);
searchField.removeActionListener(delegate);
delegate = new EditorDelegate()
{
#Override
public void setValue(Object value)
{
searchField.setValue(value);
}
#Override
public Object getCellEditorValue()
{
return searchField.getValue();
}
};
searchField.addActionListener(delegate);
}
#Override
public Component getTableCellEditorComponent(
JTable table, Object value, boolean isSelected, int row, int column)
{
SearchField searchField = (SearchField) getComponent();
searchField.setPreparingForEdit(true);
try
{
return super.getTableCellEditorComponent(
table, value, isSelected, row, column);
}
finally
{
searchField.setPreparingForEdit(false);
}
}
#Override
public boolean stopCellEditing()
{
SearchField searchField = (SearchField) getComponent();
try
{
searchField.commitEdit();
}
catch (ParseException ex)
{
ex.printStackTrace();
}
Component table = searchField.getParent();
table.requestFocusInWindow();
return super.stopCellEditing();
}
}
class SearchField extends JFormattedTextField implements PropertyChangeListener
{
private boolean _isPreparingForEdit;
private Object _oldValue;
SearchField()
{
setupFormatter();
addPropertyChangeListener("value", this);
}
void setPreparingForEdit(boolean isPreparingForEdit)
{
_isPreparingForEdit = isPreparingForEdit;
}
private void setupFormatter()
{
NumberFormat integerFormat = NumberFormat.getIntegerInstance();
integerFormat.setGroupingUsed(false);
NumberFormatter integerFormatter =
new NumberFormatter(integerFormat)
{
#Override
public Object stringToValue(String text) throws ParseException
{
return text.isEmpty() ? null : super.stringToValue(text);
}
};
integerFormatter.setValueClass(Integer.class);
integerFormatter.setMinimum(Integer.MIN_VALUE);
integerFormatter.setMaximum(Integer.MAX_VALUE);
setFormatterFactory(new DefaultFormatterFactory(integerFormatter));
}
#Override
public void propertyChange(PropertyChangeEvent evt)
{
final Object newValue = evt.getNewValue();
if (!Objects.equals(newValue, _oldValue))
{
_oldValue = newValue;
// Suppose that a value of 2 means that the data wasn't found.
// So we display a message to the user.
if (new Integer(2).equals(newValue) && !_isPreparingForEdit)
{
JOptionPane.showMessageDialog(null, "Not found: " + newValue + ".", "Warning",
JOptionPane.WARNING_MESSAGE);
}
}
}
}
Have you found any more flaws too? I would like to have your review.
Marcos
UPDATE 3
Another solution after suggestion by kleopatra :
class SearchFieldCellEditor extends DefaultCellEditor
{
SearchFieldCellEditor(final SearchField searchField)
{
super(searchField);
searchField.setShowMessageAsynchronously(true);
searchField.removeActionListener(delegate);
delegate = new EditorDelegate()
{
#Override
public void setValue(Object value)
{
searchField.setValue(value);
}
#Override
public Object getCellEditorValue()
{
return searchField.getValue();
}
};
searchField.addActionListener(delegate);
}
#Override
public Component getTableCellEditorComponent(
JTable table, Object value, boolean isSelected, int row, int column)
{
SearchField searchField = (SearchField) getComponent();
searchField.setPreparingForEdit(true);
try
{
return super.getTableCellEditorComponent(
table, value, isSelected, row, column);
}
finally
{
searchField.setPreparingForEdit(false);
}
}
#Override
public boolean stopCellEditing()
{
SearchField searchField = (SearchField) getComponent();
try
{
searchField.commitEdit();
}
catch (ParseException ex)
{
ex.printStackTrace();
}
return super.stopCellEditing();
}
}
class SearchField extends JFormattedTextField implements PropertyChangeListener
{
private boolean _showMessageAsynchronously;
private boolean _isPreparingForEdit;
private Object _oldValue;
SearchField()
{
setupFormatter();
addPropertyChangeListener("value", this);
}
public boolean isShowMessageAsynchronously()
{
return _showMessageAsynchronously;
}
public void setShowMessageAsynchronously(boolean showMessageAsynchronously)
{
_showMessageAsynchronously = showMessageAsynchronously;
}
void setPreparingForEdit(boolean isPreparingForEdit)
{
_isPreparingForEdit = isPreparingForEdit;
}
private void setupFormatter()
{
NumberFormat integerFormat = NumberFormat.getIntegerInstance();
integerFormat.setGroupingUsed(false);
NumberFormatter integerFormatter =
new NumberFormatter(integerFormat)
{
#Override
public Object stringToValue(String text) throws ParseException
{
return text.isEmpty() ? null : super.stringToValue(text);
}
};
integerFormatter.setValueClass(Integer.class);
integerFormatter.setMinimum(Integer.MIN_VALUE);
integerFormatter.setMaximum(Integer.MAX_VALUE);
setFormatterFactory(new DefaultFormatterFactory(integerFormatter));
}
#Override
public void propertyChange(PropertyChangeEvent evt)
{
final Object newValue = evt.getNewValue();
if (!Objects.equals(newValue, _oldValue))
{
_oldValue = newValue;
// Suppose that a value of 2 means that the data wasn't found.
// So we display a message to the user.
if (new Integer(2).equals(newValue) && !_isPreparingForEdit)
{
if (_showMessageAsynchronously)
{
SwingUtilities.invokeLater(
new Runnable()
{
#Override
public void run()
{
showMessage(newValue);
}
}
);
}
else
{
showMessage(newValue);
}
}
}
}
private void showMessage(Object value)
{
JOptionPane.showMessageDialog(null, "Not found: " + value + ".",
"Warning", JOptionPane.WARNING_MESSAGE);
}
}
Comments and suggestions about this last solution are still appreciated. Is this the ultimate and optimal solution?
Marcos
As I already commented: it's a bit fishy to change the state of the table in the editor, especially if it's related to focus which is brittle even at its best. So I would go to great lengths to avoid it.
The mis-behaviour feels similar to an incorrectly implemented InputVerifier which has side-effects (like grabbing the focus) in its verify vs. in its shouldYieldFocus as would be correct: in such a context the focusManager gets confused, it "forgets" about the natural last-focusOwner-before.
The remedy might be to let the manager do its job first, and show the message only when it's done. In your example code that can be achieved by wrapping into an invokeLater:
if (needsMessage()) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
JOptionPane.showMessageDialog(null, "Not found: " +
newValue + ".", "Warning",
JOptionPane.WARNING_MESSAGE);
}
});
}
Do the editing in the stopCellEditing() method.
In this example you are forced to enter a string of 5 characters:
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.text.*;
import javax.swing.event.*;
import javax.swing.border.*;
import javax.swing.table.*;
public class TableEdit extends JFrame
{
TableEdit()
{
JTable table = new JTable(5,5);
table.setPreferredScrollableViewportSize(table.getPreferredSize());
JScrollPane scrollpane = new JScrollPane(table);
add(scrollpane);
// Use a custom editor
TableCellEditor fce = new FiveCharacterEditor();
table.setDefaultEditor(Object.class, fce);
add(new JTextField(), BorderLayout.NORTH);
}
class FiveCharacterEditor extends DefaultCellEditor
{
FiveCharacterEditor()
{
super( new JTextField() );
}
public boolean stopCellEditing()
{
JTable table = (JTable)getComponent().getParent();
try
{
System.out.println(getCellEditorValue().getClass());
String editingValue = (String)getCellEditorValue();
if(editingValue.length() != 5)
{
JTextField textField = (JTextField)getComponent();
textField.setBorder(new LineBorder(Color.red));
textField.selectAll();
textField.requestFocusInWindow();
JOptionPane.showMessageDialog(
null,
"Please enter string with 5 letters.",
"Alert!",JOptionPane.ERROR_MESSAGE);
return false;
}
}
catch(ClassCastException exception)
{
return false;
}
return super.stopCellEditing();
}
public Component getTableCellEditorComponent(
JTable table, Object value, boolean isSelected, int row, int column)
{
Component c = super.getTableCellEditorComponent(
table, value, isSelected, row, column);
((JComponent)c).setBorder(new LineBorder(Color.black));
return c;
}
}
public static void main(String [] args)
{
JFrame frame = new TableEdit();
frame.setDefaultCloseOperation(EXIT_ON_CLOSE);
frame.pack();
frame.setLocationRelativeTo( null );
frame.setVisible(true);
}
}
This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
Accessing a JTextField in JTableHeader
How to make JTableHeader as editable. F.e. i make ColumnHeader as JTextField. What must i do to make JTextField editable. See the example:
import java.awt.*;
import javax.swing.*;
import javax.swing.table.*;
import java.awt.event.*;
public class Test extends JFrame {
String[][] cellValues = { { "v00", "v01", "v02" }, { "v10", "v11", "v12" },
{ "v20", "v21", "v22" }, { "v30", "v31", "v32" },
{ "v40", "v41", "v42" }, { "v50", "v51", "v52" } };
String[] columnNames = { "v00", "v01", "v02" };
javax.swing.JTable jTable1 = new javax.swing.JTable(cellValues, columnNames);
public Test() {
try {
jbInit();
} catch (Exception e) {
e.printStackTrace();
}
}
private void jbInit() throws Exception {
this.getContentPane().add(new JScrollPane(jTable1), null);
for (int i = 0; i < 3; i++)
jTable1.getColumnModel().getColumn(i)
.setHeaderRenderer(new Renderer());
}
public static void main(String[] args) {
Test frame = new Test();
frame.addWindowListener(new java.awt.event.WindowAdapter() {
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
frame.pack();
frame.setVisible(true);
}
}
class Renderer extends JPanel implements TableCellRenderer {
JLabel label = new JLabel(" ");
JTextField field = new JTextField("%");
public Renderer() {
super(new BorderLayout());
add(label, BorderLayout.NORTH);
add(field, BorderLayout.CENTER);
setBorder(BorderFactory.createEtchedBorder());
}
public Component getTableCellRendererComponent(JTable table, Object value,
boolean isSelected, boolean hasFocus, int row, int column) {
label.setText(value.toString());
return (JComponent) this;
}
public String toString() {
return label.toString();
}
public void setText(String text) {
label.setText(text);
}
}
Here's a simple approach for making editable headers (no custom renderers or UIs required):
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.border.*;
import javax.swing.table.*;
public class JTableEditableHeaderDemo implements Runnable
{
private JTable table;
private JTableHeader header;
private JPopupMenu renamePopup;
private JTextField text;
private TableColumn column;
public static void main(String[] args)
{
SwingUtilities.invokeLater(new JTableEditableHeaderDemo());
}
public JTableEditableHeaderDemo()
{
table = new JTable(10, 5);
table.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
table.setPreferredScrollableViewportSize(table.getPreferredSize());
header = table.getTableHeader();
header.addMouseListener(new MouseAdapter(){
#Override
public void mouseClicked(MouseEvent event)
{
if (event.getClickCount() == 2)
{
editColumnAt(event.getPoint());
}
}
});
text = new JTextField();
text.setBorder(null);
text.addActionListener(new ActionListener(){
public void actionPerformed(ActionEvent e)
{
renameColumn();
}
});
renamePopup = new JPopupMenu();
renamePopup.setBorder(new MatteBorder(0, 1, 1, 1, Color.DARK_GRAY));
renamePopup.add(text);
}
public void run()
{
JFrame f = new JFrame("Double-click header to edit");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.getContentPane().add(new JScrollPane(table));
f.pack();
f.setLocationRelativeTo(null);
f.setVisible(true);
}
private void editColumnAt(Point p)
{
int columnIndex = header.columnAtPoint(p);
if (columnIndex != -1)
{
column = header.getColumnModel().getColumn(columnIndex);
Rectangle columnRectangle = header.getHeaderRect(columnIndex);
text.setText(column.getHeaderValue().toString());
renamePopup.setPreferredSize(
new Dimension(columnRectangle.width, columnRectangle.height - 1));
renamePopup.show(header, columnRectangle.x, 0);
text.requestFocusInWindow();
text.selectAll();
}
}
private void renameColumn()
{
column.setHeaderValue(text.getText());
renamePopup.setVisible(false);
header.repaint();
}
}
There is a complete solution under the following link, looks quite good at the first sight!
How do I create a JTable with editable headers?