this is my first question here so please apologize if I do mistakes.
Trying to better understand oop (inheritance, polymorphism, abstractization) I have imagine the following exercise : in a JPanel with BorderLayout I add a JPanel which contains two JRadioButton and an abstract panel that extends JPanel. The abstract panel can point to 2 different concrete children ... but it doesn't work as I expected. . When I select the rectangle button on the console is printed "rectangle fires event" but the paint Component of the rectangle object is not executed and when I select the oval button on the console is printed "oval fires event" and "paint oval draw oval" which means the paint Component of the oval button is executed. Please help me to understand what do I do wrong? Why the paintComponent of the rectangle is not executed?
I repeat, my aim is to practice and understand oop, not to draw ovals and rectangles.
Here is my code
public class MyGeometry extends JPanel{
private MyShape shape;
private JPanel shapeControl = new JPanel();
private JRadioButton rbtOval = new JRadioButton("Oval");
private JRadioButton rbtRectangle = new JRadioButton("Rectangle");
MyGeometry(){
setLayout(new BorderLayout());
// setting radio buttons
ButtonGroup shapeButton = new ButtonGroup();
shapeButton.add(rbtOval);
shapeButton.add(rbtRectangle);
rbtOval.setSelected(true);
shapeControl.add(rbtOval);
shapeControl.add(rbtRectangle);
//Initialize shape to avoid null pointer exception
final MyOval oval = new MyOval();
final MyRectangle rectangle = new MyRectangle();
shape = oval;
//add components to the panel
add(shapeControl, BorderLayout.NORTH);
add(shape);
//add event handling
rbtOval.addActionListener(new ActionListener(){
public void actionPerformed(ActionEvent e){
shape = oval;
shape.repaint();
System.out.println("Oval fires event");
}
});
rbtRectangle.addActionListener(new ActionListener(){
public void actionPerformed(ActionEvent e){
shape = rectangle;
shape.repaint();
System.out.println("Rectangle fires event");
}
});
}
public Dimension getPreferredSize(){
return new Dimension(500, 500);
}
public abstract class MyShape extends JPanel{
public abstract String dummyString();
public void paintComponent(Graphics g){
super.paintComponent(g);
}
}
public class MyOval extends MyShape{
public String dummyString(){
return "Draw oval";
}
public void paintComponent(Graphics g){
super.paintComponent(g);
g.drawOval(50, 50, 100, 80);
String textOval = "paint oval " + dummyString();
System.out.println(textOval);
}
}
public class MyRectangle extends MyShape{
public String dummyString(){
return "Draw rectangle";
}
public void paintComponent(Graphics g){
super.paintComponent(g);
g.drawRect(50, 50, 100, 80);
String textRectangle = "paint rectangle " + dummyString();
System.out.println(textRectangle);
}
}
}
the paint Component of the rectangle object is not executed
All you are doing is setting a field on the JPanel, you are not changing the JPanel itself. I suspect what you intended was to remove the oval and add the rectangle.
Your OOP is sound; your Swing is not.
You started off with shape set to your oval. This shape was added to your JPanel. The rectangle was never added to the JPanel. Even when you set shape = rectangle, and then tell it to draw, Swing knows that your rectangle has no parent component, and thus will never be drawn. So telling it to repaint() will not call paintComponent().
Related
So in the past few days I've tried to implement an easier version of a graph plotter.
One big problem I was confronted with was a bug that occured on repainting.
Basically I've designed my program in one class which is responsible for drawing the whole coordinate system and the given function after clicking a JButton in an other class. The other class contains the JButton which is pressed. After pressing the JButton it calls a function in the coordinate system class which repaints the picture. Both of those classes are extending JPanel.
The bug was that when I was doing the repainting on pressing the button, the button was drawn on the coordinate System and not in its original place, so in other words on the other JPanel even though I didn't change a thing about placements and stuff. Both classes were added to a JFrame which use a GridLayout.
Can anyone tell me why super.paintComponent(g); solved that bug?
Edit: Added Code
Window class
public class main {
public static void main(String[] args) throws SemanticFailureException {
int x = 800;
int y = 600;
JFrame frame = new JFrame();
frame.setLayout(new GridLayout());
frame.setTitle("Function plotter");
frame.setSize(2*x, 2*y);
Surface test = new Surface(x, y, 10);
CommandDraw test1 = new CommandDraw(x/2,y,test);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLocationRelativeTo(null);
frame.add(test);
frame.add(test1);
frame.setVisible(true);
}
}
Coordinate System class: (changed drawing the coordinate system to a rectangle for simplicity, the bug still occures with only drawing a rectangle)
public class Surface extends JPanel {
/**
*
*/
private static final long serialVersionUID = 1L;
boolean drawFunct;
public Surface(int x1, int y1, int coordLength) {
setSize(x1,y1);
drawFunct = false;
}
public void paintComponent(Graphics g) {
super.paintComponent(g); // without this the jbutton occures on the left
// create Graphics object to get more functions
Graphics2D g2 = (Graphics2D) g;
// draw Plotter
drawFunction(g2);
if (drawFunct)
g2.drawLine(0, 0, 80, 80);
}
public void drawFunction(Graphics2D g) {
g.drawRect(40, 40, 30, 30);
}
public void redraw() {
drawFunct = true;
repaint();
}
}
The Class with the JButton:
public class CommandDraw extends JPanel {
/**
*
*/
private static final long serialVersionUID = 1L;
JButton makeDraw;
JTextField inputPoly;
Surface surf;
public CommandDraw(int x, int y, Surface surf) {
this.surf = surf;
setSize(x,y);
setLayout(new FlowLayout());
makeDraw = new JButton("draw Function");
makeDraw.setBackground(Color.LIGHT_GRAY);
makeDraw.setFocusable(false);
makeDraw.addActionListener( new ActionListener() {
#Override
public void actionPerformed(ActionEvent arg0) {
surf.redraw();
}
});
add(makeDraw);
inputPoly = new JTextField("Input polynomial");
inputPoly.setHorizontalAlignment(JTextField.CENTER);
add(inputPoly);
}
}
Can anyone tell me why super.paintComponent(g); solved that bug?
Because the call to paintComponent(g) of the superclass (JPanel) will guarantee that panel will be rendered as expected before you do your paint operations. You need to make sure that your JPanel will behave, in the Graphics perspective, like any other JPanel.
A template for a customized JPanel to draw should be something like:
import java.awt.Graphics;
import java.awt.Graphics2D;
import javax.swing.JPanel;
public class MyDrawPanel extends JPanel {
#Override
protected void paintComponent( Graphics g ) {
super.paintComponent( g );
// create a new graphics context based on the original one
Graphics2D g2d = (Graphics2D) g.create();
// draw whatever you want...
g2d.dispose();
}
}
EDIT
You need to call super.paintComponent(g) to tell that the JPanel paintComponent(Graphics) version should be execute before you start doind your customized things. It's something like to ask the JPanel to prepare its paint capabilities to be used. A good starting point to these kind of doubts is the documentation: https://docs.oracle.com/javase/10/docs/api/javax/swing/JComponent.html#paintComponent(java.awt.Graphics)
The dispose() method is being called in the copy of the original Graphics. If you dispose the Graphics that is passed to the method, you may have some issues too. You could use the original Graphics to perform you painting operations, but you shouldn't, so a better practice is to make a copy of the original Graphics and dispose it after using.
Take a look here too: How does paintComponent work?
I'm trying to let a user draw a rectangle with their mouse.
I'm having two problems which I think may be related. Firstly, the third class I posted here, Colors.java, needs to use the same rectangle object from the mouse listener, which is the second class I provided (Mouse.java).
I tried to use getters and setters with this, when I went through the program in debug mode it had the correct rectangle object in the color() method, but once it went to paintComponent() I now have a null rectangle object. I don't know why this is.
Another problem I've been having is that the repaint() method does not always call the paintComponent() method. I think this could be because of the rectangle object being null but I'm not sure.
I tried to shorten up the code and replaced some code with comments. I also didn't think including the rectangle class was necessary since most of the class is filled with other functions totally unrelated to the this problem.
Any help to lead me in the right direction would be appreciated, I'm pretty stuck right now. Thanks!
public class JavaSwing extends JFrame implements ItemListener {
//Checkbox objects here
Colors colors = new Colors();
public void createGui() {
intersection = new JCheckBox("Draw Intersections");
intersection.setMnemonic(KeyEvent.VK_C);
intersection.setSelected(false);
//checkbox objects assigned like above
//checkbox listeners
JFrame frame = new JFrame("Rectangles");
frame.setSize(600, 600);
frame.setVisible(true);
Mouse mouse = new Mouse();
colors.setLayout(new BoxLayout(colors, BoxLayout.PAGE_AXIS));
colors.addMouseListener(mouse);
colors.addMouseMotionListener(mouse);
colors.add(Box.createRigidArea(new Dimension(0, 5)));
colors.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 0));
JButton button = new JButton("Clear Image");
JPanel panel = new JPanel();
panel.setBorder(BorderFactory.createEmptyBorder(0, 10, 10, 10));
panel.setBorder(BorderFactory.createLineBorder(Color.BLACK, 2));
panel.add(Box.createHorizontalGlue());
panel.add(intersection);
panel.add(Box.createRigidArea(new Dimension(10, 0)));
panel.add(union);
panel.add(Box.createRigidArea(new Dimension(10, 0)));
panel.add(commonArea);
panel.add(Box.createRigidArea(new Dimension(10, 0)));
panel.add(button);
frame.add(colors, BorderLayout.CENTER);
frame.add(panel, BorderLayout.PAGE_END);
}
#Override
public void itemStateChanged(ItemEvent e) {
//for checkboxes
}
public static void main(String args[]) {
JavaSwing javaSwing = new JavaSwing();
javaSwing.createGui();
}
}
Second Class:
public class Mouse extends MouseInputAdapter implements MouseListener{
Rectangle rectangleOne = new Rectangle(0, 0, 0, 0);
Colors colors = new Colors();
public void mousePressed(MouseEvent e) {
rectangleOne.setX(e.getX());
rectangleOne.setY(e.getY());
rectangleToDraw = new Rectangle(rectangleOne.getX(), rectangleOne.getY(),
rectangleOne.getWidth(), rectangleOne.getHeight());
colors.setRectangle(rectangleToDraw);
colors.color();
}
public void mouseReleased(MouseEvent e) {
int x2 = e.getX();
int y2 = e.getY();
rectangleOne.setWidth(x2 - rectangleOne.getX());
rectangleOne.setHeight(y2 - rectangleOne.getY());
rectangleToDraw = new Rectangle(rectangleOne.getX(), rectangleOne.getY(),
rectangleOne.getWidth(), rectangleOne.getHeight());
colors.setRectangle(rectangleToDraw);
colors.color();
}
Third Class:
public class Colors extends JPanel {
Rectangle rectangle;
public void setRectangle(Rectangle rectangle)
{
this.rectangle = rectangle;
}
public Rectangle getRectangle() {
return rectangle;
}
public void color() {
repaint();
}
public void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(Color.GREEN);
if (getRectangle() != null) {
g.fillRect(getRectangle().getX(), getRectangle().getY(), getRectangle().getWidth(), getRectangle().getHeight());
}
}
}
The reason rectangle is null when you reach paintComponent is because the colors in Mouse is not the same as the colors in JavaSwing. In both classes you do Colors colors = new Colors(). That means you have two separate, unrelated, instances of the class. That's also the reason you aren't seeing a repaint happen when you call repaint() - you're calling it on a component that's not part of the actual component hierarchy being displayed.
If you change things so that you use the same Colors instance in both cases, it will work correctly. So change your Mouse code to this:
public class Mouse extends MouseInputAdapter implements MouseListener{
Rectangle rectangleOne = new Rectangle(0, 0, 0, 0);
Colors colors;
public Mouse(Colors colors){
this.colors = colors;
}
/* The rest of your methods here */
}
And then create your Mouse instance like this:
Mouse mouse = new Mouse(colors);
Also, although I don't think this is what you're talking about, I think I should mention that repaint() does not always result in paintComponent being called, even when you do it on the correct object. What it does is make a request that the component be repainted when the Event Dispatch Thread is next able to. Usually it will cause paintComponent to be called, but not always (for example if the component isn't visible, or if you call it multiple times before it can be repainted, which will result in it only being painted once).
I am a newb and am trying to get a line to draw with my xslider and yslider so it would create cross hairs on the canvas panel. I cannot figure this out. The idea is that when I push the "Show" button a circle is to appear centered on the crosshairs set by the sliders. I used internalframes to create the button location and canvas for the circle and sliders. I need lines connected to the sliders. I cannot change the coding as to how the sliders work in tandem, part of expectations. Please assist.
import java.awt.*;
import java.awt.event.*;
import java.awt.geom.*;
import javax.swing.*;
public class CircleViewer2 extends JPanel
{
//Variables
Ellipse2D.Double circle;
static Color FillColor = Color.blue;
static String ShowHideName = null;
static JSlider xSlider;
static JSlider xSlider2;
static JSlider ySlider;
static JSlider ySlider2;
//Creation of the circle utilizing Ellipse2D
public CircleViewer2(int radius)
{
circle = new Ellipse2D.Double(0, 0, radius, radius);
setOpaque(false);
}
//Setting PreferredSize
public Dimension getPreferredSize()
{
Rectangle bounds = circle.getBounds();
return new Dimension(bounds.width, bounds.height);
}
//Establishing parameters for Drawing the Circle Via Paint
public void paintComponent(Graphics g)
{
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
g2.setColor(FillColor);
g2.fill(circle);
}
public static void main(String[] args)
{
final JPanel center = new JPanel();
center.setLayout(null);
center.setPreferredSize(new Dimension(400,400));
ShowHideName = "Show";
final JButton ShowHideButton = new JButton(ShowHideName);
ShowHideButton.setPreferredSize(new Dimension(75, 25));
ShowHideButton.addActionListener( new ActionListener()
{
public void actionPerformed(ActionEvent event)
{
if (ShowHideName.equals("Show"))
{
int xCoord = xSlider.getValue();
System.out.println(xCoord);
int yCoord = ySlider.getValue();
System.out.println(yCoord);
CircleViewer2 component = new CircleViewer2(50);
component.setLocation(xCoord,yCoord);
component.setSize(component.getPreferredSize());
center.add(component);
ShowHideName = "Hide";
center.repaint();
}
else
{
ShowHideName = "Show";
center.removeAll();
center.updateUI();
}
ShowHideButton.setText(ShowHideName);
}
});
final JButton ColorButton = new JButton("Color");
ColorButton.setPreferredSize(new Dimension(75, 25));
ColorButton.addActionListener( new ActionListener()
{
public void actionPerformed(ActionEvent event)
{
FillColor = JColorChooser.showDialog(null, "Pick a Color", Color.blue);
}
});
JFrame frame = new JFrame();
JInternalFrame canvas = new JInternalFrame();
JInternalFrame buttonFrame = new JInternalFrame();
JPanel buttonPanel = new JPanel();
buttonPanel.add(ShowHideButton);
buttonPanel.add(ColorButton);
javax.swing.plaf.InternalFrameUI ifu= buttonFrame.getUI();
((javax.swing.plaf.basic.BasicInternalFrameUI)ifu).setNorthPane(null);
buttonFrame.setBounds(0, 500, 500, 200);
buttonFrame.add(buttonPanel, BorderLayout.CENTER);
buttonFrame.setVisible(true);
xSlider = new JSlider(SwingConstants.HORIZONTAL,0,380,10);
BoundedRangeModel xmodel = xSlider.getModel();
xSlider2 = new JSlider(SwingConstants.HORIZONTAL);
xSlider2.setModel(xmodel);
ySlider = new JSlider(SwingConstants.VERTICAL,0,350,10);
BoundedRangeModel ymodel = ySlider.getModel();
ySlider.setInverted(true);
ySlider2 = new JSlider(SwingConstants.VERTICAL);
ySlider2.setModel(ymodel);
ySlider2.setInverted(true);
canvas.add(center, BorderLayout.CENTER);
canvas.add(xSlider, BorderLayout.SOUTH);
canvas.add(xSlider2, BorderLayout.NORTH);
canvas.add(ySlider, BorderLayout.EAST);
canvas.add(ySlider2, BorderLayout.WEST);
canvas.setBounds(0, 0, 500, 550);
canvas.setVisible(true);
javax.swing.plaf.InternalFrameUI ifu2 = canvas.getUI();
((javax.swing.plaf.basic.BasicInternalFrameUI)ifu2).setNorthPane(null);
frame.add(canvas, BorderLayout.NORTH);
frame.add(buttonFrame, BorderLayout.SOUTH);
frame.setBounds(0, 0, 500, 530);
frame.setVisible(true);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
}
This is not a good implementation of the problem. You can inherit the JFrame and make the code clearer, it's the common method to play with Swing components. Any way, Add a change Listener to your sliders and change the location of the Center Panel according to the Sliders Value.
Something like that:
xSlider.addChangeListener( e -> {
center.setLocation(new Point(xSlider.getValue(), (int) center.getLocation().getY());
});
and so on.
This is a good place to start with Java/Swing GUI best practices
Your code looks way more complex than it needs to be, and I would try to simplify it greatly. Some suggestions:
Get rid of all the JInternalFrames and use JPanels instead.
Create a JPanel, say called drawingPanel, that has its paintComponent(Graphics g) overridden and perhaps its getPreferredSize() overridden, place this JPanel BorderLayout.CENTER in your main GUI.
In the paintComponent method, have the logic to draw the circles and the crosshairs based on fields of your class.
When a JSlider moves, have its ChangeListener change the state of the corresponding field, and then have it call repaint() on the drawing panel so that it will draw the changes.
When the show button is pressed, have it change the state of a boolean variable and then call repaint().
Have the drawingPanel use the boolean in its paintComponent method to decide whether or not to draw the filled circle.
Have the drawingPanel draw the lines in its paintComponent method based on the value returned by the JSliders. Again you're calling repaint in the JSlider's listeners, so the lines will move.
Do not add and remove components on button clicks since this adds unnecessary complexity which makes things much harder to code.
Don't move the center component or any component. Just use the fixed drawingPanel JPanel and move the location of the circle and the lines that it draws.
Since this is homework, I'm going to avoid posting code as the coding should be up to you. Much luck!
The idea of the program is that I have some buttons and an icon SOMEWHERE on the frame. I want the buttons to change the color. I'm only worried about making all the elements show up right now. If I comment out lines 11-13, I see "hello," printed out, with a red circle on top of it. Otherwise, I just have the button "red" without "hello" or my red circle. So here's my code:
import javax.swing.*;
import java.awt.*;
import java.awt.geom.*;
public class ButtonTester
{
public static void main (String[] args)
{
JFrame frame = new ButtonFrame();
frame.setLayout(new FlowLayout(FlowLayout.RIGHT));
JButton redButton = new JButton("Red");
frame.add(redButton);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
}
class ButtonFrame extends JFrame
{
public static final int DEFAULT_WIDTH = 300;
public static final int DEFAULT_HEIGHT = 200;
public ButtonFrame()
{
setTitle("Hello");
setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT);
ButtonPanel panel = new ButtonPanel();
add(panel);
}
}
class ButtonPanel extends JPanel
{
public void paintComponent(Graphics g)
{
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
g2.drawString("Hello !", 100, 100);
Icon ico = new ColorIcon(32);
ico.paintIcon(null, g, 75, 75);
}
}
I'm 90% sure the problem is lines 11-13, but I'm not sure what to change to make everything visible.
Your problem is that your ButtonPanel's size is 0. Have it override getPreferredSize() and you will see what I mean:
class ButtonPanel extends JPanel {
private static final int PREF_W = 150;
private static final int PREF_H = PREF_W;
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
g2.drawString("Hello !", 100, 100);
// !! Icon ico = new ColorIcon(32);
// Icon ico = new ImageIcon();
// ico.paintIcon(null, g, 75, 75);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(PREF_W, PREF_H);
}
}
Also as an unrelated aside, why are you creating an Icon inside of the paintComponent method? This doesn't make sense to me and would only serve to needlessly slow your graphics down.
Edit
You state:
Ok, I see the difference after overriding getPreferredSize() But what would be the "better" or "correct" way to create the icon? I'm just trying to follow the directions for an exercise out of a Java textbook: Exercise 4.14. Write a program that shows a frame with three buttons labeled "Red", "Green", and "Blue", and a label containing an icon showing a circle that is initially red. As the user clicks the buttons, the fill color of the circle should change. When you change the color, you need to invoke the repaint method on the label. The call to repaint ensures that the paintIcon method is called so that the icon can be repainted with the new color.
You need to think on this a different way. Myself I'd create three ImageIcons one for a blue circle, one for red, and one for green. I'd then display the ImageIcon in a JLabel on my JFrame. I'd change the color by simply swapping the label's icons via its setIcon(...) method. I wouldn't worry about futzing with paintComponent(...) but rather would try to solve this in as simple a fashion as possible.
Good day.
I develop program which must show few shapes when user clicks the button. At least it doesn't show it. What is wrong?
Code is:
public class ShowFrame extends JFrame
{
public ShowFrame()
{
this.setTitle("Show data"); //Title
this.setSize( DEF_WIDTH, DEF_HEIGHT ); //Size of frame
this.setResizable(false);
//...
JButton testButton = new JButton("Test");
buttonPanel.add(testButton);
this.add(buttonPanel, BorderLayout.SOUTH);
testButton.addActionListener( new ActionListener() { //Add listener
public void actionPerformed(ActionEvent e) {
DrawStuff stuff = new DrawStuff(); //Create class which draws shapes
add(stuff, BorderLayout.CENTER);
System.out.println("Test Button");
}
} );
}
public static final int DEF_WIDTH = 600;
public static final int DEF_HEIGHT = 400;
private JPanel buttonPanel = new JPanel();
}
Class which draws shapes:
public class DrawStuff extends JComponent
{
public void paintComponent( Graphics g )
{
Graphics2D g2 = (Graphics2D) g;
//...
Rectangle2D rect = new Rectangle2D.Double(leftX, topY, width, height);
Line2D line = new Line2D.Double(leftX, topY, 0, 0);
//...
g2.draw(rect);
g2.draw(line);
//...
}
}
When you add/remove components on a visible GUI the code should be:
panel.add(...);
panel.revalidate();
panel.repaint();
Your design of adding a new panel every time you click a button is not a very good one.
Instead you should create a custom painting panel and override the paintComponent() method. Then when you click a button you invoke a method in your custom component to set the shape you want to draw. The paintComponent() method should be smart enought to paint the shape. Then you invoke repaint() on the panel.
Read the section from the Swing tutorial on Custom Painting for more information and working examples.