Moving a JFrame with custom grip/handle bar - java

I am making a custom JFrame. I already have this layout, and it works completely fine:
The frame is undecorated, but I want to be able to move it around. I want my custom panel to be the moving grip for this, so what I did was add a MouseMotionListener to it. The mouseDragged function looks like this:
#Override
public void mouseDragged(MouseEvent e) {
parent.setBounds(e.getX(), e.getY(), parent.getWidth(), parent.getHeight());
}
The parent field is set in the constructor and is final.
When I try to drag the frame with the panel, it works, but not quite right. The frame constantly flickers between two positions on the screen. I am able to move the frame, but it looks horrible. When I don't drag the frame, it doesn't flicker. The two positions are relative to each other, so if you move the frame, the other one moves along (but doesn't stay at the same distance from the other). Another problem is that the frame doesn't move well with the mouse. So, if you move the frame like 100 pixels in the x direction, the frame moves less pixels in the same direction.
How can you make a moving grip for a JFrame without this happening (and what is actually causing it to do this)?
If more code is required, just tell me.

You need to provide a mousePressed also to get the initial point of the click. Then use that point to do some calculations.
Try something like this, where pX and pY are class fields (and assuming listeners are added in the panel constructor
addMouseListener(new MouseAdapter() {
public void mousePressed(MouseEvent me) {
// Get x,y and store them
pX = me.getX();
pY = me.getY();
}
});
addMouseMotionListener(new MouseMotionAdapter() {
public void mouseDragged(MouseEvent me) {
parent.setLocation(parent.getLocation().x + me.getX() - pX,
parent.getLocation().y + me.getY() - pY);
}
});
Here's a full example
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.border.LineBorder;
public class UndecoratedExample {
private JFrame frame = new JFrame();
class MainPanel extends JPanel {
public MainPanel() {
setBackground(Color.gray);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(400, 400);
}
}
class BorderPanel extends JPanel {
private JLabel label;
int pX, pY;
public BorderPanel() {
label = new JLabel(" X ");
label.setOpaque(true);
label.setBackground(Color.RED);
label.setForeground(Color.WHITE);
setBackground(Color.black);
setLayout(new FlowLayout(FlowLayout.RIGHT));
add(label);
label.addMouseListener(new MouseAdapter() {
public void mouseReleased(MouseEvent e) {
System.exit(0);
}
});
addMouseListener(new MouseAdapter() {
public void mousePressed(MouseEvent me) {
// Get x,y and store them
pX = me.getX();
pY = me.getY();
}
});
addMouseMotionListener(new MouseMotionAdapter() {
public void mouseDragged(MouseEvent me) {
frame.setLocation(frame.getLocation().x + me.getX() - pX,
frame.getLocation().y + me.getY() - pY);
}
});
}
}
class OutsidePanel extends JPanel {
public OutsidePanel() {
setLayout(new BorderLayout());
add(new MainPanel(), BorderLayout.CENTER);
add(new BorderPanel(), BorderLayout.PAGE_START);
setBorder(new LineBorder(Color.BLACK, 5));
}
}
private void createAnsShowGui() {
frame.setUndecorated(true);
frame.add(new OutsidePanel());
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
new UndecoratedExample().createAnsShowGui();
}
});
}
}

I want my custom panel to be the moving grip for this,
Check out Moving Windows which contains a class that will allow you to drag a window around the screen or any component around its parent container.

Related

How do I get my MouseHandler to work in my DrawPanel, so I can make a label that shows the current mouse location?

I am trying to add the MouseHandler to my DrawPanel class to eventually have a status label that updates the mouse location, but while using print statements, it seems like it is not registering any mouse input at all.
private class DrawPanel extends JPanel {
public DrawPanel() {
JPanel mousePanel = new JPanel();
this.add(mousePanel);
MouseHandler handler = new MouseHandler();
mousePanel.addMouseListener(handler);
mousePanel.addMouseMotionListener(handler);
}
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
}
private class MouseHandler extends MouseAdapter implements MouseMotionListener {
#Override
public void mousePressed(MouseEvent event) {
System.out.print("Pressed");
}
#Override
public void mouseReleased(MouseEvent event) {
System.out.print("Released");
}
#Override
public void mouseDragged(MouseEvent event) {
System.out.print("Dragged");
//lblStatus.setText(String.format("(%d,%d)",event.getX(),event.getY()));
}
#Override
public void mouseMoved(MouseEvent event) {
System.out.print("Moved");
//System.out.print("("+event.getX()+","+event.getY()+")");
//lblStatus.setText(String.format("(%d,%d)",event.getX(),event.getY()));
}
}
}
You're creating and adding another JPanel, the mousePanel, and adding it to the DrawPanel JPanel, a container that uses the default FlowLayout. This makes the mousePanel's size its preferred size which is [0, 0] meaning that the mousePanel component is being added but it is too small to be seen or to do anything significant. But why do you even have or need this extra JPanel?
Solution: get rid of the mousePanel, there is no need for it. Instead add your mouse handler to this.
Side issue, no need to implement MouseMotionListener. A MouseAdapter already implements this interface.
For example:
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class FooSwing02 extends JPanel {
private JLabel statusLabel = new JLabel("");
public FooSwing02() {
setPreferredSize(new Dimension(800, 650));
add(new JLabel("Mouse Location:"));
add(statusLabel);
MyMouse myMouse = new MyMouse();
addMouseListener(myMouse);
addMouseMotionListener(myMouse);
}
private class MyMouse extends MouseAdapter {
#Override
public void mouseMoved(MouseEvent event) {
Point p = event.getPoint();
String text = String.format("[%03d, %03d]", p.x, p.y);
statusLabel.setText(text);
}
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
JFrame frame = new JFrame("GUI");
frame.add(new FooSwing02());
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
});
}
}

Label gets added to JPanel without reason swing

I'm trying to make a simple application for painting. I have a main method that sets up a JFrame, and then adds a JPanel and a JLabel using flowlayout. The JLabel is for counting clicks.
The panel class implements a mouselistener and mousemotionlistener.
The problem is when I paint something or click on the panel, it adds the label to the JPanel aswell, and depending on where I click, it can add it twice to the panel, it's making me mad. I just can't understand why it's added to the JPanel.
Also, the JPanel is surrounded by a border, and when I click or paint something it adds a new vertical borderline somewhere on the panel, it's random every time.
Code for the two classes:
public class mainepanel extends JPanel implements MouseMotionListener, MouseListener{
Graphics globalGraphics;
int clickCount = 0;
public mainepanel(){
setBorder(BorderFactory.createLineBorder(Color.black));
setPreferredSize(new DimensionUIResource(200,200));
addMouseMotionListener(this);
addMouseListener(this);
validate();
setFocusable(true);
}
public void paintComponent(Graphics g){
globalGraphics = g.create();
}
#Override
public void mouseDragged(MouseEvent e) {
globalGraphics.fillOval(e.getX(), e.getY(), 10,10);
repaint();
}
#Override
public void mouseClicked(MouseEvent e) {
clickCount ++;
maine.setLabel(clickCount);
globalGraphics.fillOval(e.getX(), e.getY(), 10,10);
repaint();
}
maine
public class maine extends JFrame{
static JLabel label;
public maine(){
setSize(600,400);
setDefaultCloseOperation(3);
setResizable(false);
label = new JLabel("Clicks:");
setLayout(new FlowLayout());
add(label);
add(new mainepanel());
setVisible(true);
}
public static void setLabel(int clicks){
label.setText("Clicks: " + clicks);
}
public static void main(String[]args){
new maine();
}
}
Perform all the drawing within paintComponent (and be sure to call super.paintComponent) - the MouseListener/MouseMotionListener should only need to change the data model (and if necessary call repaint so the UI reflects the change).
A simple example below with a single circle created with a Mouse click, and moved with a mouse dragged:
Point center = null;
...
#Override
public void mouseClicked(MouseEvent e){
center = e.getPoint();
repaint();
}
#Override
public void mouseDragged(MouseEvent e){
center = e.getPoint();
repaint();
}
#Override
protected void paintComponent(Graphics g){
super.paintComponent(g);
if ( center != null ){
g.fillOval(center.getX(), center.getY(), 10, 10);
}
}

Prevent scrollbars disappearing when exiting component inside the JScrollPane

I made a JPanel that contains a JScrollPane that surrounds a JTextArea. I want to hide the scrollbars when the mouse exits the JScrollPane.
If I set the MouseListener on the JScrollPane, then nothing happens. If I set it on the JTextArea then the scrollbars hide/unhide correctly, but you cannot click them because they are outside the bounds of the JTextArea.\
How can I prevent the scrollbars from disappearing when you try to click them?
Code example 1. Hiding and unhiding + changing transparency works.
Problem: You cannot click the scrollbars, they disappear because you exit the JTextArea.
public class TransparentTextArea extends JTextArea {
private static final Color LIGHT_TRANSPARENT = new Color(0, 0, 0, 150);
private static final Color HEAVY_TRANSPARENT = new Color(0, 0, 0, 50);
public TransparentTextArea(final GameModel model) {
setOpaque(false);
setForeground(Color.WHITE);
setBackground(HEAVY_TRANSPARENT);
setEditable(false);
addMouseListener(new MouseListener() {
#Override
public void mouseReleased(MouseEvent e) {
// TODO Auto-generated method stub
}
#Override
public void mousePressed(MouseEvent e) {
// TODO Auto-generated method stub
}
#Override
public void mouseExited(MouseEvent e) {
model.setLogActive(false);
setBackground(HEAVY_TRANSPARENT);
}
#Override
public void mouseEntered(MouseEvent e) {
model.setLogActive(true);
setBackground(LIGHT_TRANSPARENT);
}
#Override
public void mouseClicked(MouseEvent e) {
// TODO Auto-generated method stub
}
});
}
}
public class Logger extends JScrollPane implements PanelObserver,
ChangeListener {
private GameModel model;
public Logger(final GameModel model) {
super(new TransparentTextArea(model));
this.model = model;
setOpaque(false);
getViewport().setOpaque(false);
setBorder(BorderFactory.createEmptyBorder());
setViewportBorder(BorderFactory.createEmptyBorder());
model.addPanelObserver(this);
model.addChangeListener(this);
}
#Override
public void panelResized() {
float x = model.getPanelWidth() * 0.01f;
float y = model.getPanelHeight() * 0.70f;
float width = model.getPanelWidth() * 0.30f;
float height = model.getPanelHeight() * 0.28f;
setBounds((int) x, (int) y, (int) width, (int) height);
}
#Override
public void stateChanged(ChangeEvent e) {
if (model.isLogActive()) {
setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);
setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
} else {
setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_NEVER);
setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
}
}
}
Code example 2.
Problem I tried to fix this by creating a Panel around it and using that MouseExited, but it does not work since the childs consume it.
public class Logger extends JPanel implements PanelObserver, MouseListener {
private static final Color LIGHT_TRANSPARENT = new Color(0, 0, 0, 150);
private static final Color HEAVY_TRANSPARENT = new Color(0, 0, 0, 50);
private GameModel model;
private JTextArea textArea;
private JScrollPane scrollPane;
public Logger(GameModel model) {
super(null);
this.model = model;
setOpaque(false);
textArea = new JTextArea();
textArea.setOpaque(false);
textArea.setForeground(Color.WHITE);
textArea.setBackground(HEAVY_TRANSPARENT);
textArea.setEditable(false);
scrollPane = new JScrollPane(textArea);
scrollPane.setOpaque(false);
scrollPane.getViewport().setOpaque(false);
scrollPane.setBorder(BorderFactory.createEmptyBorder());
scrollPane.setViewportBorder(BorderFactory.createEmptyBorder());
add(scrollPane);
model.addPanelObserver(this);
addMouseListener(this);
}
#Override
public void panelResized() {
float x = model.getPanelWidth() * 0.01f;
float y = model.getPanelHeight() * 0.70f;
float width = model.getPanelWidth() * 0.30f;
float height = model.getPanelHeight() * 0.28f;
setBounds((int) x, (int) y, (int) width, (int) height);
scrollPane.setBounds(0, 0, (int) width, (int) height);
}
#Override
public void mouseClicked(MouseEvent e) {}
#Override
public void mousePressed(MouseEvent e) {}
#Override
public void mouseReleased(MouseEvent e) {}
#Override
public void mouseEntered(MouseEvent e) {
scrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);
scrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
textArea.setBackground(LIGHT_TRANSPARENT);
}
#Override
public void mouseExited(MouseEvent e) {
scrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_NEVER);
scrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
textArea.setBackground(HEAVY_TRANSPARENT);
}
}
works for me, but there are a few mistakes
JScrollPane (in the case that is container) react to only one pixel around its child (1pixel Border)
mouse event are consumed by its childs, then whatever added to JScrollPane inside JPanel to consume() mouse event
JPanel has FlowLayout implementer in API, change that
don't to use NullLayout, use proper LayourManager for JPanel (BorderLayout, GridLayout)
JComponents can quite to easilly returns PreferredSize back to the container override getPreferredSize for JPanel, JTextArea(20, 20) etc
JPanel must be larger that JViewport in JScrollPane, otherwise (without override JScrollBars) JScrollBar never will be visible
near to true were #Omid +1
. . . . .
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
//http://stackoverflow.com/q/16344529/714968
public class MyLogger {
private JTextArea textArea;
private JScrollPane scrollPane;
public MyLogger() {
textArea = new JTextArea(20, 20);
scrollPane = new JScrollPane(textArea);
scrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_NEVER);
scrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
textArea.addMouseListener(new MouseListener() {
#Override
public void mouseClicked(MouseEvent e) {
}
#Override
public void mousePressed(MouseEvent e) {
}
#Override
public void mouseReleased(MouseEvent e) {
}
#Override
public void mouseEntered(MouseEvent e) {
scrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);
scrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
}
#Override
public void mouseExited(MouseEvent e) {
scrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_NEVER);
scrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
}
});
JFrame frame = new JFrame();
frame.add(scrollPane);
frame.setPreferredSize(new Dimension(100, 100));// JFrame must be smaller
frame.pack();
frame.setLocation(150, 150);
frame.setVisible(true);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
MyLogger myLogger = new MyLogger();
}
});
}
}
EDIT
again back to answer posted by #Omid, see what happens
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
//http://stackoverflow.com/q/16344529/714968
public class MyLogger {
private JTextArea textArea;
private JScrollPane scrollPane;
public MyLogger() {
textArea = new JTextArea(20, 20);
textArea.setText("This text pane contains no html. It supports letter wrapping, "
+ "\nThis text pane contains no html. It supports letter wrapping!, "
+ "\nThis text pane contains no html. It supports letter wrapping!, "
+ "\nThis text pane contains no html. It supports letter wrapping!, "
+ "\nThis text pane contains no html. It supports letter wrapping!, "
+ "\nThis text pane contains no html. It supports letter wrapping!");
textArea.addMouseListener(new MouseAdapter() {
#Override
public void mouseEntered(MouseEvent e) {
scrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);
scrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
}
});
scrollPane = new JScrollPane(textArea);
scrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_NEVER);
scrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
scrollPane.addMouseListener(new MouseAdapter() {
#Override
public void mouseExited(MouseEvent e) {
scrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_NEVER);
scrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
}
});
JFrame frame = new JFrame();
frame.add(scrollPane);
frame.setPreferredSize(new Dimension(100, 100));// JFrame must be smaller
frame.pack();
frame.setLocation(150, 150);
frame.setVisible(true);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
MyLogger myLogger = new MyLogger();
}
});
}
}
Just tried this and it works fine:
textArea.addMouseMotionListener(new MouseMotionAdapter() {
#Override
public void mouseMoved(MouseEvent arg0) {
scrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);
scrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
}
});
scrollPane.addMouseListener(new MouseAdapter() {
#Override
public void mouseExited(MouseEvent e) {
scrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_NEVER);
scrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
}
});
The idea is that as long as the mouse is moving within the textArea, the ScrollBar is visible and works fine. As soon as it moves outside the ScrollPane it's not visible anymore.
You may have to put the listener on the JScrollPane itself rather than on the backing JPanel. The JScrollPane encapsulates your JPanel so adding the mouse listener to the JPanel only will only tell you when the mouse enters/leaves that JPanel specifically, not the encapsulating JScrollPane.
scrollPane.addMouseListener(this);
You may actually have to have listeners in both, depending on how you want this all to work, and maybe set up a lock so that as long as the mouse is in at least one of them, the scroll bars will show, but if the mouse is in neither then make them invisible.

java move components with mouse

I tried to make any Component draggable by simply adding mouse listeners and using the setLocation function of java.awt.Component. I started with JButton to test if it were possible the way i thought.
Here is a code example for what I am trying to do:
import java.awt.*;
import javax.swing.*;
public class DragButton extends JButton{
private volatile int draggedAtX, draggedAtY;
public DragButton(String text){
super(text);
setDoubleBuffered(false);
setMargin(new Insets(0, 0, 0, 0));
setSize(25, 25);
setPreferredSize(new Dimension(25, 25));
addMouseListener(new MouseAdapter(){
public void mousePressed(MouseEvent e){
draggedAtX = e.getX() - getLocation().x;
draggedAtY = e.getY() - getLocation().y;
}
});
addMouseMotionListener(new MouseMotionAdapter(){
public void mouseDragged(MouseEvent e){
setLocation(e.getX() - draggedAtX, e.getY() - draggedAtY);
}
});
}
public static void main(String[] args){
JFrame frame = new JFrame("DragButton");
frame.setLayout(null);
frame.getContentPane().add(new DragButton("1"));
frame.getContentPane().add(new DragButton("2"));
frame.getContentPane().add(new DragButton("3"));
frame.setSize(300, 300);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
}
Somehow this fails to work properly and I don't get why. The actual distance dragged is half the distance of the mouse movement and it flickers around that distance while dragging as if two mouse positions are competing over the MouseMotionListener.
May anyone help a swing/awt noob? =)
Many thanks in advance.
Edit:
Ok, so the problem was that I did not know that the event would refire at each mouse location with the position being relative(!) to the firing JComponent. So this is the corrected and working code:
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class DragButton extends JButton{
private volatile int draggedAtX, draggedAtY;
public DragButton(String text){
super(text);
setDoubleBuffered(false);
setMargin(new Insets(0, 0, 0, 0));
setSize(25, 25);
setPreferredSize(new Dimension(25, 25));
addMouseListener(new MouseAdapter(){
public void mousePressed(MouseEvent e){
draggedAtX = e.getX();
draggedAtY = e.getY();
}
});
addMouseMotionListener(new MouseMotionAdapter(){
public void mouseDragged(MouseEvent e){
setLocation(e.getX() - draggedAtX + getLocation().x,
e.getY() - draggedAtY + getLocation().y);
}
});
}
public static void main(String[] args){
JFrame frame = new JFrame("DragButton");
frame.setLayout(null);
frame.getContentPane().add(new DragButton("1"));
frame.getContentPane().add(new DragButton("2"));
frame.getContentPane().add(new DragButton("3"));
frame.setSize(300, 300);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
}
Thanks Adel for your efforts and mKorbel for the link.
You have to move with JComponent, I miss this definitions in voids mousePressed/mouseDragged; in other hands, there nothing better around as #[camickr][1] excellent code for ComponentMover.
import javax.swing.*;
import java.awt.event.*;
public class movingButton extends JFrame{
private JButton button ;
public movingButton ()
{
super("Position helper");
super.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
super.setSize(500,520);
super.setVisible(true);
super.setLayout(null);
button = new JButton ("drag me ");
add(button);
button.setBounds(100, 100, 150, 40);
button.addMouseMotionListener(new MouseAdapter(){
public void mouseDragged(MouseEvent E)
{
int X=E.getX()+button.getX();
int Y=E.getY()+button.getY;
button.setBounds(X,Y,150,40);
}
});
}
public static void main (String x[])
{
new movingButton();
}
}
Why don't you use the java Transferable interface instead?
Here's a tutorial on how to do it:
http://www.javaworld.com/javaworld/jw-03-1999/jw-03-dragndrop.html
It is better if you would do
int X=E.getX() + button.getX();
int Y=E.getY() + button.getY();

How do I set hard limit on a JComponent when setMaximumSize() and setPrefferedSize() don't work?

I'm trying to make an image processing frame similar to one found in something like Photoshop or Paint Shop Pro and I'm running into problems.
Right now I have a JFrame window with a JDesktopPane. When I click a button, a JInternalFrame is made with the following components in it:
imageLabel = new JLabel("picture.png");
scrollPane.setViewPort(imageLabel);
internalFrame.add(scrollPane); // I also tried with a BorderLayout()
desktopPane.add(internalFrame);
My problem is this: I don't want the JLabel or the JScrollPane to stretch to the size of the JInternalFrame if the JLabel is smaller than the JInternalFrame.
I've tried padding the space around the JLabel with "empty" JLabels. I've tried switching layout styles of the JScrollPane. I've tried setting the preferred and maximum sizes of the JLabel and the JScrollPane to that of picture.png. None of it works for what I need. I don't want the blank "space" around the JLabel to be a part of the JScrollPane or the JLabel so that I can use various MouseEvents to trigger on the picture itself rather than the space left by the "stretched" JLabel or JScrollPane whenever I resize the JInternalFrame.
Thanks in advance.
Edit1: Here is a bit of code that highlights the problem.
import java.awt.*;
import java.awt.event.*;
class fooFrame extends JFrame implements MouseListener
{
private static fooFrame frame;
JLabel fooLabel;
public fooFrame()
{
JDesktopPane background = new JDesktopPane();
JInternalFrame internalFrame = new JInternalFrame("Internal Frame", true, true, true, true);
internalFrame.setSize(500, 500);
internalFrame.setLocation(20, 20);
internalFrame.setVisible(true);
Image image = Toolkit.getDefaultToolkit().getImage("test.gif");
fooLabel = new JLabel(new ImageIcon("test.gif"));
fooLabel.setPreferredSize(new Dimension(image.getWidth(null), image.getHeight(null)));
JScrollPane fooScrollPane = new JScrollPane(fooLabel, JScrollPane.VERTICAL_SCROLLBAR_NEVER, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
fooScrollPane.setPreferredSize(new Dimension(fooLabel.getWidth(), fooLabel.getHeight()));
fooScrollPane.setViewportView(fooLabel); // add JLabel to JScrollPane
internalFrame.add(fooScrollPane); // add JScrollPane to JInternalFrame
background.add(internalFrame); // add JInternalFrame to JDesktopPanel
this.setContentPane(background); // add JDesktopPanel to JFrame
fooLabel.addMouseListener(this);
}
public void mouseClicked(MouseEvent me)
{
if (me.getSource() == fooLabel)
System.out.println("Clicked the picture.");
}
public void mouseEntered(MouseEvent me)
{
if (me.getSource() == fooLabel)
System.out.println("Entered the picture.");
}
public void mouseExited(MouseEvent me)
{
if (me.getSource() == fooLabel)
System.out.println("Exited the picture.");
}
public void mousePressed(MouseEvent me){}
public void mouseReleased(MouseEvent me){}
public static void createAndShowGUI()
{
try
{
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
}
catch (Exception e) { }
frame = new fooFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setTitle("foo");
frame.setSize(800,600);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
}
You will have to get your own "test.gif" and if you make the internalFrame larger than the picture it fills the remaining space with the label. As all the mouseEvents fire when I cross the internalFrame rather than onto the picture like I want to have happen.
Edit2: Code modified with Kleopatra's suggestions.
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.border.LineBorder;
public class a1
{
public static void main(String[] args)
{
fooFrame.createAndShowGUI();
}
}
class fooFrame extends JFrame implements MouseListener
{
private static fooFrame frame;
JLabel fooLabel;
public fooFrame()
{
JDesktopPane background = new JDesktopPane();
JInternalFrame internalFrame = new JInternalFrame("Internal Frame", true, true, true, true);
internalFrame.setLocation(20, 20);
internalFrame.setVisible(true);
internalFrame.pack();
Image image = Toolkit.getDefaultToolkit().getImage("test.gif");
fooLabel = new JLabel(new ImageIcon(image));
fooLabel.setBorder(new LineBorder(Color.PINK));
JScrollPane fooScrollPane = new JScrollPane((fooLabel), JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
internalFrame.setLayout(new BoxLayout(internalFrame.getContentPane(), BoxLayout.LINE_AXIS));
fooScrollPane.setViewportView(fooLabel); // add JLabel to JScrollPane
internalFrame.add(fooScrollPane); // add JScrollPane to JInternalFrame
background.add(internalFrame); // add JInternalFrame to JDesktopPanel
this.setContentPane(background); // add JDesktopPanel to JFrame
fooLabel.addMouseListener(this);
}
public void mouseClicked(MouseEvent me)
{
if (me.getSource() == fooLabel)
System.out.println("Clicked the picture.");
}
public void mouseEntered(MouseEvent me)
{
if (me.getSource() == fooLabel)
System.out.println("Entered the picture.");
}
public void mouseExited(MouseEvent me)
{
if (me.getSource() == fooLabel)
System.out.println("Exited the picture.");
}
public void mousePressed(MouseEvent me)
{
}
public void mouseReleased(MouseEvent me)
{
}
#Override
public Dimension getMaximumSize()
{
return getPreferredSize();
}
public static void createAndShowGUI()
{
try
{
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (Exception e)
{
}
frame = new fooFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setTitle("foo");
frame.setSize(800, 600);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
}
In this example, the internal frame will initially be no more than MAX_SIZE pixels. Smaller pictures will be sized so that scrolling is not required.
Addendum: Reading the title more closely, you may also want to limit the internal frame's maximum size:
internalFrame.setMaximumSize(new Dimension(fooLabel.getPreferredSize()));
Addendum: This variation may help clarify the problem. With the default layout, BorderLayout.CENTER, the scroll bars appear as required, but the MouseListener does not behave as desired. With a layout that respects preferred sizes, FlowLayout, the MouseListener reports correctly, but the the scroll bars never appear, as the label's preferred size never changes. I've added synthetic images for convenience.
import java.awt.*;
import java.awt.event.*;
import java.awt.image.BufferedImage;
import javax.swing.*;
class PictureFrame extends JFrame {
private static final String NAME = "image.jpg";
public PictureFrame() {
JDesktopPane dtp = new JDesktopPane();
dtp.add(new MyFrame("Large", FauxImage.create(300, 500), 50));
dtp.add(new MyFrame("Small", FauxImage.create(200, 200), 25));
this.add(dtp);
}
private static class MyFrame extends JInternalFrame {
private static final int MAX_SIZE = 256;
public MyFrame(String title, Image image, int offset) {
super(title, true, true, true, true);
//this.setLayout(new FlowLayout());
final JLabel label = new JLabel(new ImageIcon(image));
this.add(new JScrollPane(label));
this.pack();
int w = Math.min(MAX_SIZE, image.getWidth(null));
int h = Math.min(MAX_SIZE, image.getHeight(null));
Insets i = this.getInsets();
this.setSize(w + i.left + i.right, h + i.top + i.bottom);
this.setLocation(offset, offset);
this.setVisible(true);
label.addMouseListener(new MouseAdapter() {
#Override
public void mouseEntered(MouseEvent me) {
if (me.getSource() == label) {
System.out.println("Entered.");
}
}
#Override
public void mouseExited(MouseEvent me) {
if (me.getSource() == label) {
System.out.println("Exited.");
}
}
});
}
}
private static class FauxImage {
static public Image create(int w, int h) {
BufferedImage bi = new BufferedImage(
w, h, BufferedImage.TYPE_INT_ARGB);
Graphics2D g2d = bi.createGraphics();
g2d.setPaint(Color.lightGray);
g2d.fillRect(0, 0, w, h);
g2d.setColor(Color.black);
String s = w + "\u00D7" + h;
int x = (w - g2d.getFontMetrics().stringWidth(s)) / 2;
g2d.drawString(s, x, 24);
g2d.dispose();
return bi;
}
}
public static void createAndShowGUI() {
PictureFrame frame = new PictureFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setTitle("PictureFrame");
frame.setSize(640, 400);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
createAndShowGUI();
}
});
}
}
The answer to layout problems is LayoutManager. Always. It's never setXXSize (though nothing is absolutely absolute :-)
Just to be sure I understand what you are after:
always have the label at its pref size (which by default is the size of its icon)
allow scrolling if the internalFrame is smaller than the pref, that is the scrollpane is resized to smaller, label remains at its pref
disallow strectching of the scrollPane and its content if the internalframe is larger than pref
The important LayoutManager in this scenario is the one which control the size of the scrollpane: that's the LayoutManager of the internalFrame's contentPane. The default manager is a BorderLayout: sizing its center to whatever space is available. We need to replace that with one that respects max and make the center component (the scrollpane) report a max (which typically is Short.MAX)
internalFrame.setLayout(new BoxLayout(internalFrame.getContentPane(), BoxLayout.LINE_AXIS));
Image image = Toolkit.getDefaultToolkit().getImage("test.gif");
fooLabel = new JLabel(new ImageIcon(image));
JScrollPane fooScrollPane = new JScrollPane(fooLabel),
JScrollPane.VERTICAL_SCROLLBAR_NEVER,
JScrollPane.HORIZONTAL_SCROLLBAR_NEVER) {
#Override
public Dimension getMaximumSize() {
return getPreferredSize();
}
};
internalFrame.add(fooScrollPane); // add JScrollPane to JInternalFrame

Categories