brienna
brienna

Reputation: 1604

Why doesn't this Java Graphics display in OS X?

I was given some demo code during class that worked on the Windows computer in the lab but doesn't work the same way on my 2010 MacBook, using Sierra.

Making the changes suggested in Java Graphics Not Displaying In OS X didn't fix my problem. I've also tried resizing the window, which changes the animation a bit -- it pops up intermittently after the resize. If I increase the Thread.sleep() time and resize, then the animation improves, but it still is choppy.

Why doesn't the code work on my MacBook and how can I get it to work?

The original code (which works on Windows 10 but not my Mac):

import javax.swing.*;
import java.awt.*;
import java.awt.event.*;

public class GraphicsTestA extends JFrame{
   int x1 = 60;
   int x2 = 150;
    public static void main(String [] args){
        GraphicsTestA gta = new GraphicsTestA();
      gta.animate();
    }

   private void animate()
   {
      while(true)
      {
         for(int i=0; i <100; i ++)
         {
            x1 = x1 + 1;
            x2 = x2 - 1;
            try
            {
               Thread.sleep(100);
            }
            catch(Exception ex)
            {}
            repaint();
         }

         x1 = 60;
         x2 = 150;
      }
   }
    public GraphicsTestA() {
        setSize(200,200);
        setLocationRelativeTo(null);
        setDefaultCloseOperation(EXIT_ON_CLOSE);
        setVisible(true);
        this.validate();
    }

    public void paint(Graphics g) {
        super.paint(g);

      g.drawString("Hello World",80,80);
      g.drawLine(x1,60,x2,150);
        g.drawRect(100,40,30,30);

    }
}

Upvotes: 1

Views: 1419

Answers (2)

BillRobertson42
BillRobertson42

Reputation: 12883

In swing, you're not supposed to invoke swing things outside of the swing thread. Windows lets you get away with some slop, but Java on the mac is super picky about it.

In this code, the constructor makes swing calls, but it's invoked from the main thread. You can try to split that out so the constructor is called by the Swing thread, but then there are problems with the animate method.

The animate method shouldn't be called by the Swing thread because it's calling sleep(), and pausing the swing thread is a bad idea™. However, it's also calling repaint(), which is a swing call and that needs to be called by the Swing thread.

I suggest you use EventQueue.invokeLater to construct your window, and then figure out how to use Swing Workers or some other appropriate mechanism to do your animation.

Upvotes: 3

MadProgrammer
MadProgrammer

Reputation: 347184

So a number of issues:

Extending from JFrame

It's generally discouraged to extend directly from a top level container like JFrame, as you rarely add any new re-usable functionality to it and it locks you into a single use case.

It's also a compound component, that is, there are a number of components which reside on it, which one of the main causes of issues for you.

JRootPane

Instead, start with something like JPanel and then add it to any other container you need.

Overriding paint

As a general rule, you should avoid overriding paint, it's to low level for most situations, and in the case of top level containers, just messes things up. Instead, start with paintComponent

See Performing Custom Painting and Painting in AWT and Swing for more details

Thread violations

As has already been pointed out, Swing is not thread safe AND it's single threaded.

This means that you should never update the UI or any values the UI relies on from outside the Event Dispatching Thread. It also means you should never perform any long running or blocking operations inside the context of the EDT either.

In simple cases, a Swing Timer is more then powerful enough to do the job. See How to use Swing Timers for more details

Putting it all together...

So, putting all that together might end up looking something like...

import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JFrame;
import static javax.swing.JFrame.EXIT_ON_CLOSE;
import javax.swing.JPanel;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;

public class GraphicsTestA {

    public static void main(String[] args) {
        new GraphicsTestA();
    }

    public GraphicsTestA() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                    ex.printStackTrace();
                }

                JFrame frame = new JFrame("Testing");
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.add(new TestPane());
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    public class TestPane extends JPanel {

        int x1 = 60;
        int x2 = 150;
        int loops = 0;

        public TestPane() {
            Timer timer = new Timer(100, new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    x1 = x1 + 1;
                    x2 = x2 - 1;
                    loops++;
                    if (loops > 100) {
                        x1 = 60;
                        x2 = 150;
                        ((Timer) e.getSource()).stop();
                    }
                    repaint();
                }
            });
            timer.start();
        }

        @Override
        public Dimension getPreferredSize() {
            return new Dimension(200, 200);
        }

        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            Graphics2D g2d = (Graphics2D) g.create();
            g2d.drawString("Hello World", 80, 80);
            g2d.drawLine(x1, 60, x2, 150);
            g2d.drawRect(100, 40, 30, 30);
            g2d.dispose();
        }

    }

}

Upvotes: 3

Related Questions