I am making a map editor for a game I am working on. There is a JPanel in the JScrollPane that displays the map to be edited. What I would like to do is make it that when the user is holding down the Spacebar and dragging their mouse in the JPanel, the JScrollPanel will scroll along with the dragging. Here is what I have so far:
panelMapPanel.addMouseMotionListener(new MouseMotionListener(){
#Override
public void mouseDragged(MouseEvent e) {
//Gets difference in distance x and y from last time this listener was called
int deltaX = mouseX - e.getX();
int deltaY = mouseY - e.getY();
mouseX = e.getX();
mouseY = e.getY();
if(spacePressed){
//Scroll the scrollpane according to the distance travelled
scrollPane.getVerticalScrollBar().setValue(scrollPane.getVerticalScrollBar().getValue() + deltaY);
scrollPane.getHorizontalScrollBar().setValue(scrollPane.getHorizontalScrollBar().getValue() + deltaX);
}
}
});
Currently it works but the scrolling is not smooth at all. Moving the mouse a lot at a time is fine but doing small drags makes the scrollpane go berserk.
Any ideas how to improve this?
For those who enjoy a visual to help, here is the editor:
Addition Notes (Edit):
I have tried scrollPane.getViewport().setViewPosition(new Point(scrollPane.getViewport().getViewPosition().x + deltaX, scrollPane.getViewport().getViewPosition().y + deltaY));
The dragging is more fidgety when moving the mouse slowly, while big movements are more smooth
I tried using scrollRectToVisible without luck
Okay, that ended up been much simpler then I though it would be...
First, don't mess with the JViewport, instead, use JComponent#scrollRectToVisible directly on the component which is acting as the contents of the JScrollPane, onto which the MouseListener should be attached.
The following example simply calculates the difference between the point at which the user clicked and the amount they have dragged. It then applies this delta to the JViewport's viewRect and uses JComponent#scrollRectToVisible to update the viewable area, simple :)
public class Test {
public static void main(String[] args) {
new Test();
}
public Test() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
private JLabel map;
public TestPane() {
setLayout(new BorderLayout());
try {
map = new JLabel(new ImageIcon(ImageIO.read(new File("c:/treasuremap.jpg"))));
map.setAutoscrolls(true);
add(new JScrollPane(map));
MouseAdapter ma = new MouseAdapter() {
private Point origin;
#Override
public void mousePressed(MouseEvent e) {
origin = new Point(e.getPoint());
}
#Override
public void mouseReleased(MouseEvent e) {
}
#Override
public void mouseDragged(MouseEvent e) {
if (origin != null) {
JViewport viewPort = (JViewport) SwingUtilities.getAncestorOfClass(JViewport.class, map);
if (viewPort != null) {
int deltaX = origin.x - e.getX();
int deltaY = origin.y - e.getY();
Rectangle view = viewPort.getViewRect();
view.x += deltaX;
view.y += deltaY;
map.scrollRectToVisible(view);
}
}
}
};
map.addMouseListener(ma);
map.addMouseMotionListener(ma);
} catch (IOException ex) {
ex.printStackTrace();
}
}
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
}
}
I found this (very common) requirement surprisingly hard to solve. This is the stable solution we have had in production for probably over 10 years.
The accepted answer seems very tempting, but has usability glitches once you start to play with it (e.g. try to immediately drag to the lower right and then back, and you should notice that during the backward movement, no moving takes places for a long time).
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.Point;
import java.awt.event.MouseEvent;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JViewport;
import javax.swing.border.MatteBorder;
import javax.swing.event.MouseInputAdapter;
public class Mover extends MouseInputAdapter {
public static void main(String[] args) {
JFrame f = new JFrame();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setSize(200, 160);
f.setLocationRelativeTo(null);
f.setLayout(new BorderLayout());
JScrollPane scrollPane = new JScrollPane();
f.add(scrollPane, BorderLayout.CENTER);
JPanel view = new JPanel();
view.add(new JLabel("Some text"));
view.setBorder(new MatteBorder(5, 5, 5, 5, Color.BLUE));
view.setBackground(Color.WHITE);
view.setPreferredSize(new Dimension(230, 200));
new Mover(view);
scrollPane.setViewportView(view);
f.setVisible(true);
}
private JComponent m_view = null;
private Point m_holdPointOnView = null;
public Mover(JComponent view) {
m_view = view;
m_view.addMouseListener(this);
m_view.addMouseMotionListener(this);
}
#Override
public void mousePressed(MouseEvent e) {
m_view.setCursor(Cursor.getPredefinedCursor(Cursor.MOVE_CURSOR));
m_holdPointOnView = e.getPoint();
}
#Override
public void mouseReleased(MouseEvent e) {
m_view.setCursor(null);
}
#Override
public void mouseDragged(MouseEvent e) {
Point dragEventPoint = e.getPoint();
JViewport viewport = (JViewport) m_view.getParent();
Point viewPos = viewport.getViewPosition();
int maxViewPosX = m_view.getWidth() - viewport.getWidth();
int maxViewPosY = m_view.getHeight() - viewport.getHeight();
if(m_view.getWidth() > viewport.getWidth()) {
viewPos.x -= dragEventPoint.x - m_holdPointOnView.x;
if(viewPos.x < 0) {
viewPos.x = 0;
m_holdPointOnView.x = dragEventPoint.x;
}
if(viewPos.x > maxViewPosX) {
viewPos.x = maxViewPosX;
m_holdPointOnView.x = dragEventPoint.x;
}
}
if(m_view.getHeight() > viewport.getHeight()) {
viewPos.y -= dragEventPoint.y - m_holdPointOnView.y;
if(viewPos.y < 0) {
viewPos.y = 0;
m_holdPointOnView.y = dragEventPoint.y;
}
if(viewPos.y > maxViewPosY) {
viewPos.y = maxViewPosY;
m_holdPointOnView.y = dragEventPoint.y;
}
}
viewport.setViewPosition(viewPos);
}
}
I'm currently working on a map editor myself. I have gotten mouse scrolling to work smoothly on mine although it is a pretty verbose solution.
I wrote two custom AWTEventListeners one for mouse events the other for mouse move events. I did this because my map is a custom JComponent and as such does not fill the entire view-port. This means that scroll pane mouse events wont be detected if the cursor is over the component.
For me this works very smoothly, the content scrolls in perfect lock-step with the mouse cursor.
(I should mention I use the mouse wheel click and not the space bar but it's easy to change).
Toolkit.getDefaultToolkit().addAWTEventListener(new AWTEventListener() {
public void eventDispatched(AWTEvent event) {
if(event instanceof MouseEvent){
MouseEvent e = (MouseEvent)event;
//Begin a scroll if mouse is clicked on our pane
if(isMouseInMapPane()){
if(e.getID() == MouseEvent.MOUSE_PRESSED){
if(e.getButton() == MouseEvent.BUTTON2){
mouseWheelDown = true;
currentX = MouseInfo.getPointerInfo().getLocation().x;
currentY = MouseInfo.getPointerInfo().getLocation().y;
}
}
}
//Stop the scroll if mouse is released ANYWHERE
if(e.getID() == MouseEvent.MOUSE_RELEASED){
if(e.getButton() == MouseEvent.BUTTON2){
mouseWheelDown = false;
}
}
}
}
}, AWTEvent.MOUSE_EVENT_MASK);
Toolkit.getDefaultToolkit().addAWTEventListener(new AWTEventListener() {
public void eventDispatched(AWTEvent event) {
if(event instanceof MouseEvent){
MouseEvent e = (MouseEvent)event;
//Update the scroll based on delta drag value
if(e.getID() == MouseEvent.MOUSE_DRAGGED){
if(mouseWheelDown){
int newX = MouseInfo.getPointerInfo().getLocation().x;
int newY = MouseInfo.getPointerInfo().getLocation().y;
int scrollStepX = (currentX - newX);
int scrollStepY = (currentY - newY);
currentX = newX;
currentY = newY;
//mapScroll is the reference to JScrollPane
int originalValX = mapScroll.getHorizontalScrollBar().getValue();
mapScroll.getHorizontalScrollBar().setValue(originalValX + scrollStepX);
int originalValY = mapScroll.getVerticalScrollBar().getValue();
mapScroll.getVerticalScrollBar().setValue(originalValY + scrollStepY);
}
}
}
}
}, AWTEvent.MOUSE_MOTION_EVENT_MASK);
This is the isMouseInPane method:
private boolean isMouseInMapPane(){
//Note: mapPane does not need to be your scroll pane.
//it can be an encapsulating container as long as it is in
//the same position and the same width/height as your scrollPane.
//For me I used the JPanel containing my scroll pane.
Rectangle paneBounds = mapPane.getBounds();
paneBounds.setLocation(mapPane.getLocationOnScreen());
boolean inside = paneBounds.contains(MouseInfo.getPointerInfo().getLocation());
return inside;
}
This code can be placed anywhere that you have access to your scroll pane reference or you could create a custom scroll pane class and add it there.
I hope it helps!
I've come up to solution as below (the method above didn't work for me, JDK 1.8):
Attach the MouseDragged event to your JScrollPane;
The event function is fired twice, on the start and at the end of the drag;
You'll need a global variables to store initial mouse pointer position (xS and yS);
Here's the code for your MouseDragged:
yourJScrollPaneMouseDragged(java.awt.event.MouseEvent evt) {
Rectangle view = yourJScrollPane.getVisibleRect();
if (xS == 0 && yS == 0) { // first time event fired, store the initial mouse position
xS = evt.getX();
yS = evt.getY();
} else { // second time event fired - actual scrolling
int speed = 20;
view.x += Integer.signum(xS - evt.getX()) * speed;
view.y += Integer.signum(yS - evt.getY()) * speed;
// The view is scrolled by constant value of 20.
// For some reason, periodically, second position values were off for me by alot,
// which caused unwanted jumps.
// Integer.signum gets the direction the movement was performed.
// You can ommit the signum and constant and
// check if it works for you without jagging.
yourJScrollPane.getViewport().scrollRectToVisible(view);
// you actually have to fire scrollRectToVisible with the child
// component within JScrollPane, Viewport is the top child
// reset globals:
xS = 0;
yS = 0;
}
}
Related
I'm making a desktop app (a Markdown editor) in Java, and I'm having problems on frame resizing. The frame I'm working on is undecorated. On the left side of the frame is a title bar. The content pane uses a FlowLayout(FlowLayout.LEFT, 0, 0) as the
default layout. My problem is when I use the following code to achive resizing function, I can only resize my frame on X and Y axis but not both sides at the same time (when you put your cursor on a vertex of the frame), and the layout isn't working. My goal is to complete a full-functioned resizing system which you can drag and drop the edges of the frame to adjust the size of it.
My Code:
package xmark.ui;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.WindowConstants;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Point;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
public class MyFrame extends JFrame {
private final TitleBar titleBar = new TitleBar(); // public class TitleBar extends JPanel
/**
* The original content pane on the frame. Is a holder of the title bar.
*/
public JPanel contentPane = (JPanel) getContentPane();
/**
* The default constructor. No needs to manually pass in any arguments
* because it automatically decorates the frame and sets up the title
* bar. There are two private methods used in the constructor: {#code setUI()}
* and {#code titleBar()}.
*/
public MyFrame() {
setUI();
titleBar();
}
private void setUI() {
// Basic method settings
setSize(new Dimension(1200, 750));
setMinimumSize(new Dimension(120, 75));
setLocationRelativeTo(null);
setUndecorated(true);
setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
contentPane.setBackground(UIUtilities.BASE_COLOR); // UIUtilities is a ui utility class for this app
contentPane.setLayout(new FlowLayout(FlowLayout.LEFT, 0, 0));
// Sizing function
contentPane.addMouseMotionListener(new MouseAdapter() {
boolean top = false;
boolean down = false;
boolean left = false;
boolean right = false;
Point draggingAnchor = null;
#Override
public void mouseMoved(MouseEvent e) {
double mouseY = e.getPoint().getY();
double mouseX = e.getPoint().getX();
if (mouseY > 1 && mouseY < 6) {
setCursor(Cursor.getPredefinedCursor(Cursor.N_RESIZE_CURSOR));
top = true;
} else if (Math.abs(mouseY - getSize().getHeight()) <= 5) {
setCursor(Cursor.getPredefinedCursor(Cursor.N_RESIZE_CURSOR));
down = true;
} else if (mouseX > 1 && mouseX < 6) {
setCursor(Cursor.getPredefinedCursor(Cursor.E_RESIZE_CURSOR));
left = true;
} else if (Math.abs(mouseX - getSize().getWidth()) <= 5) {
setCursor(Cursor.getPredefinedCursor(Cursor.E_RESIZE_CURSOR));
right = true;
} else {
setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
draggingAnchor = new Point(e.getX() + contentPane.getX(), e.getY() + contentPane.getY());
top = false;
down = false;
left = false;
right = false;
}
}
#Override
public void mouseDragged(MouseEvent e) {
Dimension dimension = getSize();
if (top) {
dimension.setSize(dimension.getWidth(), dimension.getHeight() - e.getY());
setSize(dimension);
setLocation(getLocationOnScreen().x, getLocationOnScreen().y + e.getY());
} else if (down) {
dimension.setSize(dimension.getWidth(), e.getY());
setSize(dimension);
} else if (left) {
dimension.setSize(dimension.getWidth() - e.getX(), dimension.getHeight());
setSize(dimension);
setLocation(getLocationOnScreen().x + e.getX(), getLocationOnScreen().y);
} else if (right) {
dimension.setSize(e.getX(), dimension.getHeight());
setSize(dimension);
} else {
setLocation(e.getLocationOnScreen().x - draggingAnchor.x, e.getLocationOnScreen().y - draggingAnchor.y);
}
}
});
}
private void titleBar() {
contentPane.add(titleBar);
}
public static void main(String[] args) {
new MyFrame().setVisible(true);
}
}
I have been trying to tame JDesktopPane to work nicely with a resizable GUI & a scroll pane, but am having some troubles doing so. It seems that unless the drag mode is outline, the desktop pane will not resize as expected (when an internal frame is dragged beyond the edge of the desktop pane) & therefore not produce scroll-bars.
Am I doing something very silly in this source? Have I missed a far better approach?
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.border.EmptyBorder;
public class MDIPreferredSize {
public static void main(String[] args) {
Runnable r = new Runnable() {
#Override
public void run() {
final JDesktopPane dt = new JDesktopPane() {
#Override
public Dimension getPreferredSize() {
Dimension prefSize = super.getPreferredSize();
System.out.println("prefSize: " + prefSize);
// inititialize the max to the first normalized bounds
Rectangle max = getAllFrames()[0].getNormalBounds();
for (JInternalFrame jif : this.getAllFrames()) {
max.add(jif.getNormalBounds());
}
System.out.println("maxBounds(): "
+ max);
int x1 = max.width + (max.x * 2) < prefSize.width
? prefSize.width
: max.width + (max.x * 2);
int y1 = max.height + (max.y * 2) < prefSize.height
? prefSize.height
: max.height + (max.y * 2);
System.out.println("x,y: "
+ x1
+ ","
+ y1);
return new Dimension(x1, y1);
}
};
dt.setAutoscrolls(true);
int xx = 5;
int yy = 5;
int vStep = 10;
int yStep = 22;
for (int ii = 0; ii < 3; ii++) {
JInternalFrame jif = new JInternalFrame(
"Internal Frame " + (ii + 1),
true,
true,
true);
dt.add(jif);
jif.setLocation(xx, yy);
xx += vStep;
yy += yStep;
jif.setSize(200, 75);
jif.setVisible(true);
}
ComponentListener componentListener = new ComponentListener() {
#Override
public void componentResized(ComponentEvent e) {
e.getComponent().validate();
}
#Override
public void componentMoved(ComponentEvent e) {
e.getComponent().validate();
}
#Override
public void componentShown(ComponentEvent e) {
e.getComponent().validate();
}
#Override
public void componentHidden(ComponentEvent e) {
// do nothing
}
};
// causes maximized internal frames to be resized..
dt.addComponentListener(componentListener);
final JCheckBox outLineDragMode = new JCheckBox("Outline Drag Mode");
ActionListener dragModeListener = new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
if (outLineDragMode.isSelected()) {
dt.setDragMode(JDesktopPane.OUTLINE_DRAG_MODE);
} else {
dt.setDragMode(JDesktopPane.LIVE_DRAG_MODE);
}
}
};
outLineDragMode.addActionListener(dragModeListener);
JPanel gui = new JPanel(new BorderLayout());
gui.add(outLineDragMode, BorderLayout.PAGE_START);
gui.setBorder(new EmptyBorder(2, 3, 2, 3));
gui.add(new JScrollPane(dt), BorderLayout.CENTER);
JFrame f = new JFrame("DTP Preferred");
f.add(gui);
// Ensures JVM closes after frame(s) closed and
// all non-daemon threads are finished
f.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
// See http://stackoverflow.com/a/7143398/418556 for demo.
f.setLocationByPlatform(true);
// ensures the frame is the minimum size it needs to be
// in order display the components within it
f.pack();
f.setMinimumSize(f.getSize());
// should be done last, to avoid flickering, moving,
// resizing artifacts.
f.setVisible(true);
printProperty("os.name");
printProperty("java.version");
printProperty("java.vendor");
}
};
// Swing GUIs should be created and updated on the EDT
// http://docs.oracle.com/javase/tutorial/uiswing/concurrency/initial.html
SwingUtilities.invokeLater(r);
}
public static void printProperty(String name) {
System.out.println(name + ": \t" + System.getProperty(name));
}
}
Edit
Amongst the information printed, see also the 3 system properties:
os.name: Windows 7
java.version: 1.7.0_21
java.vendor: Oracle Corporation
Those are the values here.
MouseMotionListener fixed code
Thanks to Jonathan Drapeau's suggestion of a MouseListener, this fixed example actually uses a MouseMotionListener to allow the desktop pane to be resized actively while dragging. It might suffer some quirks beyond use of a MouseListener that cause problems (none yet known), if so, go back to the simpler technique of 'resize desktop pane on internal frame drop' (MouseListener only).
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.border.EmptyBorder;
import javax.swing.plaf.basic.BasicInternalFrameTitlePane;
public class MDIPreferredSize {
public static void main(String[] args) {
Runnable r = new Runnable() {
#Override
public void run() {
final JDesktopPane dt = new JDesktopPane() {
#Override
public Dimension getPreferredSize() {
Dimension prefSize = super.getPreferredSize();
System.out.println("prefSize: " + prefSize);
// inititialize the max to the first normalized bounds
Rectangle max = getAllFrames()[0].getNormalBounds();
for (JInternalFrame jif : this.getAllFrames()) {
max.add(jif.getNormalBounds());
}
System.out.println("maxBounds(): "
+ max);
int x1 = max.width + (max.x * 2) < prefSize.width
? prefSize.width
: max.width + (max.x * 2);
int y1 = max.height + (max.y * 2) < prefSize.height
? prefSize.height
: max.height + (max.y * 2);
System.out.println("x,y: "
+ x1
+ ","
+ y1);
return new Dimension(x1, y1);
}
};
int xx = 5;
int yy = 5;
int vStep = 10;
int yStep = 22;
for (int ii = 0; ii < 3; ii++) {
JInternalFrame jif = new JInternalFrame(
"Internal Frame " + (ii + 1),
true,
true,
true);
dt.add(jif);
jif.setLocation(xx, yy);
xx += vStep;
yy += yStep;
jif.setSize(200, 75);
jif.setVisible(true);
}
/*final MouseListener mouseListener = new MouseAdapter() {
#Override
public void mouseReleased(MouseEvent e) {
dt.revalidate();
}
};
*/
final MouseMotionListener mouseMotionListener = new MouseMotionAdapter() {
#Override
public void mouseDragged(MouseEvent e) {
dt.revalidate();
}
};
for (JInternalFrame jif : dt.getAllFrames()) {
for (Component comp : jif.getComponents()) {
if (comp instanceof BasicInternalFrameTitlePane) {
//comp.addMouseListener(mouseListener);
comp.addMouseMotionListener(mouseMotionListener);
}
}
}
dt.setAutoscrolls(true);
final JCheckBox outLineDragMode =
new JCheckBox("Outline Drag Mode");
ActionListener dragModeListener = new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
if (outLineDragMode.isSelected()) {
dt.setDragMode(JDesktopPane.OUTLINE_DRAG_MODE);
} else {
dt.setDragMode(JDesktopPane.LIVE_DRAG_MODE);
}
}
};
outLineDragMode.addActionListener(dragModeListener);
JPanel gui = new JPanel(new BorderLayout());
gui.add(outLineDragMode, BorderLayout.PAGE_START);
gui.setBorder(new EmptyBorder(2, 3, 2, 3));
gui.add(new JScrollPane(dt), BorderLayout.CENTER);
JFrame f = new JFrame("DTP Preferred");
f.add(gui);
// Ensures JVM closes after frame(s) closed and
// all non-daemon threads are finished
f.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
// See http://stackoverflow.com/a/7143398/418556 for demo.
f.setLocationByPlatform(true);
// ensures the frame is the minimum size it needs to be
// in order display the components within it
f.pack();
f.setMinimumSize(f.getSize());
// should be done last, to avoid flickering, moving,
// resizing artifacts.
f.setVisible(true);
printProperty("os.name");
printProperty("java.version");
printProperty("java.vendor");
}
};
// Swing GUIs should be created and updated on the EDT
// http://docs.oracle.com/javase/tutorial/uiswing/concurrency/initial.html
SwingUtilities.invokeLater(r);
}
public static void printProperty(String name) {
System.out.println(name + ": \t" + System.getProperty(name));
}
}
Quirks
It might suffer some quirks beyond use of a MouseListener that cause problems (none yet known).
That was then..
In full render mode, the desktop pane will grow dynamically as far as the user drags the internal frame (even off the GUI). (Good.) In outline mode, the container will only resize on drop, not drag. (Less good, but at least the scroll-bars appear/disappear reliably.)
Adding a MouseListener to the JInternalFrame title pane while in JDesktopPane.LIVE_DRAG_MODE to revalidate the JDesktopPane after release is a way to get the exact same behavior in each mode.
final MouseListener testList = new MouseListener() {
#Override
public void mouseReleased(MouseEvent e) {
dt.revalidate();
}
#Override
public void mousePressed(MouseEvent e) {
}
#Override
public void mouseExited(MouseEvent e) {
}
#Override
public void mouseEntered(MouseEvent e) {
}
#Override
public void mouseClicked(MouseEvent e) {
}
};
// causes maximized internal frames to be resized..
dt.addComponentListener(componentListener);
for (JInternalFrame jif : dt.getAllFrames()) {
for (Component comp : jif.getComponents()) {
if (comp instanceof BasicInternalFrameTitlePane) {
comp.addMouseListener(testList);
}
}
}
final JCheckBox outLineDragMode = new JCheckBox("Outline Drag Mode");
ActionListener dragModeListener = new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
if (outLineDragMode.isSelected()) {
dt.setDragMode(JDesktopPane.OUTLINE_DRAG_MODE);
for (JInternalFrame jif : dt.getAllFrames()) {
for (Component comp : jif.getComponents()) {
if (comp instanceof BasicInternalFrameTitlePane) {
comp.removeMouseListener(testList);
}
}
}
} else {
dt.setDragMode(JDesktopPane.LIVE_DRAG_MODE);
for (JInternalFrame jif : dt.getAllFrames()) {
for (Component comp : jif.getComponents()) {
if (comp instanceof BasicInternalFrameTitlePane) {
comp.addMouseListener(testList);
}
}
}
}
}
};
I remove them in the JDesktopPane.OUTLINE_DRAG_MODE since it already reacts properly.
You should be able to use the Drag Layout to handle the resizing of the desktop pane as components are dragged.
Interesting problem for a Saturday morning :-)
No complete solution, just a couple of comments and an outline of an alternative approach:
relying on the mouse/Motion/listener is incomplete in that it doesn't handle keyboard controlled moves
per-internalframe componentListener to the rescue: works fine if not in outline mode
in outline mode the revalidation can't work anyway, because it relies on the actual frame location which is unchanged during the drag
So the real problem is the outline mode, needs to
trace the intermediate bounds of the dragged frame
let the desktop's prefSize calculation take those intermediate bounds into account
the drawing of the outline (unexpected, for me, see below [*])
The collaborator that is responsible for moving the frame is the DesktopManager.dragFrame: it's default implementation resets the frame bounds if not in outline mode or keeps track of the intermediate location and drawing the outline rectangle if in outline mode.
The obvious idea is a custom DesktopManager which overrides dragFrame:
let super do its stuff
on outline mode, get the frame's intermediate location and store it somewhere on the frame itself, f.i. as a clientProperty
Now a someone, f.i. a PropertyChangeListener can listen for changes of the intermediate location and trigger a revalidate. And the prefSize calculation of the desktopPane can account for the intermediate bounds in addition to the real bounds, something like
public static class MyDesktopManager extends DefaultDesktopManager {
private Point currentLoc;
#Override
public void dragFrame(JComponent f, int newX, int newY) {
// let super handle outline drawing
super.dragFrame(f, newX, newY);
if (isOutline(f)) {
// take over the drawing
currentLoc = new Point(newX, newY);
Rectangle bounds = new Rectangle(currentLoc, f.getSize());
f.putClientProperty("outlineBounds", bounds);
} else {
// call super only if not outline
// handle outline drawing ourselves
// super.dragFrame(f, newX, newY);
}
}
#Override
public void beginDraggingFrame(JComponent f) {
super.beginDraggingFrame(f);
if (isOutline(f)) {
currentLoc = f.getLocation();
RootPaneContainer r = (RootPaneContainer) SwingUtilities.getWindowAncestor(f);
// do the painting in the glassPane
// r.getGlassPane().setVisible(true);
}
}
#Override
public void endDraggingFrame(JComponent f) {
super.endDraggingFrame(f);
f.putClientProperty("outlineBounds", null);
if (isOutline(f)) {
RootPaneContainer r = (RootPaneContainer) SwingUtilities.getWindowAncestor(f);
r.getGlassPane().setVisible(false);
}
}
protected boolean isOutline(JComponent f) {
return ((JInternalFrame) f).getDesktopPane().getDragMode() ==
JDesktopPane.OUTLINE_DRAG_MODE;
}
}
Usage:
final JDesktopPane dt = new JDesktopPane() {
#Override
public Dimension getPreferredSize() {
Dimension prefSize = super.getPreferredSize();
System.out.println("prefSize: " + prefSize);
// inititialize the max to the first normalized bounds
Rectangle max = getAllFrames()[0].getNormalBounds();
for (JInternalFrame jif : this.getAllFrames()) {
max.add(jif.getNormalBounds());
Rectangle outline = (Rectangle) jif.getClientProperty("outlineBounds");
if (outline != null) {
max.add(outline);
}
}
int x1 = max.width + (max.x * 2) < prefSize.width ? prefSize.width
: max.width + (max.x * 2);
int y1 = max.height + (max.y * 2) < prefSize.height ? prefSize.height
: max.height + (max.y * 2);
return new Dimension(x1, y1);
}
};
dt.setDesktopManager(new MyDesktopManager());
dt.setAutoscrolls(true);
int xx = 5;
int yy = 5;
int vStep = 10;
int yStep = 22;
// oer-internalframe componentListener
ComponentListener il = new ComponentAdapter() {
#Override
public void componentMoved(ComponentEvent e) {
dt.revalidate();
}
};
// per-internalframe outlineListener
PropertyChangeListener propertyL = new PropertyChangeListener() {
#Override
public void propertyChange(PropertyChangeEvent evt) {
dt.revalidate();
}
};
for (int ii = 0; ii < 3; ii++) {
JInternalFrame jif = new JInternalFrame(
"Internal Frame " + (ii + 1),
true,
true,
true);
dt.add(jif);
jif.addComponentListener(il);
jif.addPropertyChangeListener("outlineBounds", propertyL);
jif.setLocation(xx, yy);
xx += vStep;
yy += yStep;
jif.setSize(200, 75);
jif.setVisible(true);
}
[*] The default outline painting flickers (to the extend that it is invisible) - reason being that the default implementation uses ... getGraphics() ... So we need to take over the outline painting, f.i. in a dedicated glassPane (that's done by the commented code) or probably better by a LayerUI on the desktop.
A crude glassPane, just as a poc which doesn't clip correctly and has some issues when the frame is moved back into the visible rect:
public static class OutlinePanel extends JPanel {
private JDesktopPane desktop;
public OutlinePanel(JDesktopPane desktop) {
this.desktop = desktop;
}
#Override
public boolean isOpaque() {
return false;
}
#Override
protected void paintComponent(Graphics g) {
JInternalFrame selected = desktop.getSelectedFrame();
Rectangle outline = (Rectangle) selected.getClientProperty("outlineBounds");
if (outline == null) return;
Rectangle bounds = SwingUtilities.convertRectangle(desktop, outline, this);
g.drawRect(bounds.x, bounds.y, bounds.width, bounds.height);
}
}
Update
Version with LayerUI - now we are leaving the complete new behaviour (listener registration, painting outline if needed, installing the manager) to the decoration. Advantages:
simplified usage
one location for all dirty details
The LayerUI:
public class DesktopLayerUI extends LayerUI<JDesktopPane> {
#Override
public void installUI(JComponent c) {
super.installUI(c);
final JDesktopPane dt = getDesktopPane(c);
//dt.setBorder(BorderFactory.createLineBorder(Color.RED));
dt.setDesktopManager(new MyDesktopManager());
// per-internalframe componentListener
ComponentListener il = new ComponentAdapter() {
#Override
public void componentMoved(ComponentEvent e) {
dt.revalidate();
}
};
// per-internalframe outlineListener
PropertyChangeListener propertyL = new PropertyChangeListener() {
#Override
public void propertyChange(PropertyChangeEvent evt) {
dt.revalidate();
}
};
for (JInternalFrame jif : dt.getAllFrames()) {
jif.addComponentListener(il);
jif.addPropertyChangeListener("outlineBounds", propertyL);
}
// TBD: register container listener to update frame listeners on adding/removing
// TBD: componentListener on desktop that handles maximizing frame
// (JW: didn't really understand what that one is doing in the original)
}
#Override
public Dimension getPreferredSize(JComponent c) {
JDesktopPane dt = getDesktopPane(c);
Dimension prefSize = super.getPreferredSize(c);
//System.out.println("prefSize: " + prefSize);
// inititialize the max to the first normalized bounds
Rectangle max = dt.getAllFrames()[0].getNormalBounds();
for (JInternalFrame jif : dt.getAllFrames()) {
max.add(jif.getNormalBounds());
Rectangle outline = (Rectangle) jif
.getClientProperty("outlineBounds");
if (outline != null) {
max.add(outline);
}
}
// TBD: cope with frames at negative locations
//System.out.println("maxBounds(): " + max);
int x1 = max.width + (max.x * 2) < prefSize.width ? prefSize.width
: max.width + (max.x * 2);
int y1 = max.height + (max.y * 2) < prefSize.height ? prefSize.height
: max.height + (max.y * 2);
//System.out.println("x,y: " + x1 + "," + y1);
return new Dimension(x1, y1);
}
#Override
public void paint(Graphics g, JComponent c) {
super.paint(g, c);
JDesktopPane desktop = getDesktopPane(c);
JInternalFrame selected = desktop.getSelectedFrame();
if (selected == null) return;
Rectangle outline = (Rectangle) selected.getClientProperty("outlineBounds");
if (outline == null) return;
Rectangle bounds = outline; //SwingUtilities.convertRectangle(, outline, this);
g.drawRect(bounds.x, bounds.y, bounds.width, bounds.height);
}
protected JDesktopPane getDesktopPane(JComponent c) {
JDesktopPane desktop = ((JLayer<JDesktopPane>) c).getView();
return desktop;
}
public static class MyDesktopManager extends DefaultDesktopManager {
private Point currentLoc;
#Override
public void dragFrame(JComponent f, int newX, int newY) {
if (isOutline(f)) {
// take over the outline drawing
currentLoc = new Point(newX, newY);
Rectangle bounds = new Rectangle(currentLoc, f.getSize());
f.putClientProperty("outlineBounds", bounds);
} else {
// call super only if not outline
// handle outline drawing ourselves
super.dragFrame(f, newX, newY);
}
}
#Override
public void beginDraggingFrame(JComponent f) {
super.beginDraggingFrame(f);
if (isOutline(f)) {
currentLoc = f.getLocation();
f.putClientProperty("outlineBounds", f.getBounds());
}
}
#Override
public void endDraggingFrame(JComponent f) {
if (isOutline(f) && currentLoc != null) {
setBoundsForFrame(f, currentLoc.x, currentLoc.y, f.getWidth(), f.getHeight() );
f.putClientProperty("outlineBounds", null);
} else {
super.endDraggingFrame(f);
}
}
protected boolean isOutline(JComponent f) {
return ((JInternalFrame) f).getDesktopPane().getDragMode() ==
JDesktopPane.OUTLINE_DRAG_MODE;
}
}
}
usage:
JDesktopPane dt = new JDesktopPane();
// add internalframes
...
// decorate the pane with the layer
JLayer<JDesktopPane> layer = new JLayer<>(dt, new DesktopLayerUI());
gui.add(new JScrollPane(layer), BorderLayout.CENTER);
There's a slight catch (read: didn't yet figure out how to fix it): JLayer implements Scrollable - its implementation returns false for tracksXX (if the decorated component isn't a Scrollable itself - JDesktopPane isn't), meaning that the desktop inside a scrollPane is always sized to its prefSize which shows the grayish viewport at the trailing/bottom area if the scrollPane is larger.
I have two classes. The first, JPanelImage, adds an Image to my JPanel. The second, myObjet, represents the object I want to add on my Image. The Image can move and can zoom.
The problem is that when I move the image, the object remains fixed.
Class JImagePanel:
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.MediaTracker;
import java.awt.Panel;
import java.awt.Toolkit;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionAdapter;
class JImagePanel extends Panel{
private static final long serialVersionUID = 5375994938523354306L;
private MediaTracker tracker;
private Image img;
private Dimension imgSize,iniSize;
private int zoom = 0 ;
private int MouseX;
private int MouseY;
int transX=0;
int transY=0;
public JImagePanel(String file){
//setSize(100,200);
img=Toolkit.getDefaultToolkit().getImage(file);
setLayout(null);
tracker=new MediaTracker(this);
tracker.addImage(img,0);
addMouseListener(new MouseAdapter() {
#Override
public void mousePressed(MouseEvent e) {
MouseX = e.getX();
MouseY = e.getY();
}
});
addMouseMotionListener(new MouseMotionAdapter() {
#Override
public void mouseDragged(MouseEvent e) {
transX += e.getX()-MouseX;
transY += e.getY()-MouseY;
MouseX = e.getX();
MouseY = e.getY();
repaint();
}
});
try{
tracker.waitForAll();
}
catch(Exception ie){}
imgSize=iniSize=new Dimension(img.getWidth(this),img.getHeight(this));
}
public Dimension getPreferredSize(){
return new Dimension(imgSize);
}
public void paint(Graphics g){
super.paint(g);
if(imgSize.width<=iniSize.width) {
imgSize=iniSize;
}
g.drawImage(this.img, (getWidth()-imgSize.width)/2+transX, (getHeight()-imgSize.height)/2+transY, imgSize.width,imgSize.height,this);
}
public void zoomIn(){
int x=10*imgSize.width/100;
int y=10*imgSize.height/100;
imgSize=new Dimension(imgSize.width+x,imgSize.height+y);
if(imgSize.width>iniSize.width){
setSize(imgSize);
getParent().doLayout();
}
repaint();
}
public void zoomOut(){
int x=10*imgSize.width/100;
int y=10*imgSize.height/100;
imgSize=new Dimension(imgSize.width-x,imgSize.height-y);
if(getWidth()>iniSize.width)
{
setSize(imgSize);
getParent().doLayout();
}
repaint();
}
public int getZoom() {
return zoom;
}
Class myObjet:
import java.awt.BorderLayout;
import javax.swing.ImageIcon;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
public class myObjet extends JPanel {
JLabel lblName,lblAct,lblSeuil ;
JPanel panelObjet;
public myObjet(String NameObjet ,double activite )
{
ImageIcon img = createImageIcon("images/Source.png");
lblName = new JLabel(img);
lblAct = new JLabel(String.valueOf(activite));
panelObjet = new JPanel();
panelObjet.setToolTipText(NameObjet);
panelObjet.setLayout(new BorderLayout());
panelObjet.add("North",lblName);
panelObjet.add("South",lblAct);
add(panelObjet);
isOpaque();
}
public ImageIcon createImageIcon(String path) {
java.net.URL imgURL = getClass().getResource(path);
if (imgURL != null) {
return new ImageIcon(imgURL);
} else {
JOptionPane.showMessageDialog(null, "Cette image n'existe pas : " + path, "Erreur", JOptionPane.ERROR_MESSAGE);
// System.err.println("L'image n'est pas dans : " + path);
return null;
}
}
public boolean isOpaque()
{
return true ;
}
}
MYObject is alone , i adds this Object in Panel of this Image .
Here is a concrete example of how I use these classes
public static void (String [] args )
{
imagePanel = new JImagePanel("/home/Image.png");
p = new JPanel();
p.setLayout(new FlowLayout());
// p.setBounds(0,0,0,0);
p.add(getImagePanel());
ple2.add("Center",p);
}
2/ in actionPerformed :
public void actionPerformed(ActionEvent ev) {
Object sourceEv = ev.getSource() ;
if(sourceEv == action.jpfI.btnFrame[4])
{
df = new DecimalFormat("0.00");
int x = Integer.valueOf(action.jpfI.lblTxt[4].getText());
int y =Integer.valueOf(action.jpfI.lblTxt[5].getText()) ;
x =(int)(x/0.26) ;
y =(int)(y/0.26):
objet = new myObjet("islem","0.002");
objet.setBounds(x,y , 50,50);
action.getImagePanel().addImage(objet);
action.repaint();
}
Okay, I've come across three issues.
Firstly: There's no layout manager on the image pane. No big deal, but if you're not going to use a layout manager, you become responsible for laying out any child components. I fixed this (and it's in the wrong place) by adding the following to the "myObjet" class.
Dimension size = getPreferredSize();
setBounds(0, 0, size.width, size.height);
This really should be taken care of by the JImagePanel - either add a layout manager or check the doLayout method.
Secondly: The JImagePanel is a heavy weight component. You should avoid mixing heavy and light weight components if you can (there are Z order issues amongst other things). I updated the JImagePanel to extend from a JPanel.
Thridly: You should only very rarely have to override the paint method. In your case I can understand why you did, but what you ended up doing was painting on the top of everything else (and mixed with the fact you were using a heavy weight component compounded the issue).
I changed the "paint" for "paintComponent" which paints the background and was able to get it to work nicely. I was able to move the image around and have the "myObjet" visible and static in place.
UPDATE
Okay...
public void mouseDragged(MouseEvent e) {
transX += e.getX() - MouseX;
transY += e.getY() - MouseY;
MouseX = e.getX();
MouseY = e.getY();
// Add this to your code
for (Component comp : getComponents()) {
comp.setLocation(transX, transY);
}
repaint();
}
In fact, a better solution would be to allow the parent container to handle the movement and set the image and objects statically with in the image pane (my pane was set to a static size). The basic idea you have running here just needs to be moved to the container.
The only other thing you would need to deal with is the Z order of the panes.
The blue label is meant to move when you click and drag it. This works but the x / y position then jumps in a funny way.
Here's the code:
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
#SuppressWarnings("serial")
public class test extends JFrame implements MouseListener, MouseMotionListener {
private JPanel panel = new JPanel(null);
private JLabel label1 = new JLabel();
private JLabel label2 = new JLabel();
private int mouseX = 200;
private int mouseY = 100;
private boolean drag = false;
public test() {
this.add(panel);
panel.setBackground(Color.WHITE);
panel.add(label1);
label1.setOpaque(true);
label1.setBackground(Color.BLUE);
label1.setBounds(mouseX, mouseY, 100, 50);
label1.addMouseMotionListener(this);
label1.addMouseListener(this);
panel.add(label2);
label2.setOpaque(true);
label2.setBackground(Color.RED);
label2.setBounds(mouseX + 200, mouseY, 100, 50);
label2.addMouseMotionListener(this);
label2.addMouseListener(this);
}
#Override
public void mousePressed(MouseEvent e) {
if (e.getSource() == label1) {
drag = true;
}
}
#Override
public void mouseReleased(MouseEvent e) {
drag = false;
}
#Override
public void mouseDragged(MouseEvent e) {
if (drag == true) {
mouseX = e.getX();
mouseY = e.getY();
label1.setBounds(mouseX, mouseY, 100, 50);
}
}
public void mouseMoved(MouseEvent e) {}
public void mouseEntered(MouseEvent e) {}
public void mouseExited(MouseEvent e) {}
public void mouseClicked(MouseEvent e) {}
public static void main(String[] args) {
test frame = new test();
frame.setVisible(true);
frame.setSize(600, 400);
frame.setResizable(false);
frame.setLocationRelativeTo(null);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
}
Just put this on your MouseDragged method:
public void mouseDragged(MouseEvent e)
{
if (drag == true)
{
JComponent jc = (JComponent)e.getSource();
jc.setLocation(jc.getX()+e.getX(), jc.getY()+e.getY());
}
}
The coordinates returned by MouseEvent::getX() and MouseEvent::getY() represent the location of the event relative to the event's subject (i.e. relative to the label itself), which explains why your solution results in the label erratically jumping.
By using MouseEvent::getComponent() to grab the label and then querying its position (possibly relative to the position when dragging commenced), you can devise a working solution.
Your problem is your setting your bounds based on the mouse location in the MouseListener, but the MouseListener has its location relative to the JLabel itself, but the JLabel's location should be set relative to the panel. You'll need to do some simple vector addition to figure this out.
edit: oops, I didn't see that this was already answered, and they say the same thing... sorry.
Maybe try adding something like that
The red one will do it better
private int clicX = 0;
private int clicY = 0;
public void mousePressed(MouseEvent e) {
drag = true;
if (e.getSource() == label1) {
}
if (e.getSource() == label2) {
clicX = e.getX();
clicY = e.getY();
}
}
public void mouseDragged(MouseEvent e) {
if (e.getSource() == label2) {
JComponent jc = (JComponent)e.getSource();
jc.setLocation(jc.getX()+e.getX()-clicX, jc.getY()+e.getY()-clicY);
}
Create two global variables:
int x_pressed = 0;
int y_pressed = 0;
then create two events (mousePressed and mouseDragged over JLabel):
lbl_banner.addMouseListener(new MouseAdapter()
{
#Override
public void mousePressed(MouseEvent e) {
//catching the current values for x,y coordinates on screen
x_pressed = e.getX();
y_pressed = e.getY();
}
});
lbl_banner.addMouseMotionListener(new MouseMotionAdapter(){
#Override
public void mouseDragged(MouseEvent e){
//and when the Jlabel is dragged
setLocation(e.getXOnScreen() - x_pressed, e.getYOnScreen() - y_pressed);
}
});
I searched the web for examples of draggable Swing components,
but I found either incomplete or non-working examples.
What I need is a Swing component that can be dragged by the mouse
inside an other component. While being dragged, it should already
change its position, not just 'jump' to its destination.
I would appreciate examples which work without non-standard APIs.
Thank you.
I propose a simple, but well-working solution, found out by myself ;)
What do I do?
When mouse is pressed, I record the cursor's position on screen, and
the component's position.
When mouse is dragged, I calculate the difference between new and
old cursor's position on screen, and move the
component by this difference.
Tested with latest JDK 6 unter Linux (OpenSuse, KDE3),
but hey, it's Java Swing, should work equally everywhere.
Here goes the code:
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.border.*;
public class MyDraggableComponent
extends JComponent {
private volatile int screenX = 0;
private volatile int screenY = 0;
private volatile int myX = 0;
private volatile int myY = 0;
public MyDraggableComponent() {
setBorder(new LineBorder(Color.BLUE, 3));
setBackground(Color.WHITE);
setBounds(0, 0, 100, 100);
setOpaque(false);
addMouseListener(new MouseListener() {
#Override
public void mouseClicked(MouseEvent e) { }
#Override
public void mousePressed(MouseEvent e) {
screenX = e.getXOnScreen();
screenY = e.getYOnScreen();
myX = getX();
myY = getY();
}
#Override
public void mouseReleased(MouseEvent e) { }
#Override
public void mouseEntered(MouseEvent e) { }
#Override
public void mouseExited(MouseEvent e) { }
});
addMouseMotionListener(new MouseMotionListener() {
#Override
public void mouseDragged(MouseEvent e) {
int deltaX = e.getXOnScreen() - screenX;
int deltaY = e.getYOnScreen() - screenY;
setLocation(myX + deltaX, myY + deltaY);
}
#Override
public void mouseMoved(MouseEvent e) { }
});
}
}
public class Main {
public static void main(String[] args) {
JFrame f = new JFrame("Swing Hello World");
// by doing this, we prevent Swing from resizing
// our nice component
f.setLayout(null);
MyDraggableComponent mc = new MyDraggableComponent();
f.add(mc);
f.setSize(500, 500);
f.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
f.setVisible(true);
}
}
Also, I found out that one could create an JInternalFrame inside an JFrame,
but the problem is: you get always an annoying window title bar.
To disable the title bar, sadly, a dirty workaround is necessary:
public class MyDraggableComponent extends JInternalFrame {
public MyDraggableComponent() {
InternalFrameUI thisUI = getUI();
if (thisUI instanceof BasicInternalFrameUI) {
((BasicInternalFrameUI) thisUI).setNorthPane(null);
}
}
I really miss a method like "someInternalFrame.setWindowTitleBar(false)"...
:'(