Implementing drag-and-drop from one JPanel to another - java

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

Related

Listen for mouse released event on component on which the mouse was not pressed in Swing

Is it possible to listen for mouse released event on the component on which it was not pressed?
I know that when mouse is released MouseListener.mouseReleased()is invoked on the listeners for that component when mouse press originated even if the cursor is above other component.
How to inform a component or its listeners that the mouse was over it and it was released?
If you add your MouseListener to the container that holds your components of interest, you can find out which component the mouse is over on press or drag. For instance in the code below, I've added my MouseAdapter (a combination MouseListener, MouseMotionListener, and MouseWheelListener) to the containing JPanel, and after getting the location of the mouse event on the container, I call getComponentAt(Point p) on my container to get the child component that the mouse was over:
import java.awt.Component;
import java.awt.Dimension;
import java.awt.GridLayout;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.*;
#SuppressWarnings("serial")
public class TestMouseRelease extends JPanel {
private String[] panelNames = { "Panel A", "Panel B" };
public TestMouseRelease() {
setLayout(new GridLayout(1, 0));
MouseAdapter mAdapter = new MyMouseAdapter();
addMouseListener(mAdapter);
addMouseMotionListener(mAdapter);
for (String panelName : panelNames) {
JPanel panel = new JPanel();
panel.setName(panelName);
// panel.addMouseListener(mAdapter);
// panel.addMouseMotionListener(mAdapter);
panel.setBorder(BorderFactory.createTitledBorder(panelName));
panel.add(Box.createRigidArea(new Dimension(300, 300)));
add(panel);
}
}
private class MyMouseAdapter extends MouseAdapter {
#Override
public void mousePressed(MouseEvent e) {
displayInfo(e, "mousePressed");
}
#Override
public void mouseReleased(MouseEvent e) {
displayInfo(e, "mouseReleased");
}
#Override
public void mouseDragged(MouseEvent e) {
displayInfo(e, "mouseDragged");
}
private void displayInfo(MouseEvent e, String info) {
JComponent comp = (JComponent) e.getSource();
Component childComp = comp.getComponentAt(e.getPoint());
if (childComp != null) {
String name = childComp.getName();
System.out.println(name + ": " + info);
}
}
}
private static void createAndShowGui() {
JFrame frame = new JFrame("TestMouseRelease");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(new TestMouseRelease());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGui();
}
});
}
}
Yeah, I handled something similar in my project. I use getBounds() on all component in the container and check if they contain the x,y coordinates of your mouse. Below is my code:
Component[] components = jPanel1.getComponents();
for (Component c : components) {
if (c.getBounds().contains(ev.getXOnScreen(), ev.getYOnScreen())) {
System.out.println(c.getClass());
y = ch.getPosY();
x = ch.getPosX();
}
}

Intercepting drag and drop events in a compound component?

Say I have a JPanel containing some JTextFields. I would like to perform the same drop action for this JPanel and its children. In other words, I would like the drop action onto the children to be treated the same way as a drop action onto the JPanel.
Is there any other way other than setting the same DropTargetListener for the JPanel and its children?
I know that if I set the TransferHandler of those JTextFields to null, the JPanel will receive the drag and drop event. However, this will destroy the copy and paste functionality of the textfield.
I know that I can intercept mouse events with JLayer. Is there something like this for drag events?
In the end, I added listeners separately to the child components. Because I needed the drop location relative to the parent as well, I used SwingUtilities.convertToPoint() separately on the child components. Which means a lot of different listeners used -- more memory usage. But seems to be the best way for now.
If you want to be able to drop items on a panel but you want any components on the panel to be ignored for dropping purposes you can deactivate the drop target on each of the components added to the panel. You will still be able to cut and paste within them, and there is even a way to initiate a drag from them, but you won't be able to drop anything on them - the drop event goes straight through them to the drop target associated with the panel.
To do this, simply call
component.getDropTarget().setActive(false);
for each component on the panel.
I found this useful when building a calendar panel where I wanted to be able to drag appointments around but drop them on the panel even if it was (partially or completely) covered in other appointments.
Not sure if this is what you had in mind, but, I basically added the same DropTargetListener to all of my components, which meant that it didn't matter where I dragged/dropped the incoming request, all the components triggered the same events...
import java.awt.BorderLayout;
import java.awt.EventQueue;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.dnd.DnDConstants;
import java.awt.dnd.DropTarget;
import java.awt.dnd.DropTargetDragEvent;
import java.awt.dnd.DropTargetDropEvent;
import java.awt.dnd.DropTargetEvent;
import java.awt.dnd.DropTargetListener;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class TestDragNDrop100 {
public static void main(String[] args) {
new TestDragNDrop100();
}
public TestDragNDrop100() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel implements DropTargetListener {
public TestPane() {
DropTarget dt = new DropTarget(this, DnDConstants.ACTION_COPY_OR_MOVE, this, true);
setLayout(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
gbc.fill = GridBagConstraints.HORIZONTAL;
for (int y = 0; y < 4; y++) {
gbc.gridy = y;
for (int x = 0; x < 4; x++) {
gbc.gridx = x;
JTextField field = new JTextField(10);
DropTarget child = new DropTarget(field, DnDConstants.ACTION_COPY_OR_MOVE, this, true);
add(field, gbc);
}
}
}
#Override
public void dragEnter(DropTargetDragEvent dtde) {
System.out.println("DragEnter - " + dtde.getDropTargetContext().getComponent());
}
#Override
public void dragOver(DropTargetDragEvent dtde) {
System.out.println("DragOver - " + dtde.getDropTargetContext().getComponent());
}
#Override
public void dropActionChanged(DropTargetDragEvent dtde) {
System.out.println("dropActionChanged" + dtde.getDropTargetContext().getComponent());
}
#Override
public void dragExit(DropTargetEvent dte) {
System.out.println("dragExit" + dte.getDropTargetContext().getComponent());
}
#Override
public void drop(DropTargetDropEvent dtde) {
System.out.println("drop" + dtde.getDropTargetContext().getComponent());
}
}
}
I should also note. I tested the fields cut/copy/paste functionality and had no issues.
Implement a custom TransferHandler on the container which delegates to its children as appropriate, something along the lines of:
for (int i = 0; i < 5; i++) {
parent.add(new JTextField("item" + i, 20));
};
TransferHandler handler = new TransferHandler() {
#Override
public boolean canImport(TransferSupport support) {
TransferHandler childHandler = getTargetHandler();
return childHandler.canImport(
getTargetSupport(support));
}
protected TransferSupport getTargetSupport(TransferSupport support) {
return new TransferSupport(getTarget(), support.getTransferable());
}
protected TransferHandler getTargetHandler() {
return getTarget().getTransferHandler();
}
protected JComponent getTarget() {
return (JComponent) parent.getComponent(0);
}
#Override
public boolean importData(TransferSupport support) {
return getTargetHandler().importData(getTargetSupport(support));
}
};
parent.setTransferHandler(handler);

Adding graphics into a JFrame using BorderLayout

I'm trying to do a simple piece of homework, where I display a line of text displaying whether a door object is open or not. Underneath that, I visually represent it (using the drawRect) method. And at the bottom I have two buttons, which can open or close the door, thus changing the text and rectangle.
Edit: List of code that can be compiled given now:
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTextField;
public class Test {
public static void main(String[] args) {
// Creates new JFrame called frame, with title "Door"
// (displayed at top of screen).
JFrame frame = new JFrame ("Door");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
TempDoorPanel panel = new TempDoorPanel();
frame.add(panel);
frame.pack();
frame.setVisible(true);
}
}
class Door {
private String state;
private String message;
Door (String state) {
this.state = state;
message = "The door is currently closed.";
}
public boolean isOpen() {
return state.equals ("open");
}
public boolean isClosed() {
return state.equals ("closed");
}
public void setState(String state) {
this.state = state;
}
public String getMessage() {
return message;
}
public void open() {
if (state.equals("open")) {
message = "The door is already open.";
}
else {
state = "open";
message = "The door has been opened.";
}
}
public void drawOpenDoor (Graphics page) {
page.drawRect(100, 100, 100, 100);
}
}
class TempDoorPanel extends JPanel {
private Door door;
private JTextField currentStateOfDoor;
private JButton openDoor;
public TempDoorPanel() {
super.setLayout(new BorderLayout());
door = new Door("closed");
super.setBackground(Color.blue);
super.setPreferredSize(new Dimension (360, 400));
currentStateOfDoor = new JTextField(14);
currentStateOfDoor.setText(door.getMessage());
super.add(currentStateOfDoor, BorderLayout.NORTH);
openDoor = new JButton("Open Door");
class openDoorListener implements ActionListener {
public void actionPerformed (ActionEvent event) {
door.open();
repaintText();
}
}
openDoorListener openlistener = new openDoorListener();
openDoor.addActionListener(openlistener);
JPanel holder = new JPanel();
holder.add(openDoor);
super.add(holder, BorderLayout.SOUTH);
}
private void repaintText() {
currentStateOfDoor.setText(door.getMessage());
// These methods are from Door class.
}
public void paintComponent (Graphics page) {
super.paintComponent(page);
if (door.isOpen())
door.drawOpenDoor(page);
// isOpen is a boolean method from Door class.
}
}
What works:
Buttons appear at right place on screen, at BorderLayout.SOUTH, one after the other.
The JTextField appears at right place, at BorderLayout.NORTH
Finally, the blue area appears in the right place in the centre of the screen.
What I'm trying to fix:
I have no idea how to display the rectangle properly in the middle of that blue area. I've tried changing the coordinates and size of the rectangle, which doesn't change the size of it at all. I can make it drawRect(100, 100, 100, 100) and it changes nothing.
I'm also aware that the rectangle is currently hidden behind the top left corner of the JTextField, but I can't figure out how to move it into the BorderLayout.
Questions:
How do you place a rectangle in a BorderLayout?
How do you adjust the size of a rectangle, drawn via drawrect(), in such a layout?
Because you add components to the JPanel you draw on the JTextField is covering your drawing.
Solution:
1) Either compensate for this by checking the JTextField height in your drawRect(..) method
or better
2) Dont add components to the same JPanel which you are drawing on unless it cant be helped.
So basically I made your TempDoorPanel add a new JPanel to BorderLayout.CENTER which is the drawing panel we can now use drawRect(0,0,10,10) and it will show in the top left hand corner of JPanel drawingPanel.
Also dont call setPreferredSize on JPanel rather override getPreferredSize() and return Dimensions which fit your drawings.
To invoke paintComponent outside of the class simply call repaint() its instance
See this example which uses point no.2:
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;
public class Test {
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
JFrame frame = new JFrame("Door");
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
TempDoorPanel panel = new TempDoorPanel();
frame.add(panel);
frame.pack();
frame.setVisible(true);
}
});
}
}
class Door {
private String state;
private String message;
public Door(String state) {
this.state = state;
message = "The door is currently closed.";
}
public void drawOpenDoor(Graphics page) {
page.setColor(Color.GREEN);
page.drawRect(0, 0, 10, 10);
}
}
class TempDoorPanel extends JPanel {
private Door door;
private JTextField currentStateOfDoor;
private JButton openDoor;
public TempDoorPanel() {
super.setLayout(new BorderLayout());
door = new Door("closed");
currentStateOfDoor = new JTextField(14);
//AcurrentStateOfDoor.setText(door.getMessage());
super.add(currentStateOfDoor, BorderLayout.NORTH);
openDoor = new JButton("Open Door");
final JPanel drawingPanel = new JPanel() {
#Override
protected void paintComponent(Graphics grphcs) {
super.paintComponent(grphcs);
// if (door.isOpen()) {
door.drawOpenDoor(grphcs);
// }
// isOpen is a boolean method from Door class.
}
};
drawingPanel.setBackground(Color.blue);
add(drawingPanel);
class openDoorListener implements ActionListener {
public void actionPerformed(ActionEvent event) {
//door.open();
repaintText();
drawingPanel.repaint();//so paint component of drawing panel is called
}
}
openDoorListener openlistener = new openDoorListener();
openDoor.addActionListener(openlistener);
JPanel holder = new JPanel();
holder.add(openDoor);
super.add(holder, BorderLayout.SOUTH);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(300, 300);
}
private void repaintText() {
// currentStateOfDoor.setText(door.getMessage());
// These methods are from Door class.
}
}
When you handler the door opening event with your listener;
class openDoorListener implements ActionListener {
public void actionPerformed(ActionEvent event) {
door.open();
repaintText();
}
}
you don't actually include a call to repaint the panel; hence the panel's paintComponent() method isn't called and door.drawOpenDoor() isn't called. You can test this by clicking the button and then resizing the frame. When you resize, the panel is automatically repainted and bingo, your door appears.
You can fix this by adding a call to repaint() in your ActionListener;
class openDoorListener implements ActionListener {
public void actionPerformed(ActionEvent event) {
door.open();
repaintText();
repaint(); // requests that the panel be repainted
}
}

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

How do I make JScrollPane scroll to follow input focus?

I have a Swing app with a large panel which is wrapped in a JScrollPane. Users normally move between the panel's subcomponents by tabbing, so when they tab to something out view, I want the scroll pane to autoscroll so the component with input focus is always visible.
I've tried using KeyboardFocusManager to listen for input focus changes, and then calling scrollRectToVisible.
Here's an SSCCE displaying my current strategy (just copy/paste and run!):
import java.awt.KeyboardFocusManager;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import javax.swing.*;
public class FollowFocus {
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
final int ROWS = 100;
final JPanel content = new JPanel();
content.setLayout(new BoxLayout(content, BoxLayout.Y_AXIS));
content.add(new JLabel(
"Thanks for helping out. Use tab to move around."));
for (int i = 0; i < ROWS; i++) {
JTextField field = new JTextField("" + i);
field.setName("field#" + i);
content.add(field);
}
KeyboardFocusManager.getCurrentKeyboardFocusManager()
.addPropertyChangeListener("focusOwner",
new PropertyChangeListener() {
#Override
public void propertyChange(PropertyChangeEvent evt) {
if (!(evt.getNewValue() instanceof JComponent)) {
return;
}
JComponent focused = (JComponent) evt.getNewValue();
if (content.isAncestorOf(focused)) {
System.out.println("Scrolling to " + focused.getName());
focused.scrollRectToVisible(focused.getBounds());
}
}
});
JFrame window = new JFrame("Follow focus");
window.setContentPane(new JScrollPane(content));
window.setSize(200, 200);
window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
window.setVisible(true);
}
});
}
}
If you run this example, you'll notice it doesn't work very well. It does get the focus change notifications, but the call to scrollRectToVisible doesn't appear to have any effect. In my app (which is too complex to show here), scrollRectToVisible works about half the time when I tab into something outside of the viewport.
Is there an established way to solve this problem? If it makes any difference, the Swing app is built on Netbeans RCP (and most of our customers run Windows).
My comment to the other answer:
scrollRectToVisible on the component itself is the whole point of that
method ;-) It's passed up the hierarchy until a parent doing the
scroll is found
... except when the component itself handles it - as JTextField does: it's implemented to scroll horizontally to make the caret visible. The way out is to call the method on the field's parent.
Edit
just for clarity, the replaced line is
content.scrollRectToVisible(focused.getBounds());
you have to take Rectangle from JPanel and JViewPort too, then compare, for example
notice (against down-voting) for final and nice output required some work for positions in the JViewPort
import java.awt.KeyboardFocusManager;
import java.awt.Rectangle;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import javax.swing.*;
//http://stackoverflow.com/questions/8245328/how-do-i-make-jscrollpane-scroll-to-follow-input-focus
public class FollowFocus {
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
final int ROWS = 100;
final JPanel content = new JPanel();
content.setLayout(new BoxLayout(content, BoxLayout.Y_AXIS));
content.add(new JLabel(
"Thanks for helping out. Use tab to move around."));
for (int i = 0; i < ROWS; i++) {
JTextField field = new JTextField("" + i);
field.setName("field#" + i);
content.add(field);
}
final JScrollPane scroll = new JScrollPane(content);
KeyboardFocusManager.getCurrentKeyboardFocusManager().
addPropertyChangeListener("focusOwner", new PropertyChangeListener() {
#Override
public void propertyChange(PropertyChangeEvent evt) {
if (!(evt.getNewValue() instanceof JComponent)) {
return;
}
JViewport viewport = (JViewport) content.getParent();
JComponent focused = (JComponent) evt.getNewValue();
if (content.isAncestorOf(focused)) {
System.out.println("Scrolling to " + focused.getName());
Rectangle rect = focused.getBounds();
Rectangle r2 = viewport.getVisibleRect();
content.scrollRectToVisible(new Rectangle(rect.x, rect.y, (int) r2.getWidth(), (int) r2.getHeight()));
}
}
});
JFrame window = new JFrame("Follow focus");
window.setContentPane(new JScrollPane(content));
window.setSize(200, 200);
window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
window.setVisible(true);
}
});
}
}
Here my short summary.
Add this to your Tools class:
public static void addOnEnter(Component c, Consumer<FocusEvent> onEnter) {
FocusListener fl = new FocusListener() {
#Override
public void focusGained(FocusEvent e) {
onEnter.accept(e);
}
#Override
public void focusLost(FocusEvent e) { }
};
c.addFocusListener(fl);
}
public static void scrollToFocus(FocusEvent e) {
((JComponent) e.getComponent().getParent()).scrollRectToVisible(
e.getComponent().getBounds());
}
and use it like this:
Tools.addOnEnter(component, Tools::scrollToFocus);
component can be JTextField, JButton, ...
One major issue in your code is:
focused.scrollRectToVisible(focused.getBounds());
You are calling scrollRectToVisible on the component itself! Presumably a typo.
Make your JScrollPane a final variable and call
scrollPane.getViewport().scrollRectToVisible(focused.getBounds());
Here jtextbox is the component you want to focus and jscrollpane is your scrollpane:
jScrollpane.getVerticalScrollBar().setValue(jtextbox.getLocation().x);

Categories

Resources