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.
Related
How can I implement Marquee effect in Java Swing
Here's an example using javax.swing.Timer.
import java.awt.EventQueue;
import java.awt.Font;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.Timer;
/** #see http://stackoverflow.com/questions/3617326 */
public class MarqueeTest {
private void display() {
JFrame f = new JFrame("MarqueeTest");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
String s = "Tomorrow, and tomorrow, and tomorrow, "
+ "creeps in this petty pace from day to day, "
+ "to the last syllable of recorded time; ... "
+ "It is a tale told by an idiot, full of "
+ "sound and fury signifying nothing.";
MarqueePanel mp = new MarqueePanel(s, 32);
f.add(mp);
f.pack();
f.setLocationRelativeTo(null);
f.setVisible(true);
mp.start();
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
new MarqueeTest().display();
}
});
}
}
/** Side-scroll n characters of s. */
class MarqueePanel extends JPanel implements ActionListener {
private static final int RATE = 12;
private final Timer timer = new Timer(1000 / RATE, this);
private final JLabel label = new JLabel();
private final String s;
private final int n;
private int index;
public MarqueePanel(String s, int n) {
if (s == null || n < 1) {
throw new IllegalArgumentException("Null string or n < 1");
}
StringBuilder sb = new StringBuilder(n);
for (int i = 0; i < n; i++) {
sb.append(' ');
}
this.s = sb + s + sb;
this.n = n;
label.setFont(new Font("Serif", Font.ITALIC, 36));
label.setText(sb.toString());
this.add(label);
}
public void start() {
timer.start();
}
public void stop() {
timer.stop();
}
#Override
public void actionPerformed(ActionEvent e) {
index++;
if (index > s.length() - n) {
index = 0;
}
label.setText(s.substring(index, index + n));
}
}
I know this is a late answer, but I just saw another question about a marquee that was closed because it was considered a duplicate of this answer.
So I thought I'd add my suggestion which takes a approach different from the other answers suggested here.
The MarqueePanel scrolls components on a panel not just text. So this allows you to take full advantage of any Swing component. A simple marquee can be used by adding a JLabel with text. A fancier marquee might use a JLabel with HTML so you can use different fonts and color for the text. You can even add a second component with an image.
Basic answer is you draw your text / graphic into a bitmap and then implement a component that paints the bitmap offset by some amount. Usually marquees / tickers scroll left so the offset increases which means the bitmap is painted at -offset. Your component runs a timer that fires periodically, incrementing the offset and invalidating itself so it repaints.
Things like wrapping are a little more complex to deal with but fairly straightforward. If the offset exceeds the bitmap width you reset it back to 0. If the offset + component width > bitmap width you paint the remainder of the component starting from the beginning of the bitmap.
The key to a decent ticker is to make the scrolling as smooth and as flicker free as possible. Therefore it may be necessary to consider double buffering the result, first painting the scrolling bit into a bitmap and then rendering that in one go rather than painting straight into the screen.
Here is some code that I threw together to get you started. I normally would take the ActionListener code and put that in some sort of MarqueeController class to keep this logic separate from the panel, but that's a different question about organizing the MVC architecture, and in a simple enough class like this it may not be so important.
There are also various animation libraries that would help you do this, but I don't normally like to include libraries into projects only to solve one problem like this.
public class MarqueePanel extends JPanel {
private JLabel textLabel;
private int panelLocation;
private ActionListener taskPerformer;
private boolean isRunning = false;
public static final int FRAMES_PER_SECOND = 24;
public static final int MOVEMENT_PER_FRAME = 5;
/**
* Class constructor creates a marquee panel.
*/
public MarqueePanel() {
this.setLayout(null);
this.textLabel = new JLabel("Scrolling Text Here");
this.panelLocation = 0;
this.taskPerformer = new ActionListener() {
public void actionPerformed(ActionEvent evt) {
MarqueePanel.this.tickAnimation();
}
}
}
/**
* Starts the animation.
*/
public void start() {
this.isRunning = true;
this.tickAnimation();
}
/**
* Stops the animation.
*/
public void stop() {
this.isRunning = false;
}
/**
* Moves the label one frame to the left. If it's out of display range, move it back
* to the right, out of display range.
*/
private void tickAnimation() {
this.panelLocation -= MarqueePanel.MOVEMENT_PER_FRAME;
if (this.panelLocation < this.textLabel.getWidth())
this.panelLocaton = this.getWidth();
this.textLabel.setLocation(this.panelLocation, 0);
this.repaint();
if (this.isRunning) {
Timer t = new Timer(1000 / MarqueePanel.FRAMES_PER_SECOND, this.taskPerformer);
t.setRepeats(false);
t.start();
}
}
}
Add a JLabel to your frame or panel.
ScrollText s= new ScrollText("ello Everyone.");
jLabel3.add(s);
public class ScrollText extends JComponent {
private BufferedImage image;
private Dimension imageSize;
private volatile int currOffset;
private Thread internalThread;
private volatile boolean noStopRequested;
public ScrollText(String text) {
currOffset = 0;
buildImage(text);
setMinimumSize(imageSize);
setPreferredSize(imageSize);
setMaximumSize(imageSize);
setSize(imageSize);
noStopRequested = true;
Runnable r = new Runnable() {
public void run() {
try {
runWork();
} catch (Exception x) {
x.printStackTrace();
}
}
};
internalThread = new Thread(r, "ScrollText");
internalThread.start();
}
private void buildImage(String text) {
RenderingHints renderHints = new RenderingHints(
RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
renderHints.put(RenderingHints.KEY_RENDERING,
RenderingHints.VALUE_RENDER_QUALITY);
BufferedImage scratchImage = new BufferedImage(1, 1,
BufferedImage.TYPE_INT_RGB);
Graphics2D scratchG2 = scratchImage.createGraphics();
scratchG2.setRenderingHints(renderHints);
Font font = new Font("Serif", Font.BOLD | Font.ITALIC, 24);
FontRenderContext frc = scratchG2.getFontRenderContext();
TextLayout tl = new TextLayout(text, font, frc);
Rectangle2D textBounds = tl.getBounds();
int textWidth = (int) Math.ceil(textBounds.getWidth());
int textHeight = (int) Math.ceil(textBounds.getHeight());
int horizontalPad = 600;
int verticalPad = 10;
imageSize = new Dimension(textWidth + horizontalPad, textHeight
+ verticalPad);
image = new BufferedImage(imageSize.width, imageSize.height,
BufferedImage.TYPE_INT_RGB);
Graphics2D g2 = image.createGraphics();
g2.setRenderingHints(renderHints);
int baselineOffset = (verticalPad / 2) - ((int) textBounds.getY());
g2.setColor(Color.BLACK);
g2.fillRect(0, 0, imageSize.width, imageSize.height);
g2.setColor(Color.GREEN);
tl.draw(g2, 0, baselineOffset);
// Free-up resources right away, but keep "image" for
// animation.
scratchG2.dispose();
scratchImage.flush();
g2.dispose();
}
public void paint(Graphics g) {
// Make sure to clip the edges, regardless of curr size
g.setClip(0, 0, imageSize.width, imageSize.height);
int localOffset = currOffset; // in case it changes
g.drawImage(image, -localOffset, 0, this);
g.drawImage(image, imageSize.width - localOffset, 0, this);
// draw outline
g.setColor(Color.black);
g.drawRect(0, 0, imageSize.width - 1, imageSize.height - 1);
}
private void runWork() {
while (noStopRequested) {
try {
Thread.sleep(10); // 10 frames per second
// adjust the scroll position
currOffset = (currOffset + 1) % imageSize.width;
// signal the event thread to call paint()
repaint();
} catch (InterruptedException x) {
Thread.currentThread().interrupt();
}
}
}
public void stopRequest() {
noStopRequested = false;
internalThread.interrupt();
}
public boolean isAlive() {
return internalThread.isAlive();
}
}
This is supposed to be an improvement of #camickr MarqueePanel. Please see above.
To map mouse events to the specific components added to MarqueePanel
Override add(Component comp) of MarqueePanel in order to direct all mouse events of the components
An issue here is what do do with the MouseEvents fired from the individual components.
My approach is to remove the mouse listeners form the components added and let the MarqueePanel redirect the event to the correct component.
In my case these components are supposed to be links.
#Override
public Component add(Component comp) {
comp = super.add(comp);
if(comp instanceof MouseListener)
comp.removeMouseListener((MouseListener)comp);
comp.addMouseListener(this);
return comp;
}
Then map the component x to a MarqueePanel x and finally the correct component
#Override
public void mouseClicked(MouseEvent e)
{
Component source = (Component)e.getSource();
int x = source.getX() + e.getX();
int y = source.getY();
MarqueePanel2 marqueePanel = (MarqueePanel2) ((JComponent)e.getSource()).getParent();
double x2 = marqueePanel.getWidth();
double x1 = Math.abs(marqueePanel.scrollOffset);
if(x >= x1 && x <= x2)
{
System.out.println("Bang " + x1);
Component componentAt = getComponentAt(x+marqueePanel.scrollOffset, y);
if(comp instanceof MouseListener)
((MouseListener) componentAt).mouseClicked(e);
System.out.println(componentAt.getName());
}
else
{
return;
}
//System.out.println(x);
}
I have two things that I am trying to do: highlight a JPanel on mouse hover and move a blue square on mouse drag. The thing is, this requires me to add MouseListeners to different components. When I do this, I can only use one feature - the other is blocked. What can I do so that both features work?
NOTE: Sometimes the JFrame doesn't show anything - you just have to keep running it until it does (usually takes 2-3 tries). If it does any other weird stuff just keep running it until it works. Comment if it still isn't working right after >5 tries.
What it should look like:
Main(creates the JFrame, the container, and the children, and adds the MouseListeners)
public class Main extends JFrame{
private static final long serialVersionUID = 7163215339973706671L;
private static final Dimension containerSize = new Dimension(640, 477);
static JLayeredPane layeredPane;
static JPanel container;
public Main() {
super("Multiple MouseListeners Test");
setSize(640, 477);
setLocationRelativeTo(null);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setResizable(false);
setVisible(true);
layeredPane = new JLayeredPane();
layeredPane.setPreferredSize(containerSize);
getContentPane().add(layeredPane);
createContainer();
layeredPane.add(container, JLayeredPane.DEFAULT_LAYER);
createChildren(4, 4);
new MovableObject();
MovableObjectMouseListener moml = new MovableObjectMouseListener();
//comment these two out and the highlighter works
layeredPane.addMouseListener(moml);
layeredPane.addMouseMotionListener(moml);
}
private void createChildren(int columns, int rows){
for (int i = 0; i < columns; i++){
for (int j = 0; j < rows; j++){
JPanel child = new JPanel(new BorderLayout());
child.setBackground(Color.LIGHT_GRAY);
//comment this out and you can move the MovableObject
child.addMouseListener(new HighlightJPanelsMouseListener());
container.add(child);
}
}
}
private JPanel createContainer(){
container = new JPanel();
container.setLayout(createLayout(4, 4, 1, 1));
container.setPreferredSize(containerSize);
container.setBounds(0, 0, containerSize.width, containerSize.height);
return container;
}
private GridLayout createLayout(int rows, int columns, int hGap, int vGap){
GridLayout layout = new GridLayout(rows, columns);
layout.setHgap(hGap);
layout.setVgap(vGap);
return layout;
}
public static void main(String[] args) {
new Main();
}
}
HighlightJPanelsMouseListener(creates the MouseListeners that will be added to the children)
public class HighlightJPanelsMouseListener implements MouseListener{
private Border grayBorder = BorderFactory.createLineBorder(Color.DARK_GRAY);
public HighlightJPanelsMouseListener() {
}
public void mouseEntered(MouseEvent e) {
Component comp = (Component) e.getSource();
JPanel parent = (JPanel) comp;
parent.setBorder(grayBorder);
parent.revalidate();
}
public void mouseExited(MouseEvent e) {
Component comp = (Component) e.getSource();
JPanel parent = (JPanel) comp;
parent.setBorder(null);
parent.revalidate();
}
public void mousePressed(MouseEvent e){}
public void mouseReleased(MouseEvent e){}
public void mouseClicked(MouseEvent e) {}
}
MovableObject(creates the MovableObject)
download blueSquare.png, or use another image. Remember to change the name of the image below to whatever yours is.
public class MovableObject {
public MovableObject() {
JPanel parent = (JPanel) Main.container.findComponentAt(10, 10);
ImageIcon icon = null;
//use any image you might have laying around your workspace - or download the one above
URL imgURL = MovableObject.class.getResource("/blueSquare.png");
if (imgURL != null){
icon = new ImageIcon(imgURL);
}else{
System.err.println("Couldn't find file");
}
JLabel movableObject = new JLabel(icon);
parent.add(movableObject);
parent.revalidate();
}
}
MovableObjectMouseListener(creates the MouseListener to be used for MovableObject)
public class MovableObjectMouseListener implements MouseListener, MouseMotionListener{
private JLabel replacement;
private int xAdjustment, yAdjustment;
public void mousePressed(MouseEvent e){
replacement = null;
Component c = Main.container.findComponentAt(e.getX(), e.getY());
if (!(c instanceof JLabel)){
return;
}
Point parentLocation = c.getParent().getLocation();
xAdjustment = parentLocation.x - e.getX();
yAdjustment = parentLocation.y - e.getY();
replacement = (JLabel)c;
replacement.setLocation(e.getX() + xAdjustment, e.getY() + yAdjustment);
Main.layeredPane.add(replacement, JLayeredPane.DRAG_LAYER);
Main.layeredPane.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
}
public void mouseDragged(MouseEvent me){
if (replacement == null){
return;
}
int x = me.getX() + xAdjustment;
int xMax = Main.layeredPane.getWidth() - replacement.getWidth();
x = Math.min(x, xMax);
x = Math.max(x, 0);
int y = me.getY() + yAdjustment;
int yMax = Main.layeredPane.getHeight() - replacement.getHeight();
y = Math.min(y, yMax);
y = Math.max(y, 0);
replacement.setLocation(x, y);
}
public void mouseReleased(MouseEvent e){
Main.layeredPane.setCursor(null);
if (replacement == null){
return;
}
int xMax = Main.layeredPane.getWidth() - replacement.getWidth();
int x = Math.min(e.getX(), xMax);
x = Math.max(x, 0);
int yMax = Main.layeredPane.getHeight() - replacement.getHeight();
int y = Math.min(e.getY(), yMax);
y = Math.max(y, 0);
Component c = Main.container.findComponentAt(x, y);
Container parent = (Container) c;
parent.add(replacement);
parent.validate();
}
public void mouseClicked(MouseEvent e) {}
public void mouseMoved(MouseEvent e) {}
public void mouseEntered(MouseEvent e) {}
public void mouseExited(MouseEvent e) {}
}
Didn't look at your code in detail but I would suggest that you need to add the drag MouseListener to the movable object (ie. the JLabel) and not the panel. Then the label will get the drag events and the panel will get the mouseEntered/Exited events.
This will lead to an additional problem with the mouseExited event now being generated when you move over the label (which you don't want to happen). Check out: how to avoid mouseExited to fire while on any nested component for a solution to this problem.
Is there any way to create a feature which allows a user to toggle screen magnification if they are visually impaired.
I'm asking in context of a program like eclipse, where a user who is visually impaired can toggle on and off a feature which magnifies the text, icons and navigation bar.
I use this short code for a general magnifier when cropping images.
import java.awt.*;
import java.awt.event.*;
import java.awt.image.BufferedImage;
import javax.swing.*;
class ZoomOnMouse {
Robot robot;
int zoomFactor = 2;
PointerInfo pi;
JPanel gui;
JLabel output;
Timer t;
public ZoomOnMouse() throws AWTException {
robot = new Robot();
gui = new JPanel(new BorderLayout(2, 2));
output = new JLabel("Point at something to see it zoomed!");
gui.add(output, BorderLayout.PAGE_END);
final int size = 256;
final BufferedImage bi = new BufferedImage(
size, size, BufferedImage.TYPE_INT_RGB);
final JLabel zoomLabel = new JLabel(new ImageIcon(bi));
gui.add(zoomLabel, BorderLayout.CENTER);
MouseListener factorListener = new MouseAdapter() {
#Override
public void mouseClicked(MouseEvent e) {
if (zoomFactor == 2) {
zoomFactor = 4;
} else if (zoomFactor == 4) {
zoomFactor = 8;
} else if (zoomFactor == 8) {
zoomFactor = 2;
}
showInfo();
}
};
zoomLabel.addMouseListener(factorListener);
ActionListener zoomListener = (ActionEvent e) -> {
pi = MouseInfo.getPointerInfo();
Point p = pi.getLocation();
Rectangle r = new Rectangle(
p.x - (size / (2 * zoomFactor)),
p.y - (size / (2 * zoomFactor)),
(size / zoomFactor),
(size / zoomFactor));
BufferedImage temp = robot.createScreenCapture(r);
Graphics g = bi.getGraphics();
g.drawImage(temp, 0, 0, size, size, null);
g.setColor(new Color(255,0,0,128));
int x = (size/2)-1;
int y = (size/2)-1;
g.drawLine(0,y,size,y);
g.drawLine(x,0,x,size);
g.dispose();
zoomLabel.repaint();
showInfo();
};
t = new Timer(40, zoomListener);
t.start();
}
public void stop() {
t.stop();
}
public Component getGui() {
return gui;
}
public void showInfo() {
pi = MouseInfo.getPointerInfo();
output.setText("Zoom: " + zoomFactor + " Point: " + pi.getLocation());
}
public static void main(String[] args) {
Runnable r = () -> {
try {
final ZoomOnMouse zm = new ZoomOnMouse();
final JFrame f = new JFrame("Mouse Zoom");
f.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
f.add(zm.getGui());
f.setResizable(false);
f.pack();
f.setLocationByPlatform(true);
f.setAlwaysOnTop(true);
f.setVisible(true);
WindowListener closeListener = new WindowAdapter() {
#Override
public void windowClosing(WindowEvent e) {
zm.stop();
f.dispose();
}
};
f.addWindowListener(closeListener);
} catch (AWTException e) {
e.printStackTrace();
}
};
// Swing GUIs should be created and updated on the EDT
// http://docs.oracle.com/javase/tutorial/uiswing/concurrency/initial.html
SwingUtilities.invokeLater(r);
}
}
I'm trying to set up dragging of an image over a grid of Jlabels.
it's working fine, but the image is "refreshing" and sometimes lags behind the mouse.
Is there a way to improve this and get a perfectly "smooth" movement of the image ?
EDIT: ok now, thanks to Andrew Thompson's advice I updated the paint() method to paintComponent(). But now why does my component disappear when I drag it? I'm probably missing something here...
EDIT2: why is the following behaviour: when using paint() method the component displays on top of the JLabels. But when using paintComponent() the component disappears being masked by the opaque Jlabels?
import java.awt.*;
import java.awt.event.*;
import java.io.*;
import java.net.*;
import javax.imageio.ImageIO;
import javax.swing.*;
import javax.swing.event.*;
public class DragNDrop {
public static void main(String[] args)
{
JFrame f = new JFrame("Test");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setContentPane(new DragPanel());
f.pack();
f.setLocation(200,200);
f.setVisible(true);
}
}
class DragPanel extends JPanel {
JLabel[][] labels;
SelectableAction action;
Image image;
Point p;
public DragPanel()
{
p = new Point();
setOpaque(true);
createLabels();
action = new SelectableAction(this);
addMouseListener(action);
addMouseMotionListener(action);
}
private void createLabels() {
labels = new JLabel[8][8];
Dimension dim50 = new Dimension(50,50);
GridBagConstraints gbc = new GridBagConstraints();
this.setLayout(new GridBagLayout());
for (int x=0;x<8;x++){
for (int y=0;y<8;y++){
labels[x][y] = new JLabel();
labels[x][y].setOpaque(true);
labels[x][y].setPreferredSize(dim50);
String str = new String("("+x+","+y+")");
labels[x][y].setText(str);
if ((x+y) % 2 == 0){
labels[x][y].setBackground(Color.lightGray);
} else
{
labels[x][y].setBackground(Color.white);
}
gbc.gridx = x;
gbc.gridy = 7-y;
this.add(labels[x][y],gbc);
}
URL url = getClass().getResource("images/50px-Knight.pgn");
Image img;
try {
img = ImageIO.read(url);
labels[0][0].setIcon(new ImageIcon(img));
} catch (IOException e) {
e.printStackTrace();
}
}
}
public JLabel[][] getLabels()
{
return labels;
}
public void paintComponent(Graphics g)
{
super.paintComponent(g);
if(action.dragging)
g.drawImage(image, p.x, p.y, this);
}
public void setImage(Image i)
{
image = i;
}
public void setOrigin(Point p)
{
this.p = p;
repaint();
}
}
/**
* Mouse code to enable image dragging
*/
class SelectableAction extends MouseInputAdapter
{
DragPanel MyPanel;
Image selectedImage;
boolean dragging;
Rectangle r;
Point offset;
public SelectableAction(DragPanel dp)
{
MyPanel = dp;
dragging = false;
offset = new Point();
}
public void mousePressed(MouseEvent e)
{
Point p = e.getPoint();
JLabel[][] labels = MyPanel.getLabels();
for(int i = 0; i < labels.length; i++)
{
for (int j=0;j<labels[0].length;j++){
r = labels[i][j].getBounds();
if(r.contains(p))
{
if ( ((ImageIcon)labels[i][j].getIcon()).getImage() != null) {
selectedImage = ((ImageIcon)labels[i][j].getIcon()).getImage();
MyPanel.setImage(selectedImage);
labels[i][j].setIcon(null);
offset.x = p.x - r.x;
offset.y = p.y - r.y;
dragging = true;
MyPanel.setOrigin(new Point(r.x, r.y));
break;
}
}
}
}
}
public void mouseReleased(MouseEvent e)
{
Point p = e.getPoint();
JLabel[][] labels = MyPanel.getLabels();
for(int i = 0; i < labels.length; i++)
{
for (int j=0;j<labels[0].length; j++){
r = labels[i][j].getBounds();
if(r.contains(p)) {
ImageIcon tmpIcon = new ImageIcon(selectedImage);
labels[i][j].setIcon(tmpIcon);
MyPanel.repaint();
dragging = false;
}
}
}
}
public void mouseDragged(MouseEvent e)
{
if(dragging)
{
r.x = e.getX() - offset.x;
r.y = e.getY() - offset.y;
MyPanel.setOrigin(new Point(r.x, r.y));
}
}
}
+1 to AndrewThompson and GuillaumePolet comments. especially:
It disappears because you have opaque children components that paint
themselves above.
to overcome this we dont need/want to override paintComponent(..) instead we want to override paintChildren(..) and call super.paintChildren(..) followed by our custom code. Thus all components will be drawn in call to super and our image will be drawn after, thus making it appear/visible above all others.
Replace the overridden paintComponent with below code and it will work:
#Override
protected void paintChildren(Graphics g) {
super.paintChildren(g);
if (action.dragging) {
g.drawImage(image, p.x, p.y, null);//also notice i use null and not this, unless the class you are using extends ImageObserver no need for this
}
}
I've added this listener to a JLabel, and I am able to drag the image around perfectly, However, as soon as I click on the panel(in an area where a JLabel is not present) the label returns back to it's original location. I can't figure out why that would happen. Please help me out, I've spent hours working on this. Thanks!
public class CardLabelListener extends MouseAdapter {
private MouseEvent initiateEvent = null;
#Override
public void mouseReleased(MouseEvent me) {
System.err.println("mouse release");
int dx = me.getX() - initiateEvent.getX();
int dy = me.getY() - initiateEvent.getY();
if (Math.abs(dx) > 5 || Math.abs(dy) > 5) {
Rectangle oldBound = me.getComponent().getBounds();
int newX = oldBound.x + dx;
int newY = oldBound.y + dy;
me.getComponent().setBounds(newX, newY, oldBound.width, oldBound.height);
}
initiateEvent = null;
}
public void mousePressed(MouseEvent me) {
GreetingCard.setBackground.findComponentAt(me.getX(), me.getY());
System.err.println("mouse pressed");
initiateEvent = me;
me.consume();
}
public void mouseDragged(MouseEvent me) {
System.err.println(me.getSource());
if (initiateEvent == null) return;
me.consume();
JComponent jc = (JComponent) me.getSource();
TransferHandler handler = jc.getTransferHandler();
handler.exportAsDrag(jc, me, TransferHandler.MOVE);
initiateEvent = null;
}
}
Firstly, you're not dragging the icon, your dragging the component itself.
Secondly, you're fighting against the layout manager which contains the label component. When ever the panel is invalidated, the labels are returned to their original layout positions.
Trying using something like a JLayeredPane to put your labels in
UPDATED with example based on feedback
public class MoveMe {
public static void main(String[] args) {
new MoveMe();
}
public MoveMe() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException ex) {
} catch (InstantiationException ex) {
} catch (IllegalAccessException ex) {
} catch (UnsupportedLookAndFeelException ex) {
}
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
frame.add(new MoveMePane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class MoveMePane extends JLayeredPane {
public MoveMePane() {
int width = 400;
int height = 400;
for (int index = 0; index < 10; index++) {
String text = "Label " + index;
JLabel label = new JLabel(text);
label.setSize(label.getPreferredSize());
int x = (int) Math.round(Math.random() * width);
int y = (int) Math.round(Math.random() * height);
if (x + label.getWidth() > width) {
x = width - label.getWidth();
}
if (y + label.getHeight() > width) {
y = width - label.getHeight();
}
label.setLocation(x, y);
add(label);
}
MoveMeMouseHandler handler = new MoveMeMouseHandler();
addMouseListener(handler);
addMouseMotionListener(handler);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(400, 400);
}
}
public class MoveMeMouseHandler extends MouseAdapter {
private int xOffset;
private int yOffset;
private JLabel draggy;
private String oldText;
#Override
public void mouseReleased(MouseEvent me) {
if (draggy != null) {
draggy.setText(oldText);
draggy.setSize(draggy.getPreferredSize());
draggy = null;
}
}
public void mousePressed(MouseEvent me) {
JComponent comp = (JComponent) me.getComponent();
Component child = comp.findComponentAt(me.getPoint());
if (child instanceof JLabel) {
xOffset = me.getX() - child.getX();
yOffset = me.getY() - child.getY();
draggy = (JLabel) child;
oldText = draggy.getText();
draggy.setText("What a drag");
draggy.setSize(draggy.getPreferredSize());
}
}
public void mouseDragged(MouseEvent me) {
if (draggy != null) {
draggy.setLocation(me.getX() - xOffset, me.getY() - yOffset);
}
}
}
}
For this to work you would need use a null layout on the container where the JLabel is located, otherwise the position will reset to that where the current layout manager positions the label.
A better approach would be to use DragLayout
DragLayout was designed to replace a null layout. It will respect the location of a component. By default it will use the preferred size of the component to determines its size. Finally, it will automatically calculate the preferred size of the Container.