I'm trying to implement pop-up menus in Java JTree. I've sub-classed DefaultTreeCellRenderer (to change node appearance) and DefaultTreeCellEditor (to create Components to attach event listeners to, since apparently the Components that DefaultTreeCellRenderer.getTreeCellRendererComponent() returns can't do it?). I don't really want to "edit" the nodes, just be able to pop-up a menu when a node gets right-clicked, but this is the only way I can think of doing it right now...
Below is the code that I have so far-- I'm just trying to figure out how to capture MouseEvents. It sort of works, but badly. What's a better way to accomplish what I'm trying to do here?
private class My_TreeCellRenderer extends DefaultTreeCellRenderer {
My_TreeCellRenderer() {
super ();
}
public Component getTreeCellRendererComponent(JTree tree, Object value, boolean selected, boolean expanded, boolean leaf, int row, boolean hasFocus) {
super.getTreeCellRendererComponent(tree, value, selected, expanded, leaf, row, hasFocus);
// set label text and tool tips
setText(((My_Object)value).getTreeLabel());
setToolTipText(((My_Object)value).getTreeToolTip());
return this;
}
}
private class My_TreeCellEditor extends DefaultTreeCellEditor {
private MouseAdapter ma;
My_TreeCellEditor(JTree tree, DefaultTreeCellRenderer renderer) {
super (tree, renderer);
ma = new MouseAdapter() {
public void mousePressed(MouseEvent e) {
if (e.isPopupTrigger()) {
System.out.println("My Popup");
}
}
public void mouseReleased(MouseEvent e) {
if (e.isPopupTrigger()) {
System.out.println("My Popup");
}
}
};
}
public Component getTreeCellEditorComponent(JTree tree, Object value, boolean selected, boolean expanded, boolean leaf, int row) {
String src_filename = null;
// return non-editing component
Component c = renderer.getTreeCellRendererComponent(tree, value, selected, expanded, leaf, row, true);
// add mouse listener if it's not listening already
MouseListener mouseListeners[] = c.getMouseListeners();
int i;
for (i=0; i < mouseListeners.length && mouseListeners[i] != ma; i++);
if (i >= mouseListeners.length)
c.addMouseListener(ma);
return c;
}
protected boolean canEditImmediately(EventObject event) {
if (event instanceof MouseEvent && ((MouseEvent)event).getClickCount() == 1)
return true;
else
return false;
}
}
This task is simple to accomplish, the following is all you need:
//create a class which implements the MouseListener interface and
//implement the following in your overridden mouseClicked method
#Override
public void mouseClicked(MouseEvent e) {
if (SwingUtilities.isRightMouseButton(e)) {
int row = tree.getClosestRowForLocation(e.getX(), e.getY());
tree.setSelectionRow(row);
popupMenu.show(e.getComponent(), e.getX(), e.getY());
}
}
You can then add this custom listener to your desired tree(s).
Taken right out of the JTree API
// If you are interested in detecting either double-click events or when a user clicks on a node, regardless of whether or not it was selected, we recommend you do the following:
final JTree tree = ...;
MouseListener ml = new MouseAdapter() {
public void mousePressed(MouseEvent e) {
int selRow = tree.getRowForLocation(e.getX(), e.getY());
TreePath selPath = tree.getPathForLocation(e.getX(), e.getY());
if(selRow != -1) {
if(e.getClickCount() == 1) {
mySingleClick(selRow, selPath);
}
else if(e.getClickCount() == 2) {
myDoubleClick(selRow, selPath);
}
}
}
};
tree.addMouseListener(ml);
Of course you need to modify it a bit for right click instead of left click
Thanks everyone. I knew something was wrong when I was spending that much effort on implementing a simple popup.
I dismissed this line of thought at first because it felt weird to resort to x- and y- coordinates to find the node I'm looking for, but I guess this is the way to do it.
// add MouseListener to tree
MouseAdapter ma = new MouseAdapter() {
private void myPopupEvent(MouseEvent e) {
int x = e.getX();
int y = e.getY();
JTree tree = (JTree)e.getSource();
TreePath path = tree.getPathForLocation(x, y);
if (path == null)
return;
tree.setSelectionPath(path);
My_Obj obj = (My_Obj)path.getLastPathComponent();
String label = "popup: " + obj.getTreeLabel();
JPopupMenu popup = new JPopupMenu();
popup.add(new JMenuItem(label));
popup.show(tree, x, y);
}
public void mousePressed(MouseEvent e) {
if (e.isPopupTrigger()) myPopupEvent(e);
}
public void mouseReleased(MouseEvent e) {
if (e.isPopupTrigger()) myPopupEvent(e);
}
};
(...)
JTree tree = new JTree();
tree.addMouseListener(ma);
I think you're making things way harder than they need to be.
JTree has several "add_foo_Listener" methods on it. Implement one of those (TreeSelectionListener looks about right), and then you've got the currently selected node.
Implement a MouseListener so that you can detect the right-click event (and add it to the JTree, since JTree's a Component), and then you should have everything you need to post a context-sensitive menu.
Check out this tutorial for more details.
The Renderer is only a transient "rubber stamp", so adding an input listener on that wont be particularly helpful. The Editor, as you point out, is only there once you have gestured to edit. So you want to add a listener to the JTree (assuming it isn't implemented as a composite component).
Call addRightClickListener() to add the right-click context menu listener to your JTree. Both overrides are for proper cross-platform functionality (Windows and Linux differ here).
private void addRightClickListener()
{
MouseListener mouseListener = new MouseAdapter()
{
#Override
public void mousePressed(MouseEvent mouseEvent)
{
handleContextMenu(mouseEvent);
}
#Override
public void mouseReleased(MouseEvent mouseEvent)
{
handleContextMenu(mouseEvent);
}
};
tree.addMouseListener(mouseListener);
}
private void handleContextMenu(MouseEvent mouseEvent)
{
if (mouseEvent.isPopupTrigger())
{
MyContextMenu contextMenu = new MyContextMenu();
contextMenu.show(mouseEvent.getComponent(),
mouseEvent.getX(),
mouseEvent.getY());
}
}
Related
I need help implementing my own traversal policy.
My goal was to be able to traverse with tab some of the components in a panel, not all of them.
I was able to research the FocusTraversalPolicy class and figure out a little how this works. The code below shows how far I got. But I seem to have hit a wall.
This code will allow me to traverse from one component to another, but there are in my Container smaller jpanels with jtextfields in them. For some reason, I am not able to traverse to these using tab, even though I added the jpanels into my policy.
I am aware that I can just access the textfields inside these jpanels and add them to my policy. However, since these smaller jpanels are sort of dynamic and can add extra components inside of them, I am looking more for something that will allow me to pass from the Parent container traversal cycle to the smaller one.
I could be wrong, but I believe I should add something to the getComponentAfter(...) and the getComponentBefore(...) methods. As you can see, I commented out some of the things I was trying. I would appreciate any help you can give me. I attached a picture of my panel so you can see what I mean.
public class TabFocusAdapter extends FocusTraversalPolicy implements FocusListener, MouseListener {
private ArrayList<Component> comps = new ArrayList<Component>();
private int focus, def;
private boolean mouse_focus = false;
public TabFocusAdapter(int f) {
focus = def = f;
}
#Override
/***Returns the component after the current one
* if current one is the last in the cycle, it will return the first
* it will skip all disabled components */
public Component getComponentAfter(Container aContainer, Component aComponent) {
//if(((Container) aComponent).getFocusTraversalPolicy()!=null)
//if(aComponent)
//return ((Container)aComponent).getFocusTraversalPolicy().getFirstComponent((Container) aComponent);
focus++;
if(focus == comps.size()) focus = 0;
Component comp = comps.get(focus);
while(!comp.isEnabled() || !comp.isShowing() || !comp.isVisible()) {
focus++;
if(focus == comps.size()) focus = 0;
comp = comps.get(focus);
}
return comps.get(focus);
}
#Override
/**Returns the component before the current one
* if current one is the first in the cycle, it will return the last
* it will skip all disabled components */
public Component getComponentBefore(Container aContainer, Component aComponent) {
focus--;
if(focus == -1) focus = comps.size()-1;
Component comp = comps.get(focus);
while(!comp.isEnabled() || !comp.isShowing() || !comp.isVisible()) {
focus--;
if(focus == -1) focus = comps.size()-1;
comp = comps.get(focus);
}
return comps.get(focus);
}
#Override
public Component getFirstComponent(Container aContainer) {
return comps.get(0);
}
#Override
public Component getLastComponent(Container aContainer) {
return comps.get(comps.size() - 1);
}
#Override
/**Returns the starting component */
public Component getDefaultComponent(Container aContainer) {
return comps.get(def);
}
public void addComp(Component comp) {
comps.add(comp);
comp.addMouseListener(this);
comp.addFocusListener(this);
}
#Override
public void focusGained(FocusEvent e) {
Component comp = e.getComponent();
if(!mouse_focus && comp instanceof JTextComponent)
((JTextComponent)comp).selectAll();
}
#Override
public void mousePressed(MouseEvent e) {
mouse_focus = true;
focus = comps.indexOf(e.getComponent());
}
#Override
public void mouseReleased(MouseEvent e) {
mouse_focus = false;
}
#Override
public void mouseClicked(MouseEvent e) {}
#Override
public void focusLost(FocusEvent e) {}
#Override
public void mouseEntered(MouseEvent e) {}
#Override
public void mouseExited(MouseEvent e) {}
}
I solved my problem by including a series of checks into the getComponentAfter(...) and getComponentBefore(...) methods. I am including my code below.
Basically, to determine whether to move to a lower cycle, it asks whether the next component is a Focus Cycle Root.
When concluding a cycle, to determine whether to move to an upper cycle, it asks whether the parent of the container is a Focus Traversal Policy Provider.
I got the general idea for this solution by looking at how these methods where implemented by the default traversal policies of my JFrame.
'''
public Component getComponentAfter(Container aContainer, Component aComponent) {
Component comp;
do{
focus++;
if(focus == comps.size()) {
focus = 0;
ancestor = aContainer;
do ancestor = ancestor.getParent();
while(ancestor!=null && !ancestor.isFocusTraversalPolicyProvider());
if(ancestor != null && ancestor.isFocusTraversalPolicyProvider())
return (ancestor
.getFocusTraversalPolicy()
.getComponentAfter(ancestor, aContainer));
}
comp = comps.get(focus);
} while(!comp.isEnabled() || !comp.isShowing() || !comp.isVisible());
if(comp instanceof Container && ((Container)comp).isFocusCycleRoot()) {
return ((Container)comp)
.getFocusTraversalPolicy()
.getDefaultComponent((Container) comp);
}
return comp;
}
'''
I would like to make my own method that controles when a component is 'isSelected'.
I have a JList containing multiple JPanel. The constructing class of the JPanel extends ListCellRenderer<>.
To show that one of the JList-component (the JPanels) is selected i use;
#Override
public Component getListCellRendererComponent(..., boolean isSelected, ...) {
if(isSelected){
setBackground(list.getSelectionBackground());
setForeground(list.getSelectionForeground());
} else {
setBackground(list.getBackground());
setForeground(list.getForeground());
}
return this;
}
I would like a method that keeps a selected item 'selected' eventhough I choose to select another. I understand this can be done by holding down CTRL, but .setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); does not quite do the trick. I would rather like to select multiple by clicking on them, and deselect by clicking on them.
For this i have worked with the ListSelectionMode, but i cant find a way.
When done the above I would like to implement a method that only selects a component in the list when clicked in a certain area (instead of the whole component which is preset). I have made this method, which returns true if the correct area is clicked, else false. But since I cant figure out how to override the mouseevent that makes the components 'isSelected' this has been tricky.
Here is the code for the method I would like to override the 'isSelected' method;
this.addMouseListener(new MouseAdapter() {
#Override
public void mousePressed(MouseEvent evt) {
if(ActionHandler.mouseClickedPrebuild(evt.getPoint())){
//This code runs if that special place is clicked!
//So now the component should be 'isSelected' or
//deselected if it already was 'isSelected'.
}
}
});
This code is in the constructor of my JList
And the mouseClickedPrebuild method;
public static boolean mouseClickedPrebuild(Point point) {
int index = theJList.locationToIndex(point);
Rectangle bounds = theJList.getCellBounds(index,index);
Point p = bounds.getLocation();
return ( ... long list of greater than & less than ...);
//This gives the certain area which is accepted to return true
I solved the issue!
So I get my view showing by running this line;
// UI Class JScrollPane Custom JList
UIConstructor.listview.setViewportView(new ListView( -insert ArrayList here- ));
Here is my ListView. The custom DefaultListSelectionModel I used to solve my problem was posted by #FuryComptuers right here;
JList - deselect when clicking an already selected item
I had to make a few changes to the code, since the two methods in the selectionModel will run before my mouseevent. I saved the variabels staticly, so instead of running the code in setSelectionInterval I did it inside my mousePressed.
I then could add the boolean isSelected which returns true, if a curtain area within a specific list element is clicked.
public class ListViewd extends JList {
static boolean isSelected;
static Point point;
static boolean gS = false;
static int in0;
static int in1;
#Override
public Dimension getPreferredScrollableViewportSize() {
Dimension size = super.getPreferredScrollableViewportSize();
size.setSize(new Dimension(0,0));
return size;
}
public ListView(ArrayList<System> items) {
DefaultListModel<System> list = new DefaultListModel<System>();
for (System item : items) {
list.addElement(item);
}
this.setSelectionModel(new DefaultListSelectionModel() {
boolean gestureStarted = false;
#Override
public void setSelectionInterval(int index0, int index1) {
gS = gestureStarted;
in0 = index0;
in1 = index1;
gestureStarted = true;
}
#Override
public void setValueIsAdjusting(boolean isAdjusting) {
if (!isAdjusting) {
gestureStarted = false;
}
}
});
ListSelectionModel selectionModel = this.getSelectionModel();
addMouseListener(new MouseAdapter() {
#Override
public void mousePressed(MouseEvent e) {
point = e.getPoint();
isSelected = ActionHandler.mouseClickedPrebuild(point);
if(!gS && isSelected){
if (isSelectedIndex(in0)) {
selectionModel.removeSelectionInterval(in0, in1);
} else {
selectionModel.addSelectionInterval(in0, in1);
}
}
}
});
setModel(list);
setCellRenderer(new ListModelPrebuild());
}
I have a JTree, and would like for its getTreeCellEditorComponent() method to be invoked when I single click on a node. According to the documentation for the DefaultTreeCellEditor class (which I extended), "Editing is started on a triple mouse click, or after a click, pause, click and a delay of 1200 miliseconds." Is there some way to override this behavior, so that a single-click could start the editing process?
The JTree API recommends a MouseListener, but a key binding is also handy. This example invokes startEditingAtPath() and binds to the Enter key:
final JTree tree = new JTree();
tree.setEditable(true);
MouseListener ml = new MouseAdapter() {
#Override
public void mousePressed(MouseEvent e) {
int row = tree.getRowForLocation(e.getX(), e.getY());
TreePath path = tree.getPathForLocation(e.getX(), e.getY());
if (row != -1) {
if (e.getClickCount() == 1) {
tree.startEditingAtPath(path);
}
}
}
};
tree.addMouseListener(ml);
tree.getInputMap().put(
KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0), "startEditing");
Addendum: See also this answer regarding usability.
Technically, you can subclass DefaultTreeCellEditor and tweaks its logic to start editing on the first single click:
JTree tree = new JTree();
tree.setEditable(true);
TreeCellEditor editor =
new DefaultTreeCellEditor(tree, (DefaultTreeCellRenderer) tree.getCellRenderer()) {
#Override
protected boolean canEditImmediately(EventObject event) {
if((event instanceof MouseEvent) &&
SwingUtilities.isLeftMouseButton((MouseEvent)event)) {
MouseEvent me = (MouseEvent)event;
return ((me.getClickCount() >= 1) &&
inHitRegion(me.getX(), me.getY()));
}
return (event == null);
}
};
tree.setCellEditor(editor);
There's a usability quirk, though, as now you can't select without starting an edit - which may or may not be your intention.
I use this code fragment to perform some action on a tree when the mouse is double-clicked: open a window and get the node which was double clicked by the mouse, but it doesn't return anything, it returns null:
MouseListener ml = new MouseAdapter() {
public void mousePressed(MouseEvent e) {
int selRow = contactTree.getRowForLocation(e.getX(), e.getY());
TreePath selPath = contactTree.getPathForLocation(e.getX(), e.getY());
System.out.println(contactTree.getEditingPath());
Account memberToChat;
if(selRow != -1) {
if(e.getClickCount() == 1) {
}
else if(e.getClickCount() == 2) {
new ChatWindow().setVisible(true);
memberToChat=(Account)node.getUserObject(); // node is declared somewhere in the class as DefaultMutableTreeNode node
System.out.println(memberToChat.getFirstName()+" "+memberToChat.getEmail());
}
}
}
};
for JTree to set proper setSelectionMode
add TreeSelectionListener
example with TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION
In GUI,I am displaying one JTree at the left hand side of JPanel. Now for each Node(leaf), on Mouse right click I want to display JPopup menu asking for displaying the statistics about Node in right JPanel.
As i am new to swing,Could any one help with code.
Thanks in Advance.
Regards,
Tushar Dodia.
Use JTree's method
public TreePath getPathForLocation(int x, int y)
Then TreePath
public Object getLastPathComponent()
That returns you desired node from point where user right clicked.
Seem to have caused a bit of confusion (confusing myself ;-) - so here's a code snippet for doing target location related configuration of the componentPopup
JPopupMenu popup = new JPopupMenu();
final Action action = new AbstractAction("empty") {
#Override
public void actionPerformed(ActionEvent e) {
// TODO Auto-generated method stub
}
};
popup.add(action);
JTree tree = new JTree() {
/**
* #inherited <p>
*/
#Override
public Point getPopupLocation(MouseEvent e) {
if (e != null) {
TreePath path = getClosestPathForLocation(e.getX(), e.getY());
action.putValue(Action.NAME, String.valueOf(path.getLastPathComponent()));
return e.getPoint();
}
action.putValue(Action.NAME, "no mouse");
return null;
}
};
tree.setComponentPopupMenu(popup);
I took #kleopatra solution and changed it slightly.
Maybe it isn't the best way but works for me.
JTree tree = new JTree() {
private static final long serialVersionUID = 1L;
#Override public Point getPopupLocation(MouseEvent e) {
if (e == null) return new Point(0,0);
TreePath path = getClosestPathForLocation(e.getX(), e.getY());
Object selected = path != null ? path.getLastPathComponent() : null;
setComponentPopupMenu(getMenuForTreeNode(getComponentPopupMenu(), selected));
setSelectionPath(path);
return e.getPoint();
}
};
public JPopupMenu getMenuForTreeNode(JPopupMenu menu, Object treeNode) {
if (menu == null) menu = new JPopupMenu("Menu:");
menu.removeAll();
if (treeNode instanceof MyTreeItem) {
menu.add(new JMenuItem("This is my tree item: " + treeNode.toString()));
}
return menu;
}