# Rechteck zeichnen + "special features" :-)



## guni (7. Apr 2008)

hi, 

bin ein absoluter JAVA-Anfänger und steh vor einer Aufgabe, die ich nicht ganz check ...

ich möchte in Swing ein Rechteck zeichnen und es dann ein bisschen "nachbearbeiten" können
beschränken wir uns mal aufs Zeichnen des Rechtecks:

ich hab
1. meinen JFrame
2. meine ContentPane (mit Hintergrund Bild)
3. eine GlassPane auf der ich mein Rechteck zeichnen möchte
4. einen MouseMotionListener auf die Glaspane

mein Code sieht z.Zt. so aus:


```
import java.awt.image.ImageObserver;
import java.text.AttributedCharacterIterator;

import javax.swing.*;

public class Main {
    public static void main(String[] args) {
    	
        // Erstellt das Swing-Fenster
        JFrame fenster = new JFrame("Kalibrierungsmodul");
        // Swing anweisen, das Programm zu beenden, wenn das Fenster geschlossen wird
        fenster.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        // die Content Pane des Fensters
        final Container content = fenster.getContentPane();
        content.setLayout(new FlowLayout());
        
        // erzeuge Bild
        ImageIcon karte = new ImageIcon( Main.class.getResource( "arab_pennisula.gif" ) );
        JLabel label = new JLabel( karte );
        // erzeuge Button
        JButton button = new JButton("Testbutton");
        button.setSize(100, 100);

        // füge Komponenten hinzu
        content.add(button);
        content.add(label);

        // erzeugt den Glass Pane + Button
        final JButton glassButton = new JButton("Glassbutton");
        final JPanel glass = (JPanel)fenster.getGlassPane();
        glass.add(glassButton);
        glass.setVisible(true);
        
        // erzeuge MouseListener für die GlassPane
        /*
        glass.addMouseListener(new MouseListener() {
			public void mouseClicked(MouseEvent arg0) {}
			
			public void mouseEntered(MouseEvent arg0) {}

			public void mouseExited(MouseEvent arg0) {}

			public void mousePressed(MouseEvent arg0) {
				glassButton.setText(""+arg0.getX());
			}

			public void mouseReleased(MouseEvent arg0) {
				glassButton.setText("TEST");
			}
        	
        });
        */
               
        // erzeuge MouseMotionListener für die GlassPane
        glass.addMouseMotionListener(new MouseMotionListener() {
			public void mouseDragged(MouseEvent arg0) {
				glass.paintComponents(g);
			}
			public void mouseMoved(MouseEvent arg0) {}
        });
        
        // erzeuge Action Listener für den Button
        glassButton.addActionListener(new ActionListener() {
        	public void actionPerformed(ActionEvent e) {
        		glass.setVisible(false);
        		content.repaint();
        	}
        });
        
        // Zeigt das Fenster an
        fenster.pack();
        fenster.setSize(1024, 768);
        fenster.setLocationRelativeTo(null);
        fenster.setVisible(true);
  }
    // überschreibe die PaintComponent
    public void paintComponent(Graphics g, int x1, int y1, int x2, int y2){
    	g.setColor(Color.cyan);
    	g.drawRect(x1, y1, x2, y2);
    }
}
```

Sinn der ganzen Sache wäre, dass ich im MouseDragged irgendwie das Rechteck zeichne ...
warscheinlich muss ich eh noch einen MouseListener einbauen, damit ich Start (MousePressed) und Endpunkt (MouseReleased) meines Rechtecks rausfind, aber ich scheiter schon beim Zeichnen eines einfachen Rechtecks!

die Tutorials sagen, dass es eine paintComponent Methode gibt (die ich für meine GlassPane auch finde) und dass ich diese Methode überschreiben muss ...
wie kann ich das machen?
Warum bekomme ich immer einen Fehler, dass ich meine Graphics nicht instanzieren kann? Ist sie abstrakt? Wie kann ich das lösen?

mfg, guni


----------



## André Uhres (8. Apr 2008)

Um etwas überschreiben zu können, müssen wir die entsprechende Klasse erweitern:
class MyGlassPane extends JPanel {...


```
package demo;
/*
 * Main.java
 */

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

public class Main extends JFrame {

    public Main() {
        // Erstellt das Swing-Fenster 
        super("Kalibrierungsmodul");
        // Swing anweisen, das Programm zu beenden, wenn das Fenster geschlossen wird 
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        // die Content Pane des Fensters 
        final Container content = getContentPane();
        content.setLayout(new FlowLayout());
        // erzeuge Bild 
        ImageIcon karte = new ImageIcon(Main.class.getResource("arab_pennisula.gif"));
        JLabel label = new JLabel(karte);
        // erzeuge Button 
        JButton button = new JButton("Testbutton");
        button.setSize(100, 100);
        // füge Komponenten hinzu 
        content.add(button);
        content.add(label);
        // erzeugt den Glass Pane + Button 
        final JButton glassButton = new JButton("Glassbutton");
        final MyGlassPane glass = new MyGlassPane();
        setGlassPane(glass);
        glass.add(glassButton);
        glass.setSquare(new MySquare(new Rectangle()));
        glass.setVisible(true);
        // erzeuge Action Listener für den Button 
        glassButton.addActionListener(new ActionListener() {

            public void actionPerformed(final ActionEvent e) {
                glass.setVisible(false);
                content.repaint();
            }
        });
        // Zeigt das Fenster an 
        pack();
        setSize(1024, 768);
        setLocationRelativeTo(null);
    }

    public static void main(final String[] args) {
        SwingUtilities.invokeLater(new Runnable() {

            public void run() {
                new Main().setVisible(true);
            }
        });
    }
}

class MyGlassPane extends JPanel {

    private MySquare square;

    public MyGlassPane() {
        super();
        setOpaque(false);
        addMouseMotionListener(new MouseAdapter() {

            @Override
            public void mouseDragged(final MouseEvent e) {
                square.setSize(e.getX(), e.getY());
            }
        });
        addMouseListener(new MouseAdapter() {

            @Override
            public void mousePressed(final MouseEvent e) {
                square.setLocation(e.getX(), e.getY());
            }
        });
    }
    // überschreibe die PaintComponent 
    @Override
    public void paintComponent(final Graphics g) {
        super.paintComponent(g);
        square.draw(g);
    }

    public void setSquare(final MySquare square) {
        square.setSquareContainer(this);
        this.square = square;
    }
}

class MySquare {

    private Rectangle bounds;
    private JComponent squareContainer;

    public MySquare(final Rectangle bounds) {
        this.bounds = bounds;
    }

    public void draw(final Graphics g) {
        g.setColor(Color.cyan);
        g.drawRect(bounds.x, bounds.y, bounds.width, bounds.height);
    }

    public void setSize(final int x2, final int y2) {
        bounds.width = x2 - bounds.x;
        bounds.height = y2 - bounds.y;
        squareContainer.repaint();
    }

    void setLocation(int x, int y) {
        bounds.x = x;
        bounds.y = y;
        squareContainer.repaint();
    }

    public void setSquareContainer(final JComponent squareContainer) {
        this.squareContainer = squareContainer;
    }

}
```


----------



## guni (8. Apr 2008)

hmm ... danke für deinen Code erstmal.

in der MyGlassPane hab ich einen Fehler beim addMouseMotionListener ...

The method addMouseMotionListener(MouseMotionListener) in the type Component is not applicable for the arguments (new MouseAdapter(){})

woran könnte das liegen?

mfg


----------



## Verjigorm (8. Apr 2008)

MouseListener -> MouseAdapter
MouseMotionListener -> MouseMotionAdapter!


----------



## guni (8. Apr 2008)

HEY! DANKE! Es funktioniert! Absolut cool!
kann ich das jetzt noch so machen, dass die Fläche des Rechtecks halbtransparent ist???


----------



## guni (8. Apr 2008)

... hab das ganze mal so geändert ...

```
public void draw(final Graphics g) { 
        g.setColor(Color.YELLOW);
        g.fillRect(bounds.x, bounds.y, bounds.width, bounds.height);
        g.setColor(Color.ORANGE);
        g.drawRect(bounds.x, bounds.y, bounds.width, bounds.height); 
    }
```
... aber den Rahmen hätt ich ganz gern noch ein bisschen dicker und wie gesagt: Fläche wenns geht halbtransparent ;-)


----------



## André Uhres (8. Apr 2008)

Seit *1.6* implementiert MouseAdapter auch MouseMotionListener.

Transparenz geht mit "*alpha*"-Komponente. Beispiel:
private final Color YELLOW2 = new Color(255, 255, 0, *130*);

Mit *Graphics2D#setStroke* lässt sich die Rahmenbreite ändern:
private final Stroke STROKE2 = new BasicStroke(2);
...
    ((Graphics2D)g).setStroke(STROKE2);


----------



## guni (8. Apr 2008)

jaaaa (y) 
das funktioniert!
is toll so gute unterstützung zu haben weil man dann zwischendurch doch wieder erfolgserlebnisse hat 
ein "bug" ist mir noch aufgefallen.
Nachdem ich ein rechteck gezeichnet habe, scheint es beim nächsten Klick in der Glasspane genau dort auf, wo ich hinklicke (logisch: das hängt mit meinem setLocation zusammen).
das will ich aber nicht. wenn ich irgendwo hinklicke soll das Ding verschwinden (ausser, wenn ich auf den Button klicke ...)
nur: wenn ich die das MousePressedEvent wegnehme, dann wird das Rechteck immer von der Fensterecke links oben (ich nehme mal an 0,0) gezeichnet ...
wie kann ich das umgehen?


----------



## André Uhres (8. Apr 2008)

Ohne das Programm zu verändern, können wir das Rechteck jetzt schon 
mit einem Drag nach oben oder links verschwinden lassen.

Wir können aber auch in Square eine "getBounds()" Methode einbauen:

```
public Rectangle getBounds() {
        return bounds;
    }
```
und dann in mousePressed die Breite abfragen:

```
public void mousePressed(final MouseEvent e) {
                if(square.getBounds().width > 0){
                    square.setSize(0, 0);
                }
                square.setLocation(e.getX(), e.getY());
            }
```


----------



## guni (8. Apr 2008)

hmm ... ok - zweitere Variante gefällt mir irgendwie besser ...
jetzt hab ich schon wieder ein Problem: 

dieser Code

```
ImageIcon karte = new ImageIcon(Main.class.getResource("arab_pennisula.gif"));   
JScrollPane scKarte = new JScrollPane( new JLabel(karte) );
```
funktioniert nicht so wie ich es mir erwarten würde.
Ich hätte gehofft, dass ich dann das Bild (das sich in meinem Fenster nicht ausgeht) scrollen kann ...
aber ich sehe keine Scrollbalken ...

noch eine Frage: wie kann ich das programmieren, dass ich auch dann scrollen kann, wenn der GlassPane aktiv ist? 

mein Ansatz wäre, dass ich nicht auf den GANZEN content Pane einen Glaspane daruftu, sondern nur über das Bild. Am Besten einen, der sich beim Scrollen mitbewegt ...
geht das??

danke, guni


----------



## André Uhres (8. Apr 2008)

In dem Fall würde ich direkt auf das Bild zeichnen:

```
package demo;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class Main extends JFrame {
    public Main() {
        super("Kalibrierungsmodul");
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        final Container content = getContentPane();
        ImageIcon karte = new ImageIcon(Main.class.getResource("arab_pennisula.gif"));
        JButton button = new JButton("Testbutton");
        button.setSize(100, 100);
        JPanel btPanel = new JPanel();
        btPanel.add(button);
        content.add(btPanel, BorderLayout.WEST);
        final Bild bild = new Bild();
        bild.setIcon(karte);
        bild.setPreferredSize(new Dimension(karte.getIconWidth(), karte.getIconHeight()));
        content.add(new JScrollPane(bild));
        bild.setSquare(new MySquare(new Rectangle()));
        bild.setVisible(true);
        pack();
        setSize(1024, 768);
        setLocationRelativeTo(null);
    }
    public static void main(final String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                new Main().setVisible(true);
            }
        });
    }
}
class Bild extends JLabel {
    private MySquare square;
    public Bild() {
        super();
        setOpaque(false);
        addMouseMotionListener(new MouseAdapter() {
            @Override
            public void mouseDragged(final MouseEvent e) {
                square.setSize(e.getX(), e.getY());
            }
        });
        addMouseListener(new MouseAdapter() {
            @Override
            public void mousePressed(final MouseEvent e) {
                if (square.getBounds().width > 0) {
                    square.setSize(0, 0);
                }
                square.setLocation(e.getX(), e.getY());
            }
        });
    }
    @Override
    public void paintComponent(final Graphics g) {
        super.paintComponent(g);
        square.draw(g);
    }
    public void setSquare(final MySquare square) {
        square.setSquareContainer(this);
        this.square = square;
    }
}
class MySquare {
    private Rectangle bounds;
    private JComponent squareContainer;
    private final Color YELLOW2 = new Color(255, 255, 0, 130); 
    private final Stroke STROKE2 = new BasicStroke(2); 
    public MySquare(final Rectangle bounds) {
        this.bounds = bounds;
    }
    public void draw(final Graphics g) {
        g.setColor(YELLOW2);
        g.fillRect(bounds.x, bounds.y, bounds.width, bounds.height);
        ((Graphics2D)g).setStroke(STROKE2);
        g.setColor(Color.cyan);
        g.drawRect(bounds.x, bounds.y, bounds.width, bounds.height);
    }
    public void setSize(final int x2, final int y2) {
        bounds.width = x2 - bounds.x;
        bounds.height = y2 - bounds.y;
        squareContainer.repaint();
    }
    void setLocation(int x, int y) {
        bounds.x = x;
        bounds.y = y;
        squareContainer.repaint();
    }
    public void setSquareContainer(final JComponent squareContainer) {
        this.squareContainer = squareContainer;
    }
    public Rectangle getBounds() {
        return bounds;
    }
}
```


----------



## guni (9. Apr 2008)

Hallo Andre,

danke für deine Antworten ...
leider wird das Ganze ein bisschen umständlich, wenn ich direkt auf das Bild zeichne, weil ich dann einzelne Komponenten später nicht mehr so leicht invisible setzen kann ...
gibts da keine andere Möglichkeit, das Scrollen auf dem Bild trotz Glasspane möglich zu machen???

lg, guni


----------



## André Uhres (9. Apr 2008)

Was ist dein Problem? 
Mit setVisible(false) kannst du leicht einzelne Komponenten invisble setzen. 
Das ist auf jeden Fall weniger umständlich als Glasspaneakrobatie :wink:


----------



## guni (9. Apr 2008)

hmm ... beim GlassPane muss ich eben nur eine Ebene auf invisible setzen und muss nicht mit einer Schleife meine ganzen Objekte durchlaufen ... die Motivation war irgendwie dahingehend ...

wie auch immer - du würdest mir also empfehlen, dass ich die Componenten direkt auf's Bild zeichne ...
d.h. ich würde die Kompnenten (Position und Shape) dann in eine XML-Datei oder sowas speichern und dann bei Bedarf laden, anzeigen / nicht anzeigen ...


wäre das ein performanter Ansatz?!

lg, guni


----------



## André Uhres (9. Apr 2008)

guni hat gesagt.:
			
		

> ..wäre das ein performanter Ansatz?!


Was heisst den "performant" für dich?


----------



## guni (21. Apr 2008)

"performant" ist für mich ein Programm oder Programmteil, der 

möglichst schnell verarbeitet werden kann
möglichst wenig Arbeitsspeicher benötigt
... das schreibt ein Duden vielleicht anders aber so schüttle ich mir die Definition mal aus dem Ärmel ...
natürlich kann man jetzt darüber diskutieren, dass sich Prozessorleistung und Speicherbedarf warscheinlich meistens indirekt proportional zueinander verhalten (wenn das eine zunimmt, nimmt das Andere ab), aber ich denke, dass es trotzdem Lösungen gibt, die sowohl den Prozessor als auch den Speicher stärker auslasten wie andere Lösungen ...
was also wäre deiner Meinung nach der performanteste Ansatz (lt. meiner Definition) um das obenstehende Problem zu realisieren?

lg, guni


----------



## André Uhres (22. Apr 2008)

guni hat gesagt.:
			
		

> was also wäre deiner Meinung nach der performanteste Ansatz (lt. meiner Definition)


Wir könnten versuchen, den Code so anzupassen, daß jeweils nur der relevante Teil neu gezeichnet wird, 
anstatt bei jeder Veränderung die gesamte Komponente neu zu malen. 
Vorher sollte man sich jedoch fragen, ob die Ausgabe so kompliziert werden wird, 
daß sie den zusätzlichen Entwicklungsaufwand rechtfertigt  :wink:

EDIT: Ich seh grad, eigentlich genügt es in setSize den repaint wie folgt zu ersetzen:

```
squareContainer.repaint(bounds);
```


----------

