Paint function defined in separate thread not drawing (Java) - java

I have three files, here is the main one, titled Display.java:
import java.awt.*;
import javax.swing.*;
public class Display{
static JFrame main = new JFrame("hello");
static Container c = main.getContentPane();
static StartScreen start = new StartScreen();
static screenTracker track = new screenTracker();
public static void main(String[] args) {
main.setSize(new Dimension(1920,1080));
main.setVisible(true);
if(track.screen==1) {
main.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
c.add(start, BorderLayout.CENTER);
}
}
}
My second file is titled: StartScreen.java. It contains my paint function:
import java.applet.Applet;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
public class StartScreen extends Applet{
private static final long serialVersionUID = 1L;
int x = 0;
static Container c = Display.c;
static Color gray = new Color(128,128,128);
public void paint(Graphics g) {
Thread t = new Thread() {
#Override
public void run() {
while(true) {
c.setBackground(gray);
g.clearRect( 0 , 0 , getWidth() , getHeight() );
BufferedImage img1 = null;
BufferedImage img2 = null;
BufferedImage img3 = null;
try {
img1 = ImageIO.read(new File("images/img1.png"));
img2 = ImageIO.read(new File("images/img2.png"));
img3 = ImageIO.read(new File("images/img3.png"));
}
catch(IOException e) {
g.drawString("bad", 200, 200);
}
String title1 = "hello: ";
String title2 = "Gamee ";
String title3 = "people";
Color pink = new Color(244,66,182);
Color black = new Color(0,0,0);
g.setColor(black);
g.setFont(new Font("TimesRoman", Font.PLAIN, 50));
g.drawString(title1, x+600, 200);
g.setColor(pink);
g.setFont(new Font("TimesRoman", Font.ITALIC, 50));
g.drawString(title2, 860, 200);
g.setFont(new Font("TimesRoman", Font.PLAIN, 50));
g.setColor(black);
g.drawString(title3, 960, 200);
g.drawImage(img1, 200, 250, null);
g.drawImage(img2, 700, 150, 1000, 750, null);
g.drawImage(img3, 500, 250, null);
x++;
try {
sleep(10); // milliseconds
} catch (InterruptedException ex) {}
}
}
};
t.start();
}
}
My third file is short:
public class screenTracker {
int screen = 1;
}
Right now I just want the paint function in StartScreen.java to display on my JFrame. I want hello to move across the screen. I made the thread t so the screen can close. If I get rid of the thread t, or create it in Display.java (inside the if statement, around where I set the default close operation and add the startscreen to the container c) the program draws what I want, but the Jframe won't close. I have looked in a lot of other websites, and questions, but I haven't been able to figure this out. I am new to multithreading and graphics in java,

Many problems:
Don't extend Applet. Applets are dead. For custom painting you would simply extend JPanel and add the panel to the frame.
You would override paintComponent() in the JPanel, not paint().
A painting method is for painting only you should NOT:
read the image in the method. The painting method gets called many times. You want painting to be fast and should not be doing I/O. Read the images in the constructor of your class
be creating Threads. Again since the painting method is called multiple times that you can't control you don't want to keep creating Threads. If you want animation of some kind then you should be using a Swing Timer.
I suggest you read the section from the Swing tutorial on Custom Painting. It contains working examples that will show you how to better structure your code.
There is also a section on How to Use Swing Timers.

If we speak about graphics in java we have only one thread responsible for It the EDT ([1][Event Dispatch Thread]). In other words whatever you want to do with the view will be handled by and has to be handled by EDT, yes your view also with all setbackroungs, frames...
But be aware, it is only one thread, when this tread is busy doing some calculation cannot react to the user events, so your view will freezee. What you can do in another thread is to prepare the data (in your case read the file images)
To work in the EDT you use SwingInvoker.invokelater() or check if you are already in EDT by using swingutilities.isEventDispatchThread()
[1] https://docs.oracle.com/javase/tutorial/uiswing/concurrency/dispatch.html

Related

How to draw graphs using Java Swing

I am learning Java Swing. I followed a YouTube lectures playlist that provided this github code for drawing graphs. I am providing the basic structure here:
package graphapp;
import com.sun.corba.se.impl.orbutil.graph.Graph;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import javax.swing.JComboBox;
import javax.swing.JFrame;
public class GraphApp extends JFrame {
int x,y;
int ax,by;
JComboBox cb,cb1;
String s="";
String se ="";
public GraphApp(){
setTitle("Graph App");
setSize(900,700);
String[] graphs = {"select..","parabola","ax^2+bx+c","ax^3","y=mx","y=mx+c","sin(x)","cos(x)","tan(x)","sinc function","signum(x)","X-graph","cubic function","sin+cos unequal amp","sin^3","cos^3","sin^3+cos^3","Amplitude Modulation"};
cb = new JComboBox(graphs);
cb.setBounds(700, 100, 120, 25);
add(cb);
cb.setEditable(false);
String[] select = {"Draw graph","Erase"};
cb1 = new JComboBox(select);
cb1.setBounds(700, 150, 120, 25);
add(cb1);
cb1.setEditable(false);
setLayout(null); //add it its very important otherwise Combo Box will occupy the whole screen.
setVisible(true);
setResizable(false);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
x = 30; //x=200;
y = 300;
}
public void paint(Graphics g){
super.paint(g); //This method was not called in The Master Branch at github
g.setColor(Color.BLACK);
g.drawString("Design by ..", 700, 400);
g.drawString("Debasish Roy", 700, 420);
g.drawString("Y", 310, 40);
g.drawString("Y'", 310, 600);
g.drawString("X", 30, 314);
g.drawString("X'", 600, 314);
if(x==300&&y==300){
g.drawString("Origin(0,0)", 310, 314);
}
g.drawString("Select Graphs", 710, 100);
g.drawLine(300, 30, 300, 600);
g.drawLine(30,300,600,300);
if(x>599||y<40){
g.drawString((String) cb.getSelectedItem(), 200, 200);
s= String.valueOf(cb.getSelectedItem());
se = String.valueOf( cb1.getSelectedItem());
x=30;
y=300;
}
if(s.equals("parabola")&& se.equals("Draw graph")){
g.setColor(Color.GREEN);
run1(); // function to set x and y values
}
//Other checks to find the type of graph selected
else{
g.setColor(Color.white);
run();
}
g.fillOval(x, y, 3, 3);
repaint(); // When I run this code, the window keeps flickering. I think it is because this method called without any check.
//However I don't know much about this method
}
public void run(){
try{
Thread.sleep(1);
if(x<600&&y>30&&y!=600){
ax = x-300;
by = y-300;
ax++;
by = (int) (40*(1+1*Math.cos(.2*ax/3.14))*Math.cos(ax/3.14)+40*(40*Math.sin(.2*ax/3.14))/ax); // AM+sinc(x) function
x=300+ax;
y=300-by;
}
}catch(Exception e){
System.out.println("ERROR");
}
}
public static void main(String[] args){
new GraphApp();
Thread t1 = new Thread();
t1.start();
}
}
When I run this code, no graph gets drawn. Also, when I change selection in JComboBox no change is detected. Please help me what am I missing to grab here.
When I run this code, no graph gets drawn.
You should NOT be overriding paint(...) on a JFrame. Custom painting is done by overriding paintComponent(...) of a JPanel and then you add the panel to the frame. Read the section from the Swing tutorial on Custom Painting for more information and working examples to get you started.
A painting method is for painting only. You should NOT invoke repaint(). Swing will determine when a component needs to repaint itself or you invoke repaint() in an external method. The painting code should only paint the current state of the component. If you have properties that change then you need methods to change those properties and then repaint() the component.
You should NOT invoke Thread.sleep() in a painting method.
You are creating a Thread that does nothing. You need to pass a runnable object to the Thread if you want to execute code when the Thread starts. However, in this case if you want animation of the graph then you should be using a Swing Timer for the animation. When the Timer fires you update the properties of your class and then invoke repaint().
Also, when I change selection in JComboBox no change is detected.
You need to add an ActionListener to the combo box. Read the section from the Swing tutorial on How to Use Combo Boxes.
So you need to spend time reading the Swing tutorial to learn some Swing basics before continuing on with your project.

Java Canvas Won't Paint

I want my program to display the canvas that is repainted once at the start and then whenever a change is made afterwards. I thought I had everything coded correctly, but for some reason nothing that is painted onto the canvas actually shows (I know it's repainting, I tested that).
Here are the code segments:
public TileMapCreator()
{
currentView = new BufferedImage(640, 640, BufferedImage.TYPE_INT_ARGB);
currentView.getGraphics().setFont(new Font("Arial", Font.BOLD, 100));
currentView.getGraphics().drawString("No Map Yet Open", currentView.getWidth()/2, currentView.getHeight()/2);
this.setJMenuBar(createMenuBar());
this.setContentPane(createMapPanel());
}
private JPanel createMapPanel()
{
JPanel p = new JPanel();
p.add(setUpCanvas());
p.setVisible(true);
return p;
}
private Canvas setUpCanvas()
{
mapCanvas = new Canvas(){
private static final long serialVersionUID = 1L;
public void repaint()
{
mapCanvas.getGraphics().drawImage(currentView, 0, 0, this);
}
};
mapCanvas.setIgnoreRepaint(true);
Dimension size = new Dimension(currentView.getWidth(), currentView.getHeight());
mapCanvas.setSize(size);
mapCanvas.setPreferredSize(size);
mapCanvas.setMaximumSize(size);
mapCanvas.setMinimumSize(size);
mapCanvas.setFocusable(true);
mapCanvas.addMouseListener(this);
mapCanvas.addMouseMotionListener(this);
mapCanvas.setVisible(true);
return mapCanvas;
}
Currently the area where the canvas should be painting is just the regular grey color of the Java GUI. Thanks for your help!
You appear to be mixing Swing with AWT components, and drawing in a very strange way, one that I've honestly never seen before (and I've seen a lot). Why not simply do your drawings in the paintComponent(Graphics g) method of a JPanel using the Graphics object given by the JVM, like you'll find in the Swing graphics tutorials and 98% of the Swing graphics answers on this site? Also for my money, I'd avoid using Canvas or trying to mix heavy and light weight components together. Stick with Swing all the way, and things should go more smoothly.
I'd be happy to give you more specific advice and perhaps some if you could create and post a minimal example program. Please have a look at the link and let us know if you need more information.
For example:
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.image.BufferedImage;
import javax.swing.*;
public class TestImagePanel extends JPanel {
private static final int BI_WIDTH = 640;
BufferedImage currentView = new BufferedImage(BI_WIDTH, BI_WIDTH, BufferedImage.TYPE_INT_ARGB);
public TestImagePanel() {
Graphics g = currentView.getGraphics();
Graphics2D g2 = (Graphics2D) g;
g2.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
g.setColor(Color.black);
g.setFont(new Font("Arial", Font.BOLD, 60));
g.drawString("No Map Yet Open", 20, currentView.getHeight()/2);
g.dispose();
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
if (currentView != null) {
g.drawImage(currentView, 0, 0, this);
}
}
#Override
public Dimension getPreferredSize() {
if (currentView != null) {
return new Dimension(BI_WIDTH, BI_WIDTH);
}
return super.getPreferredSize();
}
private static void createAndShowGui() {
TestImagePanel mainPanel = new TestImagePanel();
JFrame frame = new JFrame("TestImagePanel");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(mainPanel);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGui();
}
});
}
}
I've found the Swing tutorials to be a great asset, and bet you would too. Please have a look at them.
Edit
You ask:
Hmm, so apparently getting the graphics object of my buffered image twice did not result in anything actually being painted... I had to get the graphics object, assign it to a variable, and then paint.
I wouldn't say that. I'd say that your way of getting the Graphics object should work the same as mine, but yours is not safe since the Graphics objects obtained in this way cannot be disposed of, and you risk running out of resources. I think that your image didn't show up due to your very convoluted and unusual way of trying to display your image. You override repaint(), put some weird code inside of it, tell the JVM to ignore repaint calls, never call the repaint super method inside of your override, so that repaint does in fact nothing. I have to wonder -- where did you get this code? Was it from a tutorial? If so, please share the link here. And never get advice from that site again.

Painting an image on a JButton?

I'm building a board game and I have a grid with empty circles.
I created my own template (.png) of a single empty circle in photoshop and added it repeatedly (using a loop and layout manager) and used it to create multiple buttons and laid them out on a panel.
I created custom circular tokens (.png) in photoshop as well which will then sort of "fill up" or take the empty space in the circles upon the occurrence of an event. I hope you get what I mean.
I'm not entirely certain that using paint() is the only way to do this.
Could anyone drop a few hints on how I can achieve this? I'm new to GUI.
This is what my grid looks like:
And those empty white spaces are the spaces that the tokens I created will have to take up, but I'm not sure ho I will do that aside from familiarizing myself with paint()
This is the .png file that will "fill up" the empty spaces when the user clicks on a button
Have a look over this for ideas. It combines the 2 images for the 'final logic' look:
I couldn't be bothered waiting for the transparent template, so I made my own. ;)
import java.awt.*;
import java.awt.event.*;
import java.awt.geom.*;
import java.awt.image.BufferedImage;
import javax.swing.*;
import java.net.URL;
import javax.imageio.ImageIO;
class GameGrid {
public static BufferedImage getImage(BufferedImage image, boolean fill) {
int pad = 4;
BufferedImage temp = new BufferedImage(
image.getWidth()+2*pad,
image.getHeight()+2*pad,
BufferedImage.TYPE_INT_ARGB);
Graphics2D g = temp.createGraphics();
g.setColor(Color.MAGENTA.darker());
Ellipse2D.Double ellipse = new Ellipse2D.Double(
pad, pad, image.getWidth(), image.getHeight());
Rectangle2D.Double outline = new Rectangle2D.Double(
0, 0, image.getWidth()+(2*pad), image.getHeight()+(2*pad));
Area a = new Area(outline);
a.subtract(new Area(ellipse));
if (fill) {
g.drawImage(image,pad,pad,null);
}
g.setClip(a);
g.fillRect(0, 0, image.getWidth()+(2*pad), image.getHeight()+(2*pad));
g.dispose();
return temp;
}
public static void main(String[] args) throws Exception {
URL url = new URL("http://i.stack.imgur.com/t5MFE.png");
BufferedImage image = ImageIO.read(url);
final BufferedImage img1 = getImage(image, true);
final BufferedImage img2 = getImage(image, false);
Runnable r = new Runnable() {
#Override
public void run() {
JPanel gui = new JPanel(new GridLayout(0,3));
ActionListener al = new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
JButton b = (JButton)e.getSource();
b.setIcon(new ImageIcon(img2));
}
};
for (int ii=0; ii<9; ii++) {
JButton b = new JButton(new ImageIcon(img1));
b.setBackground(Color.RED);
//b.setContentAreaFilled(false);
b.setBorder(null);
b.addActionListener(al);
gui.add(b);
}
JOptionPane.showMessageDialog(null, gui);
}
};
// Swing GUIs should be created and updated on the EDT
// http://docs.oracle.com/javase/tutorial/uiswing/concurrency
SwingUtilities.invokeLater(r);
}
}

How to properly work with transparent JPanel within a non-opaque JFrame?

I am working on some application designed to be not 100% opaque, so it basically darkens the desktop of the user and my Swing interface is shown on top of this "dark veil".
It seems to me that, when some Swing components are being moved over that veil, my JFrame would need to be repainted for my moving components not to leave a trail behind them. The thing is that repainting the JFrame is too slow and my application wouldn't run smoothly anymore.
For your convenience, I created a SSCCE class that illustrates my issue, here it is:
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Toolkit;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
#SuppressWarnings("serial")
public class TransparentFrameSSCCE extends JFrame {
private static final Dimension SCREEN_DIMENSIONS = Toolkit.getDefaultToolkit().getScreenSize();
private final JPanel movingPanel;
private TransparentFrameSSCCE() {
super();
this.setUndecorated(true);
this.setResizable(false);
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.setSize(TransparentFrameSSCCE.SCREEN_DIMENSIONS);
// This makes my JFrame transparent (its alpha component is set to 0)
this.setBackground(new Color(0, 0, 0, 0));
this.movingPanel = new JPanel();
this.movingPanel.setBounds(0, 0, 50, 50);
this.movingPanel.setBackground(Color.RED);
final JPanel contentPane = new JPanel();
// This makes my panel semi-transparent (its alpha component is set to 128)
contentPane.setBackground(new Color(0, 0, 0, 128));
contentPane.setLayout(null);
contentPane.add(this.movingPanel);
this.setContentPane(contentPane);
}
#Override
public void setVisible(final boolean isVisible) {
super.setVisible(isVisible);
new Thread(new Runnable() {
#Override
public void run() {
int x, y;
for(;;) {
x = TransparentFrameSSCCE.this.movingPanel.getLocation().x;
y = TransparentFrameSSCCE.this.movingPanel.getLocation().y;
TransparentFrameSSCCE.this.movingPanel.setLocation(x + 5, y);
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
}
public static void main(final String args[]) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
new TransparentFrameSSCCE().setVisible(true);
}
});
}
}
Would anyone know any other way to do so?
UPDATE: Following #MadProgrammer's directions about Swing components transparency behavior, this is how to deal with my "dark veil". It works perfectly. Many thanks to him :)
final JPanel contentPane = new JPanel() {
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
final Graphics2D g2d = (Graphics2D) g.create();
g2d.setColor(new Color(0, 0, 0, 128));
g2d.fill(new Area(new Rectangle(new Point(0, 0), getSize())));
g2d.dispose();
}
};
contentPane.setOpaque(false); // Instead of: contentPane.setColor(new Color(0, 0, 0, 128)
Java components don't have a concept of transparency, they are either opaque or fully transparent (alright, the new transparency support for top level windows is an exception ;))
What you need to do is create a custom component that is fully transparent and the override it's paintComponent and fill the area of the component with your translucent color.
Also, don't modify the state of Swing components outside of the context of the Event Dispatching Thread, strange things begin to happen. A better solution might be to use a javax.swing.Timer
For example
Create rectangle with mouse drag, not draw
Java Swing: Transparent PNG permanently captures original background
How to make a transparent JFrame but keep everything else the same?
You may also want to take a look at Concurrency in Swing
Check out Backgrounds With Transparency for a simple explanation of the problem. Basically, you need to make sure your custom component paints the background.
Or instead of doing the custom painting you can take advantage of the AlphaContainer class which will do the painting for you:
//this.setContentPane( contentPane);
this.setContentPane( new AlphaContainer(contentPane) );

How do I add a button to Canvas without letting the button resize?

I'm working on a login screen for my game. I have a total of two images on it. One is a splash screenshot and the other is the background image. I'm using BufferedImages to render the images to the screen.
The problem I get is that when I add a standard button to the Canvas, the button takes up the whole window, and evidently, I don't want that.
I would post a picture, but alas, I do not have "enough reputation" to do that. Here's a look at my code though:
import java.awt.Button;
import java.awt.Canvas;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.TextField;
import java.awt.image.BufferStrategy;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferInt;
import javax.swing.JFrame;
import javax.swing.UIManager;
public class Login extends Canvas implements Runnable {
private static final long serialVersionUID = 1L;
private static final int WIDTH = 495;
private static final int HEIGHT = 307;
private static final int SCALE = 2;
private final Dimension size = new Dimension(WIDTH * SCALE, HEIGHT * SCALE);
private BufferedImage image = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB);
public int[] pixels = ((DataBufferInt) image.getRaster().getDataBuffer()).getData();
private BufferedImage splash = new BufferedImage(315, 177, BufferedImage.TYPE_INT_RGB);
public int[] splashPixels = ((DataBufferInt) splash.getRaster().getDataBuffer()).getData();
private Thread thread;
public static boolean isRunning = false;
JFrame frame;
MainMenu menu;
Splash splashscreen;
Button login;
Button register;
TextField username;
private Login() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (Exception exc) {
exc.printStackTrace();
}
frame = new JFrame("Game Login");
menu = new MainMenu(WIDTH, HEIGHT, "/login/login_screen.png");
splashscreen = new Splash(315, 177, "/login/splash.png");
frame.setSize(size);
frame.setVisible(true);
frame.setResizable(false);
frame.setLocationRelativeTo(null);
setPreferredSize(size);
frame.add(this);
login = new Button("Login");
login.setBounds(0, 0, getWidth(), getHeight());
frame.add(login);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
private void begin() {
createBufferStrategy(2);
thread = new Thread(this);
thread.start();
isRunning = true;
}
private void finish() throws InterruptedException {
isRunning = false;
thread.join();
}
private void updateLogin() {
for (int a = 0; a < pixels.length; a++) {
pixels[a] = menu.pixels[a];
}
}
private void renderLogin() {
BufferStrategy buffer = getBufferStrategy();
Graphics gfx = buffer.getDrawGraphics();
for (int a = 0; a < splashPixels.length; a++) {
splashPixels[a] = splashscreen.splashPixels[a];
}
gfx.drawImage(image, 0, 0, getWidth(), getHeight(), null);
gfx.drawImage(splash, 320, 37, 625, 340, null);
gfx.setColor(Color.WHITE);
gfx.drawString("Game Co © 2013", 3, (getHeight() - 4));
gfx.drawString("\"Game\" is a trademark of Blah-Blah-Blah.", (getWidth() - 268), (getHeight() - 3));
gfx.dispose();
buffer.show();
}
public void run() {
while (isRunning == true) {
updateLogin();
renderLogin();
}
try {
finish();
} catch (InterruptedException exc) {
exc.printStackTrace();
}
}
public static void main(String[] args) {
Login login = new Login();
login.begin();
}
}
Once again, my only problem is that I keep getting a enlarged button.
Thanks in advance, I know you guys are busy and whatnot and I appreciate taking the time to look over and help answer my questions.
P.S. Does anyone know how to make a password field with AWT? I'll also need that too. ;)
Solution: add your JButton (again use Swing components) First to a JPanel (which uses FlowLayout by default), and then add that to the top level window.
You could just change the layout manager for your frame to a FlowLayout so it will behave like a JPanel.
frame.setLayout(new FlowLayout());
You state:
The problem I get is that when I add a standard button to the Canvas, the button takes up the whole window, and evidently, I don't want that.
You're trying to add a component directly to a container that is using BorderLayout, likely the contentPane of a top-level window, and so by default it is added BorderLayout.CENTER and fills the container.
Solution: add your JButton (again use Swing components) First to a JPanel (which uses FlowLayout by default), and then add that to the top level window.
Again, there's no need to mix AWT and Swing components and there are in fact strong arguments not to do so. I suggest that you stick with all Swing components for your GUI.
Does anyone know how to make a password field with AWT? I'll also need that too. ;)
Again, don't use AWT but instead use a Swing JPasswordField.

Categories

Resources