I would like to have a special sort of tooltip appear when a user hovers the mouse cursor over a specific item in a treetable. This tooltip will be a thumbnail of a PDF that corresponds to the item in the treetable that the cursor is pointing at. In addition, I'd like the user to be able to then move the cursor over the thumbnail and click it, which should open up the full PDF in their system's default PDF reader (Acrobat, Adobe Reader, etc.).
I realize this is a tall order, but I've already done most of the work. I've discovered exactly where in my huge program that I need to have the setToolTip() method so that it can retrieve the appropriate thumbnail. In addition, since I've discovered that having Java create thumbnails from a PDF on the fly is far too difficult, I've already got things set up so there will be thumbnail JPGs pre-made. Thus, all the setToolTip() command will need to do is somehow retrieve the appropriate JPG. Now comes the hard part.
At first, it seemed easy. I tried this really convenient hack for putting an image in a tooltip, and it definitely gets the thumbnail showing up properly. However, surrounding the <img> tag with an anchor tag (...) doesn't quite seem to work. The thumbnail is surrounded by the tell-tale blue border, alright, but the image remains un-clickable. In addition, the tooltip sometimes just disappears before it's image can be clicked upon.
So I thought I might need to do something more deep than a simple html hack. I tried this more involved way of putting an image in a tooltip, but it seems that will only work for a static image. I need the image to be different depending on what's being hovered over with the mouse cursor. In addition, how do I set my method to use this 'custom version of a tooltip' rather than the built-in one?
To give a bit more context, the location where the setToolTip() method seems to work is inside of a getTreeCellRendererComponent() method, a part of a custom class that extends JPanel and implements TreeCellRenderer. I'll post the code if asked, but it will might be rather complicated and hard to follow. Any thoughts?
EDIT 10/09/2014, 4:57pm: Much of this code may be confusing, and for that, I apologize. Suffice it to say that it has to do with putting a tri-state checkbox inside of a JXTreeTable. Anyway, the parts that are important should be easy enough to pick out, I hope. As you can see, this class already extends JPanel, so I cannot have it extend JToolTip as well.
package info.chrismcgee.sky.treetable;
import info.chrismcgee.beans.OrderDetail;
import info.chrismcgee.components.ImageToolTip;
import info.chrismcgee.components.TristateCheckBox;
import info.chrismcgee.components.TristateState;
import info.chrismcgee.enums.OSType;
import java.awt.BorderLayout;
import java.io.File;
import javax.swing.JComponent;
import javax.swing.JPanel;
import javax.swing.JToolTip;
import javax.swing.JTree;
import javax.swing.tree.TreeCellRenderer;
import javax.swing.tree.TreePath;
import org.jdesktop.swingx.treetable.DefaultMutableTreeTableNode;
public class SkyCheckTreeCellRenderer extends JPanel implements
TreeCellRenderer {
/**
*
*/
private static final long serialVersionUID = -2728513730497144120L;
private SkyCheckTreeSelectionModel selectionModel;
private TreeCellRenderer delegate;
private boolean showRootNodeCheckBox;
private TristateCheckBox checkBox = new TristateCheckBox("");
protected SkyCheckTreeManager.CheckBoxCustomizer checkBoxCustomer;
private String jobsFolderStr = OSType.getOSType() == OSType.MAC
? "/Volumes/ArtDept/ArtDept/JOBS"
: "//SKYFS/ArtDept/ArtDept/JOBS";
public SkyCheckTreeCellRenderer(TreeCellRenderer delegate,
SkyCheckTreeSelectionModel selectionModel,
boolean showRootNodeCheckBox) {
this.delegate = delegate;
this.selectionModel = selectionModel;
this.showRootNodeCheckBox = showRootNodeCheckBox;
setLayout(new BorderLayout());
setOpaque(false);
checkBox.setOpaque(false);
}
public JToolTip createToolTip() {
return new ImageToolTip();
}
private String getToolTipText(DefaultMutableTreeTableNode node)
{
if (node.getUserObject() instanceof OrderDetail)
{
OrderDetail od = (OrderDetail) node.getUserObject();
String thousandsFolderStr = jobsFolderStr + "/"
+ od.getOrderId().substring(0, 3) + "000-"
+ od.getOrderId().substring(0, 3) + "999/";
String productFolderStr = thousandsFolderStr + od.getOrderId()
+ " Folder/";
if (!od.getProductDetail().equals(""))
productFolderStr = thousandsFolderStr + od.getOrderId() + "/";
String img = productFolderStr + od.getOrderId() + "_THUMB.jpg";
if (!od.getProductDetail().equals(""))
img = productFolderStr + od.getOrderId() + "_" + od.getProductDetail() + "_THUMB.jpg";
if (new File(img).exists())
return "<html><img src=\"file://" + img + "\"></html>";
}
return null;
}
public JComponent getTreeCellRendererComponent(JTree tree, Object value,
boolean selected, boolean expanded, boolean leaf, int row,
boolean hasFocus)
{
JComponent renderer = (JComponent) delegate.getTreeCellRendererComponent(tree, value,
selected, expanded, leaf, row, hasFocus);
if (!showRootNodeCheckBox && tree.getModel().getRoot() == value)
{
renderer.setToolTipText(getToolTipText((DefaultMutableTreeTableNode)value));
return renderer;
}
TreePath path = tree.getPathForRow(row);
if (path != null) {
if (checkBoxCustomer != null && !checkBoxCustomer.showCheckBox(path))
{
renderer.setToolTipText(getToolTipText((DefaultMutableTreeTableNode)value));
return renderer;
}
if (selectionModel.isPathSelected(path, selectionModel.isDigged()))
checkBox.getTristateModel().setState(TristateState.SELECTED);
else
checkBox.getTristateModel().setState(selectionModel.isDigged()
&& selectionModel.isPartiallySelected(path)
? TristateState.INDETERMINATE
: TristateState.DESELECTED);
}
removeAll();
add(checkBox, BorderLayout.WEST);
add(renderer, BorderLayout.CENTER);
setToolTipText(getToolTipText((DefaultMutableTreeTableNode)value));
return this;
}
}
I get that I need to somehow extend JToolTip, and that this SkyCheckTreeCellRenderer class needs to somehow reference that custom tooltip. I guess all of this is just getting so involved and complex that my simple brain is having trouble wrapping around it all. My apologies.
how do I set my method to use this 'custom version of a tooltip' rather than the built-in one?
As the example shows you need to extend the component to use the custom tool tip.
I need the image to be different depending on what's being hovered over with the mouse cursor
Then you will need to override the getToolTipText(MouseEvent) method to return a text string to represent the image you want to display.
However, surrounding the tag with an anchor tag (...) doesn't quite seem to work
You would need to use a JEditorPane if you want to respond to a hyperlink. Read the JEditorPane API for an example.
So basically I would suggest that you need to use a custom JToolTip, that uses a JEditorPane to display the appropriate Image with an appropriate Hyperlink. Here is an example that shows how to use a JLabel as an added component to a tool tip. You should be able to modify the code to use a JEditorPane.
Also, you need to extend your tree table to use this custom JToolTip.
import java.awt.*;
import java.awt.image.BufferedImage;
import java.net.URL;
import java.io.*;
import javax.imageio.ImageIO;
import javax.swing.*;
public class ToolTipImage extends JToolTip
{
private Image image;
public ToolTipImage(Image image)
{
this.image = image;
setLayout( new BorderLayout() );
add( new JLabel( new ImageIcon( image) ) );
}
#Override
public Dimension getPreferredSize()
{
return new Dimension(image.getWidth(this), image.getHeight(this));
}
private static void createAndShowGUI() throws Exception
{
final BufferedImage testImage = ImageIO.read(new File("dukewavered.gif"));
String[] columnNames = {"Column 0", "Column 1"};
Object[][] data =
{
{"Cell 0,0", "Cell 0,1"},
{"Cell 1,0", "Cell 1,1"}
};
JTable table = new JTable(data, columnNames)
{
public JToolTip createToolTip()
{
return new ToolTipImage( testImage );
}
};
// Set tool tip text so that table is registered w/ tool tip manager
table.setToolTipText(" ");
JFrame frame = new JFrame("Tool Tip Image");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add( new JScrollPane(table) );
frame.setLocationByPlatform( true );
frame.pack();
frame.setVisible( true );
}
public static void main(String[] args)
{
EventQueue.invokeLater(new Runnable()
{
public void run()
{
try
{
createAndShowGUI();
}
catch(Exception e) { System.out.println(e); }
}
});
}
}
It sounds like you need to build a custom tooltip, as detailed in JToolTip.
When clicked, you should use Runtime to open your file from the commandline. The way to do this in windows is posted here. The way to do this on ubuntu is posted here
Related
I am developing a software which gets an image from a camera and displays it live in a JavaFX ImageView. I have a thread which gets the last image (in this case a BufferedImage), and an AnimationTimer that assigns it to the ImageView. The reason I went ahead with an AnimationTimer was it seemed better than to fill the Platform with Runnable each time a new image is obtained. The refresh works fine, and FPS are decent.
However, I noticed that when the AnimationTimer was running, the menu bar in my software was not displayed properly. Some menu items went missing when I moused over others. This picture explains it:
On the left side, you have what the menu normally looks like, and on the right side, what it looks like when the AnimationTimer is running. As you can see, the "Save" menu item is missing, and the background of my live image is displayed instead. Moreover, when I was opening a new window (on a new Scene), when I moused over any kind of Node (a button, a checkbox...), the background turned black. I was able to fix this problem by setting the depth-buffer boolean to true when initializing the Scene. I have however no clue how to fix this menu bar bug, and I think these bugs show what I am doing is probably not right.
I was thinking maybe the JavaFX application thread was saturated with new images to display, and it was basically taking too much time for other elements (such as a menu item) to be painted.
Questions:
Is that really where this bug comes from?
Is there a way to improve my code, using something different from an AnimationTimer for instance?
Here's a code snippet that reproduces the bug. Change the two strings in the start function to path to images. The images should be relatively large (several MB).
Click on the "Start" button to start the animation timer. Then try to open the "File" menu, and mouse over the menu items. The bug does not appear systematically, try repeating moving your mouse up and down over, it should appear at some point.
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
import javafx.animation.AnimationTimer;
import javafx.application.Application;
import javafx.embed.swing.SwingFXUtils;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Menu;
import javafx.scene.control.MenuBar;
import javafx.scene.control.MenuItem;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.Pane;
import javafx.stage.Stage;
import javax.imageio.ImageIO;
public class ImageRefresher extends Application {
#Override
public void start(Stage primaryStage) {
//Here change the 2 Strings to a path to an image on your HDD
//The bug appears more easily with large images (>3-4MB)
String pathToImage1 = "/path/to/your/first/image";
String pathToImage2 = "/path/to/your/second/image";
try {
//Image content (contains buffered image, see below)
ImageContent image = new ImageContent(pathToImage1);
//If this line is commented, the bug does not appear
image.setImage(ImageIO.read(new File(pathToImage2)));
//JavaFX class containing nodes (see below)
MainWindow window = new MainWindow(image);
Scene scene = new Scene(window.getPane(), 300, 250);
primaryStage.setTitle("Menu refresh");
primaryStage.setScene(scene);
primaryStage.show();
} catch (IOException ex) {
Logger.getLogger(ImageRefresher.class.getName()).log(Level.SEVERE, null, ex);
}
}
public static void main(String[] args) {
launch(args);
}
public class MainWindow {
private BorderPane pane;
private MenuBar menuBar;
private ImageView displayImage;
private Button startRefreshingButton;
private ImageContent imageContent;
private AnimationTimer animationTimer;
public MainWindow(ImageContent imageContent) {
this.imageContent = imageContent;
//Builds the window's components
buildGraphic();
//The image is reset at each frame
animationTimer = new AnimationTimer() {
#Override
public void handle(long now) {
displayImage.setImage(imageContent.getDisplayableImage());
}
};
}
private void buildGraphic() {
pane = new BorderPane();
menuBar = new MenuBar();
Menu menu = new Menu("File");
menu.getItems().addAll(new MenuItem("Save"),
new MenuItem("Open"),
new MenuItem("Close"));
menuBar.getMenus().add(menu);
displayImage = new ImageView();
startRefreshingButton = new Button("Start");
startRefreshingButton.setOnAction((event) -> {
animationTimer.start();
});
pane.setTop(menuBar);
pane.setCenter(displayImage);
pane.setBottom(startRefreshingButton);
}
public Pane getPane() {
return pane;
}
}
public class ImageContent {
private BufferedImage imageContent;
//Initializes bufferedimage with the path specified
public ImageContent(String pathToImage) throws IOException {
imageContent = ImageIO.read(new File(pathToImage));
}
public void setImage(BufferedImage newImage) {
imageContent = newImage;
}
//Function called by the animation timer to
//get a JavaFX image from a bufferedimage
public Image getDisplayableImage() {
return SwingFXUtils.toFXImage(imageContent, null);
}
}
}
I guess the issue is that since you're repainting the image every frame, you're overlaying the menu popup with the image. That seems like a bug, but you're also requesting way more work from the FX Application Thread than you need.
Ideally, you should find a way to check if there's really a new image, and only update the image if there's genuinely a new file. (Consider using java.nio.file.Path to represent the file and calling Files.getLastModifiedTime(path).)
For another way to avoid flooding the FX Application Thread with too many Platform.runLater(...) calls, see Throttling javafx gui updates
In the end I didn't file any issue on jira since I was able to solve my problem. The issue came from the way I called SwingFXUtils.toFXImage(imageContent, null). I returned this function's result on every frame, and I am not sure about the details but this probably created a new object every time. A simple way to avoid that is passing a WritableImage as parameter, and binding the ImageProperty value of the ImageView to it.
If I take the MCVE posted above this could something like this (not tested, probably cleaner solutions existing):
public class MainWindow {
private BorderPane pane;
private MenuBar menuBar;
private ImageView displayImage;
private Button startRefreshingButton;
private ImageContent imageContent;
private AnimationTimer animationTimer;
// Here's the value to bind
private ObservableValue<WritableImage> imageProperty;
public MainWindow(ImageContent imageContent) {
//initialization stuff
this.imageProperty = new ObservableValue<>(imageContent.getWritableImage());
displayImage.imageProperty().bind(imageProperty);
//The image is reset at each frame
animationTimer = new AnimationTimer() {
#Override
public void handle(long now) {
SwingFXUtils.toFXImage(imageContent.getBufferedImage(), image.getWritableImage());
}
};
}
}
public class ImageContent {
private BufferedImage imageContent;
private WritableImage writableImage;
//Initializes bufferedimage with the path specified
public ImageContent(String pathToImage) throws IOException {
imageContent = ImageIO.read(new File(pathToImage));
//Get the width and height values from your image
int width = imageContent.getWidth();
int height = imageContent.getHeight();
writableImage = new WritableImage(width, height);
}
public void setImage(BufferedImage newImage) {
imageContent = newImage;
}
public WritableImage getWritableImage() {
return writableImage;
}
public BufferedImage getBufferedImage() {
return imageContent;
}
}
However, this seems to be quite memory intensive now, I'll look into it.
Okay, so what I'm trying to do is place an image by clicking. I have a boolean and I have it set so that it's true when the mouse is being pressed, and false when it's released. Then I have the following codes:
if (place == true){
msex = MouseInfo.getPointerInfo().getLocation().x;
msey = MouseInfo.getPointerInfo().getLocation().y;
}
and this on the main screen so it shows up:
if (place == true){
d.drawImage(twilightblock,msex - 45,msey - 85,this);
}
However, when I try it, I click and it shows up, but it disappears when I release the mouse button. It also moves around with the mouse instead of staying in one place. I'm wondering, is there a way to stop MouseListener in the middle, like, right after the button is pressed? If so, that would be perfect. :D
Use a MouseAdapter and select the appropriate methods: http://docs.oracle.com/javase/7/docs/api/java/awt/event/MouseAdapter.html
onClick on the image (the first component)
store the image's data or reference in a temporary structure/object
onCLick on a second component
access the image's data from the tempprary structure/object and apply it to the second component
delete the image from the first component
Answering your comment, this is a high level scaffold of what you could do:
public static void main() {
SomeOtherObject so; // use to store app data
// create possibly many components of type B
JComponentB compB = new JComponentB(so);
JComponentA compA = new JComponentA(so);
// add them to your JFrame, or other component
}
import java.awt.event.MouseAdapter;
class JComponentA extends MouseAdapter {
String imagePath;
Image myImage;
public JComponentA(SomeOtherObject so) { };
public void mouseClicked(MouseEvent e) {
this.imagePath = so.getImagePath;
// display new image, or maybe swap images
}
}
import java.awt.event.MouseAdapter;
class JComponentB extends MouseAdapter {
String imagePath;
Image myImage;
public JComponentB(SomeOtherObject so) { };
public void mouseClicked(MouseEvent e) {
so.setImagePath(this.imagePath);
// delete image, or maybe do a swap (that requires two strings in SomeOtherObject)
}
}
this is an example of what I wrote so far:
import javax.swing.*;
import java.awt.*;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
public class program {
JFrame win = new JFrame("bla bla");
final private String[] animals = { "dog", "cat", "mouse" };
private void Start() {
JPanel superior = new JPanel();
superior.setLayout(new GridLayout(3, 3));
win.getContentPane().add(superior, BorderLayout.PAGE_START);
final JComboBox<String> comboBox = new JComboBox<String>(animals);
((JLabel) comboBox.getRenderer()).setHorizontalAlignment(SwingConstants.CENTER);
superior.add(comboBox);
win.setSize(440, 290);
win.setResizable(false);
win.setLocationRelativeTo(null);
win.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
win.setVisible(true);
}
public static void main(String args[]) {
program window = new program();
window.Start();
}
}
I've a single jpg for each item of animals String array in a folder called jpg placed on the same level of (default package). I'm using eclipse.
My idea was to make a JComboBox able to display only jpgs, while using strings with certain mouse click events I've already coded (but not reported just to make it short).
I've read this, this and this, but I can't really get the job done :(
Could anyone explain me how to get what I want, maybe modifying my code so I can study it?
You'll need to supply a custom ListCellRenderer to the combobox which is capable of displaying an image (and other information as you need)
See Providing a custom renderer for more details
You can load a image using the ImageIO API. You may need to wrap the result in a ImageIcon in order to render it more easily, but that will depend on you API implementation
I would recommend using a DefaultListCellRenderer as it extends from JLabel and will make you life easier
Really simple example
I don't have enough information to form a fully runnable example, but essentially, the values added to the combo box model should, in some way, contain a reference to the image you want to load.
This way, when required, you can extract the image and display it using the cell renderer...
public class ImageCellRenderer extends DefaultListCellRenderer {
public Component getListCellRendererComponent(JList<?> list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
if (value instanceof ??) {
ImageIcon icon = ...;
setIcon(icon);
}
return this;
}
}
And apply the renderer...
JComboBox cb = new JComboBox();
cb.setRenderer(new ImageCellRenderer());
Updated
Now assuming that the images are named [animal].jpg (so dog would be dog.jpg) you should be able to build a simple Map, mapping the name to the animal image...
// List of animals...
final private String[] animals = { "dog", "cat", "mouse" };
/*...*/
// Map of animal icons...
Map<String, Icon> mapImages = new HashMap<>();
// Build the icon image mapping
for (String animal : animals) {
mapImages.put(animal, new ImageIcon(ImageIO.read(getClass().getResource("/" + animal + ".jpg))))
}
// Create a new cell renderer, passing the mappings
ImageCellRenderer renderer = new ImageCellRenderer(mapImages);
// Create a new combo box
JComboBox<String> comboBox = new JComboBox<String>(animals);
// Apply the renderer
comboBox.setRenderer(renderer);
/*...*/
public class ImageCellRenderer extends DefaultListCellRenderer {
// Icon mappings
private Map<String, Icon> mapImages
public ImageCellRenderer(Map<String, Icon> mapImages) {
// Make a new reference to the icon mappings
this.mapImages = new HashMap<>(mapImages);
setHorizontalAlignment(SwingConstants.CENTER);
}
public Component getListCellRendererComponent(JList<?> list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
if (value instanceof String) {
// Look up the icon associated with the animal...
Icon icon = mapImages.get(value.toString());
setIcon(icon);
}
return this;
}
}
I have been searching this site and google for a solution to my problem, and I can't find anything. I think it's supposed to just work; however, it doesn't. The arrow icon for my JComboBox doesn't show up, and I can't find anywhere to set its visibility to true.
Here's my code:
public class Driver implements ActionListener {
private JTextField userIDField;
private JTextField[] documentIDField;
private JComboBox repository, environment;
private JButton close, clear, submit;
private JFrame window;
public Driver()
{
window = makeWindow();
makeContents(window);
window.repaint();
}
private JFrame makeWindow()
{
JFrame window = new JFrame("");
window.setSize(500,300);
window.setLocation(50,50);
window.getContentPane().setLayout(null);
window.setResizable(false);
window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
window.setVisible(true);
return window;
}
private void makeContents(JFrame w)
{
makeDropDowns(w);
w.repaint();
}
private void makeDropDowns(JFrame w)
{
String[] repositoryArray = {"Click to select", "NSA", "Finance", "Test"};
repository = new JComboBox(repositoryArray);
repository.setSelectedIndex(0);
repository.addActionListener(this);
repository.setSize(150,20);
repository.setLocation(175,165);
repository.setEditable(false);
w.add(repository);
String[] environmentArray = {"Click to select", "Dev", "Test", "Qual"};
environment = new JComboBox(environmentArray);
environment.setSelectedIndex(0);
environment.addActionListener(this);
environment.setSize(150,20);
environment.setLocation(175,195);
//environment.setEditable(false);
w.add(environment,0);
}
public void actionPerformed(ActionEvent e)
{
String repositoryID = "null", environmentID = "null";
if (e.getSource() == repository)
{
repositoryID = (String)repository.getSelectedItem();
}
if(e.getSource() == environment)
{
environmentID = (String)environment.getSelectedItem();
}
}
}
Here's a link to a picture of the problem:
If anyone could help that would be awesome.
It doesn't appear to be the issue you were suffering from, but I found this post due to the same resulting issue of the arrow disappearing.
In my case it was due to me mistakenly using .removeAll() on the JComboBox rather than .removeAllItems() when I was attempting to empty and then reuse the JComboBox after a refresh of the data I was using. Just thought I'd include it as an answer in case someone else comes across this thread for similar reasons.
The code you show works, but it looks like you're fighting the enclosing container's default layout. Here, ComboTest is a JPanel which defaults to FlowLayout.
Addendum: In general, do not use absolute positioning, as shown in your update. I've changed the example to use GridLayout; comment out the setLayout() call to see the default, FlowLayout.
import java.awt.EventQueue;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JPanel;
/**
* #see https://stackoverflow.com/a/10824504/230513
*/
public class ComboTest extends JPanel {
private JComboBox repository = createCombo(new String[]{
"Click to select", "NSA", "Finance", "Test"});
private JComboBox environment = createCombo(new String[]{
"Click to select", "Dev", "Test", "Qual"});
public ComboTest() {
this.setLayout(new GridLayout(0, 1));
this.add(repository);
this.add(environment);
}
private JComboBox createCombo(String[] data) {
final JComboBox combo = new JComboBox(data);
combo.setSelectedIndex(1);
combo.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
System.out.println(e.getActionCommand()
+ ": " + combo.getSelectedItem().toString());
}
});
return combo;
}
private void display() {
JFrame f = new JFrame("ComboTest");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.add(this);
f.pack();
f.setLocationRelativeTo(null);
f.setVisible(true);
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
new ComboTest().display();
}
});
}
}
I had the same issue. I fixed it by revalidating and repainting the panel with the following code :
myPanel.revalidate();
myPanel.repaint();
Maybe a little late, but for those who are still looking for an easy and fail-safe way to use the JComboBox can use this:
public class FixedJComboBox<E>
extends JComboBox<E> {
// Copied constructors
public FixedJComboBox() {
super();
}
public FixedJComboBox(ComboBoxModel<E> aModel) {
super(aModel);
}
public FixedJComboBox(E[] items) {
super(items);
}
public FixedJComboBox(Vector<E> items) {
super(items);
}
#Override
public void setBounds(int x, int y, int width, int height) {
super.setBounds(x, y, width, height);
// The arrow is the first (and only) component
// that is added by default
Component[] comps = getComponents();
if (comps != null && comps.length >= 1) {
Component arrow = comps[0];
// 20 is the default width of the arrow (for me at least)
arrow.setSize(20, height);
arrow.setLocation(width - arrow.getWidth(), 0);
}
}
}
As described here, the bug is caused by incorrectly setting both the location and the size of the arrow to (0,0), followed by some repainting issues. By simply overriding the setBounds() function, the arrow is always corrected after the UI/layout manager has wrongly updated the arrow.
Also, since new components are added after the old ones (i.e. higher index), the arrow will always be at the first element in the array (assuming you don't remove and re-add the arrow).
The disadvantage is of this class is that the width of the arrow is now determined by a constant instead of the UI/layout manager.
For my application, I want a Combo Box that displays its elements when dropped down as a Tree. Problem is, I'm not versed well enough in Swing to know how to go about doing this. At least without ending up writing a new widget from scratch, or something to that effect.
How would I do something like this without creating one from scratch?
I think I would implement this as a JTree component in a JViewPort, followed by an expansion button. When collapsed, it would look like a combo box. When you click the expansion button, the viewport would expand, allowing you to scroll and select a node in the JTree. When you selected the node, the view port would collapse back to only show the selected node and the expansion button.
Hey, guess what! This is your lucky day.
I've used this framework in the past. It is very complete. I didn't know they have this
already.
JIDE Soft
alt text http://img89.imageshack.us/img89/8324/combotreejj1.png
Is not too expensive, but it will take you some time to understand the API ( it is not that is complex, but they've created a LOT of new stuff )
Override the getListCellRendererComponent methode and create the components in level order.
For every tree level move the painted string 3 spaces to right.
Example:
1
. a
. b
2
. c
The original implementation you can look from
public Component getListCellRendererComponent(
JList list,
Object value,
int index,
boolean isSelected,
boolean cellHasFocus) {
//Get the selected index. (The index param isn't
//always valid, so just use the value.)
int selectedIndex = ((Integer)value).intValue();
if (isSelected) {
setBackground(list.getSelectionBackground());
setForeground(list.getSelectionForeground());
} else {
setBackground(list.getBackground());
setForeground(list.getForeground());
}
//Set the icon and text. If icon was null, say so.
ImageIcon icon = images[selectedIndex];
String pet = petStrings[selectedIndex];
setIcon(icon);
if (icon != null) {
setText(pet);
setFont(list.getFont());
} else {
setUhOhText(pet + " (no image available)",
list.getFont());
}
return this;
}
You can create a ComboBoxEditor whose component ( returned by getEditorComponent ) is a JTree
Although you may have already tried that.
I don't know how would it look like. Post an screenshot if you make it work. :)
EDIT
I give it a quick dirty try. Its awful, but is a start.
alt text http://img120.imageshack.us/img120/2563/yiakxk2.png
Here's the code, for what is worth. :(
Probably you should start thinking in alternatives. What about a fake Combo that is a JButton without border when pushed a hidden panel will appear with the tree displayed.
import javax.swing.*;
import javax.swing.event.*;
import java.awt.*;
import java.awt.event.*;
public class ComboTree {
public static void main( String [] args ) {
JComboBox c = new JComboBox( new String [] { "Hello", "there"});
c.setModel( new CustomComboModel() );
c.setEditor( new TreeComboEditor() );
c.setRenderer( new TreeComboEditor() );
JFrame frame = new JFrame();
frame.add( c , BorderLayout.NORTH ) ;
frame.pack();
frame.setVisible( true );
}
}
class CustomComboModel implements ComboBoxModel {
public Object getSelectedItem() { return ":P"; }
public void setSelectedItem(Object anItem) {}
public void addListDataListener(ListDataListener l) {}
public Object getElementAt(int index) { return "at " + index ; }
public int getSize() { return 2; }
public void removeListDataListener(ListDataListener l) {}
}
class TreeComboEditor implements ComboBoxEditor, ListCellRenderer {
// Editor interface
public void addActionListener(ActionListener l) {}
public Component getEditorComponent() {
return new JTree() ;
}
public Object getItem() { return "";}
public void removeActionListener(ActionListener l) {}
public void selectAll() {}
public void setItem(Object anObject) {}
// Render interface
public Component getListCellRendererComponent(JList list,
Object value,
int index,
boolean isSelected,
boolean cellHasFocus) {
return new JTree();
}
}