JFrame too small and pack() doesn't seem to work - java

For some reason, the JFrame is too small when run, and using pack(); doesn't seem to fix the problem. Any ideas?
public class Window {
public Window(String title, Game game) {
// Creates new JFrame
JFrame frame = new JFrame(title);
// Adds Game to window
frame.add(game);
frame.pack();
// Settings of Window
frame.setResizable(false);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
}
The cyan background is supposed to be a 64 x 64 border in the bottom and right sides. The boxes drawn are correct, which I checked by resizing the window. However the window is too small and does not fit the canvas.
Game Class:
public class Game extends Canvas implements Runnable {
private Handler handler;
public Game() {
// Create new window
Window window = new Window("Horses Aren't Real", this);
handler = new Handler();
this.addKeyListener(new KeyInput(handler));
handler.addObject(new Daanish(100, 100, ID.Player, handler));
}
public static void main (String args[]) {
// Creates new game
new Game();
}
public void draw() {
// Creates a new BufferStrategy
BufferStrategy bs = this.getBufferStrategy();
if (bs == null) {
// This allows the game to preload 3 frames in order to prevent choppy framrate
this.createBufferStrategy(3);
return;
}
// Create Graphics
Graphics g = bs.getDrawGraphics();
g.setColor(Color.cyan);
g.fillRect(0, 0, 1024, 640);
g.setColor(Color.black);
g.fillRect(0, 0, 960, 576);
handler.draw(g);
// Remove frame from queue
g.dispose();
bs.show();
}
#Override
public Dimension getPreferredSize() {
return new Dimension(1024, 640);
}
}

So you're running into a number of issues. The first been, the size of the window must also account for the frame decorations, this means that the available space for the content is the size of the window MINUS the size of the frame decorations.
Setting the size of the window directly is ill-advised.
pack asks the content of the frame for it's preferred size and uses that to wrap the window around it. So, instead of window size - decorations = content size, you have content size + decorations = window size
From the JavaDocs
Causes this Window to be sized to fit the preferred size and layouts of its subcomponents. The resulting width and height of the window are automatically enlarged if either of dimensions is less than the minimum size as specified by the previous call to the setMinimumSize method.
So, instead, override preferredSize of the Game class and return the amount of space you want, may be something like...
public class Game extends JPanel {
//...
#Override
public Dimension getPreferredSize() {
return new Dimension(800, 600);
}
//...
}
Your next problem is, Window extends from JFrame, but in the constructor, you're creating ANOTHER Frame ... so which one is actually doing what?
Remove the confusion. Avoid extending from top level containers like JFrame, you're not adding any new functionality to it
public class Window {
public Window(String title, Game game) {
// Creates new JFrame
JFrame frame = new JFrame(title);
// Adds Game to window
frame.add(game);
frame.pack();
// Settings of Window
frame.setResizable(false);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
}
Updated...
So, I took your "updated" code, modified it so it would run, ran and didn't have any issues...
import java.awt.Canvas;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.image.BufferStrategy;
import javax.swing.JFrame;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
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();
}
Game game = new Game();
Window window = new Window("Help", game);
game.start();
}
});
}
public class Window {
public Window(String title, Game game) {
// Creates new JFrame
JFrame frame = new JFrame(title);
// Adds Game to window
frame.add(game);
frame.setResizable(false);
frame.pack();
// Settings of Window
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
}
public class Game extends Canvas { //implements Runnable {
// private Handler handler;
public Game() {
//
// handler = new Handler();
// this.addKeyListener(new KeyInput(handler));
//
// handler.addObject(new Daanish(100, 100, ID.Player, handler));
}
public void start() {
Thread thread = new Thread(new Runnable() {
#Override
public void run() {
while (true) {
draw();
try {
Thread.sleep(40);
} catch (InterruptedException ex) {
}
}
}
});
thread.start();
}
public void draw() {
// Creates a new BufferStrategy
BufferStrategy bs = this.getBufferStrategy();
if (bs == null) {
// This allows the game to preload 3 frames in order to prevent choppy framrate
this.createBufferStrategy(3);
return;
}
// Create Graphics
Graphics g = bs.getDrawGraphics();
g.setColor(Color.cyan);
g.fillRect(0, 0, 1024, 640);
g.setColor(Color.black);
g.fillRect(0, 0, 960, 576);
// handler.draw(g);
// Remove frame from queue
g.dispose();
bs.show();
}
#Override
public Dimension getPreferredSize() {
return new Dimension(1024, 640);
}
}
}
However, I'm running on MacOS. There is a little known issue with setResizable I've run across on Windows, where the window decorations change size when setResizable is called.
The "basic" solution is to call setResziable BEFORE calling pack, so that the frame decorations are modified before the the API makes decisions about how to update the size of the window.
I had thought this was fixed in later (8+) versions of Java, but since I don't run Windows, it's impossible for me to test
See My JFrame always becomes a few pixels too big. for some more details.

public class Window extends JFrame {
public Window(final int width, final int height, String title, Game game) {
super(title);
// Set dimensions
Dimension d = new Dimension(width, height);
game.setPreferredSize(d);
// Adds Game to window
add(game);
// Settings of Window
setResizable(false);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setLocationRelativeTo(null);
pack();
setVisible(true);
}
}
When you use pack, the frame generaly resize to content size

Related

Multiple panels / canvas

I am looking for a way to dinamiclly switch between panels / between a panel or a canvas.
More specific: I am developing a game. In my code there is a class that extends canvas and implements
Runnable, and in the constructor of Game, it creates a new instance of a class called window. That is window class:
public class Window extends Canvas {
private static final long serialVersionUID = -299686449326748512L;
public static JFrame frame = new JFrame();
public Window(int width, int height, String title, Game game) {
// JFrame frame = new JFrame();
frame.setPreferredSize(new Dimension(width, height));
frame.setMaximumSize(new Dimension(width, height));
frame.setMinimumSize(new Dimension(width, height));
frame.setTitle(title);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setResizable(false);
frame.setLocationRelativeTo(null);
frame.add(game);
frame.setVisible(true);
game.start();
}
}
I want to be able to remove game from the frame, activate another panel, and stop the execution of Game.
I have already tried:
game.stop();
Window.frame.remove(game);
but it makes the program to crash. Those are start() & stop() methods:
/**
* starts the game.
*/
public synchronized void start() {
thread = new Thread(this);
thread.start();
running = true;
}
/**
* tries to stop the game.
*/
public synchronized void stop() {
try {
thread.join();
running = false;
} catch (Exception e) {
e.printStackTrace();
}
}
My main goal is to be able to play a cutscene if some event happend and I am trying to use vlcj for that purpose. If anyone has an idea that will allow me to execute this goal that would be great too.
I've made an example, doing what I think you want without a card layout, and using a thread. I think this is a proof of concept, that what your asking is possible. Below, I will include a few things I would do to improve it.
import javax.swing.*;
import java.awt.*;
public class SwapCards{
Thread gameLoop;
volatile boolean running = false;
double x = 0;
double y = 0;
double theta = 0;
JFrame frame = new JFrame("swapped");
Canvas gamePanel = new Canvas(){
public void paint(Graphics g){
super.paint(g);
g.setColor(Color.BLACK);
g.drawOval((int)x, (int)y, 25, 25);
}
};
Canvas nonGame = new Canvas(){
public void paint(Graphics g){
super.paint(g);
g.setColor(Color.BLUE);
g.fillRect(0,0,200, 200);
}
};
public void step(){
x = 100 + 50*Math.sin( theta );
y = 100 + 50*Math.cos( theta );
theta += 0.02;
if(theta > 6.28) theta = 0;
}
public void startGameLoop(){
frame.remove(nonGame);
frame.add(gamePanel, BorderLayout.CENTER);
frame.validate();
running = true;
gameLoop = new Thread(()->{
while(running){
step();
gamePanel.repaint();
try{
Thread.sleep(30);
}catch (Exception e){
running = false;
throw new RuntimeException(e);
}
}
});
gameLoop.start();
}
public void stopGameLoop(){
frame.remove(gamePanel);
frame.add(nonGame, BorderLayout.CENTER);
running = false;
try{
gameLoop.join();
} catch(Exception e){
throw new RuntimeException(e);
}
}
public void buildGui(){
JButton button = new JButton("action");
button.addActionListener( evt->{
if(!running){
startGameLoop();
} else{
stopGameLoop();
}
});
frame.add(nonGame, BorderLayout.CENTER);
frame.add(button, BorderLayout.SOUTH);
frame.setSize(400, 400);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
public static void main(String[] args){
EventQueue.invokeLater( new SwapCards()::buildGui );
}
}
First off, Canvas is somewhat outdated, use a JPane and override paintComponent. That gives you more access to the power of swing.
In this example I am doing trivial work so the thread is absolutely overkill I could replace it with a javax.swing.Timer.
Timer timer = new Timer(30, evt->{
step();
gamePanel.repaint();
});
Then in the start and stop methods, I just call timer.start() or timer.stop() respectively.
Using a CardLayout makes it a bit clearer what you want to do, plus it has methods for navigating the cards. Eg. If you have a cut scene with a series of components you want to show, you can use cardLayout.next(parent).
When we create the layout:
cards = new CardLayout();
swap = new JPanel(cards);
swap.add(gamePanel, "game");
swap.add(nonGame, "nogame");
cards.last(swap);
frame.add(swap, BorderLayout.CENTER);
This will add the cards to swap and make it show "nogame". Then in the start/stop methods we just switch to the respective card.
cards.show(swap, "game");

How can I stop an image from flickering/dislocating when a JFrame is resized?

I have a JFrame. Within that JFrame I have a JLayeredPane layed out with an OverlayLayout that contains multiple Jpanels. in one of those JPanels I have a BufferedImage. When the JFrame is resized, the image quickly dissapears and dislocates, then it jumps back again, dislocates again, back again, and so on.
I have tried a lot of things to stop the image from flickering, but I don't know what the cause of the problem exactly is.
The Jpanel that holds the image contains the following code to render the image:
protected void paintComponent (Graphics g) {
super.paintComponent(g);
g.drawImage(myBufferedImage, 0, 0, 200, 200, null);
}
In trying to reconstruct and simplify the problem, I got a working version of what I wanted. I still don't know what the problem is with my other code though.
This is the code that works:
import java.awt.*;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.swing.*;
public class Main {
public Main() {
// Create the JFrame:
JFrame window = new JFrame();
window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
window.setSize(600, 400);
window.setLayout(new FlowLayout(FlowLayout.LEFT, 0, 0));
// Create the pane to hold layers:
JLayeredPane layers = new JLayeredPane();
layers.setLayout(new OverlayLayout(layers));
// Add two layers:
layers.add(new MyGraphics());
layers.add(new MyImage());
//
window.add(layers);
window.setVisible(true);
}
public static void main(String[] args) {
Main app = new Main();
}
public class MyImage extends JPanel {
public BufferedImage source;
public MyImage () {
this.setPreferredSize(new Dimension(180,180));
this.setLocation(0,0);
try {
this.source = ImageIO.read(new File("image.jpg"));
} catch (IOException ie) {
ie.printStackTrace();
}
}
protected void paintComponent (Graphics g) {
super.paintComponent(g);
g.drawImage(this.source, 0, 0, 180, 180, null);
}
}
public class MyGraphics extends JPanel {
public MyGraphics () {
this.setOpaque(false);
this.setPreferredSize(new Dimension(180,180));
this.setLocation(0,0);
}
protected void paintComponent (Graphics g) {
super.paintComponent(g);
g.drawLine(0, 0, 180, 180);
}
}
}
Try adding below line of code in constructor:
public FlickerDemo()
{
// No flickering during resize
System.setProperty("sun.awt.noerasebackground", "true");
}

JFrame adding 22 pixel offset (java.awt.Insets) to top of the Frame

I'm creating a simple Break Out style game. The main game extends JFrame and I'm adding a JPanel to the frame.
When I was using paint() to draw the game graphics the items sat within the window as expected (i.e by their x, y coordinates).
I've updated the code to use BufferStrategy as I was getting flickering. Since the, the graphics that are rendered are offset by 22px.
This means the Bricks are off the top of the screen!
The code is as follows:
package BreakOut;
import javax.swing.*;
import java.awt.*;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.image.BufferStrategy;
public class Game extends JPanel implements KeyListener{
GameStateManager gsm = new GameStateManager();
BufferStrategy strategy;
public Game() {
//add menu state to GameStateManager
gsm.add(new MenuState(gsm));
createFrame();
while(true)
{
gsm.update();
//repaint();
render();
try{
Thread.sleep(10);
}
catch(InterruptedException e)
{
}
}
}
public void createFrame()
{
JFrame frame = new JFrame("Mini Tennis");
frame.setLayout(new BorderLayout());
this.setPreferredSize(new Dimension(400,400));
frame.add(this);
frame.pack();
frame.setBackground(Color.BLACK);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
addKeyListener(this);
this.setFocusable(true);
frame.createBufferStrategy(2);
strategy = frame.getBufferStrategy();
frame.setVisible(true);
System.out.println(frame.getInsets());
}
public void render()
{
Graphics g = strategy.getDrawGraphics();
super.paint(g);
gsm.render(g);
g.dispose();
strategy.show();
}
public void keyPressed(KeyEvent k) {
switch(gsm.getState())
{
case MAIN_MENU:
if(k.getKeyCode()==KeyEvent.VK_ENTER)
{
//add the PlayState to the Stack and update enum value
gsm.add(new PlayState(gsm, this));
}
break;
case PLAYING:
if(k.getKeyCode()==KeyEvent.VK_P)
{
//add the PlayState to the Stack and update enum value
gsm.add(new PauseState(gsm));
}
break;
case PAUSE:
if(k.getKeyCode()==KeyEvent.VK_P)
{
gsm.pop();
}
break;
case GAME_OVER:
if(k.getKeyCode()==KeyEvent.VK_ENTER)
{
gsm.pop();
}
break;
}
//send input to GameStateManager
gsm.keyPressed(k.getKeyCode());
}
public void keyReleased(KeyEvent k) {
gsm.keyReleased(k.getKeyCode());
}
public void keyTyped(KeyEvent k) {
gsm.keyTyped(k.getKeyCode());
}
public static void main(String[] args) {
new Game();
}
}
When I output System.out.println(frame.getInsets()); I get
java.awt.Insets[top=22,left=0,bottom=0,right=0]
I'm obviously doing something wrong but can't figure out why adding BufferStrategy would create the JPanel to be offset by 22px
Any help would be appreciated :)
Frames have borders and decorations, which is included within the bounds of the frame (they don't get added to the outside), from the looks of things you're using MacOS and the 22pixels to the top is the window title.
Best solution is, don't use the frame as the render surface, instead, use the Game class. This will mean it will need to extend from java.awt.Canvas instead of javax.swing.JPanel and you will need to create the BufferStrategy from it
If you override the Canvas's getPreferredSize method, you can use pack on the frame it will pack the window around this, so the physical frame will be larger then the content, but the content will be the size you would prefer
You will also want to move you main/game loop to separate thread, as this is current in the risk of blocking the Event Dispatching Thread, which could cause you no end of issues (like never getting a key event)

How to add picture out of JFrame, attached to border

Hi I Would like add to my JFrame border some image.
Is this Possible to attach picture to borders for JFrame and create it as 1 object ?
Something like this:
I'm not sure if it's possible to add the image directly to the border of a JFrame (suggestions welcome). I decided to solve this issue by using a transparent content pane, and using an inner frame to "appear" like the outer frame.
The code is pretty simple, however, let me know if you'd like an explanation of how the code works.
Here's the minimum code you'll need to get up and running.
You'll need to provide your own transparent-phone.png image, in the root of the classpath (i.e. next to your PhoneWindow.java file, in the root package).
import javax.imageio.ImageIO;
import javax.swing.*;
import java.awt.*;
public class PhoneWindow {
public static void main(String[] args) {
new PhoneWindow();
}
public PhoneWindow() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
final JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
// create the inner frame
final JInternalFrame frame2 = new JInternalFrame("My Telephone");
frame2.setClosable(true);
frame2.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
// add elements to the outer frame
frame.setUndecorated(true);
frame.setBackground(new Color(0, 0, 0, 0));
JPanel pane = new TranslucentPane();
frame.setContentPane(pane);
frame.setLayout(new BorderLayout());
// add inner frame and phone picture
frame.add(frame2, BorderLayout.CENTER);
frame.add(new JLabel(new ImageIcon(ImageIO.read(getClass().getResource("/transparent-phone.png")))), BorderLayout.EAST);
frame.setLocationRelativeTo(null);
frame.setMinimumSize(new Dimension(400, 300));
frame.pack();
// show
frame2.setVisible(true);
frame.setVisible(true);
} catch (Throwable ex) {
ex.printStackTrace();
}
}
});
}
public class TranslucentPane extends JPanel {
public TranslucentPane() {
setOpaque(false);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
g2d.setComposite(AlphaComposite.SrcOver.derive(0f));
g2d.setColor(getBackground());
g2d.fillRect(0, 0, getWidth(), getHeight());
}
}
}
Here's the full Java class (including close and draggable behaviour)
https://gist.github.com/nickgrealy/16901a6428cb79d4f179
And here's a screenshot of the final product
N.B. the transparent sections inside/outside the phone.
References:
How to make a transparent JFrame but keep everything else the same?
Trying to disable dragging of a JInternalFrame

Platform independent Image in java

I am trying to paint an image onto a panel, that is contained by a frame.
Let us say I have a 320 x 480 image.
When i try to create a frame with size 320x480 and add the panel into it, I encounter a problem.
In different operating systems, the JFrame of 320x480 is of different sizes due to title bar.
Thus my correct fit image in windows XP will not be properly painted in Windows8 or Ubuntu.
A grey patch is visible because the image was not properly placed.
I tried overriding paint method and using ImageIcon.
Please do offer a solution.
TIA
Code Snippet
CLASS PA CONTENTS
setPreferredSize(new Dimension(500,500));
.
.
JLabel image= new JLabel();
ImageIcon background = new ImageIcon(getClass().getClassLoader().getResource("Flower.jpg"));
image.setBounds(0, 0, 500, 500);
image.setIcon(background);
this.add(image); //where "this" is extending from JPanel
CLASS PB CONTENTS
frame = new JFrame("Test");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
inserting(frame.getContentPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
frame.setResizable(false);
private void inserting(Container pane)
{
cardPanel=new JPanel();
CardLayout cards=new CardLayout();
cardPanel.setLayout(cards);
PA home= new PA();
cardPanel.add(home,"homeScreen");
pane.add(cardPanel);
}
Don't call setSize at all, call pack (as VGR stated in his comment). pack will size your JFrame based on size's of component's within it, and gaps between those component's.
Now.. issue you will encounter is that your JFrame will be small at startup. So override getPreferredSize method for your JPanel to return dimensions of your image:
public void getPreferredSize() {
return new Dimension(image.getWidth(), image.getHeight());
}
Now your image will fit perfectly and your application will be fully OS independent.
And also, do not override paint method. Instead, override paintComponent.
Here is a small demo I made in cases like yours:
import javax.imageio.ImageIO;
import javax.swing.*;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
public class Drawing {
JFrame frame = new JFrame();
public Drawing() {
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
frame.add(new Panel());
frame.pack();
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
new Drawing();
}
});
}
class Panel extends JPanel {
BufferedImage image = null;
Panel() {
try {
image = ImageIO.read(new File("path-to-your-image"));
} catch (IOException e) {
e.printStackTrace();
}
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawImage(image, 0, 0, this);
}
#Override
public Dimension getPreferredSize() {
// Panel will be sizes based on dimensions of image
return new Dimension(image.getWidth(), image.getHeight());
}
}
}
It seems to be the layout problem. The most obvious solution is to wrap you image panel into another container with proper layout, so your panel will always have the same size.

Categories

Resources