Search code examples
javaswingmousemotionlistener

java decrease time between mouseMotionListener intervals


I want to create a simple drawing programm in java which currently only draws a line using Graphics.fillOval() and a mouseMotionListener(). The problem is, that if you move the mouse quickly the line gets less precise and the ovals (circles in this case) spread apart.

Here is the code:

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class Drawing 
{
     private JFrame window;
     private Graphics g;
     public Drawing()
     {
        window=new JFrame();
        window.setTitle("Paint_window");
        window.setSize(1000,700);
        window.setVisible(true);
        window.setDefaultCloseOperation(window.EXIT_ON_CLOSE);
        g=window.getGraphics();
        window.addMouseMotionListener(new MouseMotionAdapter()
            {
                public void mouseDragged(MouseEvent e)
                {
                 if(SwingUtilities.isLeftMouseButton(e)
                {
                        g.fillOval((int)e.getX(),(int)e.getY(),10,10);
                    }
                }
            });
    }
}

Is there a way of improving this or a better way to this?


Solution

  • g=window.getGraphics();
    

    First of all you should not be using getGraphics() of a component. Any painting you do will only be temporary and will be erased the first time Swing determines the component needs to be repainted. In you above example just try resizing the frame to see this.

    The proper way to do custom painting is to override the paintComponent(...) method of a JPanel and add the panel to the frame. See Custom Painting for more information.

    The problem is, that if you move the mouse quickly the line gets less precise and the ovals (circles in this case) spread apart

    You will not be able to have an event generated for every pixel the mouse moves.

    Instead you need to be able to "draw a line" between consecutive points generated as you drag the mouse.

    So you need to store each point in an ArrayList and in the custom painting code iterate through all the points and draw a line.

    A basic example to get you started:

    import java.awt.*;
    import java.awt.event.MouseAdapter;
    import java.awt.event.MouseEvent;
    import java.util.ArrayList;
    
    import javax.swing.JFrame;
    import javax.swing.JPanel;
    
    class DrawingPanel extends JPanel
    {
        private ArrayList<ArrayList<Point>> previous = new ArrayList<ArrayList<Point>>();
        private ArrayList<Point> current = new ArrayList<Point>();
        private BasicStroke basicStroke;
    
        public DrawingPanel(int strokeSize)
        {
            basicStroke = new BasicStroke(strokeSize, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND);
    
            MouseAdapter ma = new MouseAdapter()
            {
                @Override
                public void mousePressed(MouseEvent e)
                {
                    current.add( new Point(e.getX(), e.getY()) );
                }
    
                @Override
                public void mouseDragged(MouseEvent e)
                {
                    current.add( new Point(e.getX(), e.getY()) );
                    repaint();
                }
    
                @Override
                public void mouseReleased(MouseEvent e)
                {
                    if (current.size() > 1)
                    {
                        previous.add( current );
                    }
    
                    current = new ArrayList<Point>();
                }
            };
    
            addMouseMotionListener( ma );
            addMouseListener( ma );
        }
    
        @Override
        protected void paintComponent(Graphics g)
        {
            super.paintComponent(g);
    
            Graphics2D g2 = (Graphics2D) g;
            g2.setStroke( basicStroke );
    
            //  Paint lines from previous drags
    
            for (int i = 0; i < previous.size(); i++)
            {
                drawLines(g, previous.get(i));
            }
    
            //  Paint line from current drag
    
            drawLines(g, current);
        }
    
        private void drawLines(Graphics g, ArrayList<Point> points)
        {
            for (int i = 0; i < points.size() - 1; i++)
            {
                int x = (int) points.get(i).getX();
                int y = (int) points.get(i).getY();
                int x2 = (int) points.get(i + 1).getX();
                int y2 = (int) points.get(i + 1).getY();
                g.drawLine(x, y, x2, y2);
            }
        }
    
        private static void createAndShowGUI()
        {
            JFrame frame = new JFrame("Drawing Panel");
            frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            frame.add(new DrawingPanel(15));
            frame.setSize(400, 400);
            frame.setLocationByPlatform( true );
            frame.setVisible( true );
        }
    
        public static void main(String[] args) throws Exception
        {
            EventQueue.invokeLater( () -> createAndShowGUI() );
    /*
            EventQueue.invokeLater(new Runnable()
            {
                public void run()
                {
                    createAndShowGUI();
                }
            });
    */
        }
    }
    

    Using the above approach you will redraw the lines every time the component is repainted.

    Another approach is to draw to a BufferedImage and then paint the BufferedImage on the panel. You can check out Custom Painting Approaches for an example of this approach.