I am using a MouseListener to detect double clicks on JTree items. That JTree is located in a JScrollPane. When I detect a double click (a MouseEvent), I get the path for the click location from the JTree. Most of the time, this works fine.
Now if I double click on a (collapsed) node with children, the node expands and therefor the scroll pane scrolls down. When I try to get the tree path from the click location, the JTree looks at the current (scrolled) view and returns the wrong item as the click location refers to the previous view (not scrolled).
Does anyone have an idea how to fix this? Below, I will attach an example demonstrating the problem.
package test;
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import javax.swing.JDialog;
import javax.swing.JScrollPane;
import javax.swing.JTree;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.TreeNode;
import javax.swing.tree.TreePath;
public class TestMain extends JDialog implements MouseListener {
protected final JTree tree;
public TestMain() {
tree = new JTree(getRootNode());
tree.addMouseListener(this);
JScrollPane pane = new JScrollPane(tree);
pane.setPreferredSize(new Dimension(250, 300));
getContentPane().add(pane, BorderLayout.CENTER);
pack();
}
private TreeNode getRootNode() {
DefaultMutableTreeNode root = new DefaultMutableTreeNode("Root");
for (int i = 0; i < 10; i++) {
root.add(new DefaultMutableTreeNode("Node " + i));
}
DefaultMutableTreeNode sub = new DefaultMutableTreeNode("Sub");
root.add(sub);
for (int i = 0; i < 10; i++) {
sub.add(new DefaultMutableTreeNode("Sub " + i));
}
return root;
}
#Override
public void mouseClicked(MouseEvent e) {
if (e.getClickCount() == 2) {
TreePath path = tree.getPathForLocation(e.getX(), e.getY());
if (path != null) {
System.out.println(path.getLastPathComponent().toString());
}
}
}
#Override
public void mousePressed(MouseEvent e) {
}
#Override
public void mouseReleased(MouseEvent e) {
}
#Override
public void mouseEntered(MouseEvent e) {
}
#Override
public void mouseExited(MouseEvent e) {
}
public static void main(String[] args) {
JDialog dialog = new TestMain();
dialog.setVisible(true);
}
}
I would recommend you to use JTree.getSelectionPath().getLastPathComponent() since it won't change on scroll.
Related
I am trying to make a custom rollover effect on the Collapsed Icon for a JTree. However, I am unsure how to target an individual handle instead of all the handles.
If you run the code below, you will see that when you hover over any handle, node, or leaf of the JTree all the collapsed handles will change to the rollover. This is not desired. So how can I change just a single handle when I am hovering over that handle, and preferably not when hovering over the node next to it?
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.tree.*;
import javax.swing.plaf.basic.*;
#SuppressWarnings("serial")
public class DirectoryExplorer extends JFrame {
private DirectoryExplorer() {
super("Directory Explorer");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setLayout(new GridLayout(1, 1));
createPanel();
setSize(800,600);
setVisible(true);
}
private void createPanel() {
JPanel panel = new JPanel(new GridLayout(1, 1));
DefaultMutableTreeNode root = new DefaultMutableTreeNode("Hello");
root.add(new DefaultMutableTreeNode("1"));
root.add(new DefaultMutableTreeNode("2"));
root.add(new DefaultMutableTreeNode("3"));
JTree tree = new JTree();
BasicTreeUI tUI = (BasicTreeUI) tree.getUI();
tUI.setCollapsedIcon(new ImageIcon("resources/closed.png"));
tUI.setExpandedIcon(new ImageIcon("resources/open.png"));
tree.setShowsRootHandles(true);
tree.addMouseMotionListener(new MouseHandler(tree));
panel.add(new JScrollPane(tree));
getContentPane().add(panel);
}
public static void main(String[] args) {
new DirectoryExplorer();
}
private class MouseHandler implements MouseMotionListener {
JTree t = null;
BasicTreeUI tUI = null;
public MouseHandler(JTree tree) {
t = tree;
tUI = (BasicTreeUI) tree.getUI();
}
#Override
public void mouseDragged(MouseEvent e) {
}
#Override
public void mouseMoved(MouseEvent e) {
TreePath selPath = t.getPathForLocation(e.getX(), e.getY());
if(selPath != null)
tUI.setCollapsedIcon(new ImageIcon("resources/rollover.png"));
else
tUI.setCollapsedIcon(new ImageIcon("resources/closed.png"));
t.repaint();
}
}
}
In order to achieve the desired result you need to override BasicTreeUI.paintExpandControl() and BasicTreeUI.MouseHandler.mouseMoved(). You will also need to create a few methods such as setRolloverIcon().
A working example of this might look like this
import java.awt.Component;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.GridLayout;
import java.awt.Insets;
import java.awt.Rectangle;
import java.awt.event.MouseEvent;
import java.net.MalformedURLException;
import java.net.URL;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTree;
import javax.swing.plaf.basic.BasicTreeUI;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.TreePath;
#SuppressWarnings("serial")
public class DirectoryExplorer extends JFrame {
private DirectoryExplorer() {
super("Directory Explorer");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setLayout(new GridLayout(1, 1));
createPanel();
setSize(800,600);
setVisible(true);
}
private void createPanel() {
JPanel panel = new JPanel(new GridLayout(1, 1));
DefaultMutableTreeNode root = new DefaultMutableTreeNode("Hello");
root.add(new DefaultMutableTreeNode("1"));
root.add(new DefaultMutableTreeNode("2"));
root.add(new DefaultMutableTreeNode("3"));
JTree tree = new JTree();
//UI Stuff//
TreeHandleUI tUI = new TreeHandleUI(tree);
tree.setUI(tUI);
try {
tUI.setCollapsedIcon(new ImageIcon(new URL("https://i.stack.imgur.com/nKJFv.png")));
tUI.setExpandedIcon(new ImageIcon(new URL("https://i.stack.imgur.com/NJvcp.png")));
tUI.setRolloverIcon(new ImageIcon(new URL("https://i.stack.imgur.com/jN6uX.png")));
} catch(MalformedURLException e) {
System.out.println("Bad URL / URLs");
}
////////////
tree.setShowsRootHandles(true);
panel.add(new JScrollPane(tree));
getContentPane().add(panel);
}
public static void main(String[] args) {
EventQueue.invokeLater(() -> new DirectoryExplorer());
}
private class TreeHandleUI extends BasicTreeUI {
///Variables
private JTree t = null;
private Icon rolloverIcon = null;
private boolean rolloverEnabled = false;
private UpdateHandler uH = null;
private boolean isLeftToRight( Component c ) {
return c.getComponentOrientation().isLeftToRight();
}
public TreeHandleUI(JTree tree) {
t = tree;
uH = new UpdateHandler(t);
t.addMouseMotionListener(uH);
}
public void setRolloverIcon(Icon rolloverG) {
Icon oldValue = rolloverIcon;
rolloverIcon = rolloverG;
setRolloverEnabled(true);
if (rolloverIcon != oldValue) {
t.repaint();
}
}
private void setRolloverEnabled(boolean handleRolloverEnabled) {
boolean oldValue = rolloverEnabled;
rolloverEnabled = handleRolloverEnabled;
if (handleRolloverEnabled != oldValue) {
t.repaint();
}
}
#Override
protected void paintExpandControl(Graphics g,
Rectangle clipBounds, Insets insets,
Rectangle bounds, TreePath path,
int row, boolean isExpanded,
boolean hasBeenExpanded,
boolean isLeaf) {
Object value = path.getLastPathComponent();
if (!isLeaf && (!hasBeenExpanded || treeModel.getChildCount(value) > 0)) {
int middleXOfKnob;
if (isLeftToRight(t)) {
middleXOfKnob = bounds.x - getRightChildIndent() + 1;
} else {
middleXOfKnob = bounds.x + bounds.width + getRightChildIndent() - 1;
}
int middleYOfKnob = bounds.y + (bounds.height / 2);
if (isExpanded) {
Icon expandedIcon = getExpandedIcon();
if(expandedIcon != null)
drawCentered(tree, g, expandedIcon, middleXOfKnob, middleYOfKnob );
} else if(isLocationInExpandControl(path, uH.getXPos(), uH.getYPos()) && !isExpanded && rolloverEnabled) {
if(row == uH.getRow()) {
if(rolloverIcon != null)
drawCentered(tree, g, rolloverIcon, middleXOfKnob, middleYOfKnob);
} else {
Icon collapsedIcon = getCollapsedIcon();
if(collapsedIcon != null)
drawCentered(tree, g, collapsedIcon, middleXOfKnob, middleYOfKnob);
}
} else {
Icon collapsedIcon = getCollapsedIcon();
if(collapsedIcon != null)
drawCentered(tree, g, collapsedIcon, middleXOfKnob, middleYOfKnob);
}
}
}
private class UpdateHandler extends BasicTreeUI.MouseHandler {
private JTree t = null;
private int xPos = 0;
private int yPos = 0;
private boolean leftToRight(Component c) {
return c.getComponentOrientation().isLeftToRight();
}
public UpdateHandler(JTree tree) {
t = tree;
}
#Override
public void mouseMoved(MouseEvent e) {
xPos = e.getX();
yPos = e.getY();
t.repaint();
}
public int getXPos() {
return xPos;
}
public int getYPos() {
return yPos;
}
public int getRow() {
return getRowForPath(t, getClosestPathForLocation(t, xPos, yPos));
}
}
}
}
Code will run without downloading images however they are available below
closed.png
open.png
rollover.png
I have a code in which I must drag two images from my desktop and drop it on a frame in two draggable buttons. The buttons have already been made on the frame. But while dragging the images, they can only be dragged to one button. The images don't get dragged to the other one. I have made a DragListener class where the dragging methods prevail and the main class DragInitialListener where I have passed objects of class DragButton so that two draggable buttons are created. I have tried everything I could think of, made two DragListener classes, passed the methods differently but the image could only be dragged in one button. I want both the buttons to be able to hold images. Please help me with it. Here's the code that I have made so far:
//This is the main class
public class DragInitialListener extends javax.swing.JFrame {
private volatile int draggedAtX, draggedAtY;
public DragInitialListener() {
initComponents();
Droptargets();
Droptarget();
}
public void Droptarget()
{
DragListener d;
DragButton db = new DragButton();
db.setSize(170,140);
d= new DragListener(db);
DropTarget drop = new DropTarget(this,d);
this.getContentPane().add(db);
}
public void Droptargets()
{
DragListener dd;
DragButton db1 = new DragButton();
db1.setSize(170,140);
dd= new DragListener(db1);
DropTarget drop1 = new DropTarget(this,dd);
this.getContentPane().add(db1);
}
// <editor-fold defaultstate="collapsed" desc="Generated Code">
private void initComponents() {
setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane());
getContentPane().setLayout(layout);
layout.setHorizontalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGap(0, 400, Short.MAX_VALUE)
);
layout.setVerticalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGap(0, 300, Short.MAX_VALUE)
);
pack();
}// </editor-fold>
public static void main(String args[]) {
/* Create and display the form */
java.awt.EventQueue.invokeLater(new Runnable() {
public void run() {
new DragInitialListener().setVisible(true);
}
});
}
// Variables declaration - do not modify
// End of variables declaration
}
//This is the DragListener class
public class DragListener extends JButton implements DropTargetListener
{
JButton imagebutton = new JButton();
// JButton imagebutton1 = new JButton();
private volatile int draggedAtX, draggedAtY;
DragListener(JButton image) {
imagebutton=image;
}
#Override
public void dragEnter(DropTargetDragEvent dtde) {
}
#Override
public void dragOver(DropTargetDragEvent dtde) {
}
#Override
public void dropActionChanged(DropTargetDragEvent dtde) {
}
#Override
public void dragExit(DropTargetEvent dte) {
}
#Override
public void drop(DropTargetDropEvent ev) {
ev.acceptDrop(DnDConstants.ACTION_COPY);
Transferable t = ev.getTransferable();
//DropTarget test = (DropTarget) ev.getSource();
DataFlavor[] df= t.getTransferDataFlavors();
for(DataFlavor f:df)
{
try
{
if(f.isFlavorJavaFileListType())
{
List<File> files =(List<File>) t.getTransferData(f);
for(File file : files)
{
displayImage(file.getPath());
}
}
}
catch(Exception ex)
{
JOptionPane.showMessageDialog(null, ex);
}
}
}
private void displayImage(String path)
{
BufferedImage img = null;
try
{
img =ImageIO.read(new File(path));
}
catch(Exception e)
{
}
ImageIcon icon = new ImageIcon(img);
imagebutton.setIcon(icon);
}
}
Start simple, get one button to work, if you can get one to work, you can get 100 to work
This is a very simple example, which makes use of the transfer API, because you really only care about dropping and not dragging
import java.awt.Component;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridBagLayout;
import java.awt.Image;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;
import java.io.File;
import java.util.List;
import javax.imageio.ImageIO;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.TransferHandler;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class Test {
public static void main(String[] args) {
new Test();
}
public Test() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
public TestPane() {
setLayout(new GridBagLayout());
JButton btn = new JButton("Drop here");
btn.setVerticalTextPosition(JButton.BOTTOM);
btn.setHorizontalTextPosition(JButton.CENTER);
btn.setTransferHandler(new ImageTransferHandler());
add(btn);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(400, 400);
}
}
public static class ImageTransferHandler extends TransferHandler {
public static final DataFlavor[] SUPPORTED_DATA_FLAVORS = new DataFlavor[]{
DataFlavor.javaFileListFlavor,
DataFlavor.imageFlavor
};
#Override
public boolean canImport(TransferHandler.TransferSupport support) {
boolean canImport = false;
for (DataFlavor flavor : SUPPORTED_DATA_FLAVORS) {
if (support.isDataFlavorSupported(flavor)) {
canImport = true;
break;
}
}
return canImport;
}
#Override
public boolean importData(TransferHandler.TransferSupport support) {
boolean accept = false;
if (canImport(support)) {
try {
Transferable t = support.getTransferable();
Component component = support.getComponent();
if (component instanceof JButton) {
Image image = null;
if (support.isDataFlavorSupported(DataFlavor.imageFlavor)) {
image = (Image) t.getTransferData(DataFlavor.imageFlavor);
} else if (support.isDataFlavorSupported(DataFlavor.javaFileListFlavor)) {
List files = (List) t.getTransferData(DataFlavor.javaFileListFlavor);
if (files.size() > 0) {
image = ImageIO.read((File) files.get(0));
}
}
ImageIcon icon = null;
if (image != null) {
icon = new ImageIcon(image);
}
((JButton) component).setIcon(icon);
accept = true;
}
} catch (Exception exp) {
exp.printStackTrace();
}
}
return accept;
}
}
}
So, by doing nothing more then changing the layout and replicating the button using
public TestPane() {
setLayout(new GridLayout(5, 5));
for (int index = 0; index < 5 * 5; index++) {
JButton btn = new JButton("Drop here");
btn.setVerticalTextPosition(JButton.BOTTOM);
btn.setHorizontalTextPosition(JButton.CENTER);
btn.setTransferHandler(new ImageTransferHandler());
add(btn);
}
}
I was able to achieve...
Updated...
So apparently I might have misunderstood the question, not the first time. From what's been explained to me, you might want to drag multiple images and have them applied to the buttons. Surprising, the process doesn't change that much.
In this example, I've applied the TransferHandler to the JPanel instead of the button and supplied it the buttons I want updated. You could easily update this to have a variable number of buttons, but I've started with two.
import java.awt.Component;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.GridBagLayout;
import java.awt.GridLayout;
import java.awt.Image;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;
import java.io.File;
import java.util.List;
import javax.imageio.ImageIO;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.TransferHandler;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class Test {
public static void main(String[] args) {
new Test();
}
public Test() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
public TestPane() {
setLayout(new GridBagLayout());
JButton left = makeButton("Left");
JButton right = makeButton("Right");
add(left);
add(right);
setTransferHandler(new ImageTransferHandler(left, right));
}
protected JButton makeButton(String text) {
JButton btn = new JButton(text);
btn.setVerticalTextPosition(JButton.BOTTOM);
btn.setHorizontalTextPosition(JButton.CENTER);
return btn;
}
#Override
public Dimension getPreferredSize() {
return new Dimension(400, 400);
}
}
public static class ImageTransferHandler extends TransferHandler {
public static final DataFlavor[] SUPPORTED_DATA_FLAVORS = new DataFlavor[]{
DataFlavor.javaFileListFlavor,};
private JButton left, right;
public ImageTransferHandler(JButton left, JButton right) {
this.left = left;
this.right = right;
}
#Override
public boolean canImport(TransferHandler.TransferSupport support) {
boolean canImport = false;
for (DataFlavor flavor : SUPPORTED_DATA_FLAVORS) {
if (support.isDataFlavorSupported(flavor)) {
canImport = true;
break;
}
}
return canImport;
}
#Override
public boolean importData(TransferHandler.TransferSupport support) {
boolean accept = false;
if (canImport(support)) {
try {
Transferable t = support.getTransferable();
Image image = null;
if (support.isDataFlavorSupported(DataFlavor.javaFileListFlavor)) {
List files = (List) t.getTransferData(DataFlavor.javaFileListFlavor);
JButton buttons[] = new JButton[]{left, right};
for (int index = 0; index < Math.min(files.size(), 2); index++) {
if (files.size() > 0) {
image = ImageIO.read((File) files.get(index));
ImageIcon icon = null;
if (image != null) {
icon = new ImageIcon(image);
}
buttons[index].setIcon(icon);
}
}
accept = true;
}
} catch (Exception exp) {
exp.printStackTrace();
}
}
return accept;
}
}
}
Now, there are rules you will need to define yourself, for example, what happens when the user only drags a single image? Do you apply it to the first button (as I have) every time, or do you try and find the button without an image and update it? What happens if all the buttons have images? Where does it go then?
Do you reject drags with more than 2 images?
my goal is to highlight a jlist item after a rightclick then show a jpopupmenu..
I read advice that overriding the method show is the way..
in my case i declare my jpopupmenu at the beginning of my class
JPopupMenu popupMenu = new JPopupMenu();
JMenuItem masterlist,settings;
then i have a method to set up my menuItems
public void setPopupMenu(int depth//ignore this var){
//popupMenu = new JPopupMenu();
popupMenu.removeAll();
masterlist = new JMenuItem("Masterlist");
settings = new JMenuItem("Settings");
//action for marsterlist
masterlist.addActionListener(new ActionListener(){
//stuff here
}
});
//action for settings
settings.addActionListener(new ActionListener(){
//stuff here
}
});
popupMenu.add(masterlist);
popupMenu.add(settings);
}
and my list is in a different method
list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
list.setLayoutOrientation(JList.HORIZONTAL_WRAP);
list.setVisibleRowCount(-1);
list.setComponentPopupMenu(popupMenu);
I tried putting this on a mouseAdapter of my list but the popupmenu fires first and ignores highlighting...
if ( SwingUtilities.isRightMouseButton(mouseEvent) )//highlight the right clicked item
{
int row = list.locationToIndex(mouseEvent.getPoint());
list.setSelectedIndex(row);
String val = (String)list.getSelectedValue();
System.out.println(val);
}
i know that overriding is something like this
popupmenu = new JPopupMenu(){
#Override
public void show(){}
}
but i cant do that because i am manipulating the menuitems on a method...
or is there any other approach that anyone can suggest...
Rather then trying to modify the state of the JPopupMenu, why not simply modify the state of the menu item when you detect a right click...
So, basically, I make use of the Actions API to define a menu item for a JPopupMenu, which allows it to register a ListSelectionListener to the underlying JList...
public class ShowItemAction extends AbstractAction {
private JList list;
public ShowItemAction(JList list) {
this.list = new JList();
putValue(NAME, "Showing ...");
list.addListSelectionListener(new ListSelectionListener() {
#Override
public void valueChanged(ListSelectionEvent e) {
if (!e.getValueIsAdjusting()) {
int index = list.getSelectedIndex();
String value = list.getSelectedValue().toString();
putValue(NAME, "Showing " + value + " # " + index);
}
}
});
}
#Override
public void actionPerformed(ActionEvent e) {
// The actual action to be performed when selected
}
}
All this does is, when the selection is changed, is change the text (NAME) of the action, which changes the text of the menu item, based on the currently selected row.
When I create the JList, I assign it a JPopupMenu via the setComponentPopupMenu method, which means I no longer need to care about it and it will be displayed appropriately based on the current look and feel requirements
JList list = new JList(model);
JPopupMenu popupMenu = new JPopupMenu();
popupMenu.add(new ShowItemAction(list));
list.setComponentPopupMenu(popupMenu);
I then add a MouseListener to the JList which I use to change the row selection when you right click on the JList...
list.addMouseListener(new MouseAdapter() {
#Override
public void mousePressed(MouseEvent e) {
JList list = (JList)e.getComponent();
if (SwingUtilities.isRightMouseButton(e)) {
int row = list.locationToIndex(e.getPoint());
list.setSelectedIndex(row);
}
}
});
This not entirely fool proof, as if you right click the JList while the popup is visible, the MouseListener doesn't appear to get notified :P
Runnable example...
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.ActionEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.AbstractAction;
import static javax.swing.Action.NAME;
import javax.swing.DefaultListModel;
import javax.swing.JFrame;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
public class Test {
public static void main(String[] args) {
new Test();
}
public Test() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
DefaultListModel model = new DefaultListModel();
model.addElement("One");
model.addElement("Two");
model.addElement("Three");
model.addElement("Four");
JList list = new JList(model);
JPopupMenu popupMenu = new JPopupMenu();
popupMenu.add(new ShowItemAction(list));
list.setComponentPopupMenu(popupMenu);
list.addMouseListener(new MouseAdapter() {
#Override
public void mousePressed(MouseEvent e) {
if (SwingUtilities.isRightMouseButton(e)) {
int row = list.locationToIndex(e.getPoint());
list.setSelectedIndex(row);
}
}
});
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new JScrollPane(list));
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class ShowItemAction extends AbstractAction {
private JList list;
public ShowItemAction(JList list) {
this.list = new JList();
putValue(NAME, "Showing ...");
list.addListSelectionListener(new ListSelectionListener() {
#Override
public void valueChanged(ListSelectionEvent e) {
if (!e.getValueIsAdjusting()) {
int index = list.getSelectedIndex();
String value = list.getSelectedValue().toString();
putValue(NAME, "Showing " + value + " # " + index);
}
}
});
}
#Override
public void actionPerformed(ActionEvent e) {
}
}
}
I want to change the cursor to hand cursor on mouse move over JTree component when the cursor is on listed elements only, not for the whole component.
The below code is for Jlist Component. I want same for the JTree, but JTree does not have getCellBound(). Is there any other way?
final JList list = new JList(new String[] {"a","b","c"});
list.addMouseMotionListener(new MouseMotionListener() {
#Override
public void mouseMoved(MouseEvent e) {
final int x = e.getX();
final int y = e.getY();
// only display a hand if the cursor is over the items
final Rectangle cellBounds = list.getCellBounds(0, list.getModel().getSize() - 1);
if (cellBounds != null && cellBounds.contains(x, y)) {
list.setCursor(new Cursor(Cursor.HAND_CURSOR));
} else {
list.setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
}
}
#Override
public void mouseDragged(MouseEvent e) {
}
});
You are looking for something like this, I guess:
import java.awt.Cursor;
import java.awt.Rectangle;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTree;
import javax.swing.SwingUtilities;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreePath;
public class TestTreeSelection {
protected void initUI() {
final DefaultMutableTreeNode root = new DefaultMutableTreeNode("Root");
fillTree(root, 5, "Some tree label");
final DefaultTreeModel model = new DefaultTreeModel(root);
final JTree tree = new JTree(model);
tree.addMouseMotionListener(new MouseAdapter() {
#Override
public void mouseMoved(MouseEvent e) {
boolean inside = false;
TreePath path = tree.getPathForLocation(e.getX(), e.getY());
if (path != null) {
Rectangle pathBounds = tree.getPathBounds(path);
inside = pathBounds.contains(e.getPoint());
}
if (inside) {
tree.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
} else {
tree.setCursor(Cursor.getDefaultCursor());
}
}
});
JFrame f = new JFrame();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.add(new JScrollPane(tree));
f.setSize(400, 600);
f.setLocationRelativeTo(null);
f.setVisible(true);
}
private void fillTree(DefaultMutableTreeNode parent, int level, String label) {
for (int i = 0; i < 5; i++) {
DefaultMutableTreeNode node = new DefaultMutableTreeNode(label + " " + i);
parent.add(node);
if (level > 0) {
fillTree(node, level - 1, label);
}
}
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
new TestTreeSelection().initUI();
}
});
}
}
This code works:
tree_object.addMouseMotionListener(new MouseMotionListener() {
#Override
public void mouseDragged(MouseEvent e) {
}
#Override
public void mouseMoved(MouseEvent e) {
TreePath tp = ((JTree)e.getSource()).getPathForLocation(e.getX(), e.getY());
if(tp != null)
{
((JTree)e.getSource()).setCursor(new Cursor(Cursor.HAND_CURSOR));
}
else
{
((JTree)e.getSource()).setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
}
}
});
I'm creating a JTree and adding some nodes doing something like this:
DefaultMutableTreeNode top = new DefaultMutableTreeNode("The Java Series");
tree = new JTree(top);
...
DefaultMutableTreeNode node = new DefaultMutableTreeNode("<html>"+code+" "+description+"</html>");
top.add(node);
...
Now I added a "addMouseMotionListener" to the node which is ok. My problem is that the mouse cursor changes whenever I hover over any part of the node. What I really want is to only change mouse cursor when hovering over the HTML hyperlink text part of the node
""+code+""
and NOT the description.
So is there a way to make the mouse cursor change only when hovering in certain parts of the node?
Thanks in advance.
OK, it took me a while, but what I described in my comment seems to work. There may be other/better ways and I would love to read about it, but so far this is all I have found.
The idea is that:
I use a JEditorPane as a TreeCellRenderer
I listen for mouse move (and also, as a bonus for mouse click)
For each event I recreate the renderer for the hovered cell
I translate the event to the component coordinates
I use viewToModel to find if I am hovering an anchor element
I change the cursor accordingly.
Here is the code and as a bonus, you also get working hyperlinks!
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Cursor;
import java.awt.Desktop;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import javax.swing.JEditorPane;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JScrollPane;
import javax.swing.JTree;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.text.AttributeSet;
import javax.swing.text.Element;
import javax.swing.text.html.HTML;
import javax.swing.text.html.HTMLDocument;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeCellRenderer;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreeCellRenderer;
import javax.swing.tree.TreeModel;
import javax.swing.tree.TreeNode;
import javax.swing.tree.TreePath;
public class TestTreeHyperlinks {
private final class HyperlinkMouseListener extends MouseAdapter {
private final JTree tree;
private HyperlinkMouseListener(JTree tree) {
this.tree = tree;
}
#Override
public void mouseExited(MouseEvent e) {
tree.setCursor(Cursor.getDefaultCursor());
}
#Override
public void mouseClicked(MouseEvent e) {
Element h = getHyperlinkElement(e);
if (h != null) {
Object attribute = h.getAttributes().getAttribute(HTML.Tag.A);
if (attribute instanceof AttributeSet) {
AttributeSet set = (AttributeSet) attribute;
String href = (String) set.getAttribute(HTML.Attribute.HREF);
if (href != null) {
try {
Desktop.getDesktop().browse(new URI(href));
} catch (IOException e1) {
e1.printStackTrace();
} catch (URISyntaxException e1) {
e1.printStackTrace();
}
}
}
}
}
#Override
public void mouseMoved(MouseEvent event) {
boolean isHyperlink = isHyperlink(event);
if (isHyperlink) {
tree.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
} else {
tree.setCursor(Cursor.getDefaultCursor());
}
}
private boolean isHyperlink(MouseEvent event) {
return getHyperlinkElement(event) != null;
}
private Element getHyperlinkElement(MouseEvent event) {
Point p = event.getPoint();
int selRow = tree.getRowForLocation(p.x, p.y);
TreeCellRenderer r = tree.getCellRenderer();
if (selRow != -1 && r != null) {
TreePath path = tree.getPathForRow(selRow);
Object lastPath = path.getLastPathComponent();
Component rComponent = r.getTreeCellRendererComponent(tree, lastPath, tree.isRowSelected(selRow), tree.isExpanded(selRow),
tree.getModel().isLeaf(lastPath), selRow, true);
if (rComponent instanceof JEditorPane) {
Rectangle pathBounds = tree.getPathBounds(path);
JEditorPane editor = (JEditorPane) rComponent;
editor.setBounds(tree.getRowBounds(selRow));
p.translate(-pathBounds.x, -pathBounds.y);
int pos = editor.getUI().viewToModel(editor, p);
if (pos >= 0 && editor.getDocument() instanceof HTMLDocument) {
HTMLDocument hdoc = (HTMLDocument) editor.getDocument();
Element elem = hdoc.getCharacterElement(pos);
if (elem.getAttributes().getAttribute(HTML.Tag.A) != null) {
return elem;
}
}
}
}
return null;
}
}
#SuppressWarnings("serial")
public class MyTreeCellRenderer extends DefaultTreeCellRenderer implements TreeCellRenderer {
private JEditorPane editorPane;
public MyTreeCellRenderer() {
editorPane = new JEditorPane("text/html", null);
}
#Override
public Component getTreeCellRendererComponent(JTree tree, Object value, boolean selected, boolean expanded, boolean leaf, int row,
boolean hasFocus) {
Component c = super.getTreeCellRendererComponent(tree, value, selected, expanded, leaf, row, hasFocus);
if (c instanceof JLabel) {
JLabel label = (JLabel) c;
editorPane.setText(label.getText());
editorPane.setToolTipText(label.getToolTipText());
editorPane.setOpaque(label.isOpaque());
editorPane.setBackground(label.getBackground());
editorPane.setBorder(label.getBorder());
}
return editorPane;
}
}
protected void initUI() {
final JTree tree = new JTree(getTreeModel());
tree.setCellRenderer(new MyTreeCellRenderer());
HyperlinkMouseListener listener = new HyperlinkMouseListener(tree);
tree.addMouseListener(listener);
tree.addMouseMotionListener(listener);
JFrame f = new JFrame(TestTreeHyperlinks.class.getSimpleName());
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.add(new JScrollPane(tree), BorderLayout.CENTER);
f.pack();
f.setSize(f.getWidth() + 100, f.getHeight() + 100);
f.setVisible(true);
}
private TreeModel getTreeModel() {
return new DefaultTreeModel(
getNodes(new DefaultMutableTreeNode("<html>Root Google</html>"), 5));
}
private TreeNode getNodes(DefaultMutableTreeNode parent, int i) {
if (i > 0) {
for (int j = 0; j < 5; j++) {
DefaultMutableTreeNode newChild = new DefaultMutableTreeNode(
"<html>Node "
+ (j + 1)
+ " a link to stackoverflow</html> and some more text not in an hyperlink");
getNodes(newChild, i - 1);
parent.add(newChild);
}
}
return parent;
}
public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException,
UnsupportedLookAndFeelException {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
new TestTreeHyperlinks().initUI();
}
});
}
}
This code works:
tree_object.addMouseMotionListener(new MouseMotionListener() {
#Override
public void mouseDragged(MouseEvent e) {
}
#Override
public void mouseMoved(MouseEvent e) {
TreePath tp = ((JTree)e.getSource()).getPathForLocation(e.getX(), e.getY());
if(tp != null)
{
((JTree)e.getSource()).setCursor(new Cursor(Cursor.HAND_CURSOR));
}
else
{
((JTree)e.getSource()).setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
}
}
});