Deselect node from JTree when click any place outside the tree - java

I am using a JTree, which is using a DefaultTreeModel. This tree model has some nodes inside, and when I click on a node, I get the information of the node and I change the background color to show that this node is selected.
It is possible to call the tree to clear the selection when I click on any place out of the tree? By clearing the selection I will be able to change the background color again, but I don't know how or where to use the clearSelection() method of the tree when I click out of the tree.
Here is the code I am using:
The example:
import javax.swing.*;
import javax.swing.tree.*;
import java.awt.*;
public class JTreeSelectDeselect {
public static void main(String[] args) {
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
JPanel panel = new JPanel(new BorderLayout());
JTree tree = new JTree();
tree.setCellRenderer(new DeselectTreeCellRenderer());
panel.add(tree, BorderLayout.LINE_START);
panel.add(new JScrollPane(new JTextArea(10, 30)));
frame.add(panel);
frame.pack();
frame.setVisible(true);
}
}
class DeselectTreeCellRenderer extends DefaultTreeCellRenderer {
#Override
public Color getBackgroundSelectionColor() {
return new Color(86, 92, 160);
}
#Override
public Color getBackground() {
return (null);
}
#Override
public Color getBackgroundNonSelectionColor() {
return new Color(23, 27, 36);
}
#Override
public Component getTreeCellRendererComponent(JTree tree, Object value,
boolean sel, boolean exp, boolean leaf, int row, boolean hasFocus) {
super.getTreeCellRendererComponent(tree, value, sel, exp, leaf, row, hasFocus);
setForeground(new Color(225, 225, 221, 255));
setOpaque(false);
return this;
}
}
I am showing here how I create the nodes and add it to the tree using a tree model and how I set my custom TreeCellRenderer.
In the cell renderer I paint the selected node with a specific color, and if the node is deselected, I paint it using another color. When I change the selection of the nodes, their background is painting correctly, but when I click outside the tree, the selected node is not deselected, so it is not painted with the specific color established in the cell renderer.
There is a way to deselect the node when I click outside the tree?
And just if someone knows, there is a way to change some of the leafs by check boxes from the TreeCellRenderer? To have some children as labels and some others as check boxes. Because when I try to add check boxes it says (as I expected) that check boxes are not DefaultMutableTreeNode objects and I can't add them to the tree model.

First of all you don't need to subclass the DefaultTreeCellRenderer if you just want to change some colors. You can create a new one, set the colors as you wish and set it to the tree. In the below code sample I've done this in the getDefaultTreeCellRenderer().
Your panel contains two elements the tree and the text area.
To achieve what you needed I added a mouse listener and a focus listener to the tree:
The Mouse Listener - on mouseClicked() is triggered both when you click inside the tree or outside it (but not in the TextArea, for that we have the focus listener).
To check whether you clicked in the boundaries of a cell we use the tree.getRowForLocation(e.getX(),e.getY()) and if it returns -1 this means we clicked outside any cell so we can clear the selection
The Focus Listener - when you lose focus from the JTree and click on the text area this will be triggered and we just clear the selection
We needed both listeners because the first one gets triggered only when you click on the tree and around it but not it the Text area and the second one gets triggered when you focus out of the tree area and focus on the text area.
import javax.swing.*;
import javax.swing.tree.DefaultTreeCellRenderer;
import java.awt.*;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
public class JTreeSelectDeselect {
public static void main(String[] args) {
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
JPanel panel = new JPanel(new BorderLayout());
JTree tree = new JTree();
tree.setCellRenderer(getDefaultTreeCellRenderer());
tree.addMouseListener(getMouseListener(tree));
tree.addFocusListener(getFocusListener(tree));
panel.add(tree, BorderLayout.LINE_START);
panel.add(new JScrollPane(new JTextArea(10, 30)));
frame.add(panel);
frame.pack();
frame.setVisible(true);
}
private static DefaultTreeCellRenderer getDefaultTreeCellRenderer() {
DefaultTreeCellRenderer defaultTreeCellRenderer = new DefaultTreeCellRenderer();
defaultTreeCellRenderer.setBackgroundSelectionColor(new Color(86, 92, 160));
defaultTreeCellRenderer.setBackgroundNonSelectionColor(new Color(135, 151, 53));
defaultTreeCellRenderer.setBackground(new Color(225, 225, 221, 255));
defaultTreeCellRenderer.setForeground(new Color(225, 225, 221, 255));
return defaultTreeCellRenderer;
}
private static FocusListener getFocusListener(final JTree tree) {
return new FocusListener() {
#Override
public void focusGained(FocusEvent e) {
}
#Override
public void focusLost(FocusEvent e) {
System.out.println("focus lost");
tree.clearSelection();
}
};
}
private static MouseListener getMouseListener(final JTree tree) {
return new MouseListener() {
#Override
public void mouseClicked(MouseEvent e) {
System.out.println("mouse clicked");
if(tree.getRowForLocation(e.getX(),e.getY()) == -1) {
System.out.println("clicked outside a specific cell");
tree.clearSelection();
}
}
#Override
public void mousePressed(MouseEvent e) {
}
#Override
public void mouseReleased(MouseEvent e) {
}
#Override
public void mouseEntered(MouseEvent e) {
}
#Override
public void mouseExited(MouseEvent e) {
}
};
}
}

If you want to deselect all selected nodes in the tree on mouse click somewhere else, you need to get notified when user clicks somewhere else. To get it you can use the global event listener (Toolkit.getDefaultToolkit().addAWTEventListener()).
If you want to display some checkboxes in your tree you need a custom cell renderer, which for a condition returns a checkbox. Also you need a data container to hold whether the node is selected and finally you need a routine to update your data model, when the node is clicked. The last thing is usually provided by cell editors (more about in the article about editors and renderes), but in your case it's easier to do using the global mouse listener, that we've installed before.
Of course my example contains a little bit "Swing magic", so when you don't understand something, feel free to ask me about ;)
import java.awt.AWTEvent;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Toolkit;
import java.awt.event.AWTEventListener;
import java.awt.event.MouseEvent;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import javax.swing.JCheckBox;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JTree;
import javax.swing.SwingUtilities;
import javax.swing.WindowConstants;
import javax.swing.tree.DefaultTreeCellRenderer;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreePath;
/**
* <code>JTreeDeselected</code>.
*/
public class JTreeSelectDeselect {
private final JTree tree = new JTree();
// model to hold nodes that must be presented as check boxes
// and whether the check boxes are selected
private final Map<Object, Boolean> checkMap = new HashMap<>();
// listener as method reference
private AWTEventListener awtListener = this::mouseClicked;
public static void main(String[] args) {
SwingUtilities.invokeLater(new JTreeSelectDeselect()::start);
}
private void start() {
checkMap.put("football", true);
checkMap.put("soccer", false);
checkMap.put("violet", true);
checkMap.put("yellow", false);
checkMap.put("pizza", true);
checkMap.put("ravioli", false);
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
JPanel panel = new JPanel(new BorderLayout());
// JTree tree = new JTree();
tree.setCellRenderer(new DeselectTreeCellRenderer(checkMap));
panel.add(new JScrollPane(tree), BorderLayout.LINE_START);
panel.add(new JScrollPane(new JTextArea(10, 30)));
frame.add(panel);
// register global listener
Toolkit.getDefaultToolkit().addAWTEventListener(awtListener, AWTEvent.MOUSE_EVENT_MASK);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
private void mouseClicked(AWTEvent evt) {
if (evt instanceof MouseEvent) {
MouseEvent me = (MouseEvent) evt;
if (me.getID() == MouseEvent.MOUSE_PRESSED) {
if (me.getComponent().equals(tree)) {
TreePath path = tree.getPathForLocation(me.getX(), me.getY());
if (path == null) {
tree.clearSelection();
} else {
// update check box value
String pathString = Objects.toString(path.getLastPathComponent(), "");
Boolean val = checkMap.get(pathString);
if (val != null) {
checkMap.put(pathString, !val);
((DefaultTreeModel) tree.getModel()).valueForPathChanged(path, pathString);
}
}
} else {
tree.clearSelection();
}
}
}
}
}
class DeselectTreeCellRenderer extends DefaultTreeCellRenderer {
private final JCheckBox checkBox = new JCheckBox();
private final Map<Object, Boolean> checkMap;
public DeselectTreeCellRenderer(Map<Object, Boolean> checkMap) {
this.checkMap = checkMap;
}
#Override
public Color getBackgroundSelectionColor() {
return new Color(86, 92, 160);
}
#Override
public Color getBackground() {
return (null);
}
#Override
public Color getBackgroundNonSelectionColor() {
return new Color(23, 27, 36);
}
#Override
public Component getTreeCellRendererComponent(JTree tree, Object value, boolean sel, boolean exp, boolean leaf, int row,
boolean hasFocus) {
super.getTreeCellRendererComponent(tree, value, sel, exp, leaf, row, hasFocus);
setForeground(new Color(225, 225, 221, 255));
setOpaque(false);
// if our "check model" contains an entry for the node,
// present the node as check box
if (checkMap.containsKey(Objects.toString(value))) {
checkBox.setOpaque(true);
checkBox.setSelected(Boolean.TRUE == checkMap.get(Objects.toString(value)));
checkBox.setBackground(sel ? getBackgroundSelectionColor() : getBackgroundNonSelectionColor());
checkBox.setForeground(getForeground());
checkBox.setFont(getFont());
checkBox.setBorder(getBorder());
checkBox.setText(getText());
return checkBox;
}
return this;
}
}

The Robot class in the Java AWT package is used to generate native system input events for the purposes of test automation, self-running demos, and other applications where control of the mouse and keyboard is needed. The primary purpose of Robot is to facilitate automated testing of Java platform implementations. In simple terms, the class provides control over the mouse and keyboard devices. In the below snippet "Robot" has been used to capture the event and clear selection from the tree.
import java.awt.AWTException;
import java.awt.Robot;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.tree.TreePath;
public class TreeDeselectionTest {
JTree createdTreeInstance = new JTree();
TreePath pathSelectionInstance;
Robot robotInstance;
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
new TreeDeselectionTest().createTreeAndCaptureEvents();
}
});
}
public void createTreeAndCaptureEvents() {
try {
robotInstance = new Robot();
} catch (AWTException exceptionInstance) {
exceptionInstance.printStackTrace();
}
createdTreeInstance.setShowsRootHandles(false);
createdTreeInstance.addMouseListener(new MouseAdapter() {
#Override
public void mousePressed(MouseEvent eventMousePressInstance) {
if (robotInstance != null && pathSelectionInstance != null && eventMousePressInstance.getButton() == MouseEvent.BUTTON1) {
createdTreeInstance.clearSelection();
robotInstance.mousePress(InputEvent.BUTTON1_MASK);
robotInstance.mouseRelease(InputEvent.BUTTON1_MASK);
}
pathSelectionInstance = createdTreeInstance.getSelectionPath();
}
});
JFrame frameConetnds = new JFrame();
frameConetnds.setContentPane(new JScrollPane(createdTreeInstance));
frameConetnds.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frameConetnds.setSize(400, 400);
frameConetnds.setLocationRelativeTo(null);
frameConetnds.setVisible(true);
}
}

Related

Enter Key work as Tab Key for every component except JButton

I am developing a Swing application, in which I want enter key work as tab key for all component of JFrame except JButton components and dialog boxes. For that I have set ENTER and TAB as default focus traversal keys.
KeyStroke enter = KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0);
KeyStroke tab = KeyStroke.getKeyStroke(KeyEvent.VK_TAB, 0);
Set<KeyStroke> keys = new HashSet<>();
keys.add(enter);
keys.add(tab);
KeyboardFocusManager.getCurrentKeyboardFocusManager().setDefaultFocusTraversalKeys(KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS, keys);
This is working well,but I want that ENTER Key work as Action on JButton and Dialog Boxes.
It's possible, but using another way: global event listener. To register a global event listener you should use the Toolkit class:
Toolkit.getDefaultToolkit().addAWTEventListener(listener, mask);
Here is an example for your case:
import java.awt.AWTEvent;
import java.awt.Toolkit;
import java.awt.event.AWTEventListener;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import javax.swing.AbstractButton;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.WindowConstants;
public class FocusTransferTest {
public static void main(String[] args) {
JFrame frm = new JFrame("Test focus transfer");
JPanel panel = new JPanel();
panel.add(new JTextField(10));
panel.add(new JTextField(10));
panel.add(new JTextField(10));
JButton btn = new JButton("Press me");
btn.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
JOptionPane.showMessageDialog(frm, "It's a message", "Info",
JOptionPane.INFORMATION_MESSAGE);
}
});
panel.add(btn);
Toolkit.getDefaultToolkit().addAWTEventListener(new AWTEnterKeyListener(),
AWTEvent.KEY_EVENT_MASK);
frm.add(panel);
frm.pack();
frm.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
frm.setLocationRelativeTo(null);
frm.setVisible(true);
}
private static class AWTEnterKeyListener implements AWTEventListener {
#Override
public void eventDispatched(AWTEvent event) {
if (event instanceof KeyEvent) {
KeyEvent key = (KeyEvent) event;
if (key.getKeyCode() == KeyEvent.VK_ENTER && key.getModifiersEx() == 0
&& key.getID() == KeyEvent.KEY_PRESSED) {
if (key.getComponent() instanceof AbstractButton) {
((AbstractButton) key.getComponent()).doClick();
} else {
key.getComponent().transferFocus();
}
}
}
}
}
}
I think even the solution using AWTEventListener would work, I would suggest avoid using AWTEventListener if another solution is available. It is because it's so powerful that it intercepts all kinds of events globally before they reach their real targets, so if anything went wrong (such as a NullPointerException) in the middle, the whole application would stop working.
My proposed solution makes use of input map & action map which adds handling of Enter key to any focused component in a particular container.
The advantage:
Safer because it affects only components in a container instead of all components.
The disadvantage:
The same handling code has to apply to all containers that need this behavior, but this could be easily accomplished by a static utility method.
Here is the sample program:
public MainFrame() {
setDefaultCloseOperation(EXIT_ON_CLOSE);
setSize(500, 500);
setLayout(new GridLayout(2, 2));
addAllComponents();
addEnterKeyAsFocusTraversal();
}
private void addAllComponents() {
add(new JTextField());
add(new JTextField());
add(new JButton("OK"));
add(new JButton("Cancel"));
}
private void addEnterKeyAsFocusTraversal() {
final String ENTER_KEY_ACTION = "EnterKeyAction";
// Here uses the content pane of type Container so a cast is required,
// in other case it could be the root container which may already be an instance of JComponent.
JComponent contentPane = (JComponent) getContentPane();
contentPane.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0), ENTER_KEY_ACTION);
contentPane.getActionMap().put(ENTER_KEY_ACTION, createEnterKeyAction());
}
private AbstractAction createEnterKeyAction() {
return new AbstractAction() {
#Override
public void actionPerformed(ActionEvent e) {
Component focusOwner = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner();
if (focusOwner != null) {
if (focusOwner instanceof AbstractButton) {
((AbstractButton) focusOwner).doClick();
} else {
focusOwner.transferFocus();
}
}
}
};
}
The approved answer is actually bit wrong.
It add Enter as focus travel key, however it DOES NOT modify the basic button behavior of .doClick();
So this code spams .doClick(); second time. I came across this issue when on button action I was writing data from bunch of text Fields to table, and it added the same record two times. solution for this is pretty simple, on instanceof JButton just make return;
repaired code upgraded to java 14 instanceof
private static class AWTListener implements AWTEventListener{
#Override
public void eventDispatched(AWTEvent event) {
if (event instanceof KeyEvent key && key.getKeyCode() == KeyEvent.VK_ENTER && key.getModifiersEx() == 0 && key.getID() == KeyEvent.KEY_PRESSED) {
if (key.getComponent() instanceof JButton) {
return;
}
key.getComponent().transferFocus();
}
}
}

mouse double click is not working

I have written this java code to detect the double click of left button on mouse, but this code is not working please help.
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTable;
public class B extends MouseAdapter {
JFrame frame = new JFrame();
Object rows[][] = new Object[5][3];
String colums[] = {"A","B","C"};
JTable table = new JTable(rows,colums);
JScrollPane scroll = new JScrollPane(table);
public static void main(String arg[]) {
new B();
}
B() {
frame.setExtendedState(JFrame.MAXIMIZED_BOTH);
table.addMouseListener(this);
frame.add(scroll);
frame.setVisible(true);
}
public void mouseClicked(MouseEvent clicked) {
if(clicked.getSource()==table && clicked.getButton()==1 && clicked.getClickCount()==2)
System.out.println("Left Double Click");
}
}
Your example won't compile
You should be using SwingUtilities.isLeftMouseButton(clicked) instead of clicked.getButton()==1
The table may be consuming the MouseEvent and installing the cell editor before your MouseListener is notified.
If you use table.setFillsViewportHeight(true); you can double click outside of the rows/columns successfully
You can change the table's CellEditors to ignore the MouseEvent (or change the number of clicks required), this will allow you MouseListener to pick up the double clicks, but will also increase your work load, as you will need to supply a CellEditor for each column Class type
TableCellEditor editor = new DefaultCellEditor(new JTextField(10)) {
#Override
public boolean isCellEditable(EventObject anEvent) {
boolean editable = false;
if (!(anEvent instanceof MouseEvent)) {
editable = super.isCellEditable(anEvent);
}
return editable;
}
};
table.setDefaultEditor(Object.class, editor);
Without more context, is difficult to know what else to suggest

Bind single event to all JLabels in Java

I have 20 JLabels and all of them have to change their background color when mouse enters and change back to original color when mouse outs.
Do I have to individually bind 2 Event-listeners of MouseEntered and MouseExited with all JLabels separately, or is there any work around so I can make just 2 events kind of monitoring all JLabels?
Like in the image below: there are about 6 JLabels and I want each one to change its background color whenever the mouse enters the scene and change back to original color when the mouse outs.
So, do I have to individually set event listeners on all JLabels, or there can be a single event listener for all JLabels?
You can register all 20 JLabels with the same mouse listener. You would do something like this:
MouseListener m = new MouseAdapter() // create our own mouse listener
{
#Override
public void mouseEntered(MouseEvent e)
{
e.getComponent().setBackground(Color.RED);; // this method changes the colours of all the labels
}
#Override
public void mouseExited(MouseEvent e)
{
e.getComponent().setBackground(Color.GREEN); // this method changes the colours back to normal
}
};
for (JLabel label: labels) // iterate over all the labels
{
label.addMouseListener(m); // give them all our mouse listener
}
Where "labels" is some collection (List, Set, array...) of your JLabels, and changeLabelColours() and changeLabelColoursBack() are two methods that you define to change the colours.
hope this helps!
EDIT: reading your edited question, I think I should point out that this code will cause ALL labels to change colour when ANY of the labels is moused-over. I think that's what you mean.
You don't "make events". You make eventListeners. And yes, you can do just 2 event listeners and bound them to all JLabels.
Or, if you prefer, you can extends Jlabel into MyJlabel, that has the event listener built in, and save yourself from the repeated binding, if it bothers you.
You can use one MouseListener reference.
You should differentiate event sources based on references or on component name.
Here is an example
final JLabel label1 = new JLabel("label1");
label1.setName("label1");
final JLabel label2 = new JLabel("label2");
label2.setName("label2");
final JLabel label3 = new JLabel("label3");
label3.setName("label3");
MouseListener mouseListener = new MouseAdapter() {
#Override
public void mouseExited(MouseEvent e) {
// you can check references
if(e.getSource() == label1) {
System.out.println("exited " + label1.getName());
} else if (e.getSource() == label2) {
System.out.println("exited " + label2.getName());
} else if (e.getSource() == label3) {
System.out.println("exited " + label3.getName());
}
}
#Override
public void mouseEntered(MouseEvent e) {
String name = ((JComponent)e.getSource()).getName();
// or you can check name of component
switch (name) {
case "label1":
case "label2":
case "label3":
System.out.println("entered " + name);
break;
}
}
};
for (JLabel l : Arrays.asList(label1, label2, label3)) {
l.addMouseListener(mouseListener);
}
Note that labels need to be opaque in order to change their bg color.
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import javax.swing.BoxLayout;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
public class CrazyLabels extends JFrame {
public CrazyLabels() {
setDefaultCloseOperation(EXIT_ON_CLOSE);
setLayout(new BorderLayout());
JPanel content = new JPanel();
add(content);
content.setLayout(new BoxLayout(content, BoxLayout.Y_AXIS));
MouseAdapter listener = new MouseAdapter() {
#Override
public void mouseEntered(MouseEvent e) {
JLabel label = (JLabel)e.getSource();
label.setBackground(Color.red);
}
#Override
public void mouseExited(MouseEvent e) {
JLabel label = (JLabel)e.getSource();
label.setBackground(UIManager.getColor("Label.background"));
}
};
for (int i = 0; i < 5; i++) {
JLabel label = new MyLabel(listener);
label.setText("---- label ----");
content.add(label);
}
pack();
setLocationRelativeTo(null);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
new CrazyLabels().setVisible(true);
}
});
}
private static class MyLabel extends JLabel {
public MyLabel(MouseListener listener) {
setOpaque(true);
addMouseListener(listener);
}
}
}
In this example a single listener is used to enforce behavior in all labels.

Detecting when JSplitPane divider is being dragged, not component being resized

I want to implement a JSplitPane (horizontal split) where the right component has a constant width i.e. when the divider is dragged it will jump back to the correct size, unless the divider is dragged far enough right in which case the right most component will have zero width.
To re-display the right component the user can then drag the divider far enough to the left.
I've got this mostly working, but when I resize the window depending on how much and how fast I change the window size the divider jumps reveal or hide the right component, where as what I want is that it should not change 'state' ie if the right component was not visible then it should remain invisible and vice versa.
I've tried heaps of things but the main obstacle is that there seems to be no way of knowing weather the divider was dragged by the user via mouse or if the code (my divider logic and/or JSplitPane internal logic) changed the divider position.
Here is a self contained test case, run it and try to drag the horizontal divider to hide and reveal the right side panel and with those hidden/shown try to resize the window.
Does not work as intended on Mac OS X Java 1.6 (Apple) or Java 7 (Oracle). With the Oracle stuff the rendering is much slower and the problem is more severe. Resizing the window slowly works, but fast window size changes cause problems.
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import javax.swing.*;
import net.miginfocom.swing.MigLayout;
public class SplitTest {
public static class MySplitPane extends JSplitPane {
boolean m_RightCollapsed;
public MySplitPane(int orientation, JComponent left, JComponent right) {
super(orientation, left, right);
addPropertyChangeListener(new PropertyChangeListener() {
#Override
public void propertyChange(PropertyChangeEvent evt) {
}
});
addComponentListener(new ComponentListener() {
#Override
public void componentShown(ComponentEvent e) {
reposDivider();
}
#Override
public void componentResized(ComponentEvent e) {
reposDivider();
}
#Override
public void componentMoved(ComponentEvent e) {
reposDivider();
}
#Override
public void componentHidden(ComponentEvent e) {
}
});
}
public void reposDivider() {
setDividerLocation(getDividerLocation());
}
public void setDividerLocation(int location) {
int newLocation;
m_RightCollapsed = location > getSize().width - getRightComponent().getPreferredSize().width / 2;
if (m_RightCollapsed)
newLocation = getSize().width;
else
newLocation = getSize().width - getInsets().right - getDividerSize() - getRightComponent().getPreferredSize().width;
super.setDividerLocation(newLocation);
}
}
static class MyScrollable extends JPanel implements Scrollable {
int m_Height;
public MyScrollable(int height) {
m_Height = height;
}
#Override
public void paint(java.awt.Graphics g) {
super.paint(g);
g.setColor(Color.CYAN);
g.fillOval(0, 0, getWidth(), 500);
}
#Override
public Dimension getPreferredScrollableViewportSize() {
//return super.getPreferredSize();
return new Dimension(100, m_Height);
}
#Override
public int getScrollableUnitIncrement(Rectangle visibleRect, int orientation, int direction) {
// TODO Auto-generated method stub
return 10;
}
#Override
public int getScrollableBlockIncrement(Rectangle visibleRect, int orientation, int direction) {
return 20;
}
#Override
public boolean getScrollableTracksViewportWidth() {
return true;
}
#Override
public boolean getScrollableTracksViewportHeight() {
return false;
}
}
public static class ShrinkGrow extends JPanel {
public ShrinkGrow(final JComponent component, final JSplitPane split) {
JButton grow = new JButton("+++");
JButton shrink = new JButton("---");
add(grow);
add(shrink);
grow.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
Dimension oldSize = component.getPreferredSize();
Dimension newSize = new Dimension(oldSize.width, oldSize.height + 10);
component.setPreferredSize(newSize);
component.setSize(newSize);
}
});
shrink.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
Dimension oldSize = component.getPreferredSize();
Dimension newSize = new Dimension(oldSize.width, oldSize.height - 10);
component.setPreferredSize(newSize);
component.setSize(newSize);
}
});
}
}
public static void main(String[] args) {
JFrame window = new JFrame();
JPanel mainView = new JPanel();
JPanel top = new JPanel();
top.setLayout(new BoxLayout(top, BoxLayout.Y_AXIS));
JPanel bottom = new JPanel();
bottom.setLayout(new BoxLayout(bottom, BoxLayout.Y_AXIS));
final JSplitPane rightSplit = new JSplitPane(JSplitPane.VERTICAL_SPLIT);
JPanel topContent = new MyScrollable(200);
JPanel topFixed = new ShrinkGrow(topContent, rightSplit);
topFixed.setLayout(new BoxLayout(topFixed, BoxLayout.X_AXIS));
JScrollPane topFlexible = new JScrollPane(topContent);
topFlexible.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED);
topFlexible.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
JPanel bottomContent = new MyScrollable(300);
JPanel bottomFixed = new ShrinkGrow(bottomContent, rightSplit);
bottomFixed.setLayout(new BoxLayout(bottomFixed, BoxLayout.X_AXIS));
JScrollPane bottomFlexible = new JScrollPane(bottomContent);
bottomFlexible.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED);
bottomFlexible.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
mainView.setBackground(Color.red);
topFixed.setBackground(Color.green);
topContent.setBackground(Color.green.darker());
bottomFixed.setBackground(Color.blue);
bottomContent.setBackground(Color.blue.darker());
mainView.setMinimumSize(new Dimension(100, 100));
mainView.setPreferredSize(new Dimension(400, 300));
mainView.setMaximumSize(new Dimension(10000, 10000));
topFixed.setMinimumSize(new Dimension(topFixed.getMinimumSize().width, 30));
topFixed.setPreferredSize(new Dimension(topFixed.getPreferredSize().width, 30));
topFixed.setMaximumSize(new Dimension(topFixed.getMaximumSize().width, 30));
bottomFixed.setMinimumSize(new Dimension(bottomFixed.getMinimumSize().width, 40));
bottomFixed.setPreferredSize(new Dimension(bottomFixed.getPreferredSize().width, 40));
bottomFixed.setMaximumSize(new Dimension(bottomFixed.getMaximumSize().width, 40));
topContent.setPreferredSize(new Dimension(100, 500));
bottomContent.setPreferredSize(new Dimension(100, 400));
top.add(topFixed);
top.add(topFlexible);
bottom.add(bottomFixed);
bottom.add(bottomFlexible);
rightSplit.setLeftComponent(top);
rightSplit.setRightComponent(bottom);
rightSplit.setMinimumSize(new Dimension(0, 0));
final JSplitPane mainSplit = new MySplitPane(JSplitPane.HORIZONTAL_SPLIT, mainView, rightSplit);
window.add(mainSplit);
window.pack();
window.setVisible(true);
}
}
Not sure if possible to catch dragging event, but for sure you can catch propertyChange event. Catching events after you move a JSplitPane‘s divider can be made possible through the PropertyChangeListener JSplitPane class. Make sure you supply the DIVIDER_LOCATION_PROPERTY as the parameter so that this listener will listent to modified divider location events.
If you do not supply this as the first parameter in the addPropertyChangeListener() method, you can always place a conditional statement if the PropertyChangeEvent‘s getPropertyName() method returns dividerLocation as the value.
jSplitPane1.addPropertyChangeListener(JSplitPane.DIVIDER_LOCATION_PROPERTY, new PropertyChangeListener() {
#Override
public void propertyChange(PropertyChangeEvent pce) {
// do here
}
});
Add a MouseListener to the JSplitPane Divider to detect when the divider is being dragged. When dragged, respond to the property change events. Sample:
https://community.oracle.com/thread/1352161?start=0&tstart=0
SplitPaneUI spui = splitPane.getUI();
if (spui instanceof BasicSplitPaneUI) {
// Setting a mouse listener directly on split pane does not work, because no events are being received.
((BasicSplitPaneUI) spui).getDivider().addMouseListener(new MouseAdapter() {
public void mouseClicked(MouseEvent e) {
...unless the divider is dragged far enough right in which case the right
most component will have zero width.
To re-display the right component the user can then drag the divider
far enough to the left.
sounds like all you have to do is disable the splitPane to disable dragging,
then setOneTouchExpandable() to true. you may need to remove one of the 'expandable'
buttons to disable expanding the wrong way

Implementing drag-and-drop from one JPanel to another

I'm new to drag-and-drop in Swing. I have a JPanel that draws an image with a caption superimposed on it. I want to implement drag and drop on this JPanel, but after going through some documentation and tutorials I didn't find any usable pointers on how it's done for this type of component. For starters, it doesn't have a setDragEnabled function.
Can I make a JPanel draggable? I want to use this DnD maneuver to pass a reference to a certain object from one panel to another.
May be this can help you.
Drag and Drop of complex custom objects in Java
You can implement drag-and-drop behavior on any JComponent. See the setTransferHandler method.
The setDragEnabled method is typically provided on components where a good default D&D behavior can be implemented in the JDK. In such cases you can just activate the default D&D by calling that method.
On a JPanel they (=the Swing developers) could probably not think of any decent default D&D behavior, so you will have to implement your own TransferHandler. I strongly suggest to read the Drag-and-drop tutorial before starting
I don't know how viable sounds but when I needed to drag and drop panels I did it this way:
Firstable I implemented action events for dragable panels and containers, it can be both
I used a static variables for selected parent, selected child and current panel
when the mouse is over a panel you set it as the current panel
when you click , mouse down, whatever, you check if currentpanel is the clicked one and set is as child panel
when the mouse is over a panel and child panel is not null, then it seems that you're dragging, current panel will turn into parent panel once you release the mouse
you have to add some validation.
If a panel is being dragged you can use your own implementation, it could be follow the mouse coords or just highlight it and highlight the parent, I used this last option to simulate the drag
ok I wrote this, is so buggy but this is the idea:
import java.awt.Color;
import java.awt.Dimension;
import java.awt.GridLayout;
import java.awt.Rectangle;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JDesktopPane;
import javax.swing.JFrame;
import javax.swing.JTextField;
/**
*
* #author porfiriopartida
*/
public class DraggablePanel extends JDesktopPane implements ContainerPanel{
public ContainerPanel parent;
static DraggablePanel over;
static ContainerPanel overParent;
static DraggablePanel dragging;
static ContainerPanel draggingParent;
public DraggablePanel(){
this(null);
}
public DraggablePanel(ContainerPanel parent){
this.parent = parent;
setBorder(BorderFactory.createLineBorder(Color.black));
setBounds(0,0,100,100);
if(parent != null)
addMouseListener(new MouseAdapter(){
#Override
public void mouseEntered(MouseEvent me) {
DraggablePanel src = (DraggablePanel) me.getSource();
DraggablePanel.over = src;
DraggablePanel.overParent = DraggablePanel.over.parent;
}
#Override
public void mouseExited(MouseEvent me) {
}
#Override
public void mouseReleased(MouseEvent me) {
if(DraggablePanel.over != null && DraggablePanel.dragging != null && DraggablePanel.overParent != null){
Rectangle bounds = DraggablePanel.dragging.getBounds();
bounds.x = me.getX();
bounds.y = me.getY();
//Remove child from parent
DraggablePanel.dragging.parent.removePanel(DraggablePanel.dragging);
if(DraggablePanel.dragging.parent != DraggablePanel.overParent){
//add child to new parent
DraggablePanel.overParent.addPanel(DraggablePanel.dragging, bounds);
}
else{
//same parent selected
DraggablePanel.dragging.parent.addPanel(DraggablePanel.dragging, bounds);
};
DraggablePanel.dragging.parent = DraggablePanel.overParent;
}
//cleaning variables
DraggablePanel.dragging = null;
DraggablePanel.over = null;
DraggablePanel.draggingParent = null;
DraggablePanel.overParent = null;
}
#Override
public void mousePressed(MouseEvent me) {
DraggablePanel.dragging = (DraggablePanel) me.getSource();
DraggablePanel.draggingParent = DraggablePanel.dragging.parent;
}
});
addMouseListener(new MouseAdapter() {
#Override
public void mouseEntered(MouseEvent me) {
super.mouseEntered(me);
ContainerPanel src = (ContainerPanel) me.getSource();
DraggablePanel.overParent = src;
if (DraggablePanel.draggingParent == null || DraggablePanel.draggingParent == src) {
setBorder(BorderFactory.createLineBorder(Color.BLACK, 2));
} else {
setBorder(BorderFactory.createLineBorder(Color.blue, 2));
}
}
#Override
public void mouseExited(MouseEvent me) {
}
#Override
public void mouseReleased(MouseEvent me) {
}
});
}
#Override
public void addPanel(DraggablePanel panel) {
panel.parent = this;
add(panel);
repaint();
revalidate();
try {
getParent().repaint();
} catch (Exception e) {
}
}
#Override
public void addPanel(DraggablePanel panel, Rectangle bounds) {
setBounds(bounds);
addPanel(panel);
}
#Override
public void removePanel(DraggablePanel panel) {
remove(panel);
}
public static void main(String args[]){
JFrame frame = new JFrame();
frame.setLayout(new GridLayout(1,1));
JTextField tf = new JTextField("textfield");
JButton btn = new JButton("Button");
DraggablePanel desktop = new DraggablePanel();
frame.add(desktop);
DraggablePanel p1 = new DraggablePanel(desktop);
p1.setLayout(new GridLayout(2,1));
p1.add(tf);
p1.setBounds(0,0,100,50);
tf.setBounds(5,5,80,30);
DraggablePanel p2 = new DraggablePanel(desktop);
p2.setLayout(new GridLayout(2,1));
p2.add(btn);
p2.setBounds(50,50,50,30);
btn.setBounds(5,5,30,20);
desktop.add(p1);
desktop.add(p2);
frame.setPreferredSize(new Dimension(600,400));
frame.pack();
frame.setVisible(true);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
}

Categories

Resources