3

I'm trying to do a simple piece of homework, where I display a line of text displaying whether a door object is open or not. Underneath that, I visually represent it (using the drawRect) method. And at the bottom I have two buttons, which can open or close the door, thus changing the text and rectangle.

Edit: List of code that can be compiled given now:

    import java.awt.BorderLayout;
    import java.awt.Color;
    import java.awt.Dimension;
    import java.awt.Graphics;
    import java.awt.event.ActionEvent;
    import java.awt.event.ActionListener;

    import javax.swing.JButton;
    import javax.swing.JFrame;
    import javax.swing.JPanel;
    import javax.swing.JTextField;

    public class Test {

    public static void main(String[] args) {

        // Creates new JFrame called frame, with title "Door" 
        // (displayed at top of screen).
        JFrame frame = new JFrame ("Door");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        TempDoorPanel panel = new TempDoorPanel();
        frame.add(panel);
        frame.pack();
        frame.setVisible(true);

        }
    }

    class Door {

    private String state;
    private String message;

    Door (String state) {
        this.state = state;
        message = "The door is currently closed.";
    }

    public boolean isOpen() {
        return state.equals ("open");
    }

    public boolean isClosed() {
        return state.equals ("closed");
    }

    public void setState(String state) {
        this.state = state;
    }

    public String getMessage() {
        return message; 
    }

    public void open() {
        if (state.equals("open")) {
            message = "The door is already open.";
        }
        else {
            state = "open";
            message = "The door has been opened.";
        }
    }

    public void drawOpenDoor (Graphics page) {
        page.drawRect(100, 100, 100, 100);
    }
    }

    class TempDoorPanel extends JPanel {

    private Door door;
    private JTextField currentStateOfDoor;
    private JButton openDoor;

    public TempDoorPanel() {
        super.setLayout(new BorderLayout());
        door = new Door("closed");
        super.setBackground(Color.blue);
        super.setPreferredSize(new Dimension (360, 400));

        currentStateOfDoor = new JTextField(14);
        currentStateOfDoor.setText(door.getMessage());
        super.add(currentStateOfDoor, BorderLayout.NORTH);

        openDoor = new JButton("Open Door");

        class openDoorListener implements ActionListener {
            public void actionPerformed (ActionEvent event) {
                door.open();
                repaintText();
            }
        }

        openDoorListener openlistener = new openDoorListener();
        openDoor.addActionListener(openlistener);

        JPanel holder = new JPanel();
        holder.add(openDoor);
        super.add(holder, BorderLayout.SOUTH);
    }

    private void repaintText() {
        currentStateOfDoor.setText(door.getMessage());
        // These methods are from Door class.
    }

    public void paintComponent (Graphics page) {
        super.paintComponent(page);
        if (door.isOpen())
            door.drawOpenDoor(page);
        // isOpen is a boolean method from Door class.
    }
}

What works:

  • Buttons appear at right place on screen, at BorderLayout.SOUTH, one after the other.
  • The JTextField appears at right place, at BorderLayout.NORTH
  • Finally, the blue area appears in the right place in the centre of the screen.

What I'm trying to fix:

  • I have no idea how to display the rectangle properly in the middle of that blue area. I've tried changing the coordinates and size of the rectangle, which doesn't change the size of it at all. I can make it drawRect(100, 100, 100, 100) and it changes nothing.
  • I'm also aware that the rectangle is currently hidden behind the top left corner of the JTextField, but I can't figure out how to move it into the BorderLayout.

Questions:

  • How do you place a rectangle in a BorderLayout?
  • How do you adjust the size of a rectangle, drawn via drawrect(), in such a layout?
mKorbel
  • 109,525
  • 20
  • 134
  • 319
Andrew Martin
  • 5,619
  • 10
  • 54
  • 92
  • 1
    Please produce an [SSCCE](http://sscce.org) which requires a single copy and paste Now we have to go and create a new project, new class and new main etc I want to simply create a class with the same name as yours and copy and paste the code. But hey thats me I am lazy :P also it should not contain errors i.e `door.isOpen()` is giving an error because you have not included the method – David Kroukamp Feb 14 '13 at 14:38
  • Working on it now, thanks for tip – Andrew Martin Feb 14 '13 at 14:44
  • The problem you cant see the rectangle is its covered by JTextField due to your co-ords try like: `page.drawRect(100, 100, 10, 10);` – David Kroukamp Feb 14 '13 at 14:44
  • Code has been reuploaded - should be able to compile it all in one go. – Andrew Martin Feb 14 '13 at 14:57
  • A better way to lay this out would to be to create a custom component for drawing the door, like a JLabel. – Kylar Feb 14 '13 at 14:57

2 Answers2

4

Because you add components to the JPanel you draw on the JTextField is covering your drawing.

Solution:

1) Either compensate for this by checking the JTextField height in your drawRect(..) method

or better

2) Dont add components to the same JPanel which you are drawing on unless it cant be helped.

So basically I made your TempDoorPanel add a new JPanel to BorderLayout.CENTER which is the drawing panel we can now use drawRect(0,0,10,10) and it will show in the top left hand corner of JPanel drawingPanel.

  • Also dont call setPreferredSize on JPanel rather override getPreferredSize() and return Dimensions which fit your drawings.

  • To invoke paintComponent outside of the class simply call repaint() its instance

See this example which uses point no.2:

enter image description here

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;

public class Test {

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                JFrame frame = new JFrame("Door");
                frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);

                TempDoorPanel panel = new TempDoorPanel();
                frame.add(panel);
                frame.pack();
                frame.setVisible(true);
            }
        });
    }
}
class Door {

    private String state;
    private String message;

    public Door(String state) {
        this.state = state;
        message = "The door is currently closed.";
    }

    public void drawOpenDoor(Graphics page) {
        page.setColor(Color.GREEN);
        page.drawRect(0, 0, 10, 10);
    }
}

class TempDoorPanel extends JPanel {

    private Door door;
    private JTextField currentStateOfDoor;
    private JButton openDoor;

    public TempDoorPanel() {
        super.setLayout(new BorderLayout());
        door = new Door("closed");

        currentStateOfDoor = new JTextField(14);
        //AcurrentStateOfDoor.setText(door.getMessage());
        super.add(currentStateOfDoor, BorderLayout.NORTH);

        openDoor = new JButton("Open Door");

        final JPanel drawingPanel = new JPanel() {
            @Override
            protected void paintComponent(Graphics grphcs) {
                super.paintComponent(grphcs);
                // if (door.isOpen()) {
                door.drawOpenDoor(grphcs);
                // }
                // isOpen is a boolean method from Door class.

            }
        };
        drawingPanel.setBackground(Color.blue);
        add(drawingPanel);

        class openDoorListener implements ActionListener {

            public void actionPerformed(ActionEvent event) {
                //door.open();
                repaintText();
                drawingPanel.repaint();//so paint component of drawing panel is called
            }
        }

        openDoorListener openlistener = new openDoorListener();
        openDoor.addActionListener(openlistener);

        JPanel holder = new JPanel();
        holder.add(openDoor);
        super.add(holder, BorderLayout.SOUTH);
    }

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

    private void repaintText() {
        // currentStateOfDoor.setText(door.getMessage());
        // These methods are from Door class.
    }
}
David Kroukamp
  • 36,155
  • 13
  • 81
  • 138
  • Is there a way to do it without the invokeLater Runnable etc part. I only ask because that's the one section my university gave me and they've given no indication that it need changed (I've no idea what a runnable is in fact). Regardless, thank you for this. – Andrew Martin Feb 14 '13 at 15:01
  • It needs changing (yes you can leave it out but that will ause problems later on). All Swing components should be created and manipulated on `Event Dispatch Thread` via `SwingUtilties.invokeLater(Runnable r)`. Read it here on [Concurrency in Swing](http://docs.oracle.com/javase/tutorial/uiswing/concurrency/index.html). So basically your university does not adhere to Swing best practices tell them about it!! – David Kroukamp Feb 14 '13 at 15:07
  • I will! One more thing. I know the paintComponent is automatically run (although it can be manually overridden). But I can't find in the API WHERE it says it is automatically run. The reason I'm curious is I want to find out what else is automatically run. When I started doing this yesterday, it took me hours to figure out I didn't need to pass a Graphics object into the paintComponent class, as it was all done for me! Where does it say why it's automatic and what else is automatic? – Andrew Martin Feb 14 '13 at 15:11
  • `paintComponent` will be called at varying intervals while its visible i.e when a change is made to the UI which calls `update(..)` or `repaint(..)` etc. We can however *force* `paintComponent` to be called by calling `repaint()` on the components instance – David Kroukamp Feb 14 '13 at 15:13
  • But where does the API tell you that it's called at varying intervals? I.e. how would I know without asking someone? – Andrew Martin Feb 14 '13 at 15:14
  • When a window becomes visible (uncovered or deminimized) or is resized, the "system" automatically calls the paintComponent() method for all areas of the screen that have to be redrawn. There is no docs stating this but certain methods are called when certain actions like resizing etc take place which will in turn call `update(..)`, `repaint(..)`,`paintComponent` – David Kroukamp Feb 14 '13 at 15:15
  • Thanks for this - will work my way through it and try and learn! – Andrew Martin Feb 14 '13 at 15:16
  • Thought I was done, but one more question - how can you place the paintComponent method in braces after creating the drawingPanel object of JPanel? I've never seen that before. – Andrew Martin Feb 14 '13 at 15:39
  • Its called a anonymous inner class. See [here](http://stackoverflow.com/questions/355167/how-are-anonymous-inner-classes-used-in-java) for more – David Kroukamp Feb 14 '13 at 16:15
3

When you handler the door opening event with your listener;

class openDoorListener implements ActionListener {
  public void actionPerformed(ActionEvent event) {
    door.open();
    repaintText();
  }
}

you don't actually include a call to repaint the panel; hence the panel's paintComponent() method isn't called and door.drawOpenDoor() isn't called. You can test this by clicking the button and then resizing the frame. When you resize, the panel is automatically repainted and bingo, your door appears.

You can fix this by adding a call to repaint() in your ActionListener;

class openDoorListener implements ActionListener {
  public void actionPerformed(ActionEvent event) {
    door.open();
    repaintText();
    repaint();   // requests that the panel be repainted
  }
}
Qwerky
  • 18,217
  • 6
  • 44
  • 80
  • Thanks for this, suddenly it all works. It's these automatic calls that are killing me. If people like yourself didn't tell me I had to do that, I wouldn't have thought to. – Andrew Martin Feb 14 '13 at 15:23