I have a JPanel that contains 11 JLabels, each of them registered with a MouseMotionListener like so (generated by Netbeans):
label1.addMouseMotionListener(new MouseMotionAdapter()
{
public void mouseDragged(MouseMotionEvent evt){
label1MouseDragged(evt);
}
and the individual labelXMouseDragged methods contain (for example):
label1.setLocation(label1.getParent().getMousePosition());
This Panel lives inside another Panel alongside various other controls. I find that I can drag my labels just fine inside the panel (I had my methods correctly checking for bounds, but I have left them like the above for simplicity). However, if the mouse is clicked anywhere that is not a control, either within the inner panel or within the parent panel, the locations of the labels reset. What is causing this to happen? I have no mouseListeners of any kind registered anywhere else, and if I make this panel by itself, I seem to have no issues with clicks.
You could achieve this by using a null layout, but I have a pathalogical dislike for null layouts, too many things go wrong with them.
The Swing API is based around the use of layout managers when laying out components.
Instead, you could create a layout manager whose sole responsibility would be to honour the position of the components it is managing
The basic benefit of this is you don't need to worry about sizing the components and that it will respond to changes to the parent container as well as changes to the containers around it without you needing to add additional listeners.
You could even devise a bounds check within the layoutContainer to ensure that the components stay within bounds
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.LayoutManager2;
import java.awt.Point;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class AbsoluteLayoutExample {
public static void main(String[] args) {
new AbsoluteLayoutExample();
}
public AbsoluteLayoutExample() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
public TestPane() {
setLayout(new AbsoluateLayoutManager());
JLabel test = new JLabel("Test");
add(test);
MouseAdapter ma = new MouseAdapter() {
private Point offset;
private Component dragComp;
#Override
public void mousePressed(MouseEvent e) {
Point point = e.getPoint();
for (Component comp : getComponents()) {
if (comp.getBounds().contains(point)) {
offset = new Point(point.x - comp.getX(), point.y - comp.getY());
dragComp = comp;
}
}
}
#Override
public void mouseReleased(MouseEvent e) {
offset = null;
dragComp = null;
}
#Override
public void mouseDragged(MouseEvent e) {
if (dragComp != null) {
Point p = e.getPoint();
Point dragP = new Point(p.x - offset.x, p.y - offset.y);
dragComp.setLocation(dragP);
}
}
};
addMouseListener(ma);
addMouseMotionListener(ma);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
}
public class AbsoluateLayoutManager implements LayoutManager2 {
#Override
public void addLayoutComponent(Component comp, Object constraints) {
}
#Override
public Dimension maximumLayoutSize(Container target) {
return preferredLayoutSize(target);
}
#Override
public float getLayoutAlignmentX(Container target) {
return 0.5f;
}
#Override
public float getLayoutAlignmentY(Container target) {
return 0.5f;
}
#Override
public void invalidateLayout(Container target) {
}
#Override
public void addLayoutComponent(String name, Component comp) {
}
#Override
public void removeLayoutComponent(Component comp) {
}
#Override
public Dimension preferredLayoutSize(Container parent) {
int maxX = 0;
int maxY = 0;
for (Component comp : parent.getComponents()) {
Dimension size = comp.getPreferredSize();
maxX = Math.max(comp.getX() + size.width, maxX);
maxY = Math.max(comp.getY() + size.height, maxY);
}
return new Dimension(maxX, maxY);
}
#Override
public Dimension minimumLayoutSize(Container parent) {
return preferredLayoutSize(parent);
}
#Override
public void layoutContainer(Container parent) {
for (Component comp : parent.getComponents()) {
Dimension size = comp.getPreferredSize();
comp.setSize(size);
}
}
}
}
You might also consider something like this example which is a "percentage" based constraint to lay out components, so thay are always at a given perctange point within the container
Related
I am using practically the same method for moving the JFrame as Java - Custom shaped draggable JFrame
I have a class that extends JPanel. In the said class, I have the previous x and y set to variables like so:
private int pressX, pressY
Then, in the mousePressed method, I have:
pressX = e.getX();
pressY = e.getY();
Finally, in mouseDragged, I have:
mainFrame.setLocation((int) Math.round(mainFrame.getLocation().getX() + e.getX() - pressX), (int) Math.round(mainFrame.getLocation().getY() + e.getY() - pressY));
However, when dragging the window around, there is a fair amount of lag or some kind of problem. Here is a video to show what happens visually.
https://i.imgur.com/KWtbE1s.gifv
I am using the swing library and am repainting using a Timer that ticks roughly every two milliseconds.
Edit:
I modified the code such that the points were relative to the JPanel, but the problem still occurs.
dragMe = new Draggable() {
private static final long serialVersionUID = 1L;
private Point press;
#Override
public void mouseClicked(MouseEvent e) {}
#Override
public void mousePressed(MouseEvent e) {
press = SwingUtilities.convertPoint(this, e.getPoint(), mainFrame);
}
#Override
public void mouseReleased(MouseEvent e) {}
#Override
public void mouseEntered(MouseEvent e) {}
#Override
public void mouseExited(MouseEvent e) {}
#Override
public void mouseDragged(MouseEvent e) {
Point drag = SwingUtilities.convertPoint(this, e.getPoint(), mainFrame);
mainFrame.setLocation((int) Math.round(mainFrame.getLocation().getX() + drag.getX() - press.getX()), (int) Math.round(mainFrame.getLocation().getY() + drag.getY() - press.getY()));
}
#Override
public void mouseMoved(MouseEvent e) {}
};
Edit 2:
Unfortunately, the example I have created here works perfectly, and I don't know why it is not working in the true code. I even tried copying the exact class I made in this example into the real application, and the problem still occurs in the true code.
It looks like I will have to look more into this.
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
import javax.swing.WindowConstants;
public class Test extends JPanel implements ActionListener {
private static final long serialVersionUID = 1L;
private static JFrame frame;
public static void main(String[] args) {
class Draggable extends JPanel implements MouseListener, MouseMotionListener {
private static final long serialVersionUID = 1L;
private Point press;
public Draggable() {
addMouseListener(this);
addMouseMotionListener(this);
}
#Override
public void mouseClicked(MouseEvent e) {}
#Override
public void mousePressed(MouseEvent e) {
press = SwingUtilities.convertPoint(this, e.getPoint(), frame);
}
#Override
public void mouseReleased(MouseEvent e) {}
#Override
public void mouseEntered(MouseEvent e) {}
#Override
public void mouseExited(MouseEvent e) {}
#Override
public void mouseDragged(MouseEvent e) {
Point drag = SwingUtilities.convertPoint(this, e.getPoint(), frame);
frame.setLocation((int) Math.round(frame.getLocation().getX() + drag.getX() - press.getX()), (int) Math.round(frame.getLocation().getY() + drag.getY() - press.getY()));
}
#Override
public void mouseMoved(MouseEvent e) {}
}
Test t = new Test();
t.setBounds(0, 0, 1200, 600);
t.setVisible(true);
Draggable drag = new Draggable();
drag.setBounds(24, 24, 24, 24);
drag.setVisible(true);
Timer repaintTimer = new Timer(2, t);
frame = new JFrame();
frame.setSize(1200, 600);
frame.add(t);
frame.add(drag);
Dimension dim = java.awt.Toolkit.getDefaultToolkit().getScreenSize();
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
frame.setLocation((dim.width - frame.getWidth()) / 2, (dim.height - frame.getHeight()) / 2);
frame.getContentPane().setLayout(null);
frame.setAlwaysOnTop(true);
frame.setResizable(false);
repaintTimer.start();
frame.setVisible(true);
frame.requestFocus();
}
#Override
public void actionPerformed(ActionEvent e) {
repaint();
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(Color.BLACK);
g.fillRect(24, 24, 24, 24);
}
}
Check out Moving Windows for a class that does this for you.
It is more complicated than the basic dragging logic because it supports other features which you may not need.
The basic logic (from the above class) for dragging a frame when adding the listeners to one (or more) components of the frame would be:
MouseInputAdapter mia = new MouseInputAdapter()
{
Point location;
Point pressed;
public void mousePressed(MouseEvent me)
{
pressed = me.getLocationOnScreen();
Window window = SwingUtilities.windowForComponent(me.getComponent());
location = window.getLocation();
}
public void mouseDragged(MouseEvent me)
{
Point dragged = me.getLocationOnScreen();
int x = (int)(location.x + dragged.getX() - pressed.getX());
int y = (int)(location.y + dragged.getY() - pressed.getY());
Window window = SwingUtilities.windowForComponent(me.getComponent());
window.setLocation(x, y);
}
};
panel.addMouseListener(mia);
panel.addMouseMotionListener(mia);
Edit:
Here is some more generic code that allows you to move a component directly or indirectly by specify a parent component to receive the events:
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.event.*;
public class BasicComponentMover extends MouseInputAdapter
{
/*
** A class that allows you to drag a component around a panel.
** Optionally you can specify a "parent" component, in which case
** the MouseEvents will be converted to be relative to this component
** which will then be dragged. This will allow you to drag a JFrame by
** dragging a component that has been added to the frame.
*/
private Component parent;
private Component destination;
private Point pressed;
BasicComponentMover() {}
{
}
BasicComponentMover(Component parent)
{
this.parent = parent;
}
#Override
public void mousePressed(MouseEvent me)
{
destination = (parent == null) ? me.getComponent() : parent;
pressed = SwingUtilities.convertPoint(me.getComponent(), me.getPoint(), destination);
}
#Override
public void mouseDragged(MouseEvent me)
{
Point location = destination.getLocation();
Point drag = SwingUtilities.convertPoint(me.getComponent(), me.getPoint(), destination);
int x = (int)(location.x - pressed.getX() + drag.getX());
int y = (int)(location.y - pressed.getY() + drag.getY());
destination.setLocation(x, y);
}
private static void createAndShowGUI()
{
JPanel background = new JPanel(null);
background.setPreferredSize(new Dimension(300, 300));
background.setSize(background.getPreferredSize());
background.setBackground(Color.RED);
JPanel foreground = new JPanel(null);
foreground.setPreferredSize(new Dimension(50, 50));
foreground.setSize(foreground.getPreferredSize());
foreground.setBackground(Color.BLUE);
background.add(foreground);
JFrame frame = new JFrame("BasicComponentMover");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(null);
frame.add(background);
frame.setSize(500, 500);
frame.setLocationByPlatform( true );
frame.setVisible( true );
// Drag frame by dragging the red background panel
BasicComponentMover bcm1 = new BasicComponentMover(frame);
background.addMouseListener(bcm1);
background.addMouseMotionListener(bcm1);
// Drag the blue forground component around the red background
BasicComponentMover bcm2 = new BasicComponentMover();
foreground.addMouseListener(bcm2);
foreground.addMouseMotionListener(bcm2);
// Or, drage the background around the frame
BasicComponentMover bcm3 = new BasicComponentMover(background);
// foreground.addMouseListener(bcm3);
// foreground.addMouseMotionListener(bcm3);
}
public static void main(String[] args) throws Exception
{
EventQueue.invokeLater( () -> createAndShowGUI() );
/*
EventQueue.invokeLater(new Runnable()
{
public void run()
{
createAndShowGUI();
}
});
*/
}
}
Try dragging the blue/red components to see the difference.
So basically if I put JPanels inside a JPanel that uses GridBagLayout and I restrict the size with setPreferredSize, eventually it reaches a point where it can't hold all of them, and it exhibits the behavior shown in the attached picture:
I'm making an accordion. This is just an example to showcase the problem I'm having. Each part of the accordion can open individually and they're of arbitrary size and get added on the fly. Its easy enough to get the heights of all the individual panels and compare them against the total height, but when too many are added it exhibits the crunching behavior I've shown. This also shrinks the heights so its much more difficult to determine when the crunching has happened. I would have to cache heights and somehow pre-calculate the heights of the new parts getting added. The end goal is to remove older panels when a new panel is added and there isn't enough room for it.
Is there an easy way to determine what height something would be if it weren't constrained, or maybe a supported way to detect when such crunching has is happening (so I can quickly thin it out before it gets painted again)? An option that makes GridBagLayout behave like some other layouts and overflow into hammerspace instead of compressing would work too.
Code for example:
import java.awt.*;
import java.awt.event.*;
import javaisms.out;
import javax.swing.*;
public class FoldDrag extends JLayeredPane {
public TexturedPanel backingPanel = new TexturedPanel(new GridBagLayout(),"data/gui/grayerbricks.png");
static JPanel windowbase=new JPanel();
static JPanel restrictedpanel=new JPanel(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
public FoldDrag() {
JButton addpan = new JButton("Add things");
windowbase.add(addpan);
windowbase.add(restrictedpanel);
restrictedpanel.setBackground(Color.red);
restrictedpanel.setPreferredSize(new Dimension(200,200));
gbc.weighty=1;
gbc.weightx=1;
gbc.gridx=0;
gbc.gridy=0;
gbc.gridheight=1;
gbc.gridwidth=1;
gbc.fill=GridBagConstraints.HORIZONTAL;
addpan.addActionListener(new ActionListener() {
int number=0;
#Override
public void actionPerformed(ActionEvent e)
{
number++;
gbc.gridy=number;
JPanel tmppanel = new JPanel();
tmppanel.setPreferredSize(new Dimension(100,30));
if(number%3==0)
tmppanel.setBackground(Color.blue);
if(number%3==1)
tmppanel.setBackground(Color.yellow);
if(number%3==2)
tmppanel.setBackground(Color.green);
restrictedpanel.add(tmppanel,gbc);
restrictedpanel.validate();
}
});
windowbase.setVisible(true);
}
private static void createAndShowUI() {
JFrame frame = new JFrame("DragLabelOnLayeredPane");
frame.getContentPane().add(windowbase);
FoldDrag thedrag=new FoldDrag();
windowbase.add(thedrag);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setPreferredSize(new Dimension(300,300));
frame.pack();
frame.setResizable(true);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public static void main(String[] args) {
out.active=true;
java.awt.EventQueue.invokeLater(new Runnable() {
public void run() {
createAndShowUI();
}
});
}
}
EDIT: Seems I didn't describe my version of the accordion very well. Here's a link.
You have particular requirement which may be better served through the use of it's layout manager. This provides you the ability to control every aspect of the layout without the need to resort to hacks or "work arounds" which never quite work or have bizarre side effects
public class AccordionLayout implements LayoutManager {
// This "could" be controlled by constraints, but that would assume
// that more then one component could be expanded at a time
private Component expanded;
public void setExpanded(Component expanded) {
this.expanded = expanded;
}
public Component getExpanded() {
return expanded;
}
#Override
public void addLayoutComponent(String name, Component comp) {
}
#Override
public void removeLayoutComponent(Component comp) {
}
#Override
public Dimension preferredLayoutSize(Container parent) {
Dimension size = minimumLayoutSize(parent);
if (expanded != null) {
size.height -= expanded.getMinimumSize().height;
size.height += expanded.getPreferredSize().height;
}
return size;
}
#Override
public Dimension minimumLayoutSize(Container parent) {
int height = 0;
int width = 0;
for (Component comp : parent.getComponents()) {
width = Math.max(width, comp.getPreferredSize().width);
height += comp.getMinimumSize().height;
}
return new Dimension(width, height);
}
#Override
public void layoutContainer(Container parent) {
Insets insets = parent.getInsets();
int availableHeight = parent.getHeight() - (insets.top + insets.bottom);
int x = insets.left;
int y = insets.top;
int maxSize = 0;
Dimension minSize = minimumLayoutSize(parent);
if (expanded != null) {
minSize.height -= expanded.getMinimumSize().height;
// Try an honour the preferred size the expanded component...
maxSize = Math.max(expanded.getPreferredSize().height, availableHeight - minSize.height);
}
int width = parent.getWidth() - (insets.left + insets.right);
for (Component comp : parent.getComponents()) {
if (expanded != comp) {
comp.setSize(width, comp.getMinimumSize().height);
} else {
comp.setSize(width, maxSize);
}
comp.setLocation(x, y);
y += comp.getHeight();
}
}
}
And the runnable example...
This goes to the enth degree, creating a specialised component to act as each "fold", but this just reduces the complexity of the API from the outside, meaning, you just need to think about the title and the content and let the rest of the API take care of itself
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.FlowLayout;
import java.awt.Insets;
import java.awt.LayoutManager;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.border.LineBorder;
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 AccordionLayout layout;
public TestPane() {
layout = new AccordionLayout();
setLayout(layout);
AccordionListener listener = new AccordionListener() {
#Override
public void accordionSelected(Component comp) {
layout.setExpanded(comp);
revalidate();
repaint();
}
};
Color colors[] = {Color.RED, Color.BLUE, Color.CYAN, Color.GREEN, Color.MAGENTA, Color.ORANGE, Color.PINK, Color.YELLOW};
String titles[] = {"Red", "Blue", "Cyan", "Green", "Magenta", "Orange", "Pink", "Yellow"};
for (int index = 0; index < colors.length; index++) {
AccordionPanel panel = new AccordionPanel(titles[index], new ContentPane(colors[index]));
panel.setAccordionListener(listener);
add(panel);
}
}
}
public class ContentPane extends JPanel {
public ContentPane(Color background) {
setBackground(background);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(100, 100);
}
}
public interface AccordionListener {
public void accordionSelected(Component comp);
}
public class AccordionPanel extends JPanel {
private JLabel title;
private JPanel header;
private Component content;
private AccordionListener accordionListener;
public AccordionPanel() {
setLayout(new BorderLayout());
title = new JLabel("Title");
header = new JPanel(new FlowLayout(FlowLayout.LEADING));
header.setBackground(Color.GRAY);
header.setBorder(new LineBorder(Color.BLACK));
header.add(title);
add(header, BorderLayout.NORTH);
header.addMouseListener(new MouseAdapter() {
#Override
public void mouseClicked(MouseEvent e) {
AccordionListener listener = getAccordionListener();
if (listener != null) {
listener.accordionSelected(AccordionPanel.this);
}
}
});
}
public AccordionPanel(String title) {
this();
setTitle(title);
}
public AccordionPanel(String title, Component content) {
this(title);
setContentPane(content);
}
public void setAccordionListener(AccordionListener accordionListener) {
this.accordionListener = accordionListener;
}
public AccordionListener getAccordionListener() {
return accordionListener;
}
public void setTitle(String text) {
title.setText(text);
revalidate();
}
public String getText() {
return title.getText();
}
public void setContentPane(Component content) {
if (this.content != null) {
remove(this.content);
}
this.content = content;
if (this.content != null) {
add(this.content);
}
revalidate();
}
public Component getContent() {
return content;
}
#Override
public Dimension getMinimumSize() {
return header.getPreferredSize();
}
#Override
public Dimension getPreferredSize() {
Dimension size = content != null ? content.getPreferredSize() : super.getPreferredSize();
Dimension min = getMinimumSize();
size.width = Math.max(min.width, size.width);
size.height += min.height;
return size;
}
}
public class AccordionLayout implements LayoutManager {
// This "could" be controled by constraints, but that would assume
// that more then one component could be expanded at a time
private Component expanded;
public void setExpanded(Component expanded) {
this.expanded = expanded;
}
public Component getExpanded() {
return expanded;
}
#Override
public void addLayoutComponent(String name, Component comp) {
}
#Override
public void removeLayoutComponent(Component comp) {
}
#Override
public Dimension preferredLayoutSize(Container parent) {
Dimension size = minimumLayoutSize(parent);
if (expanded != null) {
size.height -= expanded.getMinimumSize().height;
size.height += expanded.getPreferredSize().height;
}
return size;
}
#Override
public Dimension minimumLayoutSize(Container parent) {
int height = 0;
int width = 0;
for (Component comp : parent.getComponents()) {
width = Math.max(width, comp.getPreferredSize().width);
height += comp.getMinimumSize().height;
}
return new Dimension(width, height);
}
#Override
public void layoutContainer(Container parent) {
Insets insets = parent.getInsets();
int availableHeight = parent.getHeight() - (insets.top + insets.bottom);
int x = insets.left;
int y = insets.top;
int maxSize = 0;
Dimension minSize = minimumLayoutSize(parent);
if (expanded != null) {
minSize.height -= expanded.getMinimumSize().height;
// Try an honour the preferred size the expanded component...
maxSize = Math.max(expanded.getPreferredSize().height, availableHeight - minSize.height);
}
int width = parent.getWidth() - (insets.left + insets.right);
for (Component comp : parent.getComponents()) {
if (expanded != comp) {
comp.setSize(width, comp.getMinimumSize().height);
} else {
comp.setSize(width, maxSize);
}
comp.setLocation(x, y);
y += comp.getHeight();
}
}
}
}
Now, if you're really up for a challenge, you could use something a animated layout proxy and do something like...
The end goal is to remove older panels when a new panel is added and there isn't enough room for it
I would guess that after you add a panel you compare the preferred height with the actual height. When the preferred height is greater you have a problem and you remove components as required.
So then the next problem is to use a layout manager that doesn't change the heights of the panels. This can still be done with the GridBagLayout. You just need to override the getMinimumSize() method to return the getPreferredSize() Dimension.
Each part of the accordion can open individually and they're of arbitrary size and get added on the fly
You might want to consider using the Relative Layout. You can add components whose preferred size will be respected. So you will be able to check when the preferred height is greater than the actual height.
Then you can also add components that will be sized based on the amount of space left in the panel. These would be your expanding panels.
So in your example you example when you expand an item you could configure that component to take up the entire space available. If you expand two items then they would each get half the space available.
Maybe something like this:
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class ExpandingPanel extends JPanel
{
private JPanel expanding;
public ExpandingPanel(String text, Color color)
{
setLayout( new BorderLayout() );
JButton button = new JButton( text );
add(button, BorderLayout.NORTH);
expanding = new JPanel();
expanding.setBackground( color );
expanding.setVisible( false );
add(expanding, BorderLayout.CENTER);
button.addActionListener( new ActionListener()
{
#Override
public void actionPerformed(ActionEvent e)
{
expanding.setVisible( !expanding.isVisible() );
Container parent = ExpandingPanel.this.getParent();
LayoutManager2 layout = (LayoutManager2)parent.getLayout();
if (expanding.isVisible())
layout.addLayoutComponent(ExpandingPanel.this, new Float(1));
else
layout.addLayoutComponent(ExpandingPanel.this, null);
revalidate();
repaint();
}
});
}
private static void createAndShowGUI()
{
RelativeLayout rl = new RelativeLayout(RelativeLayout.Y_AXIS);
rl.setFill( true );
JPanel content = new JPanel( rl );
content.add( new ExpandingPanel("Red", Color.RED) );
content.add( new ExpandingPanel("Blue", Color.BLUE) );
content.add( new ExpandingPanel("Green", Color.GREEN) );
JFrame frame = new JFrame("Expanding Panel");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add( content);
frame.setLocationByPlatform( true );
frame.setSize(200, 300);
frame.setVisible( true );
}
public static void main(String[] args)
{
EventQueue.invokeLater(new Runnable()
{
public void run()
{
createAndShowGUI();
}
});
}
}
You can tell something is "crunched" when panel.getPreferredSize().height != panel.getHeight() and panel.getPreferredSize().width != panel.getWidth()
I have a dialog where additional controls cause the dialog to resize when they appear. One day I will probably find a way to animate that, but for now I'm content with it just resizing. Problem is, it flickers.
I reduced the problem to a test:
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JPanel;
import javax.swing.JRootPane;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
/**
* Shows flickering when resizing a dialog.
*/
public class FlickerTest extends FakeJDialog
{
public FlickerTest()
{
super((Window) null, "Flicker Test");
JButton button = new JButton("Bigger!");
button.addActionListener(new ActionListener()
{
#Override
public void actionPerformed(ActionEvent event)
{
Window window = SwingUtilities.getWindowAncestor((Component) event.getSource());
window.setSize(window.getWidth(), window.getHeight() + 20);
}
});
JPanel contentPane = new JPanel(new BorderLayout());
contentPane.setOpaque(true);
contentPane.add(button, BorderLayout.PAGE_START);
JRootPane rootPane = new JRootPane();
rootPane.setContentPane(contentPane);
add(rootPane);
setResizable(false);
pack();
setLocationRelativeTo(null);
}
public static void main(String[] args) throws Exception
{
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
SwingUtilities.invokeLater(new Runnable()
{
#Override
public void run()
{
new FlickerTest().setVisible(true);
}
});
}
}
Each time I click the button, the window changes size. For a noticeable amount of time, the bottom of the dialog goes black. By recording my screen, I was able to get a screenshot demonstrating it:
How can I avoid this?
Further investigation:
The following subclass of Dialog exhibits the same flickering as JDialog:
import java.awt.Component;
import java.awt.Container;
import java.awt.Dialog;
import java.awt.Window;
import javax.swing.JLayeredPane;
import javax.swing.JRootPane;
import javax.swing.RootPaneContainer;
/**
* Minimal subclass of Dialog required to cause the flickering.
* If you comment out "implements RootPaneContainer", the flickering goes away.
*/
public class FakeJDialog extends Dialog implements RootPaneContainer
{
public FakeJDialog(Window owner, String title)
{
super(owner, title, Dialog.ModalityType.MODELESS);
}
public JRootPane getRootPane()
{
throw new UnsupportedOperationException();
}
public Container getContentPane()
{
throw new UnsupportedOperationException();
}
public void setContentPane(Container contentPane)
{
throw new UnsupportedOperationException();
}
public JLayeredPane getLayeredPane()
{
throw new UnsupportedOperationException();
}
public void setLayeredPane(JLayeredPane layeredPane)
{
throw new UnsupportedOperationException();
}
public Component getGlassPane()
{
throw new UnsupportedOperationException();
}
public void setGlassPane(Component glassPane)
{
throw new UnsupportedOperationException();
}
}
I find this kind of interesting, because merely commenting out the implements RootPaneContainer is somehow enough to completely change the behaviour. Something in Swing or AWT is obviously looking for this interface and treating those components specially. So this suggests that no subclass of JDialog would avoid the issue.
I don't believe there is a way around this with JDialog. However, java.awt.Dialog doesn't have this issue.
Try this, i have made an example which removes flickering almost completely
in addition u will get well-marked resize corner
/*
* resizing swing trick in Win7+Aero demo
* #author: s1w_
*/
import java.awt.event.*;
import java.awt.*;
import javax.swing.event.*;
import javax.swing.*;
class ResizeHookDemo extends JDialog {
private final static int width = 580, height = 350;
private final JFileChooser fc;
private java.awt.geom.GeneralPath gp;
public ResizeHookDemo() {
super((JDialog)null, "Choose File", true);
fc = new JFileChooser() {
#Override
public void paint(Graphics g) {
super.paint(g);
int w = getWidth();
int h = getHeight();
g.setColor(new Color(150, 150, 150, 200));
g.drawLine(w-7, h, w, h-7);
g.drawLine(w-11, h, w, h-11);
g.drawLine(w-15, h, w, h-15);
gp = new java.awt.geom.GeneralPath();
gp.moveTo(w-17, h);
gp.lineTo(w, h-17);
gp.lineTo(w, h);
gp.closePath();
}
};
fc.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
if (e.getActionCommand().equals("CancelSelection")) {
setVisible(false);
// action...
}
else if (e.getActionCommand().equals("ApproveSelection")) {
setVisible(false);
// action...
}
}
});
MouseInputListener resizeHook = new MouseInputAdapter() {
private Point startPos = null;
public void mousePressed(MouseEvent e) {
if (gp.contains(e.getPoint()))
startPos = new Point(getWidth()-e.getX(), getHeight()-e.getY());
}
public void mouseReleased(MouseEvent mouseEvent) {
startPos = null;
}
public void mouseMoved(MouseEvent e) {
if (gp.contains(e.getPoint()))
setCursor(Cursor.getPredefinedCursor(Cursor.SE_RESIZE_CURSOR));
else
setCursor(Cursor.getDefaultCursor());
}
public void mouseDragged(MouseEvent e) {
if (startPos != null) {
int dx = e.getX() + startPos.x;
int dy = e.getY() + startPos.y;
setSize(dx, dy);
repaint();
}
}
};
fc.addMouseMotionListener(resizeHook);
fc.addMouseListener(resizeHook);
fc.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 20));
add(fc);
setResizable(false);
setMinimumSize(new Dimension(width, height));
setDefaultCloseOperation(HIDE_ON_CLOSE);
setLocationRelativeTo(null);
}
public static void main(String args[]) {
System.out.println("Starting demo...");
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
new ResizeHookDemo().setVisible(true);
}
});
}
}
Can't seem to figure out how to only show one circle. Was trying to //g.clearRect(0, 0, 400, 400); but that clears the background too. Was also trying to just fill the background with yellow again but couldn't get that working either. Any suggestions?
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
public class JMouseFrame extends JFrame
implements MouseListener {
Container con = null;
int x, y;
int size;
public JMouseFrame() {
setTitle("JMouseFrame");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
con = this.getContentPane();
addMouseListener(this);
}
public void mousePressed(MouseEvent e) {
x = e.getX();
y = e.getY();
}
public void mouseClicked(MouseEvent e) {
int whichButton = e.getButton();
if (whichButton == MouseEvent.BUTTON1) {
size = 15;
} else if (whichButton == MouseEvent.BUTTON3) {
size = 4;
}
repaint();
}
public void mouseEntered(MouseEvent e) {
con.setBackground(Color.yellow);
}
public void mouseExited(MouseEvent e) {
con.setBackground(Color.black);
}
public void paint(Graphics g) {
//g.clearRect(0, 0, 400, 400);
g.drawOval(x - size, y - size, size * 3, size * 3);
}
public static void main(String[] args) {
JMouseFrame mFrame = new JMouseFrame();
mFrame.setSize(400, 400);
mFrame.setVisible(true);
}
public void mouseReleased(MouseEvent e) {
}
}
It looks like you're mixing AWT and Swing painting. Instead, override paintComponent(), as suggested in the example below. See Painting in AWT and Swing for details.
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Point;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.JFrame;
import javax.swing.JPanel;
/** #see http://stackoverflow.com/questions/3898775 */
public class MousePanel extends JPanel {
private static final int SIZE = 20;
Point p = new Point(Short.MAX_VALUE, Short.MAX_VALUE);
public MousePanel() {
this.setPreferredSize(new Dimension(400, 400));
this.setBackground(Color.yellow);
this.addMouseListener(new MouseHandler());
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(Color.black);
g.drawOval(p.x - SIZE, p.y - SIZE, SIZE * 2, SIZE * 2);
}
private final class MouseHandler extends MouseAdapter {
#Override
public void mousePressed(MouseEvent e) {
p = e.getPoint();
repaint();
}
}
private void display() {
JFrame f = new JFrame("MousePanel");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.add(this);
f.pack();
f.setLocationRelativeTo(null);
f.setVisible(true);
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
new MousePanel().display();
}
});
}
}
Addendum:
I can't figure out how to make the code you posted work with what I have...
You can restore your mouse methods to the MouseHandler almost verbatim. The only difference is the need to qualify this, e.g.
#Override
public void mouseClicked(MouseEvent e) {
int whichButton = e.getButton();
if (whichButton == MouseEvent.BUTTON1) {
MousePanel.this.size = 15;
} else if (whichButton == MouseEvent.BUTTON3) {
MousePanel.this.size = 4;
}
repaint();
}
#Override
public void mouseEntered(MouseEvent e) {
MousePanel.this.setBackground(Color.yellow);
}
#Override
public void mouseExited(MouseEvent e) {
MousePanel.this.setBackground(Color.black);
}
I'm trying to make an applet which I can simply drag an image. And I want image object to listen events. So here is the applet code which simple run in a thread:
import java.awt.*;
import java.net.URL;
import javax.swing.JApplet;
public class Client extends JApplet implements Runnable {
private static final long serialVersionUID = 1L;
MediaTracker mediaTracker;
Image [] imgArray;
Tas t1;
public void init()
{
mediaTracker = new MediaTracker(this);
imgArray = new Image[1];
URL base = getCodeBase();
imgArray[0] = getImage(base,"okey.png");
mediaTracker.addImage(imgArray[0],1);
try {
mediaTracker.waitForAll();
} catch (InterruptedException e) {
e.printStackTrace();
}
t1 = new Tas(this, new Rectangle(0, 0, imgArray[0].getWidth(this), imgArray[0].getHeight(this)), imgArray[0]);
Thread t = new Thread(this);
t.start();
}
public void paint(Graphics g)
{
t1.paint(g);
}
#Override
public void run() {
while(true){
//System.out.println("run");
repaint();
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
And the class of object which holds image is:
import java.awt.Graphics;
import java.awt.Image;
import java.awt.Rectangle;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import javax.swing.JPanel;
#SuppressWarnings("serial")
public class Movable extends JPanel implements MouseListener {
public Client mainObj;
public Rectangle rect;
public Image image;
public Movable(Client mainObj, Rectangle rect, Image image) {
this.mainObj = mainObj;
this.rect = rect;
this.image = image;
addMouseListener(this);
}
public void paint(Graphics g) {
g.drawImage(image, rect.x, rect.y, rect.width, rect.height, this);
}
#Override
public void mouseClicked(MouseEvent arg0) {
System.out.println("clicked");
}
#Override
public void mouseEntered(MouseEvent arg0) {
}
#Override
public void mouseExited(MouseEvent arg0) {
}
#Override
public void mousePressed(MouseEvent arg0) {
System.out.println("pressed");
}
#Override
public void mouseReleased(MouseEvent arg0) {
}
}
#SuppressWarnings("serial")
class Tas extends Movable{
public String name = "";
public Tas(Client mainObj, Rectangle rect, Image image) {
super(mainObj, rect, image);
}
}
I can see the image in my applet but nothing happens when I click in or out of the image. So what's wrong with this code.
Assuming that Tas in code #1 is Moveable in code #2...
You don't actually use the Moveable as a Component, but instead ask it to paint itself onto the Applet's graphics context, here:
public void paint(Graphics g)
{
t1.paint(g);
}
Instead you should add an instance of Moveable onto the Applet's container, wherein painting will become automatic, and it will start to receive mouse events. You can also remove that paint() method then too.
First of all you should never override the paint method of a top level container (JApplet, JFrame, JDialog).
Then to do custom painting on other Swing components you override the paintComponent() method of the component, NOT the paint() method. Read the Swing tutorial on Custom Painting. So first fix those problems.
I'm not sure what the point of the Thread is but remove it from your code until you solve your other problems. If you are trying to do animation, then you should be using a Swing Timer, not a Thread.
If you want to see some code for dragging components you can take a look at Moving Windows for some generic code.
This is a working solution. It is not an applet but you can easily convert that. Hope it helps :
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionAdapter;
import java.awt.geom.Point2D;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JPanel;
#SuppressWarnings("serial")
public class ImagePanel extends JPanel {
Image image;
Point2D axis = new Point2D.Double();
boolean drag = false;
Point2D dragPoint = new Point2D.Double();
public ImagePanel(Image image) {
this.image = image;
setPreferredSize(new Dimension(300,300));
addMouseListener(new MouseAdapter() {
#Override
public void mousePressed(MouseEvent e) {
drag = true;
dragPoint = e.getPoint();
}
#Override
public void mouseReleased(MouseEvent e) {
drag = false;
}
});
addMouseMotionListener(new MouseMotionAdapter() {
#Override
public void mouseDragged(MouseEvent e) {
if (drag) {
axis.setLocation(axis.getX()
+ (e.getPoint().x - dragPoint.getX()), axis.getY()
+ (e.getPoint().y - dragPoint.getY()));
dragPoint = e.getPoint();
repaint();
}
}
});
}
#Override
public void paintComponent(Graphics g) {
g.setColor(Color.white);
g.fillRect(0, 0, getWidth(), getHeight());
g.drawImage(image, (int) axis.getX(), (int) axis.getY(), null);
}
public static void main(String[] args) {
try {
JFrame f = new JFrame();
f.getContentPane().add(
new ImagePanel(ImageIO.read(new File("image.jpg"))));
f.pack();
f.setVisible(true);
} catch (IOException e) {
e.printStackTrace();
}
}
}
The simple answer is - you don't have code to do anything in mousePressed() or mouseReleased().
There are lots of other problems in the code though...
Simplest solution I could come up with -
public class Client extends JApplet {
private MouseInputAdapter myMouseListener = new MyMouseListener();
public void init() {
// usually a very bad idea, but needed here
// since you want to move things around manually
setLayout(null);
// assuming this will get used often, so making it a method.
addLabelForImage(getImage(getCodeBase(), "okay.png"));
}
private void addLabelForImage(Image image) {
ImageIcon icon = new ImageIcon(image);
JLabel l = new JLabel(icon);
add(l);
l.setSize(l.getPreferredSize());
// you'll probably want some way to calculate initial position
// of each label based on number of images, size of images,
// size of applet, etc. - just defaulting to 100,100 now.
l.setLocation(100, 100);
l.addMouseListener(myMouseListener);
l.addMouseMotionListener(myMouseListener);
}
// Made this a MouseInputAdapter because I assume you may want to handle
// other types of mouse events later...
private static class MyMouseListener extends MouseInputAdapter {
#Override
public void mouseDragged(MouseEvent e) {
// when the mouse is dragged over a the component this listener is
// attached to (ie - one of the labels) convert the point of the mouse
// event from the internal component coordinates (0,0 is upper right
// corner of each label), to it's parent's coordinates (0,0 is upper
// right corner of the applet), and set the components location to
// that point.
Component theLabel = e.getComponent();
Container theApplet = theLabel.getParent();
Point labelPoint = e.getPoint();
Point appletPoint = SwingUtilities.convertPoint(
theLabel, labelPoint, theApplet );
theLabel.setLocation(appletPoint);
}
}
}