How do I redraw things when my JFrame is restored (deiconified)? - java

I have a very basic little JFrame with JToggleButtons and subclassed JPanels that know how to draw what I want them to draw. Selecting a button causes an oval to appear in the corresponding panel. Unselecting the buttons makes the drawings disappear.
Unfortunately, minimizing (iconifying) and then restoring (deiconifying) causes any drawn shapes to disappear. So I need to trigger redrawings manually. The problem is that I can only get the redrawing done (that is, I only see it) if I show a message box first.
Here's the deiconify event for the JFrame:
private void formWindowDeiconified(java.awt.event.WindowEvent evt)
{
//having this message makes everything work
JOptionPane.showMessageDialog(null, "Useless message this is.");
//but if I skip it, I'm SOL
//what's going on?
drawAll();
}
This method goes over all of my buttons and asks for the redraws when necessary:
public void drawAll()
{
for (int i=0; i<channels; i++)
{
if (buttons[i].isSelected())
{
lightboxes[i].drawMe();
}
}
}
and here is my subclassed JPanel:
class MyJPanel extends JPanel {
public void drawMe()
{
Graphics myGraphics = this.getGraphics();
myGraphics.fillOval(0, 0, this.getWidth(), this.getHeight());
}
public void unDraw()
{
this.invalidate();
this.repaint();
}
}

The window should automatically be repainted once it is restored by the RepaintManager. The problem is you are not performing custom painting like you should...
This is not how to do custom painting...
public void drawMe()
{
Graphics myGraphics = this.getGraphics();
myGraphics.fillOval(0, 0, this.getWidth(), this.getHeight());
}
getGraphics can return null and is, at best, a snapshot of the graphics state.
Painting in Swing can occur at any time for many different reasons, most of which you don't have control over (nor should you care).
Your job is simply to respond to these repaint requests and update your components state.
Swing has a detailed paint chain which is called automatically and which you can use.
You should be overriding paintComponent and performing all painting within this method
Take a look at Performing Custom Painting and Painting in AWT and Swing for more details

Firstly, for speed I would use double buffering. It's best to paint your graphics off screen and display them to the screen when the drawing has completed. The below should sort you out.
public class MyPanel extends JPanel {
private BufferedImage buffer;
private Graphics2D canvas;
#Override
public void paintComponent(Graphics g) {
if(buffer == null) {
buffer = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_INT_RGB);
canvas = buffer.createGraphics();
}
canvas.fillOval(0, 0, this.getWidth(), this.getHeight());
g.drawImage(buffer, 0, 0, this);
}
}

I'm just providing this answer so people can see what I ended up doing. The major lesson pointed out by everyone was to use the component's paintComponent. See the comments for issues that you might be experiencing yourself.
Edit: Updated to reflect comments from MadProgrammer.
//with help from Slihp and MadProgrammer on StackOverflow
//http://stackoverflow.com/q/17331986/1736461
class MyJPanel extends JPanel {
private boolean buttonSelected = false;
#Override
public void paintComponent(Graphics g) {
//make sure the background gets painted (wacky results otherwise)
super.paintComponent(g);
//now if the corresponding toggle button is on, plop on the circle
if (buttonSelected)
{
g.fillOval(0, 0, this.getWidth(), this.getHeight());
}
}
//an action listener for the button calls this
public void setButtonSelected(boolean buttonStateIn)
{
buttonSelected = buttonStateIn;
}
}
I subclassed the buttons too, so I can get its "ID" off of it from the event handler:
class MyJToggleButton extends JToggleButton
{
private int whoAmI;
public MyJToggleButton(int whoAmIn)
{
//the string given to the constructor becomes the button's label
//("1", "2", "3", etc..)
super(Integer.toString(whoAmIn + 1));
whoAmI = whoAmIn;
}
public int getWho()
{
return whoAmI;
}
}
The JFrame code that makes the buttons:
private void makeButtons(int howMany)
{
buttons = new MyJToggleButton[howMany];
for (int i=0; i<howMany; i++)
{
buttons[i] = new MyJToggleButton(i);
buttons[i].addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent evt) {
//find out which button this is
MyJToggleButton button = (MyJToggleButton) evt.getSource();
int which = button.getWho();
//send the button state to the corresponding lightbox
lightboxes[which].setButtonSelected(button.isSelected());
//trigger its redrawing
lightboxes[which].invalidate();
lightboxes[which].repaint();
}
});
this.add(buttons[i]);
}
}
And that's the only manual redrawing I have to do - resizing and reshowing and all those other fun things eventually hit up the paintComponent, and it just has to know if its button is pushed to know what to do. Super clean and just what I wanted.

Related

Animating a shape in a JPanel

first questing for me here. Been searching forever but cant seem to find the answer.
Im working on a school assignment. Got given an ui and are supposed to make the different panels in it do different things in separate threads. Anyway, Im trying to make a triangle rotate inside one of the JPanels. I have managed to draw it and somewhat rotate it, but when I try to make a loop to update it it just blinks and then disappears again. Heres the code Ive written.
StartAssignment, starts the application
public class StartAssignment1 {
public static void main(String[] args) {
Controller controller = new Controller();
}
Controller, recieves calls from the ui and executes various functions in other classes
public class Controller {
private GUIAssignment1 gui = new GUIAssignment1(this);
private RotateShape rotateShape;
private Thread t1;
public Controller() {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
gui.Start();
}
});
}
public void startT1(JPanel panel) {
rotateShape = new RotateShape(panel);
t1 = new Thread(rotateShape);
t1.start();
}
public void t1Shutdown() {
rotateShape.shutdown();
}
RotateShape, where Im trying to rotate the damned thing
public class RotateShape implements Runnable {
JPanel panel;
private volatile boolean t1Running = true;
public RotateShape(JPanel panel) {
this.panel = panel;
}
private void draw() {
Graphics2D g2 = (Graphics2D) panel.getGraphics();
g2.rotate(Math.toRadians(10));
g2.drawPolygon(new int[] {50, 100, 150}, new int[] {150, 50, 150}, 3);
}
public void shutdown() {
t1Running = false;
System.out.println("Shutdown");
}
#Override
public void run() {
while (t1Running) {
try {
draw();
Thread.sleep(500);
System.out.println("loop working");
panel.repaint();
panel.revalidate();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
I'm not certain because I don't have access to your GUI code and therefore cannot test it myself, but here's an idea:
It looks like you're rotating your Graphics2D object by a fixed amount on every re-render (i.e. every invocation of draw()). It's possible that the internal JPanel code initiates the Graphics2D at a default rotation before every re-render, which may be why your rotation only causes the shape to "somewhat rotate."
Maybe try storing a variable for the radian offset (e.g. double offset;) as a member field of the RotateShapeclass, incrementing it on every re-render, then calling g2.rotate(Math.toRadians(offset));?
Addendum 1:
The reason that it may draw on top of the previous render is because the Graphics2D object is not clearing the canvas between re-renders. One way to fix this is to "clear" the canvas at the beginning of the draw() method by filling a rectangle that covers the whole canvas.
Addendum 2:
When you call panel.repaint(), it triggers the paintComponent() method of the JPanel object. So, by calling repaint() and your own draw() method, you are doing two separate renders, which may cause some graphical errors. If you were able to extends the JPanel class and use that, you should define an override to replace draw():
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
// TODO: add your rendering operations here
}
This way, calls to repaint() should trigger this method.

Repaint function messes up the whole frame

I have a problem when trying to draw some elements using paint method in Swing.
As title says, my whole frame collapses and does some weird repeating.
I made a separate JPanel so I can manipulate drawn shapes:
public class PanelPovrsina extends JPanel{
private ArrayList<Oblik> listaOblika;
public PanelPovrsina() {
// svi oblici
this.listaOblika = new ArrayList<Oblik>();
this.listaOblika.add(new Kvadrat(new Tacka(50, 50), 50, "zuta", "crvena"));
this.setBackground(Color.WHITE);
this.setVisible(true);
}
public void paint(Graphics g) {
if(this.listaOblika.isEmpty()) return;
Iterator<Oblik> it = this.listaOblika.iterator();
while(it.hasNext()) {
it.next().crtajUBoji(g);
}
repaint(); // this causes problems!
}
public ArrayList<Oblik> getListaOblika() {
return this.listaOblika;
}
}
Here is the frame with this code:
And here it is without repaint method:
No, I know repaint method is essential in order to dynamically add shapes and actually draw, but I can't make this work correctly.
Also, as you can see from the code above, background of panel is set to white, but my frame would'n render it.
Hope there is enough information to solve my problem, if not, I will add code of my JFrame!
Thank you!
You should never override the paint method, as it handles a number of other things behind the scenes. You should override paintComponent instead.
As #Joe C answered, I should have been using paintComponent method, not paint! Working code:
public class PanelPovrsina extends JPanel{
private ArrayList<Oblik> listaOblika;
public PanelPovrsina() {
// svi oblici
this.listaOblika = new ArrayList<Oblik>();
this.listaOblika.add(new Kvadrat(new Tacka(50, 50), 50, "zuta", "crvena"));
this.setBackground(Color.PINK);
this.setVisible(true);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
for (Oblik obl : this.listaOblika) {
obl.crtajUBoji(g);
}
repaint();
}
public ArrayList<Oblik> getListaOblika() {
return this.listaOblika;
}
}

Button not working when trying to draw a line

I'm new to Java and teaching myself and trying to build this program. It will create dots when the mouse clicks, but won't draw a line when the button is pushed. (eventually the line will connect the dots, but I'm not there yet.) First I just need to get it to draw something when pushed and work from there. I've tried multiple things and I can't get it to work. Here is my code:
public void init()
{
LineDrawListener listener = new LineDrawListener ();
addMouseListener (listener);
Button lineGraph = new Button("Graph Line");
lineGraph.addActionListener (this);
add (lineGraph, BorderLayout.EAST);
setBackground (Color.yellow);
setSize (APPLET_WIDTH, APPLET_HEIGHT);
}
public void paint(Graphics g)
{
// draws dots on screen
g.setColor(Color.blue);
if (current != null)
{
xcoordinates.addElement (current.x);
ycoordinates.addElement (current.y);
count++;
g.fillOval (current.x-4, current.y-4, 10, 10);
repaint();
}
if (push == true)
{
g.drawLine (5, 5, 30 , 30);
repaint();
}
}
class LineDrawListener extends MouseAdapter
{
public void mouseClicked (MouseEvent event)
{
current = event.getPoint();
repaint();
}
}
public void actionPerformed (ActionEvent event)
{
Object action = event.getSource();
if(action==lineGraph)
{
push = true;
}
}
Any help on how to get the button to work would be much appreciated. Thanks in advance.
The reason you can not get the line to draw is that repaint() posts a request to repaint the component. You are using it as if it refreshes the view somehow. You need to change three things in your code, all in paint():
Do not call repaint() in paint()
Override paintComponent() rather than paint() so your borders get drawn if you should ever have them at a later time.
Call super.paintComponent() in paintComponent() to do some initialization for you. It is probably OK not to for something this simple, but good practice for the future.
Here is what the code would look like:
public void paintComponent(Graphics g)
{
super.paintComponent(g);
// draws dots on screen
g.setColor(Color.blue);
if (current != null)
{
xcoordinates.addElement (current.x);
ycoordinates.addElement (current.y);
count++;
g.fillOval (current.x-4, current.y-4, 10, 10);
}
if (push == true)
{
g.drawLine (5, 5, 30 , 30);
}
}
Don't paint on top-level containers like JApplet
Rather paint on a JPanel and override the paintComponent method and call super.paintComponet(g)
#Override
protected void paintComponent(Graphic g){
super.paintComponent(g);
...
}
Don't call repaint() from inside the paint() method. Call it in your actionPerformed()
Learn to post an SSCCE
Number 3 is your most dire problem
Don't override paint(). Don't invoke repaint() in a painting method, this can cause in infinite loop.
Check out Custom Painting Approaches for working examples of the two common ways to do custom paintint:
by using a List to track the objects to be painted
by painting to a BufferedImage

My JComponent will only repaint() once I manually resize the window, how do I get it to work as it should?

I have tried my best to read around on the topic but I cannot find/understand how to apply the answers to my piece code (and those I have applied don't work)
I have used an example from "Ivor Horton's Beginning Java 2 JDK book" and it is the first time I'm using repaint() but it doesn't seem to work unless I resize the window. It tries to repaint but leaves the screen blank.
Here's the code please let me know if there is something I'm doing wrong.
public class CurveDrawings extends JFrame{
public CurveDrawings (String title){
setTitle(title);
setDefaultCloseOperation(EXIT_ON_CLOSE);
pane = new CurvePane(); //Create pane containing curves
Container content = getContentPane(); //Get the content pane
//Add the pane containing the curves to the content pane for the applet
content.add(pane);
MouseHandler handler = new MouseHandler();
pane.addMouseListener(handler);
pane.addMouseMotionListener(handler);
}
//Class defining pane on which to draw
class CurvePane extends JComponent{
public CurvePane(){
quadCurve = new QuadCurve2D.Double(
startQ.x, startQ.y, control.x, control.y, endQ.x, endQ.y
);
cubicCurve = new CubicCurve2D.Double(
startC.x, startC.y, controlStart.x, controlStart.y,
controlEnd.x, controlEnd.y, endC.x, endC.y
);
}
}
class Marker{//not needed for my problem}
class MouseHandler extends MouseInputAdapter{
public void mousePressed(MouseEvent e){
if(ctrlQuad.contains(e.getX(),e.getY())){
selected = ctrlQuad;}
else if(ctrlCubic1.contains(e.getX(),e.getY())){
selected = ctrlCubic1;}
else if(ctrlCubic2.contains(e.getX(),e.getY())){
selected = ctrlCubic2;}
}
public void mouseReleased (MouseEvent e){
selected = null;
}
public void mouseDragged (MouseEvent e){
System.out.println("DRAGGED");
if(selected != null){
//Set the marker to current cursor position
selected.setLocation(e.getX(),e.getY());
pane.validate();
pane.repaint();
}
}
Marker selected = null;
}
public void paint (Graphics g){
Graphics2D g2D = (Graphics2D)g; //Get a 2D device context
//Code to draw each component
}
//Points for quadratic curve
//Points for cubic curve
//More unnecessary code}
Incase it is any help here's the 'Launcher' class for the application
Thanks in advance.
You will need to call
super.paint(g);
in your paint method in your CurveDrawings class to avail of the painting functionality already provided in the JFrame super class.
Note, the standard way of using custom painting in Swing is to use custom components that are based on javax.swing.JComponent that use and override paintComponent. This approach would leverage Swing's optimized painting model.
See: Performing Custom Painting

Visual Artifacts appearing on JPanel

I am trying to create a program with 2 JPanel using BorderLayout. The center panel is for random drawing of rectangle while the south panel is for the buttons.
I am getting a weird image of the button on the top left corner of the JFrame whenever I hover the mouse cursor over the North or South button. I did some research and found out that this could be the reason for having a transparent background. I tried using super.paintComponent(g) for the panel but the rest of the rectangles drawn earlier disappear. I need the rectangles to stay in the JPanel but not the weird image on the top left.
I don't know what I am doing wrong, hopefully someone can help or give some clue on how to solve this problem.
public class TwoBRandomRec extends JFrame{
private static final long serialVersionUID = 1L;
public static void main(String[] args) {
TwoBRandomRec rec = new TwoBRandomRec();
rec.setSize(500,500);
rec.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
rec.setVisible(true);
}
public TwoBRandomRec() {
//Create the buttons
JButton north = new JButton("North");
JButton south = new JButton("South");
DrawPanel drawPanel = new DrawPanel(500,500);
JPanel southP = new JPanel();
southP.add(south);
southP.add(north);
this.add(drawPanel, BorderLayout.CENTER);
this.add(southP, BorderLayout.SOUTH);
this.setTitle("TwoButtonRandomRec");
this.pack();
}
public class DrawPanel extends JPanel {
private static final long serialVersionUID = 1L;
private Random rand = new Random();
private int x,y,xSize,ySize;
private int height,width;
public DrawPanel(int w,int h) {
width = w;
height = h;
}
public void RandomX(){
x=rand.nextInt(width-1);
xSize=rand.nextInt(width-x);
}
public void RandomY(){
y=rand.nextInt(height-1);
ySize=rand.nextInt(height-y);
}
public Color RandomColour(){
rand.nextInt(height);
return new Color(rand.nextInt(255),rand.nextInt(255),rand.nextInt(255));
}
#Override
protected void paintComponent(Graphics g) {
RandomX();
RandomY();
g.setColor(RandomColour());
g.fillRect(x, y, xSize, ySize);
try {
Thread.sleep(50);
} catch (InterruptedException e) {
}
repaint();
}
}
}
You're not calling super.paintComponent
protected void paintComponent(Graphics g) {
super.paintComponent(g); // <-- Insert me here and watch problem go away
RandomX();
RandomY();
g.setColor(RandomColour());
g.fillRect(x, y, xSize, ySize);
try {
Thread.sleep(50); // <-- This is an INCREDIBLY bad idea, NEVER, EVER do this
} catch (InterruptedException e) {
}
repaint(); // <-- This is a bad idea, watch CPU max out...
}
You are obligated to call super.paintComponent to ensure that the paint chain is upheld correctly and things like opacity and cleaning up of the graphics context takes place.
The Graphics object is shared between components through a single repaint pass, failure to honor the correct paint chain will result in, well, problems like yours
Never update the UI in anyway from any paint method (this includes calling repaint), this is just causing your paint method to be recalled, over and over and over...until you CPU hits 100% and program hangs.
Never, EVER do any time consuming or block operations within the paint methods (or the UI generally), this will make it look like the program as hung and make the users upset (you think zombi hordes are bad :P). Blocking in this way prevents the EDT from responding to paint requests...
I'd recommend having a read through:
Performing Custom Painting
2D Graphics
Painting in AWT and Swing
Concurrency in Swing

Categories

Resources