Can not add JComponent to JPanel - java

Have problem with adding class extended from JComponent to MyPanel class.
After choose from JComboBox list and press Start/Restart, the right panel doesnt update. Mean after adding to the Panel MyComponent class where is all drawing stuff. If someone can take a look and tell me when i am doing the mistakes or if its the different way to do that, please help me :)!
Run app class :
import javax.swing.JFrame;
public class RunApp {
public static void main(String[] args) {
JFrame mainFrame = new MyPanel();
mainFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
mainFrame.setExtendedState(JFrame.MAXIMIZED_BOTH);
mainFrame.setVisible(true);
}
}
MyPanel class :
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class MyPanel extends JFrame {
private JPanel leftPanel = null;
private JPanel rightPanel = null;
private JPanel mainPanel = null;
private JButton start = null;
private JButton restart = null;
private JComboBox<String> menuBox = new JComboBox<>();
private Toolkit kit = Toolkit.getDefaultToolkit();
private Dimension screenSize = kit.getScreenSize();
private int screenHeight = screenSize.height;
private int screenWidth = screenSize.width;
public MyPanel() {
mainPanel = new JPanel();
mainPanel.setBackground(Color.orange);
mainPanel.setPreferredSize(getPreferredSize());
leftPanel = new JPanel();
leftPanel.setBackground(Color.blue);
leftPanel.setPreferredSize(new Dimension(screenWidth/6, screenHeight));
menuBox.addItem("Gaussian Wave - non Dispersive");
menuBox.addItem("Gaussian Wave - Dispersive");
start = new JButton("Start");
restart = new JButton("Restart");
leftPanel.add(menuBox);
leftPanel.add(start);
leftPanel.add(restart);
rightPanel = new JPanel();
rightPanel.setBackground(Color.red);
rightPanel.setPreferredSize(new Dimension(screenWidth -( screenWidth/5 ), screenHeight));
start.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
if(menuBox.getItemAt(menuBox.getSelectedIndex()).equals("Gaussian Wave - non Dispersive")) {
rightPanel.add(new MyComponent());
rightPanel.revalidate();
rightPanel.repaint();
} else if(menuBox.getItemAt(menuBox.getSelectedIndex()).equals("Gaussian Wave - Dispersive")) {
rightPanel.add(new MyComponent());
rightPanel.revalidate();
rightPanel.repaint();
}
}
});
restart.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
rightPanel.removeAll();
rightPanel.revalidate();
rightPanel.repaint();
rightPanel.setBackground(Color.RED);
}
});
mainPanel.add(leftPanel);
mainPanel.add(rightPanel);
add(mainPanel);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(screenWidth, screenHeight);
}
}
Like i say the MyComponent class is not working when i add it in this way. Normally when i add this class directly like :
public MyPanel() {
add(new MyComponent);
}
Working perfect, but i want to add it after choose from the list to the divided screen. Thx !
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.geom.GeneralPath;
import java.util.function.Function;
import javax.swing.JComponent;
public class MyComponent extends JComponent {
private int wx, wy;
private double xMin, xMax, yMin, yMax, xInc, time;
private Function<Double, Double>gaussFunction;
private Function<Double, Double>dispersiveGaussFunction;
public MyComponent() {
init();
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
makeGraphics(g2);
}
private void makeGraphics(Graphics2D g2) {
GeneralPath path = new GeneralPath(GeneralPath.WIND_EVEN_ODD);
if(time < 5) {
countGaussianWave(this.time);
double x = xMin;
double y = gaussFunction.apply(x);
path.moveTo(
mapX(x), mapY(y)
);
x+=xInc;
while(x < xMax) {
y = gaussFunction.apply(x);
path.lineTo(
mapX(x), mapY(y)
);
x+=xInc;
}
g2.draw(path);
repaint();
this.time+=0.001;
if(this.time > 3.5) {
this.time = -3.5;
System.out.println(time);
}
}
}
private void init() {
wx = 700;
wy = 700;
xMin = -1;
xMax = 1;
yMin = -1;
yMax = 1;
xInc = 0.01;
setTime(time);
}
private double mapX(double x) {
double fx = wx / 2;
double sx = wx / (xMax - xMin);
return x * fx + sx;
}
private double mapY(double y) {
double fy = wy / 2;
double sy = wy / (yMax - yMin);
return -y * sy + fy;
}
public void countGaussianWave(double time) {
double lambda = 1;
this.gaussFunction = x -> {
return x = Math.exp(-(Math.pow((x-time ), 2)))
* (Math.cos((2*Math.PI*(x-time))/lambda)
+ Math.sin((2*Math.PI*(x-time))/lambda)); // need complex
};
}
// public void countDispersiveGaussianWave(double time) {
//
// double lambda = 1;
// double k = (2 * Math.PI) / lambda;
//
// this.dispersiveGaussFunction = x -> {
// return x = (1/(Math.sqrt(1 + 2 * time)) *
// Math.exp(-(Math.pow((1 / Math.pow(1 + 4 * time, 2)) * (x - k * time), 2)))
// * Math.exp(Math.pow(, b));
// );
// };
// }
public double getTime() {
return time;
}
public void setTime(double time) {
this.time = time;
}
}
app ss

Don't use "==" when comparing objects.
Instead you should be using the equals(...) method.
When you add (or remove) a component to a visible GUI the basic code is:
panel.add(...);
panel.revalidate();
panel.repaint();
Basically you need to make sure the layout manager is invoked otherwise the component has a size of (0, 0) so there is nothing to paint.
Also, you need to override the getPreferredSize() method to return the size of the component. Otherwise the size is (0, 0) so there is nothing to paint.
Edit
I'm guessing your components isn't showing because of your poor GUI design and hardcoding of values. A FlowLayout is a terrible layout to use for ALL the components added to the frame. You need to make an effort to logically organize the components on panels with appropriate layout managers. When lots of components are added the components will wrap to a second row. However, the preferred size does not automatically change so you may not see all the components. So
rightPanel.setPreferredSize(new Dimension(...) );
Don't hardcode a preferred size. Each component is responsible for determining its own size which is why you override the getPreferredSize() method. Then the layout manages can do their job properly. So get rid of all the setPreferrededSize() statements.
The default layout for a Jframe is a BorderLayout. I would stick with that layout. There is no need for your mainPanel as the content pane for the frame is already a JPanel.
So I would rename your "leftPanel" and maybe call it "topPanel". Then you can just add the fixed components to that panel and add that panel to the frame:
JPanel topPanel = new JPanel();
topPanel.add( comboBox );
topPanel.add( startButton );
topPanel.add( restartButton );
frame.add(topPanel, BorderLayout.PAGE_START);
So these components will appear at the top of the frame.
Now there is also no need for the "rightPanel". Instead you can just add your component directly to the frame.
frame.add( new MyComponent(), BorderLayout.CENTER);
frame.revalidate();
frame.repaint();
This component will now appear in the center of the frame and take up all the extra space available in the frame.

To start, you can simplify the start buttons ActionListener logic like so
if(combobox.getSelectedItem().equals("Text")
{
doSomething();
}
Also, after adding or removing new components, you need to call Revalidate & Repaint
If you're trying to paint in one of the components, make sure to #Override it and call it's super method super.paintComponent(g);
You also have 2 action listeners assigned to the same button. I suggest removing one of those
Along with #Camickr's answer of Overriding the getPreferredSize() method, you should change the restart button's ActionListener to this
rightPanel.removeAll();
rightPanel.revalidate();
rightPanel.repaint();
rightPanel.setBackground(Color.RED);
You can Override the getPreferredSize(); method like so:
#Override
public Dimension getPreferredSize()
{
int width = Toolkit.getDefaultToolkit().getScreenSize().width / 2;
int height = Toolkit.getDefaultToolkit().getScreenSize().height;
return new Dimension(width, height);
}

Related

How can I layer two JPanels on top of each other?

I try to layer triangles (JPanel) on top of a map (JPanel). I tried it with a JLayeredPane but the triangle and the map is not layered (see attached picture). In the end, I would like to set a position for a triangle and place it onto the map. I tried to set absolute positions for the triangle and the map with setBounds() but it did not work for me. Same result. This is my code:
import javax.swing.*;
import java.awt.*;
public class MainFrame extends JFrame {
Container mainContainer;
JLayeredPane mapLayer;
BackgroundMap bgMap;
Mesocyclone meso1;
public MainFrame() {
initUI();
}
private void initUI() {
mainContainer = getContentPane();
bgMap = new BackgroundMap();
mapLayer = new JLayeredPane();
mapLayer.setLayout(new FlowLayout());
mainContainer.setLayout(new FlowLayout(FlowLayout.LEFT));
mainContainer.add(mapLayer);
meso1 = new Mesocyclone();
mapLayer.add(bgMap, JLayeredPane.DEFAULT_LAYER);
mapLayer.add(meso1, JLayeredPane.POPUP_LAYER);
setSize(900, 1000);
setTitle("Mesodetect | v1.0 \u00A9 ");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
}
import javax.swing.*;
import java.awt.*;
import java.awt.geom.Path2D;
public class Mesocyclone extends JPanel {
private static final int PREF_W = 100;
private static final int PREF_H = PREF_W;
private static final Color COLOR = Color.RED;
private Path2D myPath = new Path2D.Double();
public Mesocyclone() {
double firstX = (PREF_W / 2.0) * (1 - 1 / Math.sqrt(3));
double firstY = 3.0 * PREF_H / 4.0;
myPath.moveTo(firstX, firstY);
myPath.lineTo(PREF_W - firstX, firstY);
myPath.lineTo(PREF_W / 2.0, PREF_H / 4.0);
myPath.closePath();
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
// to smooth out the jaggies
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g2.setPaint(COLOR); // just for fun!
g2.fill(myPath); // fill my triangle
}
#Override
public Dimension getPreferredSize() {
if (isPreferredSizeSet()) {
return super.getPreferredSize();
}
return new Dimension(PREF_W, PREF_H);
}
}
import javax.imageio.ImageIO;
import javax.swing.*;
import javax.swing.border.BevelBorder;
import javax.swing.border.TitledBorder;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.net.URL;
public class BackgroundMap extends JLabel {
private Image image;
private BevelBorder border;
private int WIDTH = 620;
private int HEIGHT = 850;
public BackgroundMap() {
setPreferredSize(new Dimension(WIDTH, HEIGHT));
try {
image = ImageIO.read(new File("res/map_bg.png"));
this.setIcon(new ImageIcon(image));
} catch (IOException e) {
e.printStackTrace();
}
border = new BevelBorder(1);
setBorder(border);
}
public Image getImage() {
return this.image;
}
}
import java.awt.*;
public class Application {
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
new MainFrame().setVisible(true);
}
});
}
}
mapLayer = new JLayeredPane();
mapLayer.setLayout(new FlowLayout());
mainContainer.setLayout(new FlowLayout(FlowLayout.LEFT));
mainContainer.add(mapLayer);
meso1 = new Mesocyclone();
mapLayer.add(bgMap, JLayeredPane.DEFAULT_LAYER);
mapLayer.add(meso1, JLayeredPane.POPUP_LAYER);
you should not be setting a layout manager for the layered pane. The will cause the components to be displayed in a FlowLayout instead of being layered because the location of each component will be set based on the rules of the FlowLayout. However, if you don't't set the layout manager you are now responsible for setting the size/location of each component you add to the layered pane.
Don't use "DEFAULT_LAYER" AND "POPUP_LAYER", those are special layers used by the layered panel. You are just using "regular" layers when you add your own components. So you should be using "new Integer(1)" and "new Integer(2)".
Read the section from the Swing tutorial on How to Use LayeredPanes for a working example of the above two suggestions.
The other option is to not use a layered pane and just use Swings default parent/child relationship between components.
So your image label would become the parent and the triangle component becomes the child. Then your logic would become:
JPanel child = new ...
child.setSize( child.getPreferredSize() );
child.setLocation(10, 10);
JLabel parent = new JLabel( new ImageIcon(...) );
parent.add( child );
Note you would also need to use setOpaque(false) on the child component.
As long as the child component will fit in the bounds of the labels image, this is the easier solution.

Remove a JPanel with a JButton [JAVA]

I have the class with the Frame:
import java.awt.*;
import javax.swing.*;
public class Menu_Frame extends JFrame{
Menu_Panel menu_panel = new Menu_Panel();
public Menu_Frame(){
Toolkit kit = Toolkit.getDefaultToolkit();
Dimension screenSize = kit.getScreenSize();
int screenHeight = screenSize.height;
int screenWidth = screenSize.width;
setExtendedState(JFrame.MAXIMIZED_BOTH);
setPreferredSize(new Dimension(screenWidth, screenHeight));
setLocationByPlatform(true);
getContentPane().add(menu_panel);
}
}
Here i have the panel:
import java.awt.*;
import javax.swing.*;
public class Menu_Panel extends JPanel{
Play_Button play = new Play_Button();
public Menu_Panel() {
Toolkit kit = Toolkit.getDefaultToolkit();
Dimension screenSize = kit.getScreenSize();
int screenHeight = screenSize.height;
int screenWidth = screenSize.width;
setLayout(new BorderLayout());
setSize(screenWidth, screenHeight);
setLocation(0,0);
setBackground(Color.BLACK);
add(play);
}
}
And here i have the button class:
public class Play_Button extends JButton implements ActionListener{
private static final long serialVersionUID = -5408317246901761857L;
protected int size_x = 150;
protected int size_y = 75;
protected int location_x = 0;
protected int location_y = 0;
Font f= new Font("Helvetica", Font.BOLD, 30);
String title = "PLAY";
Menu_Frame frame;
Menu_Panel menu;
public Play_Button() {
setFont(new Font("Helvetica", Font.BOLD, 25));
setText(title);
Toolkit kit = Toolkit.getDefaultToolkit();
Dimension screenSize = kit.getScreenSize();
location_x = (screenSize.height/2)+180;
location_y = screenSize.width/5;
setBorder(new RoundedBorder(20));
setSize(size_x,size_y);
setLocation(location_x,location_y);
setBackground(Color.BLACK);
setForeground(Color.RED);
setFocusPainted(false);
setBorderPainted(false);
setContentAreaFilled(false);
setVisible(true);
JPanel panel = new JPanel();
panel.setSize(100,100);
panel.setLocation(100, 100);
panel.setBackground(Color.GREEN);
panel.setVisible(true);
addActionListener(this);
addMouseListener(new MouseAdapter() {
public void mouseEntered(MouseEvent e){
setBorderPainted(true);
setForeground(Color.RED);
}
public void mouseExited(MouseEvent e){
setBorderPainted(false);
}
});
}
#Override
public void actionPerformed(ActionEvent e) {
frame.getContentPane().remove(menu);
}
}
And Finally here i have the class for round the border of play_button:
import java.awt.Component;
import java.awt.Graphics;
import java.awt.Insets;
import javax.swing.border.Border;
public class RoundedBorder implements Border {
private int radius;
RoundedBorder(int radius) {
this.radius = radius;
}
public Insets getBorderInsets(Component c) {
return new Insets(this.radius+1, this.radius+1, this.radius+2, this.radius);
}
public boolean isBorderOpaque() {
return true;
}
public void paintBorder(Component c, Graphics g, int x, int y, int width, int height) {
g.drawRoundRect(x, y, width-1, height-1, radius, radius);
}
}
Now, the panel doesn't disappear when i click the button, and i don't know why. I try with a CarLayout, with setVisible(false), but it doesn't work. How can i fix it?
Preambule...
I would advice against using Toolkit#getScreenSize this way, as it doesn't take into account additional OS elements, like the task bar or dock. As a general user recommendation, don't set the size of the main frame, instead simply allow the preferred size to be the packed size and expand the frame to it's OS maximum using setExtendedState as you have
setSize, setBounds, setLocation are all irrelevant while the components are under the control of a layout manager. Also, components are visible by default.
Core...
Removing a component is the opposite of adding one (no, seriously). Simply call remove on the container which contains the component, passing a reference of the component you want to remove. When you're done updating the UI, simply call revalidate and repaint on the container to trigger a layout and paint pass.
Having said that, generally speaking, it's much better to make use of a CardLayout, which provides you the ability to simply switch between different views.
See How to Use CardLayout for more details
The issue is with your actionPerformed method
public void actionPerformed(ActionEvent e)
{
frame.getContentPane().remove(menu);
}
frame and menu are declared fields of Play_Button, but are never set to anything
If you print out what they are, they should be null
When you create your button, make sure to set those two fields:
Menu_Panel menu_panel = new Menu_Panel();
menu_panel.play.menu = menu_panel;
menu_panel.play.frame = this;
While this may work, it is not great program flow

Transparent JFrame with draggable components [duplicate]

This question already has answers here:
Animation on Transparent-background JFrame Linux
(3 answers)
Closed 7 years ago.
I want to create a hud-like ui by using draggable JComponents on a transparent background. Minimal example of what I have so far:
import java.awt.Color;
import java.awt.Component;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.JFrame;
import javax.swing.JLabel;
public class TransparentFrame
extends JFrame {
public TransparentFrame() {
setLayout(null);
setUndecorated(true);
setBackground(new Color(1, 1, 1, 0.0f));
setLocationRelativeTo(null);
setSize(600, 500);
setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
JLabel label = new JLabel("ABC");
label.setBounds(100, 100, 60, 30);
label.setOpaque(true);
label.setBackground(Color.RED);
label.setForeground(Color.WHITE);
DraggableComponentListener listener = new DraggableComponentListener(label);
label.addMouseListener(listener);
label.addMouseMotionListener(listener);
add(label);
setVisible(true);
}
public static void main(String[] args) {
new TransparentFrame();
}
}
class DraggableComponentListener
extends MouseAdapter {
private Component component;
private volatile int screenX = 0;
private volatile int screenY = 0;
private volatile int componentX = 0;
private volatile int componentY = 0;
public DraggableComponentListener(Component component) {
this.component = component;
}
#Override
public void mousePressed(MouseEvent e) {
screenX = e.getXOnScreen();
screenY = e.getYOnScreen();
componentX = component.getX();
componentY = component.getY();
}
#Override
public void mouseDragged(MouseEvent e) {
int deltaX = e.getXOnScreen() - screenX;
int deltaY = e.getYOnScreen() - screenY;
component.setLocation(componentX + deltaX, componentY + deltaY);
}
}
When the label is dragged, it leaves traces behind: it is painted at all positions it has been at. I tried adding the label to a JPanel, the panel to the frame and overriding the panel's paintComponent method as described here: https://tips4java.wordpress.com/2009/05/31/backgrounds-with-transparency/, the result was the same.
Any suggestions?
Unfortunately, it is working properly on my machine (doesn't leave any traces). Try adding a frame.repaint() after the location changed of the component. Use the repaint variation that takes the paint area for performance.

Java polygon resize

I am trying to make a program that has a window which displays a polygon and two buttons. The polygon starts at 3 points (a triangle) and allows the user to press a "+" and "-" button to add or subtract sides of the polygon. Here is my code:
In TestPolygonBox:
package testpolygonbox
import javax.swing.*;
import java.awt.*;
public class TestPolygonBox extends JFrame {
public TestPolygonBox(){
setLayout(new BorderLayout(5,5));
add(new PolygonBox ());
}
public static void main(String[] args) {
TestPolygonBox frame = new TestPolygonBox();
frame.setTitle("Polygon Box");
frame.setSize(400,420);
frame.setLocationRelativeTo(null);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
}
next there is the control class:
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
public class PolygonBox extends JPanel {
private JButton jbtEnlarge = new JButton("+");
private JButton jbtShrink = new JButton("-");
private PolygonPanel polygonPanel = new PolygonPanel();
public PolygonBox(){
JPanel panel = new JPanel();
panel.add(jbtEnlarge);
panel.add(jbtShrink);
setLayout(new BorderLayout());
this.add(polygonPanel,BorderLayout.CENTER);
this.add(panel, BorderLayout.SOUTH);
jbtEnlarge.addActionListener(new EnlargeListener());
jbtShrink.addActionListener(new ShrinkListener());
}
class EnlargeListener implements ActionListener{
#Override
public void actionPerformed(ActionEvent e){
polygonPanel.enlarge();
}
}
class ShrinkListener implements ActionListener{
#Override
public void actionPerformed(ActionEvent e){
polygonPanel.shrink();
}
}
}
class PolygonPanel extends JPanel{
private int polygonSides = 3;
/** Add side to the polygon*/
public void enlarge(){
polygonSides++;
repaint();
}
public void shrink(){
polygonSides--;
repaint();
}
#Override /** Draw requested Shape*/
protected void paintComponent(Graphics g){
int frameWidth = getWidth() / 2;
int frameHeight = getHeight() / 2;
int radius = (int)(Math.min(getWidth(),getHeight())* 0.4);
int xCenter = getWidth() / 2;
int yCenter = getHeight() / 2;
g.setColor(Color.BLUE);
Polygon polygon = new Polygon();
polygon.addPoint(xCenter + radius, yCenter);
polygon.addPoint((int)(xCenter + radius * Math.cos(2 * Math.PI/polygonSides)),
(int)(yCenter - radius * Math.sin(2 * Math.PI / polygonSides)));
for (int polygonPoint = 2; polygonPoint <= polygonSides; polygonPoint++){
polygon.addPoint((int)(xCenter + radius * Math.cos(polygonPoint * 2 * Math.PI/polygonSides)),
(int)(yCenter - radius * Math.sin(polygonPoint * 2 * Math.PI / polygonSides)));
}
g.fillPolygon(polygon);
}
}
When i try to run this program i get the error:
Exception in thread "main" java.lang.IllegalArgumentException: adding a window to a container
at java.awt.Container.checkNotAWindow(Container.java:483)
at java.awt.Container.addImpl(Container.java:1084)
at java.awt.Container.add(Container.java:998)
at javax.swing.JFrame.addImpl(JFrame.java:562)
at java.awt.Container.add(Container.java:410)
at testpolygonbox.TestPolygonBox.(TestPolygonBox.java:21)
at testpolygonbox.TestPolygonBox.main(TestPolygonBox.java:24)
Java Result: 1
if anyone could tell me where i am adding a window to a container please? Im not sure what im doing wrong.
PolygonBox is a JFrame window but you're adding it to another JFrame. Change the class so it extends JPanel instead.
public class PolygonBox extends JPanel {
There are 4 severe problems with your code.
You are not executing the Swing code in the Event dispatch thread (EDT). Your main has to look like this:
public static void main(String args[])
{
SwingUtilities.invokeLater(new Runnable()
{
public void run()
{
initializeGUI();
}
}
}
This is very important even the Swing Hello World - the simplest Swing program - does it. If you want to know why this has to be like this read this concurrency tutorial. In short, If you don't do this properly once your program gets bigger and more complex you'll see a lot of inconsistent, non-reproducible exceptions and bugs that occur in the Swing classes and you can't track down.
Second, as stated by Reimeus, you can't put a frame inside another frame, PolygonBox has to be a JPanel.
Third you are not populating the panels correctly. Look at this code:
JPanel panel = new JPanel();
panel.add(jbtEnlarge);
panel.add(jbtShrink);
This code looks weird, and it is. The question that pops up when you see it is: "Where are those buttons added?" Well in the panel of course, but where in the panel? That depends of the layout that the panel is using. And what is the layout of that panel? Well since you didn't explicitly specify one it is using the default layout for JPanels: the BorderLayout. With the border layout panel.add(component); is equivalent to panel.add(component, BorderLayout.CENTER);. However you can't put two components in the CENTER, you can put a panel with a lot of components or whatever, but you can't directly put two components, that is why that code doesn't work. Then how do you populate the panel? Pick a layout learn to use it and then add the components in a proper way. For example I'll do this with the GridLayout.
JPanel panel = new JPanel(new GridLayout(1, 2));
panel.add(_enlargeButton);
panel.add(_shrinkButton);
With the Grid layout because I said that I want a grid 1x2, those adds are correct because the panel knows now that he has to put the components in each cell of the grid.
Lastly you will see that there are painting problems: the polygons are not erased, they are painted one over the other, adn you might see the image of the buttons in the panel... This is because you have to call super.paintComponent(g) in your paintComponent method. Thing is that code paints a polygon, yes, but you also have to do all the other work the component was doing for painting itself, so the first thing you have to do is call the original code you are overriding and then paint your component. For an extensive tutorial of why this works like this see Painting in AWT and Swing.
With all that, changing some variables names to follow the Java conventions and reformatting the code for a better organization. This is your fixed program:
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.GridLayout;
import java.awt.Polygon;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class TestPolygonBox extends JFrame
{
public static void main(String[] args)
{
SwingUtilities.invokeLater(new Runnable() { //Error 1: executing Swing code outside the EDT fixed
public void run()
{
TestPolygonBox frame = new TestPolygonBox();
frame.setVisible(true);
}
});
}
public TestPolygonBox()
{
super("Polygon Box"); //this is JFrame("title") since we are extending a frame
setLayout(new BorderLayout());//this line doesn't actually do nothing since the frame already usesk BorderLayout by default
add(buildPolygonBoxPanel());
setSize(400, 420);
setLocationRelativeTo(null);
setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
}
private JPanel buildPolygonBoxPanel() //Error 2: PolygonBox has to be a panel, also I made it into a mthod returning the panel instead of a whole class since it was unnecessary.
{
JPanel polygonBox = new JPanel(new BorderLayout());
_polygonPanel = new PolygonPanel();
JPanel buttonsPanel = new JPanel(new GridLayout(1, 2)); //Error 4 adding the buttons properly to a panel.
_enlargeButton = new JButton("+1");
_enlargeButton.addActionListener(new EnlargeListener());
_shrinkButton = new JButton("-1");
_shrinkButton.addActionListener(new ShrinkListener());
buttonsPanel.add(_enlargeButton);
buttonsPanel.add(_shrinkButton);
polygonBox.add(_polygonPanel, BorderLayout.CENTER);
polygonBox.add(buttonsPanel, BorderLayout.SOUTH);
return polygonBox;
}
class EnlargeListener implements ActionListener
{
#Override
public void actionPerformed(ActionEvent e)
{
_polygonPanel.enlarge();
}
}
class ShrinkListener implements ActionListener
{
#Override
public void actionPerformed(ActionEvent e)
{
_polygonPanel.shrink();
}
}
private JButton _enlargeButton;
private JButton _shrinkButton;
private PolygonPanel _polygonPanel;
}
class PolygonPanel extends JPanel
{
private int _polygonSides = 3;
/** Add side to the polygon */
public void enlarge()
{
_polygonSides++;
repaint();
}
public void shrink()
{
if (_polygonSides > 3) _polygonSides--;
repaint();
}
#Override
/** Draw requested Shape*/
protected void paintComponent(Graphics g)
{
super.paintComponent(g); //Error 4 fixed, call super.paintComponent
int radius = (int) (Math.min(getWidth(), getHeight()) * 0.4);
int xCenter = getWidth() / 2;
int yCenter = getHeight() / 2;
g.setColor(Color.BLUE);
Polygon polygon = new Polygon();
polygon.addPoint(xCenter + radius, yCenter);
polygon.addPoint((int) (xCenter + radius * Math.cos(2 * Math.PI / _polygonSides)), (int) (yCenter - radius * Math.sin(2 * Math.PI / _polygonSides)));
for (int polygonPoint = 2; polygonPoint <= _polygonSides; polygonPoint++)
{
polygon.addPoint((int) (xCenter + radius * Math.cos(polygonPoint * 2 * Math.PI / _polygonSides)), (int) (yCenter - radius * Math.sin(polygonPoint * 2 * Math.PI / _polygonSides)));
}
g.fillPolygon(polygon);
}
}
Just copy and execute it and it looks like this:

Multi-threaded bouncing balls, issue displaying balls

I'm trying to use multithreading to draw balls that bounce around inside a JFrame. I can get the coordinates of each ball to update and print out, but I can't get the balls to display. I'm not very strong in graphics, and I'm not quite sure what I'm missing. I think I need to add each instance of Ball to the panel I have inside my frame, but when I tried that it didn't make a difference. I also have a class used to view the JFrame, that I've omitted. What am I missing here?
Ball
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.util.Random;
import javax.swing.JPanel;
public class Ball extends JPanel implements Runnable {
JPanel pan;
private static int radius = 10;
private Color color;
private int xPos;
private int yPos;
private int dx;
private int dy;
Dimension d;
public Ball(JPanel p) {
Random r = new Random();
this.pan = p;
this.d = pan.getSize();
xPos = r.nextInt(d.width-50)+25;
yPos = r.nextInt(d.height-50)+25;
dx = r.nextInt(3)+1;
dy = r.nextInt(3)+1;
color = new Color(r.nextInt(255*255*255));
paintComponent(pan.getGraphics());
}
public void move() {
xPos += dx;
yPos += dy;
if (xPos+radius <= 0 || xPos+radius >= d.width)
dx = -dx;
if (yPos+radius <= 0 || yPos+radius >= d.height)
dy = -dy;
}
public void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(color);
g.fillOval(xPos-radius, yPos-radius, 2*radius, 2*radius);
g.dispose();
}
public void animate() {
paintComponent(pan.getGraphics());
move();
//pan.validate();//this didn't
//pan.repaint();// work
try {
Thread.sleep(40);
} catch (InterruptedException e) {}
}
public void run() {
while(true)
animate();
}
}
BallTracker
import java.util.ArrayList;
public class BallTracker {
private ArrayList<Ball> balls;
public BallTracker() {
balls = new ArrayList<Ball>();
}
public void addBall(Ball b) {
balls.add(b);
Thread t = new Thread(b);
t.start();
}
}
BallFrame
import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class BallFrame extends JFrame {
public static final int WIDTH = 500;
public static final int HEIGHT = 550;
private BallTracker tracker;
private JPanel ballPanel;
private JPanel buttonPanel;
public BallFrame() {
super("BallFrame");
tracker = new BallTracker();
// set up ball panel
ballPanel = new JPanel();
ballPanel.setSize(WIDTH, 500);
// listener to add a new ball to tracker
class bListener implements ActionListener {
public void actionPerformed( ActionEvent event ) {
Ball b = new Ball(ballPanel);
tracker.addBall(b);
}
}
// set up button panel
buttonPanel = new JPanel();
buttonPanel.setSize(WIDTH, 50);
JButton addBallButton = new JButton();
addBallButton.setText("Add ball");
addBallButton.addActionListener(new bListener());
buttonPanel.add(addBallButton);
// add panels to frame
add(buttonPanel, BorderLayout.SOUTH);
add(ballPanel, BorderLayout.CENTER);
setSize( WIDTH, HEIGHT );
}
}
It seems your ball extends jpanel and has a paint method, but your ballPanel would need to do the painting and your ball doesn't really seem to need to be a panel at all.
set up ball panel
ballPanel = new JPanel();
ballPanel.setSize(WIDTH, 500);
I was mainly looking for issues with the way I am trying to draw here, and why I don't see a ball
The Ball isn't added to any panel.
Even when you do add the ball to the panel, the Ball has no size, so there is nothing to paint.
Even if you did give the panel a size only one Ball would ever show up because you Ball panel is opaque.
Your code is attempting to paint the ball at a location within the ball panel. Instead you should be painting the ball at location (0, 0) within your ball panel and then set the location of the Ball panel relative to the parent container.
The parent container should be using a null layout so you can randomly set the location of your Ball panel.
I'm sure there are other issues as well...
I suggest you forget about multithreading and start with the basics of custom painting and using Timers.

Categories

Resources