I need a certain image to be redrawn at different locations constantly as the program runs. So I set up a while loop that should move an image across the screen, but it just redraws the image on top of itself over and over again. What am I doing wrong? Is there a way to delete the old image before drawing it in a new location?
JFrame frame = buildFrame();
final BufferedImage image = ImageIO.read(new File("BeachRoad_double_size.png"));
JPanel pane = new JPanel() {
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
int num = 0;
boolean fluff = true;
while (fluff == true) {
num = num + 1;
g.drawImage(image, num, 0, null);
if (num == 105) {
fluff = false;
}
}
}
};
frame.add(pane);
You can't code a loop in the paintComponent() method. The code will execute so fast that the image will only be painted in the final position, which in your case should be with an x position of 105.
Instead you need to use a Swing Timer to schedule the animation every 100 milliseconds or so. Then when the timer fires you update the x position and invoke repaint() on the panel. Read the Swing tutorial on Using Swing Timers for more information.
Putting a while loop inside a paintComponent method is not the way to do it. Instead, there should be some setup like the following:
...
final int num = 0;
final JPanel pane;
Timer timer = new Timer(10, new ActionListener() {
public void actionPerformed(ActionEvent e) {
num++;
pane.repaint();
}
});
pane = new JPanel() {
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawImage(image, num, 0, null);
}
});
timer.start();
This will move the image ever 10 milliseconds, as specified in the Timer constructor.
This is a common issue people starting out in animation have, as I did. You can't 'remove an image' from the screen. However, you can repaint the entire screen, then redraw your image at a new location.
In psuedocode:
while (condition)
background(white); //or whatever color your background is
drawImage(x,y);
The code above clears the screen so it's safe for you to redraw your image. This effectively 'deletes' your image.
Edit: I didn't read your code, I just addressed your question. So other answers that fix your code are probably better than mine.
Related
I am coding a game in Java. The first screen is the begin screen. On a mouse click, the screen is supposed to switch to a screen that lets you choose a character. Then, the next screen is the first game question. The character choosing screen isn't coming. It is just going to the first question. drawq is the boolean that determines if the question has been drawn. I had initialized it to false in the beginning of my code. Here is my mouselistener method, please help me find a way to get the screen that displays "Choose your character" and the image of the girl.
public void mouseReleased(MouseEvent e) {
// TODO Auto-generated method stub
//background image
g.drawImage(theme.getImage(),0,0,WIDTH,HEIGHT,null);
//Choosing character screen
g.drawString("Choose your character", WIDTH-1550, HEIGHT/6);
g.drawImage(girl.getImage(),WIDTH-1550,HEIGHT/2,237,338,null);
//switching drawq to false
drawq=true;
//draw next question on mouse click
if(drawq==true) {
questions[whichquestion].draw(g);
drawq=false;
repaint();
}
It seems like you have got the order of your code mixed up, you set drawq to true and then immediately afterwards check if it's true, which will obviously always result in the if clause beeing run.
There are tons of tutorials for developing games on Java. Also here on SO there are many questions and answers. Just take a look at one or the other and let yourself be inspired.
An example of mouse input and screen output:
public class Main extends JPanel{
private int clicks = 0;
private Random rnd = new Random();
public static void main(String[] args){
var frame = new JFrame();
frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
frame.setContentPane(new Main());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setResizable(false);
frame.setVisible(true);
}
public Main(){
addMouseListener(new MouseInputAdapter(){
#Override
public void mouseClicked(MouseEvent e){
clicks = e.getClickCount();
repaint();
}
});
setPreferredSize(new Dimension(640, 480));
repaint();
}
#Override
public void paintComponent(Graphics g){
g.setColor(Color.BLACK);
g.fillRect(0, 0, getWidth(), getHeight());
g.setColor(Color.WHITE);
g.drawString("Click your Mousebutton! One, two, three times or more ;-)", 20, 20);
if(clicks > 0) {
g.drawString("Clicks counted: " + clicks, 20, 40);
for(int i = 0; i < clicks; i++){
g.setColor(new Color(rnd.nextInt(256),rnd.nextInt(256),rnd.nextInt(256),255));
g.drawLine(20, 60 + i * 10, 100 + i * 20, 60 + i * 10);
}
}
}
}
I suggest that you go back to the drawing board. You should create a JFrame subclass that has a JLabel and a JImage on it. Then when the user clicks the button, just show that JFrame.
In gneeral, don't use a Graphics object outside of paintComponent(). And don't use drawImage() or drawText() unless you have a good reason. In this case, a JLabel and a JImage are much more appropriate for implementing the elements in the screen.
I have class that creates a new thread.
`
public ScreenG(JPanel PanelR)
{
Panel = PanelR;
RenderImage = new BufferedImage(1, 1, BufferedImage.TYPE_INT_RGB);
FPS = 25;
Hide = false;
(new Thread()
{
public void run()
{
while(true)
{
if(Hide == false)
{
Timer = System.currentTimeMillis() + (1000/FPS);
if(!DrawRendering)
{
Graphics g = Panel.getGraphics();
g.drawImage(RenderImage, 0, 0, null);
}
DrawRendering = false;
while(System.currentTimeMillis() <= Timer) try { Thread.sleep(1); } catch (InterruptedException e) {Thread.currentThread().interrupt();}
}
}
}
}).start();
}
public void draw(BufferedImage ImageR)
{
DrawRendering = true;
RenderImage = ImageR;
Graphics g = Panel.getGraphics();
g.drawImage(RenderImage, 0, 0, null);
}`
In my main I create a new instance of ScreenG. This will start a new thread that draws a bufferedImage onto a JPanel with a consistent FPS.
In the main I would then call draw with the image that I created. Sometimes it works but sometimes the image on the panel flickers. I try variations like the draw function taking over the drawing. Non of them work. I could only REDUCE the flickering.
Not possible by design. Swing does not synchronize to the bitmap raster DMA that's actually sending the screen data to your monitor, so it always possible that the screen buffer is read by the DMA while you're busy rendering to it (possible exception is Fullscreen mode).
To at least minimize flickering follow the recommended method of custom Swing painting: https://docs.oracle.com/javase/tutorial/uiswing/painting/
You can easily trigger periodic repaints on the EDT using a Swing timer, or SwingUtilities.invokeAndWait/invokeLater from another thread (whatever works best in your design).
The flickering can be because the rendering isn't fast enough from an update.
Now I do recommend you use Swings paintComponent (Graphics g) when rendering its components. That being said. To solve the flickering for you add a BufferStrategy in your JFrame
Without that code avaible I can only provide a general solution.
JFrame jframe = new JFrame ();
...
BufferStrategy bufferstrategy = jframe.getBufferStrategy ();
if (bufferstrategy == null) {
jframe.createBufferStrategy(3);
return;
}
g.dispose();
bufferstrategy.show();
To read more about BufferStrategy I recommend a read over at the documentation.
Small Note
There is no reason in your code to either store JPanel PanelR or BufferedImage ImageR. You can instead directly invoke methods directly on PanelR resp. ImageR.
Thank you for the answers. I read Oracle tutorial that you recommended and get my paintComponent() function working correctly on the main thread. To do that I am calling JPanel().repaint() from the draw() function. I will learn about using BufferStrategy next.
I am trying to add a JLabel under the boards that is painted with graphics. I set the x and y position of JLabel somewhere under the board
pName = new JLabel("Yoooooo");
pName.setBounds(180,500,50,50);
this.add(pName);
However I can't view it when I run the code. What can be the reason?
Edit: Now I can view it thx to Andrew but I can't locate it to the position that I want.
JLabel should be under the board.
pName.setBounds(180,500,50,50);
seems not working. Why can it be?
public class EnemyPanel extends JPanel{
private char enemyBoard[][] = new char[10][10]; //to keep state of squares
private Rectangle r[][] = new Rectangle[10][10];//to give coordinates and boundaries to squares
private int size;
private JLabel pName;
public EnemyPanel()
{
size=Constant.rectSize;
for(int i=0;i<10;i++){
for(int j=0;j<10;j++){
enemyBoard[i][j]='*'; //initialization type
r[i][j]= new Rectangle(j*size+30,i*size+30, size, size);
}
}
pName = new JLabel("Yoooooo");
pName.setBounds(180,500,50,50);
this.add(pName);
}
public void paint(Graphics g){
super.paintComponent(g);
for(int i=0;i<10;i++){
for(int j=0;j<10;j++){
if(enemyBoard[i][j]=='!'){
//hit square's color is changed to white
g.setColor(Color.white);
}
else if(enemyBoard[i][j]=='$')
//miss square's color is changed to gray
g.setColor(Color.gray);
else
//undiscovered ones are green
g.setColor(Color.green);
g.fill3DRect((int)r[i][j].getX() ,(int)r[i][j].getY(),(int)r[i][j].getWidth(), (int)r[i][j].getHeight(), true);
}
}
}
}
Here is the output:
Once you override paint(Graphics g) you are on your own on painting the component. This means that no matter what other stuff you do outside paint, it may not count much as the real paint happens in your custom implementation and not in the default JPanel one.
My first problem was that I couldn't view the JLabel component.
According to Andrew's comment, I solved this problem.
If you are using other components in your panel, you should use paintComponent other than paint.
Secondly, I couldn't view my JLabel where I wanted. So In order to set a specific position to JLabel, you should set the JPanel layout to null.
this.setLayout(null) solved my second problem.
I made some menu and it is to update conmmon variables (for text on grid) then the out-of-focus dialog must repaint the grid. Here is the screenshot:
The main control panel is always at top position and 'Data Display' panel is always sitting behind it. When press a button on front panel, Data Display must update its grid. Currently, the common variable 0.4 on the grid is updated by adding listener and works fine. But the grid itself is not repainting anymore. How can I repaint the out-of-focus dialog in real time?
Here is the code of the front panel:
public class MainDisplayForm extends javax.swing.JFrame {
Storage st = new Storage();
DisplayForm dF = new DisplayForm();
....
public MainDisplayForm() {
initComponents();
Btn_IncreaseGain.addActionListener(new ButtonListener_IncreaseGain());
}
....
} //MainDisplayForm ends here.
class ButtonListener_IncreaseGain implements ActionListener {
DisplayForm dF = new DisplayForm();
Storage st = new Storage();
ButtonListener_IncreaseGain()
{
}
public void actionPerformed(ActionEvent e) {
st.iGain = 20;
dF.revalidate();
dF.repaint();
System.out.println("Testing");
}
}//Listener ends here.
Here is code of Data Display:
public void paint(Graphics g)
{
g2 = (Graphics2D) g;
paintComponents(g2);
//added numbers are for adjustment.
int x = this.jPanel1.getX()+8;
int y = this.jPanel1.getY()+30;
int width = this.jPanel1.getWidth()+19;
int height = this.jPanel1.getHeight()+40;
//labelling voltages
label0.setText(st.zero);
label1.setText(st.v1);
label2.setText(st.v2);
label3.setText(st.v3);
label4.setText(st.v4);
label5.setText(st.v3);
label6.setText(st.v4);
g2.setColor(Color.darkGray);
for(int i=x; i<width; i=i+80)
{
g2.drawLine(i, y, i, height);
}
int j = 0;
for(int i=y; i<height; i=i+80)
{
j++;
//st.iGain
g2.setColor(Color.orange);
if(j==1)
{
double k1 = st.iGain * 0.4;
st.v1 = Double.toString(k1);
g2.drawString(st.v1, x+5, y+10);
}
if(j==2)
{
double k2 = st.iGain * 0.3;
st.v2 = Double.toString(k2);
g2.drawString(st.v2, x+5, y+90);
}
g2.setColor(Color.DARK_GRAY);
g2.drawLine(x, i, width, i);
....
} //grid info is not completed yet.
Thanks,
Focus isn't the issue and has nothing to do with your current problem. The solution is to change the properties of the data grid by updating fields it contains via setter methods and calling repaint on the JComponent (perhaps a JPanel, or some other component that derives ultimately from JComponent) held by the data grid. The paintComponent method of this component should use its class fields to update what it draws.
You almost never paint in the paint method of a JComponent and certainly you don't want to draw directly into a top-level window. You also probably don't want to set text of JLabels, JTextFields, or any other JTextComponent. from within paint/paintComponent.
I can't see why your code is not working and can only guess that the likely cause of your problem is in code not shown.
Edit 1:
Just guessing, but you may have a problem of references. I notice that your listener class creates new DisplayForm and Storage objects:
DisplayForm dF = new DisplayForm();
Storage st = new Storage();
There's a good possibility that these objects are not the ones being displayed, especially if you create these objects elsewhere and display them. Again I'm just guessing since I don't see the rest of your code, but perhaps you should to pass references for these objects into the DisplayForm via constructor or setter method parameters.
Edit 2:
e.g.,
public void setDisplayForm(DisplayForm dF) {
this.dF = dF;
}
// same for Storage
And in the main program:
public MainDisplayForm() {
initComponents();
ButtonListener_IncreaseGain btnListenerIncreaseGain = new ButtonListener_IncreaseGain();
btnListenerIncreaseGain.setDisplayForm(....);
btnListenerIncreaseGain.setStorage(....);
Btn_IncreaseGain.addActionListener(btnListenerIncreaseGain);
}
I wish to place a small Jframe right above the Button, on ActionPerformed
I directly tried to get the X (getX()) and Y(getY()) co-ordinates of the JScrollPane in which the button is added, but it always seems to return wrong co-coordinates
values returned by jScrollPane1.getLocation()
java.awt.Point[x=10,y=170]
The above values are same independent on where I place the JScrollPane on the screen.
This works if I remove the JScrollPane and directly try to get the Jpanels co-ordinates!!
for example
private void showDialog() {
if (canShow) {
location = myButton.getLocationOnScreen();
int x = location.x;
int y = location.y;
dialog.setLocation(x - 466, y - 514);
if (!(dialog.isVisible())) {
Runnable doRun = new Runnable() {
#Override
public void run() {
dialog.setVisible(true);
//setFocusButton();
//another method that moving Focus to the desired JComponent
}
};
SwingUtilities.invokeLater(doRun);
}
}
}
This nice method will help you:
// Convert a coordinate relative to a component's bounds to screen coordinates
Point pt = new Point(component.getLocation());
SwingUtilities.convertPointToScreen(pt, component);
// pt is now the absolute screen coordinate of the component
Add: I didn't realise, but like mKorbel wrote, you can simply call
Point pt = component.getLocationOnScreen();
Since you want to spawn a new frame right above a given component, you want to get the screen coordinates of your component.
For this, you need to use the getLocationOnScreen() method of your component.
Here is a useful code snippet :
public void showFrameAboveCmp(Frame frame, Component cmp) {
Dimension size = cmp.getSize();
Point loc = cmp.getLocationOnScreen();
Dimension frameSize = frame.getSize();
loc.x += (size.width - frameSize.width)/2;
loc.y += (size.height - frameSize.height)/2;
frame.setBounds(loc.x, loc.y, frameSize.width, frameSize.height);
frame.setVisible(true);
}