I'm trying to make a level editor for my platformer game, I want my levels to be 100 by 100 squares.
So far the editor works, but I can't scroll through the JPanel. I've been playing around and I've made a small test class to fiddle with which I'll post. If you run it, all it does it show the grid. However if I swap out two variables (I'll comment where) it can show an image and scroll according to the size of that image.
I want that scrolling ability only for the JPanel, so that I can scroll through my 100 x 100 square level.
import java.awt.BorderLayout;
public class ScrollPaneJ extends JFrame {
// setting the panels
private JPanel contentPane;
private JScrollPane scrollPane;
// dimensions/ variables of the grid
int size = 16;
int startX = 112;
int startY = 48;
int width = 30;
int height = 30;
// this is the grid
String[][] grid = new String[width][height];
// this is from the full editor class
String currentImage = new String("platform");
ImageIcon currentBackIcon = new ImageIcon("Resources/backdirttile.jpg");
/**
* Launch the application.
*/
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
// adding the scrollpane
ScrollPaneJ frame = new ScrollPaneJ();
frame.setVisible(true);
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
/**
* Create the frame.
*/
public ScrollPaneJ() {
setTitle("Scrolling Pane Application");
setSize(new Dimension(300, 200));
setBackground(Color.gray);
setDefaultCloseOperation(EXIT_ON_CLOSE);
// defining the top and bottom panels, bottom is what I think I'm
// drawing on, top is where the scrollpanel goes, I copied this code
// from the internet and I'm not too sure how it works
JPanel topPanel = new JPanel();
JPanel bottomPanel = new JPanel(new GridLayout());
bottomPanel.setLayout(new BorderLayout());
getContentPane().add(bottomPanel);
topPanel.setLayout(new BorderLayout());
getContentPane().add(topPanel);
// this is the label I was talking about
Icon image = new ImageIcon("src/MenuDesign.jpg");
JLabel label = new JLabel(image);
// Create a tabbed pane
// if you set it to say label instead of bottomPanel, you can scroll
// through the size of the label
scrollPane = new JScrollPane(bottomPanel);
scrollPane.setBounds(40, 40, 100, 100);
// set it label here as well.
scrollPane.getViewport().add(bottomPanel);
// I was hoping this would force the scrollbar in but it does nothing
scrollPane
.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
scrollPane
.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);
scrollPane.setBounds(50, 30, 300, 50);
JPanel contentPane = new JPanel(null);
contentPane.setPreferredSize(new Dimension(500, 400));
contentPane.add(scrollPane);
topPanel.add(scrollPane, BorderLayout.CENTER);
init();
}
public void init() {
// this sets the grid to empty
for (int x = 0; x < width; x++) {
for (int y = 0; y < height; y++) {
grid[x][y] = "";
}
}
}
#Override
public void paint(Graphics g) {
// this paints the grid
super.paint(g);
Graphics2D g2d = (Graphics2D) g;
g2d.setColor(Color.black);
for (int x = 0; x < width; x++) {
for (int y = 0; y < height; y++) {
g2d.drawRect(x * size + startX, y * size + startY, size, size);
if (grid[x][y].equals("")) {
g2d.drawImage(currentBackIcon.getImage(),
x * size + startX, y * size + startY, null);
}
g2d.setColor(Color.black);
g2d.drawRect((x * size) + 1 + startX, (y * size) + 1 + startY,
size, size);
}
}
}
public void drawTile() {
// this isn't enabled which is why you can't paint the grid, however it
// would change the tile of the square you're mouse is on, to the
// current tile, it works and isn't really important for what i need
// help with
PointerInfo a = MouseInfo.getPointerInfo();
Point b = a.getLocation();
int mouseX = (int) b.getX();
int mouseY = (int) b.getY();
int gMX = ((mouseX - 48) / 16) - 4;
int gMY = ((mouseY - 48) / 16) - 3;
grid[gMX][gMY] = currentImage;
repaint();
}
}
scrollPane.getViewport().add(bottomPanel); should be more like scrollPane.getViewportView(bottomPanel);
You shouldn't be painting directly to the frame, as child components can be painted without the notification to the parents, meaning that what ever you've painted could be partially wiped out. Instead, this kind of painting should be done within a custom component which acts as the JScrollPane's, JViewport's view.
A JScrollPane needs two things, first, the size that the component would like to be (the preferredSize) and the size of the viewport view. If the component doesn't implement the Scrollable interface, then the component's preferredSize is used to determine that as well. This is why a JLabel will work.
A JScrollPane has a JViewport as it's primary child component. The JViewport should only have a single component, typically assigned either via JScrollPane#setViewportView or JViewport#setView methods
See How to Use Scroll Panes for more details
Create a custom component that extends JPanel and override it's getPreferredSize method to return the size of the component you want. Override it's paintComponent method and perform you custom painting their.
Overlaying custom painting ontop of other components is more difficult
You can also add JScrollPane in your panel like this
JPanel p = new JPanel();
add(new JScrollPane(p));
Related
I want to draw a 13x13 tiles board using JFrame.
Here's the code:
public static void drawBoard() {
final int TILE_SIZE = 60;
final int TILES = 13;
JFrame jFrame = new JFrame("Board");
jFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jFrame.setSize(TILE_SIZE * TILES + 17, TILE_SIZE * TILES);
jFrame.setResizable(false);
JButton button = null;
ImageIcon icon = null;
for (int y = 0; y < TILES; y++) {
for (int x = 0; x < TILES; x++) {
button = new JButton(alphabet[x] + "" + alphabet[y]);
button.setName(alphabet[x] + "" + alphabet[y]);
button.setBounds(TILE_SIZE * y, TILE_SIZE * x, TILE_SIZE, TILE_SIZE);
button.addActionListener(new Clicked());
button.setBackground(Color.WHITE);
jFrame.getContentPane().add(button);
}
}
jFrame.setVisible(true);
}//end drawBoard
Now, when I run the code, it shows me a grid of buttons, but the one at the bottom right corner is the same size of the frame.
but the one at the bottom right corner is the same size of the frame.
Swing was designed to be used with layout managers. The layout manager will determine the size/location of a component based on the rules of the layout manager.
In the case of a JFrame, the default layout manager is the BorderLayout. When you add a component to the BorderLayout without using a "constraint" the component is added to the CENTER. However only one component can be added to the CENTER. So only the last component added is give a size/location by the layout manager. In this case the rule is to make the button the size of the space available in the frame.
If you want to have a grid, then you should be using a GridLayout. Then the layout manager will make each button the same size.
Read the section from the Swing tutorial on Layout Managers for more information. There are working examples for both the BorderLayout and the GridLayout.
You should really look into a layout that handles the component size instead of setting it on the component itself (See: MIG Layout), but this should get you were you want to be.
public static void drawBoard(){
final int TILE_SIZE = 60;
final int TILES = 13;
JFrame jFrame = new JFrame("Board");
jFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jFrame.setSize(TILE_SIZE * TILES + 17, TILE_SIZE * TILES);
jFrame.getContentPane().setLayout(new GridLayout(TILES, TILES));
jFrame.setResizable(false);
JButton button = null;
Dimension dim = new Dimension(TILE_SIZE, TILE_SIZE);
for (int y = 0; y < TILES; y++) {
for (int x = 0; x < TILES; x++) {
button = new JButton(alphabet[x] + "" + alphabet[y]);
button.setSize(dim);
button.addActionListener(new Clicked());
button.setBackground(Color.WHITE);
jFrame.getContentPane().add(button);
}
}
jFrame.setVisible(true);
}
In short, I want to set the text of a JLabel to be that of a JTextField in a JPanel (pnlUser) and then drag the JLabel across the screen from JPanel onto another JTextField in another JPanel (pnlGrid).
Here are the details.
I have written a "Solitaire Scrabble" program. The user can either position the text cursor in a grid cell (a JTextField in pnlGrid) and type a letter that is in the list of "User letters" (a JTextField in pnlUser) OR the user can simulate dragging a letter from "User letters" and dropping it into the destination grid cell in pnlGrid.
I say "simulate" because the selected letter is not actually dragged across the screen. I use the mouse pointer HAND_CURSOR to make the drag/drop as real as possible, but I haven't figured out how to make the HAND_CURSOR "grab" the letter and physically drag the letter across the board to its destination.
As it is, the letter is highlighted but left in the "User letters" area while the HAND_CURSOR moves along the grid during the drag operation. When it gets to the destination cell in pnlGrid and the mouse button is released, the letter is erased from "User letters" and suddenly appears in the grid cell.
So the letter is more or less "teleported" (beam me up, Scotty) from "User letters" to a grid cell. This is too abstract. I want the user letter to be at the tip of the HAND_CURSOR's pointing finger and be dragged along the grid into the grid cell where it will be dropped, as shown in the 3 pictures below.
I've successfully made it happen in a small test program (source below) using JLayeredPane, but I can't make it happen in the game. But I knew nothing about JLayeredPane until two days ago so I don't really know what I'm doing. (I adapted an Oracle tutorial program that demos JLayeredPane.)
I just read about the "glass pane" and thought it would maybe be easier to implement until I downloaded the source for that demo, which is quite long, so since it's totally new and will be even harder to adapt.
So I thought before I spend more hours in frustration I should ask:
Is a JLayeredPane or a setGlassPane approach appropriate? Is there an easier or better way to drag a JLabel from one JPanel onto another another JPanel?
(The approach in the program is to determine which "User letter" is being pointed at, store that letter in a JLabel, and then make sure that during mouseDragged the HAND_CURSOR fingertip is right at the bottom center of the letter.)
package mousemoveletter;
import javax.swing.*;
import javax.swing.border.*;
import java.awt.*;
import static java.awt.Color.*;
import java.awt.event.*;
import static javax.swing.SwingUtilities.invokeLater;
public class LayeredPaneDemo extends JPanel
{
private static final int USER7 = 7;
static Cursor HAND = new Cursor(Cursor.HAND_CURSOR);
static Cursor ARROW = new Cursor(Cursor.DEFAULT_CURSOR);
private static JLayeredPane layeredPane;
private static JLabel lblToMove;
private static JPanel pnlUser;
private static JPanel pnlGrid;
private static final JTextField[] txtUser = new JTextField[USER7];
public LayeredPaneDemo() // constructor
{
pnlGrid = new JPanel();
setLayout(new BoxLayout(this, BoxLayout.PAGE_AXIS));
layeredPane = new JLayeredPane();
layeredPane.setPreferredSize(new Dimension(240, 240));
pnlGrid.setSize(140, 140);
pnlGrid.setBorder(new EtchedBorder(RED, GREEN));
pnlGrid.setBackground(YELLOW);
lblToMove = new JLabel("XXX");
lblToMove.setSize(new Dimension(40,40));
layeredPane.add(pnlGrid, 0,0);
layeredPane.add(lblToMove, new Integer(0), -1);
add(layeredPane);
}
private static void createAndShowGUI() {
JFrame frame = new JFrame("LayeredPaneDemo");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JComponent newContentPane = new LayeredPaneDemo();
newContentPane.setOpaque(true); //content panes must be opaque
frame.setContentPane(newContentPane);
makeUser();
frame.add(pnlUser);
frame.pack();
frame.setVisible(true);
}
public static void main(String[] args) {
invokeLater(new Runnable()
{
public void run() {
createAndShowGUI();
}
});
}
private static void makeUser(){
pnlUser = new JPanel(new GridLayout(1,USER7));
pnlUser.setPreferredSize(new Dimension(225, 50));
pnlUser.setBackground(Color.green);
pnlUser.setBorder(BorderFactory.createLineBorder(Color.BLUE));
for(int k = 0; k < USER7; k++)
{
txtUser[k] = new JTextField("" + (char)(Math.random()*26+65));
txtUser[k].setName("" + k);
txtUser[k].setEditable(false);
txtUser[k].addMouseMotionListener(new MouseMotionAdapter()
{
public void mouseDragged(MouseEvent e)
{
lblToMove.setCursor(HAND);
int w = Integer.parseInt(e.getComponent().getName());
lblToMove.setText(txtUser[w].getText());
layeredPane.setLayer(lblToMove, 0, 0);
lblToMove.setLocation(e.getX() + (e.getComponent().getWidth())*w,
e.getY() + layeredPane.getHeight() - e.getComponent().getHeight()/2);
};
});
txtUser[k].addMouseListener(new MouseAdapter()
{
public void mouseReleased(MouseEvent e)
{
lblToMove.setCursor(ARROW);
}
});
pnlUser.add(txtUser[k]);
}
}
}
Thanks to #trashgod, I figured it out by following his links to this example and variation; I adapted the drag/drop of the chessboard found there to my own particular needs for "Scrabble".
The code below is not final code for my Solitaire Scrabble program, but proof-of-concept, possibly usable by others wishing to drag a cell from a 1xN grid onto a MxM grid.
package components;
import java.awt.*;
import static java.awt.BorderLayout.NORTH;
import static java.awt.BorderLayout.SOUTH;
import java.awt.event.*;
import static java.lang.Integer.parseInt;
import javax.swing.*;
import static javax.swing.WindowConstants.DISPOSE_ON_CLOSE;
import javax.swing.event.MouseInputAdapter;
public class ChessBoard //implements MouseListener, MouseMotionListener
{
static Point parentLocation;
int homeRow, homeCol; // where to restore moved user letter if dropped on occupied cell
static int N = 11; // NxN 'chessboard' squares
static int S = 44; // square dimensions: SxS
static int W ; // chessboard dimensions: WxW
static int USER7 = 7;
static Font dragFont;
static JFrame frame;
JLayeredPane layeredPane;
static JPanel gamePanel, // encompasses both pnlGrid and pnlUser
pnlGrid,
pnlUser;
JLabel userDragLetter = new JLabel(); // main item to drag around or restore if needed
int xAdjustment, yAdjustment; // how to locate drops accurately
String userLetters[] ;
public ChessBoard() // constructor
{
W = S*N;
dragFont = new Font("Courier", Font.PLAIN, S);
userLetters = new String[USER7];
for (int i = 0; i < USER7; i++)
userLetters[i] = "" + (char)(65 + Math.random()*26);
Dimension gridSize = new Dimension(W, W);
Dimension userSize = new Dimension(W, S);
Dimension gameSize = new Dimension(W, (W + S));
frame = new JFrame();
frame.setSize(new Dimension(gameSize)); // DO NOT USE PREFERRED
layeredPane = new JLayeredPane();
layeredPane.setPreferredSize( gameSize ); // NO PREFERRED => NO GRID!
gamePanel = new JPanel();
// **EDIT** LOSE THIS LINE gamePanel.setLayout(new BorderLayout());
gamePanel.setPreferredSize(gameSize);
pnlGrid = new JPanel();
pnlGrid.setLayout(new GridLayout(N, N));
pnlGrid.setPreferredSize( gridSize );
pnlGrid.setBounds(0, 0, gridSize.width, gridSize.height);
pnlUser = new JPanel();
pnlUser.setLayout(new GridLayout(1, N));
pnlUser.setPreferredSize(userSize);
pnlUser.setBounds(0, gridSize.height, userSize.width, userSize.height);
layeredPane.add(pnlGrid, JLayeredPane.DEFAULT_LAYER); // panels to drag over
layeredPane.add(pnlUser, JLayeredPane.DEFAULT_LAYER); // " "
for (int i = 0; i < N; i++){
for (int j = 0; j < N; j++){
JPanel square = new JPanel();
square.setBackground( (i + j) % 2 == 0 ? Color.red : Color.white );
pnlGrid.add( square );
}
}
for (int i = 0; i < N; i++) {
JPanel square = new JPanel(new BorderLayout());
square.setBackground(Color.YELLOW);
pnlUser.add(square);
}
for (int i = 0; i < USER7; i++)
addPiece(i, 0, userLetters[i]);
gamePanel.addMouseListener(new MouseInputAdapter()
{
public void mousePressed (MouseEvent e){mousePressedActionPerformed (e);}
public void mouseReleased(MouseEvent e){mouseReleasedActionPerformed(e);}
});
gamePanel.addMouseMotionListener(new MouseMotionAdapter()
{
public void mouseDragged(MouseEvent me){mouseDraggedActionPerformed(me);}
});
// **EDIT: LOSE THE NEXT TWO LINES AND REPLACE BY THE LINE AFTER THEM**
// gamePanel.add(layeredPane, NORTH);
// gamePanel.add(pnlUser, SOUTH);
gamePanel.add(layeredPane);
}
private void addPiece(int col, int row, String glyph) {
JLabel piece = new JLabel(glyph, JLabel.CENTER);
piece.setFont(dragFont);
JPanel panel = (JPanel) pnlUser.getComponent(col + row * N);
piece.setName("piece " + glyph + " # " + row + " " + col);
panel.add(piece);
}
void mousePressedActionPerformed(MouseEvent e)
{
userDragLetter = null; // signal that we're not dragging if no piece is in the square
gamePanel.setCursor(new Cursor(Cursor.HAND_CURSOR));
Component c = pnlGrid.findComponentAt(e.getX(), e.getY());
if(c != null)
return; // Illegal to click pnlGrid
c = pnlUser.findComponentAt(e.getX(), e.getY() - pnlGrid.getHeight());
if(c == null | c instanceof JPanel)
return; // letter already played; can't drag empty cell
parentLocation = c.getParent().getLocation();
xAdjustment = parentLocation.x - e.getX();
yAdjustment = parentLocation.y - e.getY() + gamePanel.getHeight() - pnlUser.getHeight();
userDragLetter = (JLabel)c;
userDragLetter.setPreferredSize(new Dimension(S, S)); // prevent 2 letters in a square
userDragLetter.setLocation(e.getX() + xAdjustment, e.getY() + yAdjustment);
layeredPane.add(userDragLetter, JLayeredPane.DRAG_LAYER);
homeRow = parseInt(userDragLetter.getName().substring(10,11)); // save restore location
homeCol = parseInt(userDragLetter.getName().substring(12,13));
}
void mouseDraggedActionPerformed(MouseEvent me)
{
if (userDragLetter == null)
return; // nothing to drag
int x = me.getX() + xAdjustment; // make sure grid cell will be chosen in-bounds
int xMax = layeredPane.getWidth() - userDragLetter.getWidth();
x = Math.min(x, xMax);
x = Math.max(x, 0);
int y = me.getY() + yAdjustment;
int yMax = layeredPane.getHeight() - userDragLetter.getHeight();
y = Math.min(y, yMax);
y = Math.max(y, 0);
if(y >= pnlGrid.getHeight())
return; // can't drag to location off grid
userDragLetter.setLocation(x, y);
}
void mouseReleasedActionPerformed(MouseEvent e)
{
//**EDIT: CHANGED NEXT LINE**
gamePanel.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
if (userDragLetter == null)
return; // nothing to drag; nothing to release
// Make sure the chess piece is no longer painted on the layered pane
userDragLetter.setVisible(false);
layeredPane.remove(userDragLetter);
userDragLetter.setVisible(true);
int xMax = layeredPane.getWidth() - userDragLetter.getWidth();
int x = Math.min(e.getX(), xMax);
x = Math.max(x, 0);
int yMax = layeredPane.getHeight()- userDragLetter.getHeight();
int y = Math.min(e.getY(), yMax);
y = Math.max(y, 0);
Component c = pnlGrid.findComponentAt(x, y); // find deepest nested child component
if(c == null) // then grid cell is unoccupied so ...
c = pnlUser.findComponentAt(x, y); // see if there's a letter there ...
if(c == null | (c instanceof JLabel)){ // and if illegal or there is one, put it back...
userDragLetter.setLocation(parentLocation.x + xAdjustment,
parentLocation.y + yAdjustment + gamePanel.getHeight());
userDragLetter.setVisible(true);
addPiece(homeCol, homeRow,userDragLetter.getName().substring(6,7));
layeredPane.remove(userDragLetter);
return;
}
else // but if NO letter ...
{
Container parent = (Container)c;
parent.add( userDragLetter ); // put one in the grid cell
parent.validate();
}
userDragLetter.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
}
public static void main(String[] args)
{
new ChessBoard();
frame.add(gamePanel);
frame.setDefaultCloseOperation( DISPOSE_ON_CLOSE );
// frame.setResizable( false );
frame.pack();
frame.setLocationRelativeTo( null );
frame.setVisible(true);
}
}
I'm trying to code a zoom-able image in a JScrollPane.
When the image is fully zoomed out it should be centered horizontally and vertically. When both scroll bars have appeared the zooming should always happen relative to the mouse coordinate, i.e. the same point of the image should be under the mouse before and after the zoom event.
I have almost achieves my goal. Unfortunately the "scrollPane.getViewport().setViewPosition()" method sometimes fails to update the view position correctly. Calling the method twice (hack!) overcomes the issue in most cases, but the view still flickers.
I have no explanation as to why this is happening. However I'm confident that it's not a math problem.
Below is a MWE. To see what my problem is in particular you can do the following:
Zoom in until you have some scroll bars (200% zoom or so)
Scroll into the bottom right corner by clicking the scroll bars
Place the mouse in the corner and zoom in twice. The second time you'll see how the scroll position jumps towards the center.
I would really appreciate if someone could tell me where the problem lies. Thank you!
package com.vitco;
import javax.swing.*;
import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseWheelEvent;
import java.awt.image.BufferedImage;
import java.util.Random;
/**
* Zoom-able scroll panel test case
*/
public class ZoomScrollPanel {
// the size of our image
private final static int IMAGE_SIZE = 600;
// create an image to display
private BufferedImage getImage() {
BufferedImage image = new BufferedImage(IMAGE_SIZE, IMAGE_SIZE, BufferedImage.TYPE_INT_RGB);
Graphics g = image.getGraphics();
// draw the small pixel first
Random rand = new Random();
for (int x = 0; x < IMAGE_SIZE; x += 10) {
for (int y = 0; y < IMAGE_SIZE; y += 10) {
g.setColor(new Color(rand.nextInt(255),rand.nextInt(255),rand.nextInt(255)));
g.fillRect(x, y, 10, 10);
}
}
// draw the larger transparent pixel second
for (int x = 0; x < IMAGE_SIZE; x += 100) {
for (int y = 0; y < IMAGE_SIZE; y += 100) {
g.setColor(new Color(rand.nextInt(255),rand.nextInt(255),rand.nextInt(255), 180));
g.fillRect(x, y, 100, 100);
}
}
return image;
}
// the image panel that resizes according to zoom level
private class ImagePanel extends JPanel {
private final BufferedImage image = getImage();
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D)g.create();
g2.scale(scale, scale);
g2.drawImage(image, 0, 0, null);
g2.dispose();
}
#Override
public Dimension getPreferredSize() {
return new Dimension((int)Math.round(IMAGE_SIZE * scale), (int)Math.round(IMAGE_SIZE * scale));
}
}
// the current zoom level (100 means the image is shown in original size)
private double zoom = 100;
// the current scale (scale = zoom/100)
private double scale = 1;
// the last seen scale
private double lastScale = 1;
public void alignViewPort(Point mousePosition) {
// if the scale didn't change there is nothing we should do
if (scale != lastScale) {
// compute the factor by that the image zoom has changed
double scaleChange = scale / lastScale;
// compute the scaled mouse position
Point scaledMousePosition = new Point(
(int)Math.round(mousePosition.x * scaleChange),
(int)Math.round(mousePosition.y * scaleChange)
);
// retrieve the current viewport position
Point viewportPosition = scrollPane.getViewport().getViewPosition();
// compute the new viewport position
Point newViewportPosition = new Point(
viewportPosition.x + scaledMousePosition.x - mousePosition.x,
viewportPosition.y + scaledMousePosition.y - mousePosition.y
);
// update the viewport position
// IMPORTANT: This call doesn't always update the viewport position. If the call is made twice
// it works correctly. However the screen still "flickers".
scrollPane.getViewport().setViewPosition(newViewportPosition);
// debug
if (!newViewportPosition.equals(scrollPane.getViewport().getViewPosition())) {
System.out.println("Error: " + newViewportPosition + " != " + scrollPane.getViewport().getViewPosition());
}
// remember the last scale
lastScale = scale;
}
}
// reference to the scroll pane container
private final JScrollPane scrollPane;
// constructor
public ZoomScrollPanel() {
// initialize the frame
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
frame.setSize(600, 600);
// initialize the components
final ImagePanel imagePanel = new ImagePanel();
final JPanel centerPanel = new JPanel();
centerPanel.setLayout(new GridBagLayout());
centerPanel.add(imagePanel);
scrollPane = new JScrollPane(centerPanel);
scrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);
scrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_ALWAYS);
frame.add(scrollPane);
// add mouse wheel listener
imagePanel.addMouseWheelListener(new MouseAdapter() {
#Override
public void mouseWheelMoved(MouseWheelEvent e) {
super.mouseWheelMoved(e);
// check the rotation of the mousewheel
int rotation = e.getWheelRotation();
boolean zoomed = false;
if (rotation > 0) {
// only zoom out until no scrollbars are visible
if (scrollPane.getHeight() < imagePanel.getPreferredSize().getHeight() ||
scrollPane.getWidth() < imagePanel.getPreferredSize().getWidth()) {
zoom = zoom / 1.3;
zoomed = true;
}
} else {
// zoom in until maximum zoom size is reached
double newCurrentZoom = zoom * 1.3;
if (newCurrentZoom < 1000) { // 1000 ~ 10 times zoom
zoom = newCurrentZoom;
zoomed = true;
}
}
// check if a zoom happened
if (zoomed) {
// compute the scale
scale = (float) (zoom / 100f);
// align our viewport
alignViewPort(e.getPoint());
// invalidate and repaint to update components
imagePanel.revalidate();
scrollPane.repaint();
}
}
});
// display our frame
frame.setVisible(true);
}
// the main method
public static void main(String[] args) {
new ZoomScrollPanel();
}
}
Note: I have also looked at the question here JScrollPane setViewPosition After "Zoom" but unfortunately the problem and solution are slightly different and do not apply.
Edit
I have solved the issue by using a hack, however I'm still no closer to understanding as to what the underlying problem is. What is happening is that when the setViewPosition is called some internal state changes trigger additional calls to setViewPosition. These additional calls only happen occasionally. When I'm blocking them everything works perfectly.
To fix the problem I simply introduced a new boolean variable "blocked = false;" and replaced the lines
scrollPane = new JScrollPane(centerPanel);
and
scrollPane.getViewport().setViewPosition(newViewportPosition);
with
scrollPane = new JScrollPane();
scrollPane.setViewport(new JViewport() {
private boolean inCall = false;
#Override
public void setViewPosition(Point pos) {
if (!inCall || !blocked) {
inCall = true;
super.setViewPosition(pos);
inCall = false;
}
}
});
scrollPane.getViewport().add(centerPanel);
and
blocked = true;
scrollPane.getViewport().setViewPosition(newViewportPosition);
blocked = false;
I would still really appreciate if someone could make sense of this!
Why does this hack work? Is there a cleaner way to achieve the same functionality?
Here is the completed, fully functional Code. I still don't understand why the hack is necessary, but at least it now works as expected:
import javax.swing.*;
import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseWheelEvent;
import java.awt.image.BufferedImage;
import java.util.Random;
/**
* Zoom-able scroll panel
*/
public class ZoomScrollPanel {
// the size of our image
private final static int IMAGE_SIZE = 600;
// create an image to display
private BufferedImage getImage() {
BufferedImage image = new BufferedImage(IMAGE_SIZE, IMAGE_SIZE, BufferedImage.TYPE_INT_RGB);
Graphics g = image.getGraphics();
// draw the small pixel first
Random rand = new Random();
for (int x = 0; x < IMAGE_SIZE; x += 10) {
for (int y = 0; y < IMAGE_SIZE; y += 10) {
g.setColor(new Color(rand.nextInt(255),rand.nextInt(255),rand.nextInt(255)));
g.fillRect(x, y, 10, 10);
}
}
// draw the larger transparent pixel second
for (int x = 0; x < IMAGE_SIZE; x += 100) {
for (int y = 0; y < IMAGE_SIZE; y += 100) {
g.setColor(new Color(rand.nextInt(255),rand.nextInt(255),rand.nextInt(255), 180));
g.fillRect(x, y, 100, 100);
}
}
return image;
}
// the image panel that resizes according to zoom level
private class ImagePanel extends JPanel {
private final BufferedImage image = getImage();
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D)g.create();
g2.scale(scale, scale);
g2.drawImage(image, 0, 0, null);
g2.dispose();
}
#Override
public Dimension getPreferredSize() {
return new Dimension((int)Math.round(IMAGE_SIZE * scale), (int)Math.round(IMAGE_SIZE * scale));
}
}
// the current zoom level (100 means the image is shown in original size)
private double zoom = 100;
// the current scale (scale = zoom/100)
private double scale = 1;
// the last seen scale
private double lastScale = 1;
// true if currently executing setViewPosition
private boolean blocked = false;
public void alignViewPort(Point mousePosition) {
// if the scale didn't change there is nothing we should do
if (scale != lastScale) {
// compute the factor by that the image zoom has changed
double scaleChange = scale / lastScale;
// compute the scaled mouse position
Point scaledMousePosition = new Point(
(int)Math.round(mousePosition.x * scaleChange),
(int)Math.round(mousePosition.y * scaleChange)
);
// retrieve the current viewport position
Point viewportPosition = scrollPane.getViewport().getViewPosition();
// compute the new viewport position
Point newViewportPosition = new Point(
viewportPosition.x + scaledMousePosition.x - mousePosition.x,
viewportPosition.y + scaledMousePosition.y - mousePosition.y
);
// update the viewport position
blocked = true;
scrollPane.getViewport().setViewPosition(newViewportPosition);
blocked = false;
// remember the last scale
lastScale = scale;
}
}
// reference to the scroll pane container
private final JScrollPane scrollPane;
// constructor
public ZoomScrollPanel() {
// initialize the frame
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
frame.setSize(600, 600);
// initialize the components
final ImagePanel imagePanel = new ImagePanel();
final JPanel centerPanel = new JPanel();
centerPanel.setLayout(new GridBagLayout());
centerPanel.add(imagePanel);
scrollPane = new JScrollPane();
scrollPane.setViewport(new JViewport() {
private boolean inCall = false;
#Override
public void setViewPosition(Point pos) {
if (!inCall || !blocked) {
inCall = true;
super.setViewPosition(pos);
inCall = false;
}
}
});
scrollPane.getViewport().add(centerPanel);
scrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);
scrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_ALWAYS);
frame.add(scrollPane);
// add mouse wheel listener
imagePanel.addMouseWheelListener(new MouseAdapter() {
#Override
public void mouseWheelMoved(MouseWheelEvent e) {
super.mouseWheelMoved(e);
// check the rotation of the mousewheel
int rotation = e.getWheelRotation();
boolean zoomed = false;
if (rotation > 0) {
// only zoom out until no scrollbars are visible
if (scrollPane.getHeight() < imagePanel.getPreferredSize().getHeight() ||
scrollPane.getWidth() < imagePanel.getPreferredSize().getWidth()) {
zoom = zoom / 1.3;
zoomed = true;
}
} else {
// zoom in until maximum zoom size is reached
double newCurrentZoom = zoom * 1.3;
if (newCurrentZoom < 1000) { // 1000 ~ 10 times zoom
zoom = newCurrentZoom;
zoomed = true;
}
}
// check if a zoom happened
if (zoomed) {
// compute the scale
scale = (float) (zoom / 100f);
// align our viewport
alignViewPort(e.getPoint());
// invalidate and repaint to update components
imagePanel.revalidate();
scrollPane.repaint();
}
}
});
// display our frame
frame.setVisible(true);
}
// the main method
public static void main(String[] args) {
new ZoomScrollPanel();
}
}
Some time ago I was facing the same issue. I had some scalable/zoomable content (SWT widgets) stored in Viewport in JScrollPane and some features implemented to enable panning and zooming the content. I didn't look into your code if it's basically the same, but the issue that I was observing was completely the same. When zooming outside from the right/bottom side, sometimes, the view position jumped a little bit into the center (from my point-of-view that definitely points to a scale factor). Using doubled "setViewPosition" somehow enhanced the behavior but still not usable.
After some investigation, I've found out that the issue on my side was between the moment when I changed the scale factor of the content inside the scroll panel and the moment when view position was set in scroll panel. The thing is that scroll panel doesn't know about the content size updates until layout is done. So basically, it's updating the position based on old content size, extent size and view position.
So, at my side, this helped a lot.
// updating scroll panel content scale goes here
viewport.doLayout();
// setting view position in viewport goes here
Checking method BasicScrollPaneUI#syncScrollPaneWithViewport() was very useful on my side.
very useful example, excellent zoom at mouse pointer, here is the same code slightly modified to include mouse panning:
original code added taken from --> Scroll JScrollPane by dragging mouse (Java swing)
import javax.swing.*;
import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseWheelEvent;
import java.awt.image.BufferedImage;
import java.util.Random;
/**
* Zoom-able scroll panel
*/
// https://stackoverflow.com/questions/22649636/zoomable-jscrollpane-setviewposition-fails-to-update
public class ZoomPanScrollPanel {
// the size of our image
private final static int IMAGE_SIZE = 1600;
// create an image to display
private BufferedImage getImage() {
BufferedImage image = new BufferedImage(IMAGE_SIZE, IMAGE_SIZE, BufferedImage.TYPE_INT_ARGB);
Graphics g = image.getGraphics();
// draw the small pixel first
Random rand = new Random();
for (int x = 0; x < IMAGE_SIZE; x += 10) {
for (int y = 0; y < IMAGE_SIZE; y += 10) {
g.setColor(new Color(rand.nextInt(255),rand.nextInt(255),rand.nextInt(255), rand.nextInt(255)));
g.fillRect(x, y, 10, 10);
}
}
// draw the larger transparent pixel second
for (int x = 0; x < IMAGE_SIZE; x += 100) {
for (int y = 0; y < IMAGE_SIZE; y += 100) {
g.setColor(new Color(rand.nextInt(255),rand.nextInt(255),rand.nextInt(255), 180));
g.fillRect(x, y, 100, 100);
}
}
return image;
}
// the image panel that resizes according to zoom level
private class ImagePanel extends JPanel {
private final BufferedImage image = getImage();
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D)g.create();
g2.scale(scale, scale);
g2.drawImage(image, 0, 0, null);
g2.dispose();
}
#Override
public Dimension getPreferredSize() {
return new Dimension((int)Math.round(IMAGE_SIZE * scale), (int)Math.round(IMAGE_SIZE * scale));
}
}
// the current zoom level (100 means the image is shown in original size)
private double zoom = 100;
// the current scale (scale = zoom/100)
private double scale = 1;
// the last seen scale
private double lastScale = 1;
// true if currently executing setViewPosition
private boolean blocked = false;
public void alignViewPort(Point mousePosition) {
// if the scale didn't change there is nothing we should do
if (scale != lastScale) {
// compute the factor by that the image zoom has changed
double scaleChange = scale / lastScale;
// compute the scaled mouse position
Point scaledMousePosition = new Point(
(int)Math.round(mousePosition.x * scaleChange),
(int)Math.round(mousePosition.y * scaleChange)
);
// retrieve the current viewport position
Point viewportPosition = scrollPane.getViewport().getViewPosition();
// compute the new viewport position
Point newViewportPosition = new Point(
viewportPosition.x + scaledMousePosition.x - mousePosition.x,
viewportPosition.y + scaledMousePosition.y - mousePosition.y
);
// update the viewport position
blocked = true;
scrollPane.getViewport().setViewPosition(newViewportPosition);
blocked = false;
// remember the last scale
lastScale = scale;
}
}
// reference to the scroll pane container
private final JScrollPane scrollPane;
// constructor
public ZoomPanScrollPanel() {
// initialize the frame
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
frame.setSize(600, 600);
// initialize the components
final ImagePanel imagePanel = new ImagePanel();
final JPanel centerPanel = new JPanel();
centerPanel.setLayout(new GridBagLayout());
centerPanel.add(imagePanel);
scrollPane = new JScrollPane();
scrollPane.setViewport(new JViewport() {
private boolean inCall = false;
#Override
public void setViewPosition(Point pos) {
if (!inCall || !blocked) {
inCall = true;
super.setViewPosition(pos);
inCall = false;
}
}
});
scrollPane.getViewport().add(centerPanel);
scrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);
scrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_ALWAYS);
frame.add(scrollPane);
// add mouse wheel listener
imagePanel.addMouseWheelListener(new MouseAdapter() {
#Override
public void mouseWheelMoved(MouseWheelEvent e) {
super.mouseWheelMoved(e);
// check the rotation of the mousewheel
int rotation = e.getWheelRotation();
boolean zoomed = false;
if (rotation > 0) {
// only zoom out until no scrollbars are visible
if (scrollPane.getHeight() < imagePanel.getPreferredSize().getHeight() ||
scrollPane.getWidth() < imagePanel.getPreferredSize().getWidth()) {
zoom = zoom / 1.3;
zoomed = true;
}
} else {
// zoom in until maximum zoom size is reached
double newCurrentZoom = zoom * 1.3;
if (newCurrentZoom < 1000) { // 1000 ~ 10 times zoom
zoom = newCurrentZoom;
zoomed = true;
}
}
// check if a zoom happened
if (zoomed) {
// compute the scale
scale = (float) (zoom / 100f);
// align our viewport
alignViewPort(e.getPoint());
// invalidate and repaint to update components
imagePanel.revalidate();
scrollPane.repaint();
}
}
});
//mouse panning
//original code: https://stackoverflow.com/questions/31171502/scroll-jscrollpane-by-dragging-mouse-java-swing
MouseAdapter ma = new MouseAdapter() {
private Point origin;
#Override
public void mousePressed(MouseEvent e) {
origin = new Point(e.getPoint());
}
#Override
public void mouseReleased(MouseEvent e) {
}
#Override
public void mouseDragged(MouseEvent e) {
if (origin != null) {
JViewport viewPort = (JViewport) SwingUtilities.getAncestorOfClass(JViewport.class, imagePanel);
if (viewPort != null) {
int deltaX = origin.x - e.getX();
int deltaY = origin.y - e.getY();
System.out.println("X pan = "+ deltaX);
System.out.println("Y pan = "+ deltaY);
Rectangle view = viewPort.getViewRect();
view.x += deltaX;
view.y += deltaY;
imagePanel.scrollRectToVisible(view);
}
}
}
};
imagePanel.addMouseListener(ma);
imagePanel.addMouseMotionListener(ma);
imagePanel.setAutoscrolls(true);
// display our frame
frame.setVisible(true);
}
// the main method
public static void main(String[] args) {
new ZoomPanScrollPanel();
}
}
I have a JPanel 7width 9 height board. I can also place my pieces on top of the board. My problem now is how I will call the pieces:
pseudo code:
if(whero1 is on row 0 column 5 then....
code is below:
public class Board extends JPanel{
private static final String imageFolderPath = "src/resources/images/";
Dimension dimension = new Dimension(500, 500);
JPanel board;
JLabel piece;
MovePiece mp = new MovePiece(this);
public Board(){
//set size of panel;
this.setPreferredSize(dimension);
this.addMouseListener(mp);
this.addMouseMotionListener(mp);
//create the Board
board = new JPanel();
board.setLayout(new GridLayout(9,7));
board.setPreferredSize(dimension);
board.setBounds(0, 0, dimension.width, dimension.height);
this.add(board);
for (int i = 0; i < 63; i++) {
JPanel square = new JPanel(new BorderLayout());
square.setBorder(BorderFactory.createEtchedBorder(EtchedBorder.RAISED));
board.add(square);
square.setBackground(new Color(185, 156, 107));
}
JLabel whero1 = new JLabel( new ImageIcon(imageFolderPath+"/pieces/bhero.png") );
JPanel panel = (JPanel)board.getComponent(60);
panel.add(whero1);
//I'm trying this, but i.m going nowhere
int x =whero1.getParent().getX();
int y = whero1.getParent().getY();
System.out.println(x);
System.out.println(y);
/*if(x==0&&y==0){
whero1.setIcon(new ImageIcon(imageFolderPath+"/pieces/bdg.png"));
}*/
}
}
The easiest solution would be to maintain some kind of virtual model of the board. In this way you could simply update the state of the game and request that the UI update itself to reflect the state of the model.
Much simpler then trying to interrogate n-depth contains and convert to/from coordinate systems
nb: This...
int x =whero1.getParent().getX();
int y = whero1.getParent().getY();
Is going to return the pixel x/y position of the whereo1s parent's in relation to it's parent container, not convinced that this would really help at all
I'm building a Tic Tac Toe game in Java with a Swing GUI, and it renders correctly in Ubuntu 10.4 and Windows XP. This is how it looks like in Ubuntu:
When I copied the bin-folder with all the class files and tried to run the program in Windows 7 it looked like this instead:
I just can't understand what's wrong. As I said, it works perfectly in Ubuntu 10.4 and Windows XP.
I would be very happy if someone could help me out! I'll post the code related to the GUI, just in case it is needed to solve the problem.
Here is the code I use to initialize the GUI:
//Initializing GUI.
frame = new JFrame(); //Creating the window.
frame.setTitle("Tic Tac Toe"); //Setting the title of the window.
frame.addMouseListener(this);
frame.getContentPane().add(BorderLayout.CENTER, grid.getPanel()); //Adding the grid panel.
info = new JLabel(" Initializing game..."); //Creating info text.
frame.getContentPane().add(BorderLayout.SOUTH, info); //Adding info text.
//Setting GUI properties.
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(300, 300);
frame.setVisible(true);
The panel with the grid itself is created in my GameGrid class, which have a method "JPanel getPanel()". Here is the initialization of that panel (the code belongs in the constructor of GameGrid):
GridBox temp;
layout = new GridLayout(getHeight(), getWidth());
panel = new JPanel(layout);
panel.setBorder(
BorderFactory.createCompoundBorder(
BorderFactory.createTitledBorder("Click in a box to place a marker:"),
BorderFactory.createEmptyBorder(5,5,5,5)));
//Creating a GridBox for each cell, and adding them to the panel in the right order..
for(int i = 0; i < getHeight(); i++) {
for(int j = 0; j < getWidth(); j++) {
temp = new GridBox(j, i);
temp.addMouseListener(listener);
panel.add(temp);
}
}
GridBox is a subclass of JPanel, which I modified to automatically show the contents of the grid at the coordinates specified.
class GridBox extends JPanel {
private static final long serialVersionUID = 1L;
int fontsize, x, y, value, signHeight, signWidth;
char print;
FontMetrics fm;
LineMetrics lm;
public GridBox(int a, int b) {
x = a; //TODO - input control
y = b;
}
public Move getMove() {
Move m = new Move(x, y);
return m;
}
public void paintComponent(Graphics g) {
Border blackline = BorderFactory.createLineBorder(Color.black);
setBorder(blackline);
Dimension size = getSize();
Rectangle2D rect;
fontsize = (int)(size.getHeight()*0.75);
value = getGridValue(x, y);
if(value == EMPTY)
print = ' ';
else if(value == 0)
print = 'X';
else if(value == 1)
print = 'O';
else
print = (char)value;
Font font = new Font("Times New Roman", Font.PLAIN, fontsize);
g.setFont(font);
fm = g.getFontMetrics();
rect = fm.getStringBounds(Character.toString(print), g);
signHeight = (int)rect.getHeight();
signWidth = (int)rect.getWidth();
g.setColor(Color.black);
g.drawString(Character.toString(print), (size.width/2)-(signWidth/2), (size.height/2)-(signHeight/2)+fm.getAscent());
}
}
Thanks in advance!
There's an obvious problem in the you change the border whilst repainting the component. That's going to cause all sorts of problems.
Also, I don't see where you paint the background of the panel. You should have
super.paintComponent(g);
at the top of the method.