Can serialization be avoided in DnD between JComponents within the same application? - java

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;
}
}

Related

How to change description image in JList java

Below is my code that displays images in a JList. I want to edit the description by each of the images shown in the JList. I don't know how to do it & need help. Thanks...
import java.util.*;
import javax.imageio.ImageIO;
import javax.swing.*;
import javax.swing.border.Border;
import java.awt.*;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.Serializable;
public class DesignPicture2 {
private static String imageName;
static ArrayList<String> imgName = new ArrayList<String>();
public static void main(String[] args) throws Exception {
DesignPicture2 mm = new DesignPicture2();
mm.getImageName("C:\\Images 2 display");
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
JFrame frame = new JFrame("Image panel");
frame.setSize(800, 500);
//frame.setLocationByPlatform(true);
frame.setLocation(600, 300);
JList imageList = createImageList();
frame.getContentPane().add(new JScrollPane(imageList));
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
private static JList createImageList() {
JList imageList = new JList(createModel("C:\\Images 2 display"));
imageList.setCellRenderer(new ImageCellRenderer());
imageList.setLayoutOrientation(JList.HORIZONTAL_WRAP);
imageList.setVisibleRowCount(0);
imageList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
imageList.setFixedCellWidth(240);
imageList.setFixedCellHeight(120);
// imageList.setDragEnabled(false);
//imageList.setDropMode(DropMode.INSERT);
imageList.setTransferHandler(new ImageTransferHandler(imageList));
return imageList;
}
private static DefaultListModel createModel(String path) {
File folder = new File(path);
File[] listOfFiles = folder.listFiles();
DefaultListModel model = new DefaultListModel();
int count = 0;
for (int i = 0; i < listOfFiles.length - 1; i++) {
System.out.println("check path: " + listOfFiles[i]);
imageName = imgName.get(i).toString();
String name = listOfFiles[i].toString();
//load only JPEGS
if (name.endsWith("jpg")) {
try {
ImageIcon ii = new ImageIcon(ImageIO.read(listOfFiles[i]));
model.add(count, ii);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
return model;
}
static class ImageTransferHandler extends TransferHandler {
private static final DataFlavor DATA_FLAVOUR = new DataFlavor(ColorIcon.class, "Images");
private final JList previewList;
private boolean inDrag;
ImageTransferHandler(JList previewList) {
this.previewList = previewList;
}
public int getSourceActions(JComponent c) {
return TransferHandler.MOVE;
}
protected Transferable createTransferable(JComponent c) {
inDrag = true;
return new Transferable() {
public DataFlavor[] getTransferDataFlavors() {
return new DataFlavor[]{DATA_FLAVOUR};
}
public boolean isDataFlavorSupported(DataFlavor flavor) {
return flavor.equals(DATA_FLAVOUR);
}
public Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorException, IOException {
return previewList.getSelectedValue();
}
};
}
public boolean canImport(TransferSupport support) {
if (!inDrag || !support.isDataFlavorSupported(DATA_FLAVOUR)) {
return false;
}
JList.DropLocation dl = (JList.DropLocation) support.getDropLocation();
if (dl.getIndex() == -1) {
return false;
} else {
return true;
}
}
public boolean importData(TransferSupport support) {
if (!canImport(support)) {
return false;
}
Transferable transferable = support.getTransferable();
try {
Object draggedImage = transferable.getTransferData(DATA_FLAVOUR);
JList.DropLocation dl = (JList.DropLocation) support.getDropLocation();
DefaultListModel model = (DefaultListModel) previewList.getModel();
int dropIndex = dl.getIndex();
if (model.indexOf(draggedImage) < dropIndex) {
dropIndex--;
}
model.removeElement(draggedImage);
model.add(dropIndex, draggedImage);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
protected void exportDone(JComponent source, Transferable data, int action) {
super.exportDone(source, data, action);
inDrag = false;
}
}
static class ImageCellRenderer extends JPanel implements ListCellRenderer {
int count = 0;
DefaultListCellRenderer defaultListCellRenderer = new DefaultListCellRenderer();
JLabel imageLabel = new JLabel();
JLabel descriptionLabel = new JLabel();
ImageCellRenderer() {
setLayout(new BorderLayout());
Border emptyBorder = BorderFactory.createEmptyBorder(5, 5, 5, 5);
imageLabel.setBorder(emptyBorder);
descriptionLabel.setBorder(emptyBorder);
add(imageLabel, BorderLayout.AFTER_LINE_ENDS);
add(descriptionLabel, BorderLayout.SOUTH);
// imageLabel.setText(imgName.get(0).toString());
}
public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
defaultListCellRenderer.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
setBorder(defaultListCellRenderer.getBorder());
setBackground(defaultListCellRenderer.getBackground());
imageLabel.setIcon((Icon) value);
if (count > imgName.size() - 1) {
count = 0;
} else {
descriptionLabel.setText(imgName.get(count).toString());
}
return this;
}
}
public void getImageName(String path) {
int c = 0;
final File dir = new File(path);
// array of supported extensions (use a List if you prefer)
final String[] EXTENSIONS = new String[]{
"jpg", "gif", "png", "bmp" // and other formats you need
// filter to identify images based on their extensions
};
final FilenameFilter IMAGE_FILTER = new FilenameFilter() {
#Override
public boolean accept(final File dir, final String name) {
for (final String ext : EXTENSIONS) {
if (name.endsWith("." + ext)) {
return (true);
}
}
return (false);
}
};
if (dir.isDirectory()) { // make sure it's a directory
for (final File f : dir.listFiles(IMAGE_FILTER)) {
BufferedImage img = null;
c++;
try {
img = ImageIO.read(f);
// you probably want something more involved here
// to display in your UI
System.out.println("image: " + f.getName());
imgName.add(f.getName().toString());
} catch (final IOException e) {
// handle errors here
System.out.println("Error!");
}
}
System.out.println("C: " + c);
} else {
System.out.println("Invalid Directory!");
}
}
static class ColorIcon implements Icon, Serializable {
private Color color;
ColorIcon(Color color) {
this.color = color;
}
public void paintIcon(Component c, Graphics g, int x, int y) {
g.setColor(color);
g.fillRect(x, y, getIconWidth(), getIconHeight());
}
public int getIconWidth() {
return 200;
}
public int getIconHeight() {
return 100;
}
public boolean equals(Object o) {
if (o == null || getClass() != o.getClass()) {
return false;
}
return color.equals(((ColorIcon) o).color);
}
}
}
When I run the above code, it show the image in proper way, but the description of each image is fixed and I don't know how to change it. Hope anyone can help me.
I agree with trashgod (+1 to his suggestion), a JTable will be a simpler solution, here's why...
JList doesn't support editability, so you'd need to create it...
So, first, we'd need some kind of ListModel that provided some additional functionality, in particular, the ability to set the value at a particular index...
import javax.swing.ListModel;
public interface MutableListModel<E> extends ListModel<E> {
public void setElementAt(E value, int index);
public boolean isCellEditable(int index);
}
Next, we'd need some kind editor, in this case, following standard Swing API conventions, this asks for some kind of base interface
import java.awt.Component;
import javax.swing.CellEditor;
import javax.swing.JList;
public interface ListCellEditor<E> extends CellEditor {
public Component getListCellEditorComponent(
JList<E> list,
E value,
boolean isSelected,
int index);
public void applyEditorValue(E value);
}
Now, we need to create ourselves a custom JList capable of actually performing all the required functionality of editing a cell value...
Things like...
Recognising a "start editing" event
Determine if the cell can be edited
Managing the editing process, preparing and showing the editor, knowing when the editor has stopped or canceled and clean up appropriately...
Handling selection changes while editing is in progress
Handling component focus change (which I've not done cause that's an awesome amount of fun in itself...)
For example...
import java.awt.Component;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import javax.swing.AbstractAction;
import javax.swing.ActionMap;
import javax.swing.InputMap;
import javax.swing.JList;
import javax.swing.KeyStroke;
import javax.swing.ListModel;
import javax.swing.event.CellEditorListener;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
public class EditableList<E> extends JList<E> {
private ListCellEditor<E> editor;
private int editingCell = -1;
private Component editorComponent;
private CellEditorHandler handler;
public EditableList(MutableListModel<E> model) {
this();
setModel(model);
}
public EditableList() {
InputMap im = getInputMap(WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_F2, 0), "editorCell");
ActionMap am = getActionMap();
am.put("editorCell", new AbstractAction() {
#Override
public void actionPerformed(ActionEvent e) {
System.out.println("Edit baby");
int cell = getSelectedIndex();
editCellAt(cell);
}
});
addListSelectionListener(new ListSelectionListener() {
#Override
public void valueChanged(ListSelectionEvent e) {
if (isEditing()) {
if (!stopCellEditing()) {
cancelCellEditing();
}
}
}
});
}
public boolean isEditing() {
return editorComponent != null;
}
public void cancelCellEditing() {
getEditor().cancelCellEditing();
}
public boolean stopCellEditing() {
return getEditor().stopCellEditing();
}
public CellEditorHandler getCellEditorHandler() {
if (handler == null) {
handler = new CellEditorHandler();
}
return handler;
}
public void setEditor(ListCellEditor<E> value) {
if (value != editor) {
ListCellEditor old = editor;
editor = value;
firePropertyChange("editor", old, editor);
}
}
public ListCellEditor<E> getEditor() {
return editor;
}
public boolean isCellEditable(int cell) {
boolean isEditable = false;
ListModel model = getModel();
if (model instanceof MutableListModel) {
MutableListModel mcm = (MutableListModel) model;
isEditable = mcm.isCellEditable(cell);
}
return isEditable;
}
protected void editCellAt(int index) {
if (isCellEditable(index)) {
ListCellEditor<E> editor = getEditor();
if (editor != null) {
Rectangle cellBounds = getCellBounds(index, index);
E value = getModel().getElementAt(index);
boolean selected = isSelectedIndex(index);
editingCell = index;
editor.addCellEditorListener(getCellEditorHandler());
editorComponent = editor.getListCellEditorComponent(this, value, selected, index);
editorComponent.setBounds(cellBounds);
ensureIndexIsVisible(index);
add(editorComponent);
revalidate();
}
}
}
public int getEditingCell() {
return editingCell;
}
protected void editingHasStopped(ListCellEditor editor) {
editingCell = -1;
if (editorComponent != null) {
remove(editorComponent);
}
if (editor != null) {
editor.removeCellEditorListener(getCellEditorHandler());
}
}
public class CellEditorHandler implements CellEditorListener {
#Override
public void editingStopped(ChangeEvent e) {
E value = getModel().getElementAt(getEditingCell());
getEditor().applyEditorValue(value);
((MutableListModel) getModel()).setElementAt(value, getEditingCell());
editingHasStopped((ListCellEditor)e.getSource());
}
#Override
public void editingCanceled(ChangeEvent e) {
editingHasStopped((ListCellEditor)e.getSource());
}
}
}
Now, having done all that, you will need change the way you've structured your program, instead of using a List and ListModel to manage the descriptions and images separately, you should probably merge the concept into a single, manageable object, for example...
public class ImagePreview {
private String name;
private ImageIcon image;
public ImagePreview(String name, ImageIcon image) {
this.name = name;
this.image = image;
}
public String getDescription() {
return name;
}
public ImageIcon getImage() {
return image;
}
protected void setDescription(String description) {
this.name = description;
}
}
Even if you choose to use a JTable instead, you'll find this easier to manage...
Now we need some way to render and edit these values, to this end, I choose to start with a base component which could display the image and text...
public class ImagePreviewPane extends JPanel {
private JLabel imageLabel = new JLabel();
private JLabel descriptionLabel = new JLabel();
public ImagePreviewPane() {
setLayout(new BorderLayout());
Border emptyBorder = BorderFactory.createEmptyBorder(5, 5, 5, 5);
imageLabel.setBorder(emptyBorder);
descriptionLabel.setBorder(emptyBorder);
add(imageLabel, BorderLayout.CENTER);
add(descriptionLabel, BorderLayout.SOUTH);
}
protected JLabel getDescriptionLabel() {
return descriptionLabel;
}
protected JLabel getImageLabel() {
return imageLabel;
}
public void setImage(ImageIcon icon) {
imageLabel.setIcon(icon);
}
public void setDescription(String text) {
descriptionLabel.setText(text);
}
}
Create an extended version that could handle editing...
public static class ImagePreviewEditorPane extends ImagePreviewPane {
private JTextField editor;
public ImagePreviewEditorPane() {
super();
editor = new JTextField();
remove(getDescriptionLabel());
add(editor, BorderLayout.SOUTH);
}
#Override
public void setDescription(String text) {
editor.setText(text);
}
public String getDescription() {
return editor.getText();
}
public void setImagePreview(ImagePreview preview) {
setImage(preview.getImage());
setDescription(preview.getDescription());
}
#Override
public void addNotify() {
super.addNotify();
editor.requestFocusInWindow();
}
}
This is done to try and make it easier to modify the components later...
Next, a ListCellRenderer
public class ImageCellRenderer extends ImagePreviewPane implements ListCellRenderer {
public ImageCellRenderer() {
}
#Override
public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
Color bg = null;
Color fg = null;
JList.DropLocation dropLocation = list.getDropLocation();
if (dropLocation != null
&& !dropLocation.isInsert()
&& dropLocation.getIndex() == index) {
bg = DefaultLookup.getColor(this, getUI(), "List.dropCellBackground");
fg = DefaultLookup.getColor(this, getUI(), "List.dropCellForeground");
isSelected = true;
}
if (isSelected) {
setBackground(bg == null ? list.getSelectionBackground() : bg);
setForeground(fg == null ? list.getSelectionForeground() : fg);
} else {
setBackground(list.getBackground());
setForeground(list.getForeground());
}
if (value instanceof ImagePreview) {
ImagePreview ip = (ImagePreview) value;
setImage(ip.getImage());
setDescription(ip.getDescription());
} else {
setImage(null);
setDescription("??");
}
setEnabled(list.isEnabled());
setFont(list.getFont());
return this;
}
}
And editor...
public class ImagePreviewListCellEditor extends AbstactListCellEditor<ImagePreview> {
private ImagePreviewEditorPane previewPane;
public ImagePreviewListCellEditor() {
previewPane = new ImagePreviewEditorPane();
InputMap im = previewPane.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0), "accept");
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), "cancel");
ActionMap am = previewPane.getActionMap();
am.put("accept", new AbstractAction() {
#Override
public void actionPerformed(ActionEvent e) {
stopCellEditing();
}
});
am.put("cancel", new AbstractAction() {
#Override
public void actionPerformed(ActionEvent e) {
cancelCellEditing();
}
});
}
public void applyEditorValue(ImagePreview preview) {
preview.setDescription(previewPane.getDescription());
}
#Override
public Component getListCellEditorComponent(JList<ImagePreview> list, ImagePreview value, boolean isSelected, int index) {
Color bg = null;
Color fg = null;
JList.DropLocation dropLocation = list.getDropLocation();
if (dropLocation != null
&& !dropLocation.isInsert()
&& dropLocation.getIndex() == index) {
bg = DefaultLookup.getColor(previewPane, previewPane.getUI(), "List.dropCellBackground");
fg = DefaultLookup.getColor(previewPane, previewPane.getUI(), "List.dropCellForeground");
isSelected = true;
}
if (isSelected) {
previewPane.setBackground(bg == null ? list.getSelectionBackground() : bg);
previewPane.setForeground(fg == null ? list.getSelectionForeground() : fg);
} else {
previewPane.setBackground(list.getBackground());
previewPane.setForeground(list.getForeground());
}
if (value instanceof ImagePreview) {
ImagePreview preview = (ImagePreview)value;
previewPane.setImagePreview(preview);
} else {
previewPane.setImagePreview(null);
}
return previewPane;
}
}
And finally, putting it altogether...
public class DesignPicture2 {
public static void main(String[] args) throws Exception {
DesignPicture2 mm = new DesignPicture2();
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
JFrame frame = new JFrame("Image panel");
frame.setSize(800, 500);
frame.setLocation(600, 300);
JList imageList = createImageList();
frame.getContentPane().add(new JScrollPane(imageList));
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
private static JList createImageList() {
EditableList<ImagePreview> imageList = new EditableList(createModel("..."));
imageList.setEditor(new ImagePreviewListCellEditor());
imageList.setCellRenderer(new ImageCellRenderer());
imageList.setLayoutOrientation(JList.HORIZONTAL_WRAP);
imageList.setVisibleRowCount(0);
imageList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
imageList.setFixedCellWidth(240);
imageList.setFixedCellHeight(120);
return imageList;
}
private static MutableListModel<ImagePreview> createModel(String path) {
File folder = new File(path);
File[] listOfFiles = folder.listFiles();
DefaultMutableListModel<ImagePreview> model = new DefaultMutableListModel<>();
int count = 0;
for (int i = 0; i < listOfFiles.length - 1; i++) {
System.out.println("check path: " + listOfFiles[i]);
String name = listOfFiles[i].toString();
if (name.endsWith("jpg")) {
try {
ImageIcon ii = new ImageIcon(ImageIO.read(listOfFiles[i]));
model.addElement(new ImagePreview(name, ii));
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
return model;
}
}
JList does not support the notion of a cell editor. Instead, use a two-column JTable. Some important points:
Your model's isCellEditable() implementation should return true for the description column in order to make it editable.
Your model's implementation of getColumnClass() can let the default renderer display your image by returning ImageIcon or Icon .

Enable stringFlavor of Transfersupport in Java Swing

I am implementing a functionality to Drag and Drop the Textattribute of JLabels into the cells of a JTable. So I've created a custom TransferHandler for the table.
But every call of
support.isDataFlavorSupported(DataFlavor.stringFlavor)
returns false
How can I make sure that my TransferHandler is able to import Strings?
Here is the source
public class TableHandler extends TransferHandler {
private static final long serialVersionUID = 1L;
#Override
public boolean canImport(TransferSupport support) {
if (!support.isDrop()) {
return false;
}
//only Strings
if(!support.isDataFlavorSupported(DataFlavor.stringFlavor)){
return false;
}
return true;
}
#Override
public boolean importData(TransferSupport support) {
if (!canImport(support)) {
return false;
}
Transferable tansferable = support.getTransferable();
String line;
try {
line = (String) tansferable
.getTransferData(DataFlavor.stringFlavor);
} catch (Exception e) {
return false;
}
JTable.DropLocation dl = (JTable.DropLocation) support
.getDropLocation();
int column = dl.getColumn();
int row = dl.getRow();
String[] data = line.split(",");
for (String item : data) {
if (!item.isEmpty()) {
table.getTableModel().setValueAt(item, row, column);
}
}
return true;
}
}
Check this example seems to work fine:
Before any dragging / dropping of JLabels:
Clicked and dragged Hello JLabel to 1st cell:
On release of click over the first cell:
import java.awt.BorderLayout;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.awt.dnd.DnDConstants;
import java.awt.dnd.DragGestureEvent;
import java.awt.dnd.DragGestureListener;
import java.awt.dnd.DragSource;
import java.io.IOException;
import javax.swing.DropMode;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.SwingUtilities;
import javax.swing.TransferHandler;
public class Test {
public static void main(String[] args) {
createAndShowJFrame();
}
public static void createAndShowJFrame() {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
JFrame frame = createJFrame();
frame.setVisible(true);
}
});
}
private static JFrame createJFrame() {
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setTitle("Test");
JTable table = createJTable();
JScrollPane js = new JScrollPane(table);
frame.add(js, BorderLayout.CENTER);
frame.add(createJLabelPanel(), BorderLayout.SOUTH);
frame.pack();
//frame.setResizable(false);//make it un-resizeable
//frame.setLocationRelativeTo(null);//center JFrame
return frame;
}
private static JTable createJTable() {
// 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
final JTable table = new JTable(data, columns);
// set up drag and drop
table.setDragEnabled(true);
table.setDropMode(DropMode.USE_SELECTION);
table.setFillsViewportHeight(true);
TransferHandler dnd = new TransferHandler() {
#Override
public boolean canImport(TransferSupport support) {
if (!support.isDrop()) {
return false;
}
//only Strings
if (!support.isDataFlavorSupported(DataFlavor.stringFlavor)) {
return false;
}
return true;
}
#Override
public boolean importData(TransferSupport support) {
if (!canImport(support)) {
return false;
}
Transferable tansferable = support.getTransferable();
String line;
try {
line = (String) tansferable.getTransferData(DataFlavor.stringFlavor);
} catch (Exception e) {
e.printStackTrace();
return false;
}
JTable.DropLocation dl = (JTable.DropLocation) support.getDropLocation();
int column = dl.getColumn();
int row = dl.getRow();
String[] data = line.split(",");
for (String item : data) {
if (!item.isEmpty()) {
table.getModel().setValueAt(item, row, column);
}
}
return true;
}
};
table.setTransferHandler(dnd);
table.setPreferredScrollableViewportSize(table.getPreferredSize());
return table;
}
private static JPanel createJLabelPanel() {
JPanel panel = new JPanel();
JLabel label1 = new JLabel("Hello");
JLabel label2 = new JLabel("Yay");
JLabel label3 = new JLabel("Bye");
MyDragGestureListener dlistener = new MyDragGestureListener();
DragSource ds1 = new DragSource();
ds1.createDefaultDragGestureRecognizer(label1, DnDConstants.ACTION_COPY, dlistener);
DragSource ds2 = new DragSource();
ds2.createDefaultDragGestureRecognizer(label2, DnDConstants.ACTION_COPY, dlistener);
DragSource ds3 = new DragSource();
ds3.createDefaultDragGestureRecognizer(label3, DnDConstants.ACTION_COPY, dlistener);
panel.add(label1);
panel.add(label2);
panel.add(label3);
return panel;
}
}
class MyDragGestureListener implements DragGestureListener {
#Override
public void dragGestureRecognized(DragGestureEvent event) {
JLabel label = (JLabel) event.getComponent();
final String text = label.getText();
Transferable transferable = new Transferable() {
#Override
public DataFlavor[] getTransferDataFlavors() {
return new DataFlavor[]{DataFlavor.stringFlavor};
}
#Override
public boolean isDataFlavorSupported(DataFlavor flavor) {
if (!isDataFlavorSupported(flavor)) {
return false;
}
return true;
}
#Override
public Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorException, IOException {
return text;
}
};
event.startDrag(null, transferable);
}
}
An higher-level alternative to David's low-level approach (for the drag off the label), is to use the in-build property transfer on it. It's installed by simply setting the transferHandler and a mouseListener which triggers the export:
MouseListener listener = new MouseAdapter() {
#Override
public void mousePressed(MouseEvent e) {
JComponent c = (JComponent) e.getSource();
TransferHandler handler = c.getTransferHandler();
handler.exportAsDrag(c, e, TransferHandler.COPY);
}
};
// a transferHandler configured to export the text property
TransferHandler handler = new TransferHandler("text");
JComponent labels = new JPanel();
for (int i = 0; i < 4; i++) {
JLabel label1 = new JLabel("item: " + i);
label1.addMouseListener(listener);
label1.setTransferHandler(handler);
labels.add(label1);
}
The price to pay is slightly more work in the TableTransferHandler: it now has to check not only against the pre-defined stringFlavor but check the representationClass against String and accept that as well:
public class TableHandlerExt extends TransferHandler {
/**
* Implemented to return true if the support can provide string values.
*/
#Override
public boolean canImport(TransferSupport support) {
if (!support.isDrop()) {
return false;
}
return isStringDataSupported(support);
}
/**
* Returns a boolean indicating whether or not the support can
* provide a string value. Checks for predefined DataFlavor.stringFlavor
* and flavors with a representationClass of String.
*/
protected boolean isStringDataSupported(TransferSupport support) {
if (support.isDataFlavorSupported(DataFlavor.stringFlavor)) return true;
DataFlavor[] flavors = support.getDataFlavors();
for (DataFlavor dataFlavor : flavors) {
if (dataFlavor.getRepresentationClass() == String.class) return true;
}
return false;
}
#Override
public boolean importData(TransferSupport support) {
if (!canImport(support)) {
return false;
}
String line;
try {
line = getStringData(support);
} catch (Exception e) {
e.printStackTrace();
return false;
}
JTable table = (JTable) support.getComponent();
JTable.DropLocation dl = (JTable.DropLocation) support.getDropLocation();
int column = dl.getColumn();
int row = dl.getRow();
if (!line.isEmpty()) {
// note: we need to use table api to access the table, as the
// row/column coordinates are view coordinates
table.setValueAt(line, row, column);
}
return true;
}
/**
* Returns the String provided by the support.
* Tries for predefined DataFlavor.stringFlavor
* and flavors with a representationClass of String.
*/
protected String getStringData(TransferSupport support)
throws UnsupportedFlavorException, IOException {
if (support.isDataFlavorSupported(DataFlavor.stringFlavor)) {
return (String) support.getTransferable().getTransferData(DataFlavor.stringFlavor);
}
DataFlavor[] flavors = support.getDataFlavors();
for (DataFlavor dataFlavor : flavors) {
if (dataFlavor.getRepresentationClass() == String.class) {
return (String) support.getTransferable().getTransferData(dataFlavor);
}
}
return "";
}
}

JTable loses focus when a dialog is shown from a cell editor component

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);
}
}

ToolTipManager in Java

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);
}
});
}
}

jcombobox filter in java - Look and feel independent

I have a simple JComboBox filter code like this :
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.util.ArrayList;
import java.util.List;
import javax.swing.DefaultComboBoxModel;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
public class FilterComboBox extends JComboBox {
private List<String> array;
public FilterComboBox(List<String> array) {
super(array.toArray());
this.array = array;
this.setEditable(true);
final JTextField textfield = (JTextField) this.getEditor().getEditorComponent();
textfield.addKeyListener(new KeyAdapter() {
public void keyReleased(KeyEvent ke) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
comboFilter(textfield.getText());
}
});
}
});
}
public void comboFilter(String enteredText) {
List<String> filterArray= new ArrayList<String>();
for (int i = 0; i < array.size(); i++) {
if (array.get(i).toLowerCase().contains(enteredText.toLowerCase())) {
filterArray.add(array.get(i));
}
}
if (filterArray.size() > 0) {
this.setModel(new DefaultComboBoxModel(filterArray.toArray()));
this.setSelectedItem(enteredText);
this.showPopup();
}
else {
this.hidePopup();
}
}
/* Testing Codes */
public static List<String> populateArray() {
List<String> test = new ArrayList<String>();
test.add("");
test.add("Mountain Flight");
test.add("Mount Climbing");
test.add("Trekking");
test.add("Rafting");
test.add("Jungle Safari");
test.add("Bungie Jumping");
test.add("Para Gliding");
return test;
}
public static void makeUI() {
JFrame frame = new JFrame("Adventure in Nepal - Combo Filter Test");
FilterComboBox acb = new FilterComboBox(populateArray());
frame.getContentPane().add(acb);
frame.pack();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
public static void main(String[] args) throws Exception {
//UIManager.setLookAndFeel("com.sun.java.swing.plaf.windows.WindowsLookAndFeel");
makeUI();
}
}
The performance of the combo filter is not so good but it is fine for few data set. My problem is - when I remove the comment UIManager.setLookAndFeel("com.sun.java.swing.plaf.windows.WindowsLookAndFeel"); to change look and feel, the filter doesn't work. In WindowsLookAndFeel, the combo box only takes single character in it by replacing the previously entered character.
Can you please tell me whats going on? Manoj Shrestha's answer below helps in some way but , can you please provide some other suggestions to achieve combo box filter in Java?
Firstly you are creating new model everytime and then invoking show popup from code which leads to flickering etc. We can modify the model itself. Secondly you set the currently entered text as selected item which seems to have selectAll behavior as noted by others. I have modified the code as follows:
public void comboFilter(String enteredText) {
if (!this.isPopupVisible()) {
this.showPopup();
}
List<String> filterArray= new ArrayList<String>();
for (int i = 0; i < array.size(); i++) {
if (array.get(i).toLowerCase().contains(enteredText.toLowerCase())) {
filterArray.add(array.get(i));
}
}
if (filterArray.size() > 0) {
DefaultComboBoxModel model = (DefaultComboBoxModel) this.getModel();
model.removeAllElements();
for (String s: filterArray)
model.addElement(s);
JTextField textfield = (JTextField) this.getEditor().getEditorComponent();
textfield.setText(enteredText);
}
}
Hope it works for you.
very long answer, I think that exelent example about how different Look and Feel have got implemented methods in API and works
KeyListener isn't proper Listener for Swing JComponents, you really to have bothering with KeyBindings,
KeyListener is simple asynchronous,
JComboBox is Compound JComponent, then there is required override internal JComponents, all output from KeyListener must be wrapped into invokeLater(), notice I can create event from coumpond JComponents that twice invokeLater() doesn't returns expected output to the GUI, only Swing Timer with Swing Action can do that correctly, simple why to bothering wiht that example about wrong way,
code
import java.awt.Component;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.plaf.basic.BasicComboBoxRenderer;
public class ComboBoxHoverOver {
private JComboBox combo = new JComboBox();
public ComboBoxHoverOver() {
combo.setPrototypeDisplayValue("XXXXXXXXXXXXXXXXXXXXXX");
combo.setRenderer(new ComboToolTipRenderer(combo));
combo.addItemListener(new ItemListener() {
#Override
public void itemStateChanged(ItemEvent e) {
//System.out.println(combo.getSelectedItem().toString());
}
});
combo.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
//System.out.println(combo.getSelectedItem().toString());
}
});
combo.addItem("");
combo.addItem("Long text 4");
combo.addItem("Long text 3");
combo.addItem("Long text 2");
combo.addItem("Long text 1");
JFrame f = new JFrame();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.add(combo);
f.pack();
f.setVisible(true);
}
private class ComboToolTipRenderer extends BasicComboBoxRenderer {
private static final long serialVersionUID = 1L;
private JComboBox combo;
private JList comboList;
ComboToolTipRenderer(JComboBox combo) {
this.combo = combo;
}
#Override
public Component getListCellRendererComponent(JList list, Object value,
int index, boolean isSelected, boolean cellHasFocus) {
super.getListCellRendererComponent(
list, value, index, isSelected, cellHasFocus);
if (comboList == null) {
comboList = list;
KeyAdapter listener = new KeyAdapter() {
#Override
public void keyReleased(KeyEvent e) {
if (e.getKeyCode() == KeyEvent.VK_DOWN || e.getKeyCode() == KeyEvent.VK_UP) {
int x = 5;
int y = comboList.indexToLocation(comboList.getSelectedIndex()).y;
System.out.println(comboList.getSelectedIndex());
}
}
};
combo.addKeyListener(listener);
combo.getEditor().getEditorComponent().addKeyListener(listener);
}
if (isSelected) {
//System.out.println(value.toString());
}
return this;
}
}
public static void main(String[] args) throws ClassNotFoundException,
InstantiationException, IllegalAccessException, UnsupportedLookAndFeelException {
UIManager.setLookAndFeel("com.sun.java.swing.plaf.windows.WindowsLookAndFeel");
java.awt.EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
ComboBoxHoverOver comboBoxHoverOver = new ComboBoxHoverOver();
}
});
}
}
JComboBox is Compound JComponent, then there is required override BasicComboBoxUI, please sorry I lazy to write and simulating too much longer code as code from first point
otherwise all effort from above two point are useless and contraproductive, nothing else, only DOT
please can someone to test follows code in *nix and apple OS X
from my Java6 WinXP compo (all important is hidden in the used methods, enless kudos for anonymous author from former Sun Microsystems)
Substance L&F
WindowsLookAndFeel L&F
Nimbus L&F
Metal L&F
from Java Classes
main
import java.awt.*;
import java.util.ArrayList;
import javax.swing.*;
import javax.swing.UIManager.LookAndFeelInfo;
import org.pushingpixels.substance.api.skin.SubstanceOfficeSilver2007LookAndFeel;
public class AutoCompleteTextField {
private static JFrame frame = new JFrame();
private ArrayList<String> listSomeString = new ArrayList<String>();
private Java2sAutoTextField someTextField = new Java2sAutoTextField(listSomeString);
private ArrayList<String> listSomeAnotherString = new ArrayList<String>();
private Java2sAutoComboBox someComboBox = new Java2sAutoComboBox(listSomeAnotherString);
public AutoCompleteTextField() {
listSomeString.add("-");
listSomeString.add("Snowboarding");
listSomeString.add("Rowing");
listSomeString.add("Knitting");
listSomeString.add("Speed reading");
listSomeString.add("Pool");
listSomeString.add("None of the above");
//
listSomeAnotherString.add("-");
listSomeAnotherString.add("XxxZxx Snowboarding");
listSomeAnotherString.add("AaaBbb Rowing");
listSomeAnotherString.add("CccDdd Knitting");
listSomeAnotherString.add("Eee Fff Speed reading");
listSomeAnotherString.add("Eee Fff Pool");
listSomeAnotherString.add("Eee Fff None of the above");
//
someTextField.setFont(new Font("Serif", Font.BOLD, 16));
someTextField.setForeground(Color.black);
someTextField.setBackground(Color.orange);
someTextField.setName("someTextField");
someTextField.setDataList(listSomeString);
//
someComboBox.setPrototypeDisplayValue("xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx");
someComboBox.setFont(new Font("Serif", Font.BOLD, 16));
someComboBox.setForeground(Color.black);
someComboBox.setBackground(Color.YELLOW);
someComboBox.getEditor().selectAll();
someComboBox.getEditor().getEditorComponent().setBackground(Color.YELLOW);
((JTextField) someComboBox.getEditor().getEditorComponent()).setDisabledTextColor(Color.black);
someComboBox.setName("someComboBox");
someComboBox.setDataList(listSomeAnotherString);
//
frame.setLayout(new GridLayout(0, 1, 10, 10));
frame.add(someTextField);
frame.add(someComboBox);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLocation(100, 100);
frame.pack();
frame.setVisible(true);
//
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
someTextField.setText("-");
someComboBox.getEditor().setItem(0);
someComboBox.getEditor().selectAll();
someTextField.grabFocus();
someTextField.requestFocus();
someTextField.setText(someTextField.getText());
someTextField.selectAll();
}
});
}
public static void main(String[] args) {
/*SwingUtilities.invokeLater(new Runnable() {
public void run() {
try {
UIManager.setLookAndFeel(new SubstanceOfficeSilver2007LookAndFeel());
SwingUtilities.updateComponentTreeUI(frame);
} catch (UnsupportedLookAndFeelException e) {
throw new RuntimeException(e);
}
}
});*/
/*try {
UIManager.setLookAndFeel("com.sun.java.swing.plaf.windows.WindowsLookAndFeel");
for (LookAndFeelInfo info : UIManager.getInstalledLookAndFeels()) {
System.out.println(info.getName());
if ("Nimbus".equals(info.getName())) {
UIManager.setLookAndFeel(info.getClassName());
break;
}
}
} catch (UnsupportedLookAndFeelException e) {
// handle exception
} catch (ClassNotFoundException e) {
// handle exception
} catch (InstantiationException e) {
// handle exception
} catch (IllegalAccessException e) {
// handle exception
}*/
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
AutoCompleteTextField aCTF = new AutoCompleteTextField();
}
});
}
}
AutoComboBox
import java.awt.event.ItemEvent;
import javax.swing.DefaultComboBoxModel;
import javax.swing.JComboBox;
import javax.swing.plaf.basic.BasicComboBoxEditor;
public class Java2sAutoComboBox extends JComboBox {
private static final long serialVersionUID = 1L;
private AutoTextFieldEditor autoTextFieldEditor;
private boolean isFired;
private class AutoTextFieldEditor extends BasicComboBoxEditor {
private Java2sAutoTextField getAutoTextFieldEditor() {
return (Java2sAutoTextField) editor;
}
AutoTextFieldEditor(java.util.List<String> list) {
editor = new Java2sAutoTextField(list, Java2sAutoComboBox.this);
}
}
public Java2sAutoComboBox(java.util.List<String> list) {
isFired = false;
autoTextFieldEditor = new AutoTextFieldEditor(list);
setEditable(true);
setModel(new DefaultComboBoxModel(list.toArray()) {
private static final long serialVersionUID = 1L;
#Override
protected void fireContentsChanged(Object obj, int i, int j) {
if (!isFired) {
super.fireContentsChanged(obj, i, j);
}
}
});
setEditor(autoTextFieldEditor);
}
public boolean isCaseSensitive() {
return autoTextFieldEditor.getAutoTextFieldEditor().isCaseSensitive();
}
public void setCaseSensitive(boolean flag) {
autoTextFieldEditor.getAutoTextFieldEditor().setCaseSensitive(flag);
}
public boolean isStrict() {
return autoTextFieldEditor.getAutoTextFieldEditor().isStrict();
}
public void setStrict(boolean flag) {
autoTextFieldEditor.getAutoTextFieldEditor().setStrict(flag);
}
public java.util.List<String> getDataList() {
return autoTextFieldEditor.getAutoTextFieldEditor().getDataList();
}
public void setDataList(java.util.List<String> list) {
autoTextFieldEditor.getAutoTextFieldEditor().setDataList(list);
setModel(new DefaultComboBoxModel(list.toArray()));
}
void setSelectedValue(Object obj) {
if (isFired) {
return;
} else {
isFired = true;
setSelectedItem(obj);
fireItemStateChanged(new ItemEvent(this, 701, selectedItemReminder, 1));
isFired = false;
return;
}
}
#Override
protected void fireActionEvent() {
if (!isFired) {
super.fireActionEvent();
}
}
}
AutoTextField
import java.util.List;
import javax.swing.JTextField;
import javax.swing.text.*;
public class Java2sAutoTextField extends JTextField {
private static final long serialVersionUID = 1L;
private List<String> dataList;
private boolean isCaseSensitive;
private boolean isStrict;
private Java2sAutoComboBox autoComboBox;
public class AutoDocument extends PlainDocument {
private static final long serialVersionUID = 1L;
#Override
public void replace(int i, int j, String s, AttributeSet attributeset)
throws BadLocationException {
super.remove(i, j);
insertString(i, s, attributeset);
}
#Override
public void insertString(int i, String s, AttributeSet attributeset)
throws BadLocationException {
if (s == null || "".equals(s)) {
return;
}
String s1 = getText(0, i);
String s2 = getMatch(s1 + s);
int j = (i + s.length()) - 1;
if (isStrict && s2 == null) {
s2 = getMatch(s1);
j--;
} else if (!isStrict && s2 == null) {
super.insertString(i, s, attributeset);
return;
}
if (autoComboBox != null && s2 != null) {
autoComboBox.setSelectedValue(s2);
}
super.remove(0, getLength());
super.insertString(0, s2, attributeset);
setSelectionStart(j + 1);
setSelectionEnd(getLength());
}
#Override
public void remove(int i, int j) throws BadLocationException {
int k = getSelectionStart();
if (k > 0) {
k--;
}
String s = getMatch(getText(0, k));
if (!isStrict && s == null) {
super.remove(i, j);
} else {
super.remove(0, getLength());
super.insertString(0, s, null);
}
if (autoComboBox != null && s != null) {
autoComboBox.setSelectedValue(s);
}
try {
setSelectionStart(k);
setSelectionEnd(getLength());
} catch (Exception exception) {
}
}
}
public Java2sAutoTextField(List<String> list) {
isCaseSensitive = false;
isStrict = true;
autoComboBox = null;
if (list == null) {
throw new IllegalArgumentException("values can not be null");
} else {
dataList = list;
init();
return;
}
}
Java2sAutoTextField(List<String> list, Java2sAutoComboBox b) {
isCaseSensitive = false;
isStrict = true;
autoComboBox = null;
if (list == null) {
throw new IllegalArgumentException("values can not be null");
} else {
dataList = list;
autoComboBox = b;
init();
return;
}
}
private void init() {
setDocument(new AutoDocument());
if (isStrict && dataList.size() > 0) {
setText(dataList.get(0).toString());
}
}
private String getMatch(String s) {
for (int i = 0; i < dataList.size(); i++) {
String s1 = dataList.get(i).toString();
if (s1 != null) {
if (!isCaseSensitive
&& s1.toLowerCase().startsWith(s.toLowerCase())) {
return s1;
}
if (isCaseSensitive && s1.startsWith(s)) {
return s1;
}
}
}
return null;
}
#Override
public void replaceSelection(String s) {
AutoDocument _lb = (AutoDocument) getDocument();
if (_lb != null) {
try {
int i = Math.min(getCaret().getDot(), getCaret().getMark());
int j = Math.max(getCaret().getDot(), getCaret().getMark());
_lb.replace(i, j - i, s, null);
} catch (Exception exception) {
}
}
}
public boolean isCaseSensitive() {
return isCaseSensitive;
}
public void setCaseSensitive(boolean flag) {
isCaseSensitive = flag;
}
public boolean isStrict() {
return isStrict;
}
public void setStrict(boolean flag) {
isStrict = flag;
}
public List<String> getDataList() {
return dataList;
}
public void setDataList(List<String> list) {
if (list == null) {
throw new IllegalArgumentException("values can not be null");
} else {
dataList = list;
return;
}
}
}
EDIT
output from Win7 64b / Java7
Metal L&F
Windows L&F (funny empty white space near Button in JComboBox)
Nimbus L&F
feel free for edit(s)
This component is called autocomplete and is included in a so called swing extensions porject.
Just have a look at: http://swingx.java.net/
There is a webstart: http://swinglabs-demos.java.net/demos/swingxset6/swingxset.jnlp
Autocomplete is the Menu to select. Have fun and less error prone code :)
obviously the glitch is in the textfield component used. What happens is, when windows look and feel is used , the text in this component is selected just as using the line "textfield.selectAll();", and hence when you type anything else the selected text is cleared form the textfield component. So in the below code the caret position of this component is adjusted. This is how it works, first store the current caret position of text field in a variable "currentCaretPosition", then move the caret position to the beginning in the text in the component. After filtering restoring the caret position back to the "currentCaretPosition" variable value. I hope it works as you want it to.
The refined code is given below:-
/****Beginning of code****/
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.util.ArrayList;
import java.util.List;
import javax.swing.DefaultComboBoxModel;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
public class FilterComboBox extends JComboBox {
private List<String> array;
private int currentCaretPosition=0;
public FilterComboBox(List<String> array) {
super(array.toArray());
this.array = array;
this.setEditable(true);
final JTextField textfield = (JTextField) this.getEditor().getEditorComponent();
textfield.addKeyListener(new KeyAdapter() {
public void keyPressed(KeyEvent ke) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
currentCaretPosition=textfield.getCaretPosition();
if(textfield.getSelectedText()==null)
{
textfield.setCaretPosition(0);
comboFilter(textfield.getText());
textfield.setCaretPosition(currentCaretPosition);
}
}
});
}
});
}
public void comboFilter(String enteredText) {
List<String> filterArray= new ArrayList<String>();
for (int i = 0; i < array.size(); i++) {
if (array.get(i).toLowerCase().contains(enteredText.toLowerCase())) {
filterArray.add(array.get(i));
}
}
if (filterArray.size() > 0) {
this.setModel(new DefaultComboBoxModel(filterArray.toArray()));
this.setSelectedItem(enteredText);
this.showPopup();
}
else {
this.hidePopup();
}
}
/* Testing Codes */
public static List<String> populateArray() {
List<String> test = new ArrayList<String>();
test.add("");
test.add("Mountain Flight");
test.add("Mount Climbing");
test.add("Trekking");
test.add("Rafting");
test.add("Jungle Safari");
test.add("Bungie Jumping");
test.add("Para Gliding");
return test;
}
public static void makeUI() {
JFrame frame = new JFrame("Adventure in Nepal - Combo Filter Test");
FilterComboBox acb = new FilterComboBox(populateArray());
frame.getContentPane().add(acb);
frame.pack();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
public static void main(String[] args) throws Exception {
UIManager.setLookAndFeel("com.sun.java.swing.plaf.windows.WindowsLookAndFeel");
makeUI();
}
}
/******* End of code**********/
It looks like, as you mentioned, when user inputs any texts in combobox, the Windows Look & Feel selects (highlights) the entered text. So, when you press another key, it replaces the previous one. So, the solution is not to highlight the entered texts. You can achieve this by adding any one of the following statements in your keylistener.
textfield.setCaretPosition(textfield.getText().length());
OR
textfield.setSelectionStart(textfield.getText().length());
So, your keylistener should look like this :
final JTextField textfield = (JTextField) this.getEditor().getEditorComponent();
textfield.addKeyListener(new KeyAdapter() {
public void keyReleased(KeyEvent ke) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
comboFilter(textfield.getText());
textfield.setCaretPosition(textfield.getText().length());
}
});
}
});

Categories