I've seen a number of posters argue that Swing components should not be extended, and that functionality should instead be added via composition. So say I want to reusably create a JPanel with absolute positioning (no layout manager) in which I can reposition components with the mouse:
public class MoveableComponentPanel
{
private final JPanel m_panel;
public MoveableComponentPanel()
{
m_panel = new JPanel(null);
}
public void add(Component c)
{
m_panel.add(c);
c.addMouseListener(m_mover);
c.setSize(c.getPreferredSize());
}
private final MouseListener m_mover = new MouseListener()
{
private int m_startX, m_startY;
#Override
public void mousePressed(MouseEvent e)
{
if (e.getButton() == MouseEvent.BUTTON1)
{
m_startX = e.getX();
m_startY = e.getY();
}
}
#Override
public void mouseReleased(MouseEvent e)
{
if (e.getButton() == MouseEvent.BUTTON1)
{
Component c = e.getComponent();
Point p = c.getLocation();
p.x += e.getX() - m_startX;
p.y += e.getY() - m_startY;
c.setLocation(p);
c.repaint();
}
}
#Override
public void mouseClicked(MouseEvent e) {}
#Override
public void mouseEntered(MouseEvent e) {}
#Override
public void mouseExited(MouseEvent e) {}
};
}
Looks nice, but there's no way to add the panel to a container. Further headaches if I want to allow a calling class to interact with the panel, e.g. change size, background color, register event listeners, etc. Either I add a getPanel() accessor, which breaks encapsulation, or write pass-through methods for the JPanel methods/properties I want to expose (potentially many). Is there some other strategy I'm missing here? What is the accepted best practice?
in which I can reposition components with the mouse:
Then you just add the MouseListener/MouseMotionListener to the components that you want to drag. There is no need to extend any component to add that functionality.
Check out the Component Mover class for examples of ways to do basic dragging of a component as well as a more complex dragging solution.
Related
I have made a custome JFrame called mainWindow that is undecorated. I have added a JLabel named dragBar at the top of it and gave it desired dimensions (as shown below). When I click on the label I make the window move according to my mouse by using two listeners; one MouseListener and one MouseMotionListener.
The problem is that whenever I click on the label the window does move according to my mouse's location but it spazzes all over my screen until I stop moving the mouse or let go of the click button.
Is my method wrong? What is causing this issue?
Here is my code:
//what i use to make the dragBar
private JLabel dragBar = new JLabel();
private Point initialClick; //the initial point where I click on the label
//my mainWindow JFrame
private JFrame mainWindow = new JFrame();
private Dimension mainWindowSize = new Dimension(680,410);
//the code I use to set up my mainWindow JFrame
mainWindow.setUndecorated(true);
mainWindow.setShape(new RoundRectangle2D.Double(0, 0, 670, 400, 5, 5));
mainWindow.setSize(mainWindowSize);
mainWindow.setMinimumSize(mainWindowSize);
mainWindow.setResizable(false);
mainWindow.setLocation((screen_size.width/2)- mainWindow.getWidth()/2, (screen_size.height/2)- mainWindow.getHeight()/2);
mainWindow.getContentPane().setBackground(new Color(46, 48, 50, 255));
//the code I use to set up my dragBar label
topContainer.add(dragBar,3); //a Jlayeredpane that contains the dragBar label and is added to the mainWindow
dragBar.setSize(topContainer.getSize());
dragBar.setLocation(0,0);
dragBar.addMouseListener(new MouseListener() {
#Override public void mouseClicked(MouseEvent e) {}
#Override
public void mousePressed(MouseEvent e) {
initialClick = e.getPoint();
}
#Override public void mouseReleased(MouseEvent e) {}
#Override public void mouseEntered(MouseEvent e) {}
#Override public void mouseExited(MouseEvent e) {}
});
dragBar.addMouseMotionListener(new MouseMotionListener() {
#Override
public void mouseDragged(MouseEvent e) {
int changeX = e.getX()-initialClick.x;
int changeY = e.getY()-initialClick.y;
mainWindow.setLocation(mainWindow.getX()+changeX, mainWindow.getY()+changeY);
}
#Override public void mouseMoved(MouseEvent e) {}
});
a Jlayeredpane that contains the dragBar label
Don't think I would use a JLayeredPane for this. Just add a component to the BorderLayout.PAGE_START of the frame.
The basic logic for dragging a component is something like:
public class DragListener extends MouseInputAdapter
{
Point location;
MouseEvent pressed;
public void mousePressed(MouseEvent me)
{
pressed = me;
}
public void mouseDragged(MouseEvent me)
{
Component component = me.getComponent();
location = component.getLocation(location);
int x = location.x - pressed.getX() + me.getX();
int y = location.y - pressed.getY() + me.getY();
component.setLocation(x, y);
}
}
However in your case you don't want to drag the label, but instead drag the window, you your logic needs to forward the events to the window.
Check out Moving Windows for a more complex implementation of the above code that also adds additional features that easily allow you to move a window.
I want to add a hovering-effect to my customized Swing.JButton similar to the icon on my Chrome Browser:
Before hover >>
After hover >>
I am able to set the button in the "before" status when it is created, but I am not able to create the "border + raised-background" when it is hovered. When I try to re-add the border to the button, I got a moving effect as after repainting a new border is inserted.
This is my current code:
public class MyButton extends JButton implements MouseListener {
public MyButton(String iconPath, String toolTip) {
super(new ImageIcon(TipButton.class.getResource(iconPath)));
addMouseListener(this);
setBorder(null);
setBorderPainted(false);
setFocusPainted(false);
setOpaque(false);
setContentAreaFilled(false);
setToolTipText(toolTip);
}
public MyButton(String iconPath, String name, String toolTip) {
this(observers, iconPath, toolTip);
setText(name);
}
#Override
public void mouseClicked(MouseEvent e) {}
#Override
public void mousePressed(MouseEvent e) {}
#Override
public void mouseReleased(MouseEvent e) {}
#Override
public void mouseEntered(MouseEvent e) {
if (e.getSource() != this) return;
setBorder(BorderFactory.createBevelBorder(BevelBorder.RAISED));
}
#Override
public void mouseExited(MouseEvent e) {
if (e.getSource() != this) return;
setBorder(null);
}
}
I suppose the main logic should be in the methods mouseEntered/mouseExited but I don't know how to get the wanted effect. Any idea?
I think I have found a solution. Using EmptyBorder with the same sizes (insets) of a raised border does the trick. Code:
public class SwingUtils {
public static JButton createMyButton (String iconPath, String toolTip) {
final JButton b = new JButton (new ImageIcon(SwingUtils.class.getResource(iconPath)));
final Border raisedBevelBorder = BorderFactory.createRaisedBevelBorder();
final Insets insets = raisedBevelBorder.getBorderInsets(b);
final EmptyBorder emptyBorder = new EmptyBorder(insets);
b.setBorder(emptyBorder);
b.setFocusPainted(false);
b.setOpaque(false);
b.setContentAreaFilled(false);
b.setToolTipText(toolTip);
b.getModel().addChangeListener(new ChangeListener() {
#Override
public void stateChanged(ChangeEvent e) {
ButtonModel model = (ButtonModel) e.getSource();
if (model.isRollover()) {
b.setBorder(raisedBevelBorder);
} else {
b.setBorder(emptyBorder);
}
}
});
return b;
}
}
Note: as mKorbel says it would be using ChangeListener and the button created in a factory method instead of subclass JButton.
Use different images for every state. You can set a different Icon for selected, disabled selected, disabled, pressed, rollover, rolloverEnabled, rolloverSelected. More info here.
In java 7 there is a BevelBorder class that looks to be what you are looking for. The 2 methods you will probably be interested in are
paintRaisedBevel(Component c, Graphics g, int x, int y, int width, int height)
and
paintLoweredBevel(Component c, Graphics g, int x, int y, int width, int height)
Here is the documentation on the class: http://docs.oracle.com/javase/7/docs/api/javax/swing/border/BevelBorder.html
there (maybe) no reason to create extends JButton implements MouseListener {, there aren't overroaded any of JButtons methods, and use composition with returns instead,
better could be to create a local variable and to use methods implemented in API
don't to use MouseListener for Buttons Components, all these events are implemented in JButtons API and correctly
use set(Xxx)Icon for JButton
easiest of ways is to use JButton.getModel from ChangeListener, for example
Hi I want to make my JPanel disappear so I wrote these lines of code
removeAll();
updateUI();
revalidate();
That only made the JComponents and JButtons disappear. I would like to make the images that I have displayed with the paint method disappear also. If I do setVisible(false), then I cannot add another JPanel behind it.
This is my class:
package screens;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class menuScreen extends JPanel implements MouseListener{
private static final long serialVersionUID = 1L;
//-------------VARIABLES---------------//
Image wallpaper = (Image)Toolkit.getDefaultToolkit().getImage(getClass().getResource("images/wallpaper.jpg"));
Image title_text = (Image)Toolkit.getDefaultToolkit().getImage(getClass().getResource("images/title-text.png"));
ImageIcon startGameimg = new ImageIcon(Toolkit.getDefaultToolkit().getImage(getClass().getResource("images/startGame.png")));
ImageIcon optionsimg = new ImageIcon(Toolkit.getDefaultToolkit().getImage(getClass().getResource("images/options.png")));
//JButton start = new JButton(basketball);
JLabel options = new JLabel(optionsimg);
JLabel startGame = new JLabel(startGameimg);
gameScreen gS = new gameScreen();
CardLayout scenechange = new CardLayout();
JPanel scenechange1 = new JPanel (scenechange);
//-------------PAINT FUNCTION----------//
public void paintComponent(Graphics g){
g.drawImage(wallpaper,0,0,this);
g.drawImage(title_text,0,0,this);
//g.drawImage(basketball1,110,180,this);
}
//-------------CONSTRUCTOR-------------//
public menuScreen(){
scenechange.addLayoutComponent(this,"menuScreen");
scenechange.addLayoutComponent(gS,"gameScreen");
//scenechange.show(this,"menuScreen");
this.setLayout(null);
this.add(options);
this.add(startGame);
startGame.setBounds(110,180,110,110);
options.setBounds(110,300,110,110);
startGame.addMouseListener(this);
options.addMouseListener(this);
}
public void mouseClicked(MouseEvent e) {
if(e.getSource() == (startGame)){
removeAll();
revalidate();
add(gS);
}
if(e.getSource() == (options)){
setVisible(false);
}
}
public void mousePressed(MouseEvent e) {
// TODO Auto-generated method stub
}
public void mouseReleased(MouseEvent e) {
// TODO Auto-generated method stub
}
public void mouseEntered(MouseEvent e) {
// TODO Auto-generated method stub
}
public void mouseExited(MouseEvent e) {
// TODO Auto-generated method stub
}
}//END OF CLASS startingScreen
Thanks in advance.
First, don't call updateUI, it's related to the Look and Feel and not (directly) to updating your components.
If you have provided a custom paint routine within in your panel, then you need away to stop it from painting the images (without preventing it from painting it's own content). removeXxx will remove child components that you have previously added to the container.
A little more code would be useful
UPDATE
Fisrt, the images your painting aren't components of you container, they are been "stamped", you need some way to tell the component not to the paint the images
public void paintComponent(Graphics g){
super.paintComponent(g); // this is super important
if (paintImages){ // you need to define and set this flag
g.drawImage(wallpaper,0,0,this);
g.drawImage(title_text,0,0,this);
}
}
Now, this will stop the images from been painted.
If, however, you no longer want to use the component (ie, you want to remove it from the screen so you can place a new component on the screen in its place), you need to remove this component from it's parent, which Code-Guru has suggested (so I won't steal his answer ;))
UPDATE
Okay, you had a kernel of an idea but either didn't quite know how to implement it or decided to discard it.
Basically, from the looks of your code, you were either trying to, or had, implement a CardLayout, unfortunately, you kind of got the wrong idea with it.
With CardLayout, you need to "controller", a component that is responsible for switching the screens...
public class ScreenController extends JPanel {
private static final long serialVersionUID = 1L;
//-------------VARIABLES---------------//
MenuScreen ms = new MenuScreen();
GameScreen gs = new GameScreen();
CardLayout sceneChange;
//-------------CONSTRUCTOR-------------//
public ScreenController() {
sceneChange = new CardLayout();
this.setLayout(sceneChange);
add(ms, "menuScreen");
add(gs, "gameScreen");
sceneChange.show(this, "menuScreen");
ms.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
if (e.getActionCommand().equalsIgnoreCase("startgame")) {
sceneChange.show(ScreenController.this, "gameScreen");
}
}
});
}
}//END OF CLASS startingScreen
Then you have your menu and game screens...
public class MenuScreen extends JPanel implements MouseListener {
private static final long serialVersionUID = 1L;
//-------------VARIABLES---------------//
//JButton start = new JButton(basketball);
JLabel options = new JLabel("Options");
JLabel startGame = new JLabel(" >> Start << ");
// gameScreen gS = new gameScreen();
BufferedImage wallpaper;
//-------------PAINT FUNCTION----------//
#Override
public void paintComponent(Graphics g) {
System.out.println("paint");
super.paintComponent(g);
if (wallpaper != null) {
g.drawImage(wallpaper, 0, 0, this);
}
}
//-------------CONSTRUCTOR-------------//
public MenuScreen() {
// Please handle your exceptions better
try {
wallpaper = ImageIO.read(getClass().getResource("/Menu.png"));
setPreferredSize(new Dimension(wallpaper.getWidth(), wallpaper.getHeight()));
} catch (IOException ex) {
ex.printStackTrace();
}
setLayout(new GridBagLayout());
Cursor cusor = Cursor.getPredefinedCursor(Cursor.HAND_CURSOR);
options.setCursor(cusor);
startGame.setCursor(cusor);
Font font = UIManager.getFont("Label.font").deriveFont(Font.BOLD, 48);
options.setFont(font);
startGame.setFont(font);
GridBagConstraints gbc = new GridBagConstraints();
gbc.gridx = 0;
gbc.gridy = 0;
this.add(options, gbc);
gbc.gridy++;
this.add(startGame, gbc);
startGame.addMouseListener(this);
options.addMouseListener(this);
}
public void mouseClicked(MouseEvent e) {
if (e.getSource() == (startGame)) {
fireActionPerformed("startGame");
}
if (e.getSource() == (options)) {
fireActionPerformed("gameOptions");
}
}
public void mousePressed(MouseEvent e) {
}
public void mouseReleased(MouseEvent e) {
}
public void mouseEntered(MouseEvent e) {
}
public void mouseExited(MouseEvent e) {
}
public void addActionListener(ActionListener listener) {
listenerList.add(ActionListener.class, listener);
}
public void removeActionListener(ActionListener listener) {
listenerList.remove(ActionListener.class, listener);
}
protected void fireActionPerformed(String cmd) {
ActionListener[] listeners = listenerList.getListeners(ActionListener.class);
if (listeners != null && listeners.length > 0) {
ActionEvent evt = new ActionEvent(this, ActionEvent.ACTION_PERFORMED, cmd);
for (ActionListener listener : listeners) {
listener.actionPerformed(evt);
}
}
}
}
Menu Screen...
And when you click start...the game screen...
Now this is an EXAMPLE. Please try and take the time to understand what it going on in the code before you march ahead and implement it. I used by own images, you'll need to get your own..
There are several ways to stop your JPanel from "appearing" depending on exactly what you want to accomplish. One was it to to call setOpaque(false);. I'm not entirely sure how this affects custom painting, though.
Another posibility is
Container parent = getParent().remove(this);
parent.validate();
A third posibility is to add a flag in your class which is set when you click on a JLabel (or better yet a JButton -- see comments below). Then in your paintComponent() method you can check the flag and draw accordingly.
Note:
You are incorrectly using a JLabel and mouse events to respond to user input. Typically in a Swing application, we use JButtons and ActionListeners to accomplish what you are trying to do here. One advantage of this is that you only have to implement one method called onActionPerformed() and don't need to worry about adding all the mouse event handlers that you don't want to even respond to.
Is there a possibility to move window by clicking on one of the panels in the window when that window is undecorated?
I have a main panel with matte border 40 pixels size, and few panels with controls inside, and I would like to move the window when clicking on that border. Is that possible?
You can place another panel over the panel with the border, leaving the border visible.Use the following code to move your window.
public class MotionPanel extends JPanel{
private Point initialClick;
private JFrame parent;
public MotionPanel(final JFrame parent){
this.parent = parent;
addMouseListener(new MouseAdapter() {
public void mousePressed(MouseEvent e) {
initialClick = e.getPoint();
getComponentAt(initialClick);
}
});
addMouseMotionListener(new MouseMotionAdapter() {
#Override
public void mouseDragged(MouseEvent e) {
// get location of Window
int thisX = parent.getLocation().x;
int thisY = parent.getLocation().y;
// Determine how much the mouse moved since the initial click
int xMoved = e.getX() - initialClick.x;
int yMoved = e.getY() - initialClick.y;
// Move window to this position
int X = thisX + xMoved;
int Y = thisY + yMoved;
parent.setLocation(X, Y);
}
});
}
}
I've been working with this code for a while now to make a custom titlebar for undecorated windows.
P.S.:You can generalize this example by extending JComponent instead of JPanel.
I have a main panel with matte border 40 pixels size, and few panels with controls inside, and I would like to move the window when clicking on that border
I think that ComponetMover by #camickr is right class for you
Yes, it is very possible. You need a MouseListener to listen on mouse events. you start moving on mousedown and stop moving on mouseup. Then you simply translate the window position by the same amount the mouse translates during that phase (calculate the delta bewteen old mouse position and new mouse position and add that to the frames position). You should be able to do this with a mouse listener fairly easily.
I have a simple solution from my project. Here is my undecorated JDialog class.
public class TimerDialog extends JDialog {
// some fields here
private Point mouseClickPoint; // Will reference to the last pressing (not clicking) position
private TimerDialog() {
initComponents();
addEventsForDragging();
}
private void addEventsForDragging() {
// Here is the code does moving
addMouseListener(new MouseAdapter() {
#Override
public void mousePressed(MouseEvent e) {
mouseClickPoint = e.getPoint(); // update the position
}
});
addMouseMotionListener(new MouseAdapter() {
#Override
public void mouseDragged(MouseEvent e) {
Point newPoint = event.getLocationOnScreen();
newPoint.translate(-mouseClickPoint.x, -mouseClickPoint.y); // Moves the point by given values from its location
setLocation(newPoint); // set the new location
}
});
}
private void initComponents() {
setLayout(new FlowLayout());
// adding components
setDefaultCloseOperation(DISPOSE_ON_CLOSE);
setAlwaysOnTop(true);
setUndecorated(true);
setResizable(false);
pack();
}
}
int xMoved = (thisX + e.getX()) - (thisX + initialClick.x);
thisX + -thisX = 0
int xMoved = e.getX()-initialClick.x;
What i'm using.
public class MouseLiestenerX implements MouseListener,MouseMotionListener{
private theFrame;
public MouseLiestenerX(Frame theFrame){
this.theFrame = theFrame;
}
private Point startClick;
public void mouseDragged(MouseEvent e) {
int deltaX = e.getX()-startClick.x;
int deltaY = e.getY()-startClick.y;
Core.getSp().setLocation(theFrame.getLocation().x+deltaX, theFrame.getLocation().y+deltaY);
}
public void mousePressed(MouseEvent e) {
startClick = e.getPoint();
}
public void mouseMoved(MouseEvent e){
}
#Override
public void mouseClicked(MouseEvent e) {
}
#Override
public void mouseEntered(MouseEvent e) {
// TODO Auto-generated method stub
}
#Override
public void mouseExited(MouseEvent e) {
// TODO Auto-generated method stub
}
#Override
public void mouseReleased(MouseEvent e) {
}
}
and in your Frame constructor
MouseLiestenerX IMove = new MouseLiestenerX(this);
addMouseListener(IMove);
addMouseMotionListener(IMove);
Excuse me:
I just can't know how to link these successive operation?
Mouse pressed and then drag then release. If an user doesn't do this operation some action won't happen...
Should I add code as the is already pressed to distinguish that?
The constant MOUSE_MOVED doesn't work since Eclipse told me it doesn't know it although I find the parameter in mouse event api
I don't know what's going on... Please help!
Implement a MouseInputListener using a MouseInputAdapter subclass and handle the mousePressed, mouseDragged, and the mouseReleased events.
Take a look at this tutorial for examples.
Here is a simple class that encapsulates the drag detection:
public abstract static class MouseDragListener {
java.awt.Component component;
MouseEvent dragStart;
public MouseDragListener(java.awt.Component component) {
super();
this.component = component;
component.addMouseMotionListener(new MouseAdapter() {
public void mouseMoved(MouseEvent e) {
dragStart = null;
}
public void mouseDragged(MouseEvent e) {
if (dragStart == null)
dragStart = e;
}
});
component.addMouseListener(new MouseAdapter() {
public void mouseReleased(MouseEvent e) {
if (dragStart != null) {
dragReleased(dragStart, e);
}
}
});
}
then to use:
new MouseDragListener(center){
void dragReleased(MouseEvent start,MouseEvent end){
// do something ...
}
}