1

So basically I have some code I was working on a couple of days ago that is kind of like Paint, which allows you to essentially draw on the screen using the mouse. I kind of discovered this property by accident, and I realized that it is really inefficient and i'm wondering if there is a more practical way to do this. There isn't really any reason to give all of my code, but here are the important parts

private static void createAndShowGui() {
    SimpleDraw mainPanel = new SimpleDraw();
    MenuBar.createMenuBar();
    JLabel label = new JLabel();
    label.setText("Drawing prototype 0.0.1");
     // label.setHorizontalTextPosition(JLabel.NORTH);
    label.setFont(new Font("Serif", Font.BOLD, 20));
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    frame.getContentPane().add(mainPanel);
    frame.pack();
    frame.setLocationByPlatform(true);
    frame.getContentPane().setLayout(new BoxLayout(frame.getContentPane(),BoxLayout.PAGE_AXIS));
    frame.setVisible(true);
    frame.setJMenuBar(MenuBar.getMenuBar());
    frame.setBackground(Color.WHITE);
    frame.add(label);

The code block above sets up the jframe (the window)

 @Override
    public void mouseDragged(MouseEvent e)
    {
    // These console outputs are just so that I know what is happening
        System.out.println("Event: MOUSE_DRAG");
        System.out.println(e.getX());
        System.out.println(e.getY());
        System.out.println(e.getComponent());
        System.out.println(e.getWhen());
        System.out.println(e.getButton());
         MOUSE_X = e.getX() - 5;  //-5 so that the cursor represents the center of the square, not the top left corner.
         MOUSE_Y = e.getY() - 5;  //^
         rect = new Rectangle(MOUSE_X, MOUSE_Y, 10, 10 ); //This doesn't ever come into action.
         repaint();  

     }

The code above pretty much just sets the MOUSE_X and MOUSE_Y variables and the repaint(); method

@Override
    protected void paintComponent(Graphics g) {

    Graphics2D g2 = (Graphics2D) g;
    if (rect != null) {

        if (!colorChoice.equals("Default"))
        {
            g2.setColor(Color.BLACK);
        }

        switch(colorChoice) {

        case "GRAY":
            g2.setColor(Color.GRAY);
            break;
        case "CYAN":
            g2.setColor(Color.CYAN);
            break;
        case "BLUE":
            g2.setColor(Color.BLUE);
            break;
        case "RED":
            g2.setColor(Color.RED);
            break;
        case "PINK":
            g2.setColor(Color.PINK);
            break;
        case "YELLOW":
            g2.setColor(Color.YELLOW);
            break;
        case "GREEN":
            g2.setColor(Color.GREEN);
            break;
        case "PURPLE":
            g2.setColor(Color.MAGENTA);
            break;
        case "RESET":
            g2.setColor(Color.WHITE);
        case "WHITE":
            g2.setColor(Color.WHITE);

        }





        g2.fillRect(MOUSE_X, MOUSE_Y, 15, 15); 

        if (colorChoice.equals("RESET")) 
        resetColorOnCursor(); 

        }
    }

    public static void clearBoard()
    {
    tempColor = colorChoice;
    setColorChoice("RESET");
    frame.repaint();




    }


    public static void resetColorOnCursor()
    {
    setColorChoice(tempColor);
    }

This is the thing I came across accidentally. What I was trying to do when I found this out was basically make a square follow your cursor whenever you moved your mouse. But I forgot to type the code part paintComponent(g);, which turns this program into the thing that I originally intended. The bottom parts of this are essentially how I would clear the board. I'm 100% sure that this isn't the proper way to clear/reset a frame like this, but I couldn't find another way. If anyone has any tips or better methods to use to do this properly I would be very appreciative. Thanks! :D

Peter_Browning
  • 249
  • 1
  • 2
  • 10
  • Nope, you should be calling `super.paintComponent` before painting the current state of the component. Otherwise you could end up with all sorts of mess appearing on your component, like other ghost images of your component, for example – MadProgrammer Oct 07 '15 at 00:20
  • To "rest" the view, rest the values which determine how or what is painted before calling `repaint` – MadProgrammer Oct 07 '15 at 00:21
  • Calling super.paintComponent(); makes a moveable square. This is what i DONT want. I wan't to make the drawing aspect more efficient. Calling super.paintComponent(); Will turn this program back into the boring "moveable square" application – Peter_Browning Oct 07 '15 at 00:24
  • Sorry to say this, tough, you're actually violating the requirements of the paint chain by doing what you are doing. Define a anchor point with mouse down and use the mouse drag to define the extend of the selection area – MadProgrammer Oct 07 '15 at 00:27
  • I'm not violating anything. All I did was make a program in java and I think it can be more efficient, so I ask for help on this server. If you don't have anything helpful to say don't say it at all – Peter_Browning Oct 07 '15 at 00:32
  • `paintComponent` does an very important job, which you've "neglected" to perform either directly or indirectly, violating the basic requirements of the paint chain. What you've generated is actually a side effect of this violation, which will, when placed into a large program cause no end of paint artifacts. I'm simply trying to help you solve a problem you don't yet know you have – MadProgrammer Oct 07 '15 at 00:39
  • 1
    `I'm not violating anything.` - yes you are, you don't understand the basics of how Swing painting works. `If anyone has any tips or better methods to use to do this properly I would be very appreciative.` - apparently not very appreciative since you just ignored the advice given and you don't even understand it and to people not to say anything. – camickr Oct 07 '15 at 00:40
  • *"If anyone has any tips or better methods to use to do this properly I would be very appreciative"* - Call `super.paintComponent`; define a anchor point by using a `MouseListener` when `mousePressed` is called, use a `MouseMotionListener` and `mouseDragged` to define the extent of the selection area; call `repaint` to update the UI. You can clear the selection by resting the shape which represents the selection, maybe by setting it to `null` – MadProgrammer Oct 07 '15 at 00:45

3 Answers3

10

You're current approach is basically breaking the requirements of the paint chain, by not calling super.paintComponent. The paintComponent method does a set of operations, which you are not taking over and which could result in some very weird paint artifacts which are difficult to replicate consistently.

Graphics is a shared resource, so the Graphics context which was used to paint some other control will be the same which is used to paint your component, unless you are "cleaning" the context before hand, what was previously painted to the context will remain (which is why you code currently "seems" to work).

Instead, you should use a MouseListener to define a anchor point, which represents the point at which the mouse was pressed and then use the MouseMotionListener to define the extent of the selection area, for example...

Paint Selection

import java.awt.AlphaComposite;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;

public class SelectExample {

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

    public SelectExample() {
        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 {

        private Rectangle selection;

        public TestPane() {
            MouseAdapter ma = new MouseAdapter() {

                private Point clickPoint;

                @Override
                public void mousePressed(MouseEvent e) {
                    clickPoint = e.getPoint();
                    selection = null;
                }

                @Override
                public void mouseDragged(MouseEvent e) {
                    Point dragPoint = e.getPoint();
                    int x = Math.min(clickPoint.x, dragPoint.x);
                    int y = Math.min(clickPoint.y, dragPoint.y);

                    int width = Math.max(clickPoint.x, dragPoint.x) - x;
                    int height = Math.max(clickPoint.y, dragPoint.y) - y;

                    if (selection == null) {
                        selection = new Rectangle(x, y, width, height);
                    } else {
                        selection.setBounds(x, y, width, height);
                    }
                    repaint();
                }

                @Override
                public void mouseReleased(MouseEvent e) {
                    selection = null;
                    repaint();
                }

            };

            addMouseListener(ma);
            addMouseMotionListener(ma);
        }

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

        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            if (selection != null) {
                g.setColor(UIManager.getColor("List.selectionBackground"));
                Graphics2D g2d = (Graphics2D) g.create();
                g2d.setComposite(AlphaComposite.SrcOver.derive(0.5f));
                g2d.fill(selection);
                g2d.dispose();
                g2d = (Graphics2D) g.create();
                g2d.draw(selection);
                g2d.dispose();
            }
        }

    }

}

Just to highlight the issue you will face if you continue to violate the requirements of the paintComponent method, this is what happens when I don't call super.paintComponent

Violated

I simply added two JButton's to the JFrame (so not even directly to the panel). paintComponent does a series of important jobs, which you neglected to perform, which is going to cause more problems and issues.

Free form line example...

A free form line is actually a illusion, it's a series of (small) lines drawn between a series of points, the reason for this is because the MouseListener won't report every mouse position it moves across, depending on the speed the mouse is moved, you might get lots of call backs or a few.

So, instead of drawing to just draw the points, we store the points in a List and draw lines between them, for example...

FreeForm

import java.awt.AlphaComposite;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;

public class FreeFormLines {

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

    public FreeFormLines() {
        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 {

        private List<List<Point>> points;

        public TestPane() {
            points = new ArrayList<>(25);
            MouseAdapter ma = new MouseAdapter() {

                private List<Point> currentPath;

                @Override
                public void mousePressed(MouseEvent e) {
                    currentPath = new ArrayList<>(25);
                    currentPath.add(e.getPoint());

                    points.add(currentPath);
                }

                @Override
                public void mouseDragged(MouseEvent e) {
                    Point dragPoint = e.getPoint();
                    currentPath.add(dragPoint);
                    repaint();
                }

                @Override
                public void mouseReleased(MouseEvent e) {
                    currentPath = null;
                }

            };

            addMouseListener(ma);
            addMouseMotionListener(ma);
        }

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

        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            Graphics2D g2d = (Graphics2D) g.create();
            for (List<Point> path : points) {
                Point from = null;
                for (Point p : path) {
                    if (from != null) {
                        g2d.drawLine(from.x, from.y, p.x, p.y);
                    }
                    from = p;
                }
            }
            g2d.dispose();
        }

    }

}
Community
  • 1
  • 1
MadProgrammer
  • 343,457
  • 22
  • 230
  • 366
  • So obviously you are more experienced than me, but there is obviously also a communication error here. The program I am trying to create is basically a drawing software. EX: Drag the mouse and create a line, not a box. – Peter_Browning Oct 07 '15 at 00:51
  • Okay, so instead of creating a `Rectangle`, create `Line2D` as demonstrated [here](http://stackoverflow.com/questions/32979814/how-can-i-set-the-stroke-color-at-runtime-in-java/32981345#32981345) (and add them to some kind of `List` so you can re-draw them when you need to) – MadProgrammer Oct 07 '15 at 00:53
  • Ok, I get what you are saying, but using your method, how would you create free-form lines? (curves) – Peter_Browning Oct 07 '15 at 00:57
  • You mean like "not" straight? That's actually a little illusion, give me a minute – MadProgrammer Oct 07 '15 at 00:59
  • I added a "free form line" example, see if that meets more of your needs. To clear the drawing, you would simply either discard the contents of the `points` `List` (using clear for example) or remove one or more of the sub paths from it and the call `repaint` – MadProgrammer Oct 07 '15 at 01:06
  • That's exactly what I was looking for. I don't fully understand the whole `Uimanager.setlookandfeel` part, but I'm sure I can figure it out. Thank you much and sorry for any hostility or disrespect that I may have shown – Peter_Browning Oct 07 '15 at 01:10
  • Sometimes, it's hard to see the forest for the trees ;) – MadProgrammer Oct 07 '15 at 01:13
0

This is a simple example for a practical paint Application, where you can control and change the size and the Color of your drawing.

public class Main extends Application{
    @Override
    public void start(Stage stage){
        try{
            g = can.getGraphicsContext2D();
            g.setStroke(Color.BLACK);
            g.setLineWidth(1);
            c.setValue(Color.BLACK);
            c.setOnAction(e->{
                g.setStroke(c.getValue());
            });
            sli.setMin(1);
            sli.setMax(100);
            sli.setShowTickLabels(true);
            sli.setShowTickMarks(true);
            sli.valueProperty().addListener(e->{
                double val = sli.getValue();
                String str = String.format("%.1f",  val);
                lab.setText(str);
                g.setLineWidth(val);
            });
            gri.addRow(0,  c, sli, lab);
            gri.setHgap(20);
            gri.setAlignement(Pos.TOP_CENTER);
            gri.setPadding( new Insets( 20, 0, 0, 0));

            scene.setOnMousePressed(e->{.
               g.beginPath();
               g.lineTo(e.getSceneX(), e.getSceneY());
               g.stroke();
            });
            scene.setOnMoudrDragged(e->{. 
                g.lineTo(e.getSceneX(),  e.getSceneY());
                g.stroke();
            });
            pan.getChildren().addAll(can, gri);
            stage.setScene(scene);
            stage.show();
        }catch(Exception e){

            e.printStrackTrace();
        }

       Canvas can = new Canvas(760, 490);
       GraphicsContext  g ;
       ColorPicker  c =  new ColorPicker();
       Slider sli = new Slider();
       Label lab = new Label("1.0");
       GridPane gri = new GridPane();
       StackPane pan =  new StackPane();
       Scene  scene = new Scene(pan, 760, 490);
   public static void main (String [] args){
       launch(args);
   }
}
Dharman
  • 30,962
  • 25
  • 85
  • 135
0

Or we can try drawing for only java code , I think it's so easy and powerful.

package drawingbymouse;

import java.awt.*;
import java.awt.event.*;

public class DrawingByMouse extends Frame 
                          implements MouseMotionListener{

    DrawingByMouse(){
        addMouseMotionListener(this);
        setSize(400, 400);
        setLayout(null);
        setVisible(true);
    }
    @Override
    public void mouseDragged(MouseEvent e){
        Graphics g = getGraphics();
        g.setColor(Color.BLACK);
        g.fillOval(e.getX(), e.getY(), 10, 10);
    }
    public void mouseMoved(MouseEvent e){
    }
    public static void main (String[]args){
        new DrawingByMouse();
    }
}