# Bounds eines gedrehten Objekts berechnen



## Antimon (14. Dez 2008)

Hallo zusammen,

irgendwie habe ich das Gefühl ich suche nach den falschen Begriffen oder es gibt recht wenig zu dem Thema...

Ich setze auf ein JPanel mehrere JComponents, wobei jede der JComponents ein grafisches Symbol darstellt (z.B. Kreis, Rechteck, ...).

Die Symbole möchte ich skalieren, rotieren etc. und verwende dafür eine AffineTransform. Allerdings besteht ja das Problem, dass sich die Bounds beim Drehen ändern, rotiere ich beispielsweise ein Quadrat um 45°, so ist die neue Höhe des Quadrats, bezogen auf das Bildschirmkoordinatensystem, größer.

Wie kann ich die neuen Werte berechnen? Ich brauche sie ja, damit ich die JComponent, auf der ich zeichne, ebenfalls vergrößern kann, sonst werden mir ja die Ecken abgeschnitten...

Oder gibts irgendwie eine intelligentere Art, Symbole zu drehen, skalieren etc.? Ziel ist es, Symbole auf einer Oberfläche platzieren und anpassen zu können um beispielsweise einen Grundriss eines Hauses zeichnen zu können...

Für alle Tips bin ich dankbar!


----------



## Marco13 (14. Dez 2008)

Die Geometrie, die gezeichnet werden soll, ist "in JComponents gespeichert"? Vielleicht liest byto das ja, und erinnert sich dann auch an diesen Thread http://www.java-forum.org/de/viewtopic.php?t=78615&postdays=0&postorder=asc&start=0  :bae: :wink: Jedenfalls wurde dort das Problem, das du beschreibst, schon angesprochen. (Musst jetzt aber nicht den ganzen Thread durchlesen - lohnt sich echt nicht....)

Günstiger wäre es, wenn die Objekte NICHT alle JComponents wären - dann würde man sich das ganze bounds-gewurste sparen (bzw. würde es höchstens einbauen, wenn man irgendwelche fragwürdigen Optimierungen vornehmen wollte). Stattdessen könnten alle Objekte einfach als "Geometrie-Modell" gespeichert und das dann in einem JPanel gemalt werden (ob man dafür nun einen "Renderer" verwendet, der von JComponent abgeleitet ist, oder nicht ... DAS steht im anderen Thread).

Ich rate mal ins blaue: Du hast z.B. ein Rechteck von JComponent abgeleitet. Wenn das Rechteck gedreht dargestellt werden soll, änderst du die AffineTransform des Graphics der Rechtecks-JComponent? Damit ist natürlich erstmal sämtliche Information über die gedrehte Geometrie "weg" (bzw. man kann nicht vernünftig drauf zugreifen). Eine pragmatische Lösung wäre, die "bekannte" Geometrie des Rechtecks (d.h. seine Eckpunkte) mit der gleichen AffineTransform zu transformieren, und aus den Eckpunkten die BoundingBox zu bestimmen. Für allgemeine Objekte könnte man evtl. einen GeneralPath verwenden. Für GANZ allgemeine Objekte (einschlißelich Kreise usw) wird das richtig fummelig....


----------



## Antimon (14. Dez 2008)

Eieieiei okay, dass man da so viel Wind drumrum machen kann, wusste ich nicht 

Ich hab den Thread mal überflogen, die Essenz für mich liegt da bei sagen wir mal 10% 

Vielleicht ists aber ned schlecht, wenn ich mal detaillierter erkläre, was ich machen möchte:

Das Ziel ist eine Software für die Gebäudeautomatisierung. Man hat beispielsweise einen Grundriss eines Geschosses, dort werden verschiedene Symbole eingeblendet, die dann den Schaltzustand von Lampen, Messwerte von Temperatursensoren usw. anzeigen.

Das Ganze soll sich im Bearbeitungsmodus grob verhalten wie Vektorprogramme à la CorelDraw - ich klicke ein Symbol an, habe Haltegriffe zum Skalieren, kann es verschieben oder drehen. Es gibt simple Elemente wie Wände (können ja durch zusammengesetzte Rechtecke dargestellt werden) und kompliziertere wie BarGraph-Anzeigen, die aufwendigere Grafikoperationen benötigen.

Diese Symbole sollen auch mehrere Layer unterstützen, also kann ich beispielsweise ein BarGraph über eine Wand lappen lassen.

Soweit zu meinen Anforderungen - wie du richtig erraten hast, habe ich eine Component genommen, die abgeleitet und die paintComponent()-Methode überschrieben... und das ist also nicht so optimal.

Allerdings stellt sich für mich nun die Frage, wie bekomme ich es dann hin, dass jedes Symbol auf MouseEvents reagieren kann, wenn auf die Grafik geklickt wurde? Und wie zeichne ich beim Verschieben die Position neu - kümmert sich dann das Panel, auf dem alle Grafiken liegen da drum oder wie läuft das ab?

Bei der JComponent ists halt recht einfach, MouseListener hinzufügen, setLocation() bzw. setSize() aufrufen und innerhalb der Komponente zeichnen... das wars schon - aber wenn ich das anders implementieren soll bin ich dann schon überfragt, zumindest fällt mir momentan nicht viel dazu ein (gut, ich habe auch noch nicht so viel mit Java2D gemacht...)

GeneralPath habe ich mir mal angeschaut, nur bekomme ich die Brücke zwischen dem Objekt und der Platzierung bzw. Darstellung auf dem Panel nicht geschlagen...

Prinzipiell hört sich es aber gut an, denn mein Ziel ist es natürlich dass man ein Objekt nur dann markieren kann, wenn man auf eine sichtbare Fläche klickt. Und bei einer um 45° gedrehten Box könnte ich theoretisch auch in die Ecken klicken um es zu markieren, weil ja die Component ja rechteckig is und da die Ecken auch zur Component gehören...


----------



## Marco13 (15. Dez 2008)

Ja, da kann man so viel Wind drum machen, und noch viel mehr. Du willst eine ziemlich komplexe Anwendung mit ziemlich komplexer Funktionalität schreiben - über solche Fragen wie "WAS soll man anklicken können (z.B. bei einem gedrehten Rechteck)?" sollte man sich eigentlich vorher Gedanken machen. 

Also: Du hast irgendwelche Geometrie, die im Zweifelsfall aus einer Menge von Punkten (und Linien) besteht. Um die BoundingBox so einer Geometrie nach der Transformation auszurechnen, musst du alle Punkte transformieren, und danach die BoundingBox der transformierten Punkte berechnen.

Damit ist deine ursprüngliche Frage prinzipiell beantwortet.

Alles weitere (wie die Frage, ob die "Geometrien" denn von Component erben sollten, oder wie man das Picking und die Mausinteraktion handhaben könnte) sind Design- und Architekturfragen, die man (IMHO) nicht in einem kurzen Forenbeitrag klären kann.


----------



## Antimon (15. Dez 2008)

Tja die Frage nach der BoundingBox hat sich für mich gestellt, weil ich die Bounds für die JComponent dementsprechend anpassen müsste.

Aber angenommen, ich würde das ganze ohne JComponent realisieren, stellt sich für mich die Frage, wie dann gezeichnet wird - und wer das Picking etc. übernimmt. Deswegen war ich eigentlich ganz froh, dass sich darum bisher die Component gekümmert hat.

Ich gebs zu, die Software ist schon etwas umfangreicher... am liebsten wärs mir es gäbe ein Framework für die Geschichte, aber da habe ich leider (noch) nichts vernünftiges gefunden.

Aber nochmal zum zeichnen, vielleicht eine dumme Frage: Aber wie wird ohne die JComponent-Lösung gezeichnet? Angenommen jedes Symbol hat die Informationen zu seiner Geometrie gespeichert - fragt dann das Panel, auf dem gezeichnet wird alle Symbole nach Position und Geometrie ab und zeichnet oder wo geschieht das Ganze?


----------



## Marco13 (15. Dez 2008)

Ja, wie gesagt, das sind Architekturfragen .... ein paar spontane Gedanken

```
interface Paintable 
{
    void paintOn(Graphics g);
}

interface Pickable
{
    boolean isHitBy(int x, int y);
}

interface Geometry extends Paintable, Pickable {}

class GeometryPanel extends JPanel
{
    private List<Geometry> geometries = ... // Liste, add/remove-Methoden

    public void paintComponent(Graphics g)
    {
        super.paintComponent(g);
        for (Geometry geometry : geometries)
        {
             geometry.paintOn(g);
        }
    }


    public Geometry getFirstGeometryThaIsHitByMouseClickAt(int x, int y)
    {
        for (Geometry geometry : geometries)
        {
             if (geometry.isHitBy(x,y))
             {
                 return geometry;
             }
        }
        return null;
    }
}
```

Ob da dann noch irgendwo eine Methode "setTransform" reinkommt, WIE genau die "isHitBy"-Methode für die eine oder andere Geometrie implementiert ist, wie man draggen usw. regelt, und wie man die "Schichten" implementiert, muss man sich dann noch überlegen. Ggf. könnte es eine Klasse "HouseModel" geben, das die ganzen Geometrie-Objekte schon in Schichten speichert, und es gibt dann Renderer für die Geometrie, oder sonstwas.... aber ... mal nicht so viel Wind um solche Sachen machen :wink:


----------



## Antimon (15. Dez 2008)

Aaaahhh okay jetzt wird mir einiges klarer... was ich noch nicht verstanden habe war die Weiterleitung der Mouse-Events. So wie ich das sehe, lege ich den MouseListener auf das Panel, auf dem gezeichnet wird, der fängt die Events ab und prüft anhand seiner Liste mit Symbolen ob auf eins von denen geklickt wurde mit einer isHitBy-Methode, die von den Symbolen bereitgestellt wird... okay die Ausdrucksweise war nicht ganz sauber, aber ich denke die Botschaft stimmt.

Draggen, Skalieren, Markieren mehrerer Symbole etc. ist alles bereits implementiert - aber eben auf die JComponents angepasst... ich werd mir das mal auf die vorgeschlagene Art und Weise zu Gemüte führen - da ist dann auf jeden Fall nicht mehr das direkte Problem mit den Bounds vorhanden - ausser es verschiebt jemand das Symbol an den Rand des Panels, aber das bekomme ich dann auch hin zu berechnen...

Danke für deine Mühe!


----------



## Marco13 (15. Dez 2008)

Ja, jetzt aber nicht denken "so geht das" und es stupide runterschreiben - dann werden nämlich früher oder später wieder "un"lösbare Frage auftauchen. Das meiste sollte schon "im Kopf (oder auf Papier) stehen", bevor's ans Implementieren geht.


----------



## byte (15. Dez 2008)

Antimon hat gesagt.:
			
		

> Wie kann ich die neuen Werte berechnen? Ich brauche sie ja, damit ich die JComponent, auf der ich zeichne, ebenfalls vergrößern kann, sonst werden mir ja die Ecken abgeschnitten...


Shape#getBounds() liefert Dir die Bounding-Box nach der Transformation. Damit dann einfach die JComponent resizen.


----------



## tfa (15. Dez 2008)

Also ich fand den ersten Ansatz mit den JComponents nicht schlecht für diesen Anwendungsfall. Gerade, weil du die Komponenten verschieben (vielleicht noch mit DnD), anklicken, skalieren etc. möchtest. Eine Frickellösung mit paintComponent() wird dir recht schnell um die Ohren fliegen, wenn es umfangreicher wird (oder unnötig viel Kopfschmerzen bereiten).
Wenn du nicht gerade zehntausende Komponenten zeichnen willst, sollte es auch keine spürbaren Performancenachteile geben.


----------



## Marco13 (15. Dez 2008)

Ich würd' ja schon gern mal ein ... "richtiges" Programm sehen, wo jemand jeden **** als JComponent eingebaut hat. 


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

class C extends JComponent
{
    private int x0;
    private int y0;
    private int x1;
    private int y1;

    public C(int x0, int y0, int x1, int y1)
    {
        this.x0 = x0;
        this.y0 = y0;
        this.x1 = x1;
        this.y1 = y1;
    }

    public void paintComponent(Graphics g)
    {
        super.paintComponent(g);
        g.fillRect(x0,y0,(x1-x0),10);
        g.fillRect(x0,y0,10,(y1-y0));
        g.fillRect(x0,y1,(x1-x0),10);
    }
}


class NoComponent extends JFrame
{
    C c0;
    C c1;

    public static void main(String args[])
    {
        new NoComponent().setVisible(true);
    }

    public NoComponent()
    {
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        getContentPane().setLayout(null);

        c0 = new C(10,10,100,100);
        c0.setBounds(50,50,110,110);
        getContentPane().add(c0);

        c1 = new C(30,30,80,80);
        c1.setBounds(80,90,90,90);
        getContentPane().add(c1);

        MouseListener mouseListener = new MouseAdapter()
        {
            public void mouseClicked(MouseEvent e)
            {
                // ???
                System.out.println("Here you should print whether c0 or c1 was clicked");
                // ???
            }
        };

        // ???

        setSize(300,300);
    }
}
```

Kommentare dazu, und Vorschläge, was anstelle der ??? einzusetzen ist, damit sowas vernünftig funktioniert, werde ich zur Kenntnis nehmen (nicht notwendigerweise darauf reagieren, aber sie zur Kenntnis nehmen). Vielleicht fehlt mir da einfach nur die Flexibilität im Denken oder so....


----------



## byte (15. Dez 2008)

Und was soll uns dieser Code-Ausschnitt nun sagen? Blicke bei Dir echt nicht mehr durch...  :lol:


----------



## Marco13 (15. Dez 2008)

Man hat zwei Geometrie-Objekte, und will eins anklicken (und dann im Idealfall auch noch rausfinden, welches das ist). Wie soll man das machen, wenn die Objekte Components sind? (Und um das gleich vorneweg zu klären: Es war ja nicht von "Renderern" etc. wie im anderen Thread die Rede, sondern davon, dass die Dinge, die man in der Gegend rum-draggt usw. components sein sollen)


----------



## byte (15. Dez 2008)

Marco13 hat gesagt.:
			
		

> Man hat zwei Geometrie-Objekte, und will eins anklicken (und dann im Idealfall auch noch rausfinden, welches das ist). Wie soll man das machen, wenn die Objekte Components sind?


Du fragst jetzt nicht ernsthaft, wie man einer JComponent einen MouseListener hinzufügt!? :autsch:


----------



## tfa (15. Dez 2008)

Hier mal eine schnell hingefrickelte Inspiration:

```
import java.awt.Graphics;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.Random;

import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLayeredPane;


public class ComponentTest extends JFrame {
	
	static class Square extends JComponent { 
		
	    private int width; 

	    public Square(int width) { 
	        this.width = width; 
	        setSize(width+10, width+10);
	        addMouseListener(new MouseAdapter() {

				@Override
				public void mousePressed(MouseEvent e) {
					if (e.getButton()==MouseEvent.BUTTON1) {
						scaleDouble();					
					}
					else if (e.getButton()==MouseEvent.BUTTON3) {
						scaleHalf();					
					} 
				}});
	    } 

	    public void paintComponent(Graphics g) { 
	        super.paintComponent(g); 
	        g.fillRect(0,0,width,5); 
	        g.fillRect(0,5,5,width); 
	        g.fillRect(5,width,width,5);
	        g.fillRect(width,0,5,width);
	    } 
	    
	    public void scaleDouble() {	    	
	    	setWidth(width*2);
	    }
	    public void scaleHalf() {
	    	setWidth(width/2);
	    }
	    
	    public void setWidth(int w) {
	    	width = w;
	    	setSize(width+5, width+5);
	    }
	}
	
	private JLayeredPane pane;
	
	public ComponentTest() {
		setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		pane = new JLayeredPane();
		getContentPane().add(pane);
		initComponents();
		setSize(800,600);
	}
	
	private void initComponents() {
		Random rnd = new Random();
		for (int i=0; i<10; i++) {
			Square s = new Square(rnd.nextInt(40)+10);
			s.setLocation(rnd.nextInt(700), rnd.nextInt(500));
			pane.add(s, JLayeredPane.DEFAULT_LAYER);
		}		
	}
	
	public static void main(String... args) {
		new ComponentTest().setVisible(true);
	}
}
```

Linksklick auf ein Quadrat: Größe verdoppeln
Rechtsklick auf ein Quadrat: Größe halbieren

Als Übungsaufgabe kannst du ja jetzt jeder Komponente ein Popup-Menü spendieren oder Drag&Drop implementieren.


----------



## Marco13 (15. Dez 2008)

byto hat gesagt.:
			
		

> Du fragst jetzt nicht ernsthaft, wie man einer JComponent einen MouseListener hinzufügt!? :autsch:


Welcher? Dem Haupt-JPanel? Oder c0? Oder c1? Und wenn man objekte entfernt und hinzufügt frickelt man jedes mal einen kleinen MouseListener rein? Und was ist, wenn das Objekt (wie in meinem suggestiven Beispiel) die Form von einem C hat, und man _eigentlich_ ins leere klickt? Was macht man, wenn man etwas malt, wo - im Hinblick auf die ursprüngliche Frage - das gezeichnete nicht durch die Größe der Component bestimmt wird, sondern es umgekehrt ist?
Ich behaupte nicht, dass die Antworten auf alle diese Fragen OHNE die Verwendung von Components trivial wäre. Zumindest hat byto ja im anderen Thread schon zugestimmt, dass man NICHT seine Datenklassen von Component erben lassen sollte, "weil man sie dann so schön leicht zeichnen kann". Aber das letzte Beispiel jetzt...  :?  hm.... aber was schlag' ich mich rum. Jeder kann schließlich programmieren wie und was er will :roll:


----------



## tfa (16. Dez 2008)

Marco13 hat gesagt.:
			
		

> byto hat gesagt.:
> 
> 
> 
> ...


Lass mich raten, du hast projektweit nur einen einzigen Mouselistener/Actionlistener mit jeweils einer 100fachen if-Kaskade zur Bestimmung der Event-Aktion?



> Jeder kann schließlich programmieren wie und was er will :roll:


Glücklicherweise. Ich programmiere am liebsten objektorientiert mit hoher Kapselung und geringer Kopplung. Und da sind Komponenten enorm vorteilhaft. Ich sage nicht, dass man damit jeden Scheiß machen sollte bzw. kann, aber du scheinst das ja generell abzulehnen.


----------



## byte (16. Dez 2008)

@Marco13: Ich will diesen Kleinkrieg jetzt nicht weiter fortführen. Wir haben beide unsere Meinung zu dem Thema, also belassen wir es dabei.

Falls es Dich interessiert, habe ich aber noch einen Tipp bzgl. Custom Painting in Swing für Dich: guck Dir mal die Painting API von SwingX an: http://developerlife.com/tutorials/?p=140
Auf diese Weise kapsele ich meinen Painting Code und separiere ihn von den eigentlichen JComponents (in diesem Fall JXPanels). Das Konzept ist imo ziemlich gut, weil man die Painter wiederverwenden und kombinieren kann.


----------



## SegFault (16. Dez 2008)

ich weiss ja nicht wie abgeschlossen das ursprungsthema ist. Mir ist nur aufgefallen das Es einen Problem sehr ähnelt welches ich vor kurzer Zeit bearbeitet habe. Ich musste auch verschiedene Geometrische Objekte in ein Panel Zeichnen.

Prinzipiell habe ich auch begonnen die Einzelnen Objekte als JComponent zu entwerfen was spätestens beim Skalieren der Objekte oder bei überlappenden Objekten mit den Listenern enorm aufwändig wird da man zuviele Sonderfälle abprüft. 

Daher war der zweite Schritt komplett eigene Klassen welche nicht direkt MouseListener methoden anbieten sondern abgewandelte Versionen z.B. 


```
boolean mouseEvent(MouseEvent e)
```

Das Panel in dem die Objekte gespeichert werden hat den einzig echten MouseListener und ähnliches und dispatched die Events dann an alle Objekte die in den Panel gezeichnet werden. Die Objekte haben ggf solche funktionen wie isMouseOver um grundlegend zu ermitteln ob die Mouse sich in reichweite des Objektes befindet o.ä. wenn das der fall ist wird halt das entsprechende MouseEvent des objekts aufgerufen und das Objekt gibt in dem rückgabewert der Methode mit true zurück das dieses Event wirklich für das objekt war oder mit false das es ggf doch ein anderes Objekt gibt welches für das Event in Frage kommt. 

Dinge die für alle solche Objekte in Frage kommen (z.B. verschieben etc) habe ich mit einer weiteren Klasse gelöst welche das eigentliche Objekt kapselt. Das ist etwas komplizierter zu erklären aber um z.b ein Objekt bewegbar zu machen muss es das Interface IMover mit der funktion moveRealtive(xoffs, yoffs) implementieren. Die moverklasse macht dann nichts weiter als zu ermitteln ob ein Mousedragged ereignis über ihr auftritt und dann das moveRelative des eigentlichen Objekts ausführen. 

Auf jedenfall kam ich somit besser als mit wirklichen Components. Der Preis dafür ist das es bei vielen Objekten langsamer wird weil ich die optimierung mit den ungültig machen von Teilbereichen nicht drinnen habe. Das heißt wenn ich nur ein Objekt bewege müssen trotz allem alle neu Gezeichnet werden.

Ich hoffe die ausführung war ein klein wenig hilfreicher. Wens dazu rückfragen gibt bin ich gerne auch bereit kleine Quelltextauszüge zu bringen.


----------



## Marco13 (16. Dez 2008)

tfa hat gesagt.:
			
		

> Lass mich raten, du hast projektweit nur einen einzigen Mouselistener/Actionlistener mit jeweils einer 100fachen if-Kaskade zur Bestimmung der Event-Aktion?


Natürlich nicht. Dedizierte Listener sind sinnvoll - gerade im Hinblick auf die angesprochene Kapselung und geringe Kopplung. Aber wenn ich eine (sich ggf. ständig verändernde) Menge von 100 (im weitesten Sinne "gleichartigen") Objekten habe, dann werde ich nich 100 Instanzen von Listenern verwalten wollen, sondern EINEN, der "nur nachschaut", welches der 100 Objekte angeklickt wurde...
Wie man bei sowas wie "Square extends Component" aber von einer geringen Kopplung (in diesem Fall wieder mal zwischen Modell und View) reden kann, ist mir nicht klar. Eine stärkere Kopplung als "Vererbung" gibt's ja nicht...



			
				byto hat gesagt.:
			
		

> @Marco13: Ich will diesen Kleinkrieg jetzt nicht weiter fortführen. Wir haben beide unsere Meinung zu dem Thema, also belassen wir es dabei.
> 
> Falls es Dich interessiert, habe ich aber noch einen Tipp bzgl. Custom Painting in Swing für Dich: guck Dir mal die Painting API von SwingX an: http://developerlife.com/tutorials/?p=140
> Auf diese Weise kapsele ich meinen Painting Code und separiere ihn von den eigentlichen JComponents (in diesem Fall JXPanels). Das Konzept ist imo ziemlich gut, weil man die Painter wiederverwenden und kombinieren kann.



Ich habe das nicht als Krieg aufgefasst. Im letzten Thread war es (bis auf einige (einseitige) Flames und ein bißchen Polemik) eher eine Diskussion mit "verhärteten Fronten", die sich letztendlich als Mißverständnis herausgestellt hat. Was auch durch den geposteten Link verdeutlicht wird: Wir haben offenbar GENAU die gleiche Auffassung davon, wie man """so was""" machen sollte. Zitat von der verlinkten Seite


			
				http://developerlife.com/tutorials/?p=140 hat gesagt.:
			
		

> All the Java2D code that you want to use to paint your component, *that would otherwise go in a subclass of a Swing component who’s paint() or paintComponent() method you’ve overridden*, is placed in a class which implements the Painter<T> interface, ...


Ja. Genau so. Weil er _otherwise_ eben in eine Subklasse von Component rein müßte.

Im letzten Thread war es deine Mißverständliche (IMHO zu) kurze Aussage "Lokomotive extends Component" die dazu geführt hat, dass ich (und potentiell der dortige Threadersteller) davon ausgegangen bin, dass dort die "Verdengelung durch Vererbung" propagiert werden sollte, die ja z.B. durch das Konzept der "Painter" im geposteten Link gerade _vermieden_ werden soll: Man hat ein Objekt der Klasse X, das man zeichnen will - das macht man aber NICHT indem man hinschreibt "X extends Component", sondern indem man eine Klasse "XRenderer" erstellt, der das Zeichnen übernimmt. Und DIESE Klasse kann dann meinetwegen von JComponent erben - das ist ja egal, weil es nichts mehr mit X zu tun hat. Wenn ich aber nun sehe dass (dieser Teil speziell @tfa: ) eine Klasse "Square", die einfach nur ein Quadrat repräsentieren soll (ein Quadrat, ein Quadrat und nichts als ein Quadrat) plätzlich von JComponent erbt, dann zeigt mir dass, dass meine Befürchtungen aus dem anderen Thread (vielleicht aus der allgemeinen Foren-Erfahrung heraus) aufs höchste gerechtfertigt waren. Und dazu kann auch aus REIN subjektiver(!) (aber ja letztendlich durch byto und den geposteten Link getützter :wink Sicht nur sagen, dass man das nicht so machen sollte....


----------



## tfa (16. Dez 2008)

Um Himmels Willen! Niemals würde ich Fachklassen von GUI-Klassen ableiten! Dies wurde meiner Meinung im vergangenen Thread genug behandelt. Deswegen schrieb ich "gefrickelte Inspiration" (im Grunde eine abgewandelte Kopie deines Quelltestes im Posting davor). 
Dass man in der Praxis nach MVC trennt habe ich vorausgesetzt. Sonst könnte ich mir ja auch wohl kaum OO auf die Fahne schreiben. Ich hätte die Klasse vielleicht SquareRendere nennen sollen.


----------



## Marco13 (16. Dez 2008)

Antimon hat gesagt.:
			
		

> ...wie du richtig erraten hast, habe ich eine Component genommen, die abgeleitet und die paintComponent()-Methode überschrieben...





			
				tfa hat gesagt.:
			
		

> Also ich fand den ersten Ansatz mit den JComponents nicht schlecht für diesen Anwendungsfall.



 ???:L 

Offenbar bin ich sehr anfällig für Mißverständnisse - zumindest, was dieses Thema angeht.... Im letzten Thread war es nämlich genau das gleiche. Vielleicht überinterpretiere ich auch - oder überbewerte die Gefahr, dass jemand seine Modellklassen vom Component ableitet - aber nochmal: Das kam (hier im Forum) schon _verdammt_ oft vor, und einige Aussagen (die ich eben und vorher schon zitiert hatte) klangen, also "wäre das in Ordnung so". Immer hat sich herausgestellt, dass diejenigen, die diese Aussagen gemacht haben, sie NICHT so gemeint hatten, wie sie sie gesa... äh... wie ich sie verstanden hatte....


----------



## tfa (16. Dez 2008)

Renderer != Fachobjekt (siehe oben!)



			
				Marco13 hat gesagt.:
			
		

> sondern indem man eine Klasse "XRenderer" erstellt, der das Zeichnen übernimmt. Und DIESE Klasse kann dann meinetwegen von JComponent erben - das ist ja egal, weil es nichts mehr mit X zu tun hat.





			
				tfa hat gesagt.:
			
		

> Niemals würde ich Fachklassen von GUI-Klassen ableiten! [...] Ich hätte die Klasse vielleicht SquareRendere nennen sollen.


----------



## Antimon (17. Dez 2008)

Hmm ausgezeichnet, hab mir schon fast gedacht dass es wieder ne heisse Diskussion gibt... dann würd ich mal sagen:

*ding* *ding*, nächste Runde:

Was ist von der Performance her am besten?

Ausgangssituation: Es gibt ein Objekt, das 8 "Haltegriffe" um ein Symbol zeichnet (bekannt aus Programmen wie CorelDraw). Diese Griffe sollen es ermöglichen, das Symbol zu skalieren.

Jetzt geht es darum, dass der Mauszeiger sein Aussehen entsprechend verändert, wenn ich über diese Haltegriffe fahre, beispielsweise wird beim rechten oberen ein Mauszeiger angezeigt mit einem Pfeil nach schräg rechts oben und schräg links unten (also ich skaliere das rechte obere Eck).

Das Panel auf dem gezeichnet wird, empfängt Mausbewegungen und leitet die an das "Markierungs-Objekt", das die Haltegriffe anzeigt, weiter.
Dieses muss nun zuerst rausfinden, welcher Haltegriff eventuell erreicht wurde und dann entsprechend den Mauszeiger wechseln. Wenn nun der Haltegriff aber vom Mauszeiger verlassen wird, soll wieder der Standard Mauszeiger verwendet werden.

Die erste Frage ist nun: Erzeuge ich das entsprechende Cursor-Objekt sobald ich weiss welcher Haltegriff erreicht wurde? Oder ist es besser ich erzeuge bereits im Konstruktor vom Markierungs-Objekt bereits die entsprechenden Objekte und speicher sie einzeln ab, um sie lediglich austauschen zu müssen? Ich tendiere zu letzterem, dürfte performanter sein - was meint Ihr?

Die nächste Frage wäre die: Ich brauche ja die Mauszeigerkoordinaten - die bekomme ich ja bekanntlicherweise mit e.getPoint(), wenn e mein MouseEvent ist.

Was ist nun besser, wenn ich in der Methode mouseMoved(MouseEvent e) mit den Koordinaten einiges anstellen möchte?

a) Bei jeder Berechnung arbeite ich direkt mit e.getPoint(), also beispielsweise sowas wie:


```
if (e.getPoint().x > x && e.getPoint.y > y && e.getPoint().x < (x + width) && e.getPoint().y < (y + height)) { ...
```

b) Ich erzeuge ein eigenes Objekt für den Punkt, mit dem ich rechne, dadurch spare ich mir den Aufruf der getPoint()-Methode:


```
Point p = e.getPoint();
if (p.x > x && p.y > y && p.x < (x + width) && p.y < (y + height)) { ...
```

c) Ich erzeuge primitive Variablen, mit denen ich rechne:


```
int pos_x = e.getPoint().x;
int pos_y = e.getPoint().y;
if (pos_x > x && pos_y > y && pos_x < (x + width) && pos_y < (y + height)) { ...
```


Oder ist das Ganze total egal und der Performanceunterschied ist überhaupt nicht spürbar??

Jetzt bin ich gespannt auf Eure Meinungen und Antworten...


Übrigens probiere ich gerade die Umsetzung der Geschichte ohne Components - mal sehen was es bringt...


Ach genau noch ein Nachtrag:

Angenommen es gibt eine Methode getLocationOnScreen() sowie eine getSizeOnScreen(). Nun möchte ich eine weitere Methode getBounds() haben, die könnte ich auf zweierlei Arten implementieren:

a) 

```
public Rectangle getBounds() {
   return new Rectangle(getLocationOnScreen().x, getLocationOnScreen().y, getSizeOnScreen().width, getSizeOnScreen().height);
}
```

oder 
b)

```
public Rectangle getBounds() {
   Point p = getLocationOnScreen();
   Dimension d = getSizeOnScreen();
   return new Rectangle(p.x, p.y, d.width, d.height);
}
```

Was ist besser von der Performance? Gut klar, es hängt natürlich davon ab, wie groß die Berechnungsroutine ist, aber angenommen das sind 5-6 Zeilen Berechnungen mit Multiplikationen, Additionen, etc.
Gibt es einen großen Unterschied ob jetzt die Berechnung 2x (für jede Achse einmal) durchgeführt wird, wie in Fall a)
oder ob die Berechnung nur einmal durchgeführt wird, aber dafür ein neues Objekt erzeugt werden muss?

Kann man da eine pauschale Aussage treffen? Also wenn die Berechnung wahnsinnig aufwändig wäre, wäre es klar, das Zwischenergebnis zu speichern - aber bei nur wenig Berechngungsaufwand?


----------



## Marco13 (17. Dez 2008)

Von der Performance her ist das i.a. egal, sofern nicht EXTREM oft so getPoint usw. gemacht wird. Was aber wichtig ist, ist die Übersichtlichkeit. Ich bevorzuge in beiden Fällen die Variante b) : Die ist übersichtlich (und nebenbei auch noch performant).


----------



## SegFault (18. Dez 2008)

Also mal zu den MouseCursor sofern du nicht vorhast die für jedes Objekt welches du vergrößern kannst anders zu gestalten wäre es sinnvoll die sogar statisch zu laden. 

das mit den getPoint() kannst du wie Marco schon angemerkt machen wie du willst. Java Arbeitet eh im grunde fast nur mit referenzen also dürfte es sich auf die Geschwindigkeit nur minimal IMHO sogar gar nicht auswirken

Ich mach mal meine Resizer Klasse mit hinten ran ggf bringts ja ein paar anregungen hatte ja vor kurzen ein ähnliches Problem zu lösen. Vielleicht nützt ja der Code was. Der Weisheit letzter schluss ist er nicht weil einiges nicht 
ganz sauber ist (Vor allem das herausfinden in welche richtung ein Objekt vergrößert werden muss anhand des Mousezeigers) Aber er funktioniert und tut seine Pflicht. 


```
/**
 * Klasse welche es ermöglicht GPNPResizeable Objekte mit der Mouse zu 
 * vergrößern
 * @author Heiko Weiß
 */
public class GPNDCResizer 
{
    /**
     * enum welches die möglichkeiten der Vergrößerung aufzählt
     */
    public enum resizeCoords
    {
        upper,upperleft,left,lowerleft,lower,lowerright,right,upperright
    };
    
    /**
     * wird auf true gesetzt wenn ein andere mousecursor als der standard angezeigt wird
     */
    private boolean mouseCursorShown = false;
    
    /**
     * wird auf true gesetzt wenn eine vergrößerungsaktion begonnen hat
     * (Drag & Drop im randbereich des Objektes)
     */
    private boolean resizeActionRunning = false;
    
    /**
     * Punkte an denen eine Vergrößerung möglich ist
     */
    private Rectangle[] resizePoints = new Rectangle[8];
    
    
    GPNDResizeableObject interior;
    Component parent;
    
    /**
     * grenze ab wann sich der Vergrößerungscursor wieder in einen normalen Cursor verwandelt
     * diese ist gerechnet wiviele Pixel vom Rand eines objektes sich der mousecursor wieder in 
     * den standardcursor verwandelt
     */
    private static int cursorBorder = 10;
    
    /**
     * grenze der Vergrößerung, wie klein sich maximal ein Objekt verkleinern lässt
     */
    private static int minSize = 10;
    
    /**
     * liste mit resize cursorn damit diese nicht immer neu geladen werden brauchen
     */
    private static Cursor[] cursors = new Cursor[9];
    
    static
    {
        cursors[ resizeCoords.upper.ordinal() ] = new Cursor(Cursor.N_RESIZE_CURSOR);
        cursors[ resizeCoords.upperleft.ordinal() ] = new Cursor(Cursor.NW_RESIZE_CURSOR);
        cursors[ resizeCoords.left.ordinal() ] = new Cursor(Cursor.W_RESIZE_CURSOR);
        cursors[ resizeCoords.lowerleft.ordinal() ] = new Cursor(Cursor.SW_RESIZE_CURSOR);
        cursors[ resizeCoords.lower.ordinal() ] = new Cursor(Cursor.S_RESIZE_CURSOR);
        cursors[ resizeCoords.lowerright.ordinal() ] = new Cursor(Cursor.SE_RESIZE_CURSOR);
        cursors[ resizeCoords.right.ordinal() ] = new Cursor(Cursor.E_RESIZE_CURSOR);
        cursors[ resizeCoords.upperright.ordinal() ] = new Cursor(Cursor.NE_RESIZE_CURSOR);
        cursors[8] = new Cursor(Cursor.DEFAULT_CURSOR);
    }
    
    /**
     * liste mit listenern welche informiert werden falls ein objekt vergrößert wird
     */
    private static Vector<IResizeListener> resizeListeners = new Vector<IResizeListener>();
    
    /**
     * fügt einen listener hinzu welcher benachrichtigt wird falls ein objekt vergrößert wird
     * @param l der neue listener
     */
    public static void addResizeListener(IResizeListener l)
    {
        resizeListeners.add(l);
        
    }
    
    /**
     * liefert das innen liegende Objekt welches sich vergrößern soll zurück
     * @return das Objekt welches vergrößert werden soll
     */
    public GPNDResizeableObject getObject()
    {
        return this.interior;
    }
    
    /**
     * löscht einen Listener 
     * @param l
     */
    public static void removeResizeListener(IResizeListener l)
    {
        resizeListeners.remove(l);
    }
    
    /**
     * constructor
     * @param interior das Objekt welches vergrößert werden soll
     * @param parent die Componente auf der sich das zu vergrößernde objekt befindet
     */
    public GPNDCResizer(GPNDResizeableObject interior, Component parent)
    {
        
        this.parent = parent;
        this.interior = interior;
        for (int i =0 ; i < 8; i++ )resizePoints[i] = new Rectangle();
        recalcResizePoints();
    }
    
    
    /**
     * berechnet die position der vergrößerungspunkt neu so das dieße
     * sich immer in den ecken und der mitte des Objektes befinden
     */
    private void recalcResizePoints()
    {
        int w = interior.getWidth();
        int h = interior.getHeight();
        int x = interior.getX();
        int y = interior.getY();
                
        resizePoints[ resizeCoords.upperleft.ordinal() ].setBounds(x, y, 5, 5);
        resizePoints[ resizeCoords.upper.ordinal() ].setBounds(x+(w/2)-2,y,5,5);
        resizePoints[ resizeCoords.upperright.ordinal() ].setBounds(x+w-5,y,5,5);
        resizePoints[ resizeCoords.right.ordinal() ].setBounds(x+w-5,y+(h/2)-2,5,5);
        resizePoints[ resizeCoords.lowerright.ordinal() ].setBounds(x+w-5,y+h-5,5,5);
        resizePoints[ resizeCoords.lower.ordinal() ].setBounds(x+w/2-2,y+h-5,5,5);
        resizePoints[ resizeCoords.lowerleft.ordinal() ].setBounds(x+0,y+h-5,5,5);
        resizePoints[ resizeCoords.left.ordinal() ].setBounds(x+0,y+(h/2)-2,5,5);
        
    }
    
    /**
     * paint Methode welche den rahmen und die vergrößerungspunkte um das innenliegende objekt malt
     * @param g graphics schnittstelle
     */
    public void draw(Graphics g)
    {
        g.setColor(Color.BLACK);
        //neue position der resizepoints berechnen
        recalcResizePoints();
        if ( interior.isSelected() )
        {
            g.drawRect(interior.getX(), interior.getY(), interior.getWidth(), interior.getHeight());
            for ( int i = 0; i < 8; ++i)
            {
                g.fillRect(resizePoints[i].x,resizePoints[i].y,resizePoints[i].width,resizePoints[i].height);
            }
        }
    }
    
    /**
     * maushandler
     * @param e das eingetroffene Mouseevent
     * @return true wenn e von den handler entgegen genommen wurde ansonsten false
     */
    public boolean mousePressed(MouseEvent e) 
    {
        //Prüfen ob ggf eine ResizeCursor angezeigt wird und damit eine vergrößerungsaktion starten
        if ( mouseCursorShown )
        {
            resizeActionRunning = true;
            return true;
            
        }
        return false;
    }
    
    /**
     * maushandler
     * @param e das eingetroffene Mouseevent
     * @return true wenn e von den handler entgegen genommen wurde ansonsten false
     */
    public boolean mouseReleased(MouseEvent e) 
    {
        //Prüfen ob ein ResizeCursor angezeigt wird und eine laufende vergrößerungsaktion beenden
        if ( mouseCursorShown )
        {
            resizeActionRunning = false;
            return true;
        }
        return false;
    }
    
    /**
     * maushandler
     * @param e das eingetroffene Mouseevent
     * @return true wenn e von den handler entgegen genommen wurde ansonsten false
     */
    public boolean mouseDragged(MouseEvent e) 
    {
        if ( resizeActionRunning )
        {
            //über den Cursortyp prüfen in welche richtung ggf vergrößert wird und eine vergrößerung ausführen
            Cursor c = parent.getCursor();
            switch ( c.getType() )
            {
                case Cursor.NW_RESIZE_CURSOR:
                    interior.resizeUpperLeft( e.getPoint());
                    for ( IResizeListener rl : resizeListeners)
                    {
                        rl.resizeObject(interior);
                    }
                    return true;
                case Cursor.N_RESIZE_CURSOR:
                    interior.resizeUpper( e.getPoint());
                    for ( IResizeListener rl : resizeListeners)
                    {
                        rl.resizeObject(interior);
                    }                
                    return true;
                case Cursor.NE_RESIZE_CURSOR:
                    interior.resizeUpperRight( e.getPoint());
                    for ( IResizeListener rl : resizeListeners)
                    {
                        rl.resizeObject(interior);
                    }                
                    return true;                 
                case Cursor.E_RESIZE_CURSOR:
                    interior.resizeRight( e.getPoint());
                    for ( IResizeListener rl : resizeListeners)
                    {
                        rl.resizeObject(interior);
                    }                
                    return true;
                case Cursor.SE_RESIZE_CURSOR:
                    interior.resizeLowerRight( e.getPoint());
                    for ( IResizeListener rl : resizeListeners)
                    {
                        rl.resizeObject(interior);
                    }                
                    return true;
                case Cursor.S_RESIZE_CURSOR:
                    interior.resizeLower( e.getPoint());
                    for ( IResizeListener rl : resizeListeners)
                    {
                        rl.resizeObject(interior);
                    }                
                    return true;   
                case Cursor.SW_RESIZE_CURSOR:
                    interior.resizeLowerLeft( e.getPoint());
                    for ( IResizeListener rl : resizeListeners)
                    {
                        rl.resizeObject(interior);
                    }                
                    return true;
                case Cursor.W_RESIZE_CURSOR:
                    interior.resizeLeft( e.getPoint());
                    for ( IResizeListener rl : resizeListeners)
                    {
                        rl.resizeObject(interior);
                    }                
                    return true;
               
            }
            
        }
        return false;
    }
    
    /**
     * maushandler
     * @param e das eingetroffene Mouseevent
     * @return true wenn e von den handler entgegen genommen wurde ansonsten false
     */
    public boolean mouseMoved(MouseEvent e) 
    {
        //Prüfen ob gerade keine vergrößerungsaktion läuft
        if ( !resizeActionRunning && interior.isSelected() )
        {
            //Prüfen ob wir innerhalb eines vergrößerungspunktes sind
            for ( int i = 0; i < 8; ++i)
            {
                if ( resizePoints[i].contains(e.getPoint()))
                {
                    //Wenn innerhalb eines Vergrößerungspunktes einen entsprechenden Cursor anzeigen
                    parent.setCursor(cursors[i]);
                    mouseCursorShown = true;
                    break;
                }
            }
            //Wenn ein geänderter Mausecursor angezeigt wird prüfen ob wir die entsprechenden grenzen verlassen
            //und wieder auf einen standardcursor umgestellt wird
            if ( mouseCursorShown )
            {
                Rectangle r = new Rectangle(interior.getX()+cursorBorder,interior.getY()+cursorBorder,interior.getWidth()-(2*cursorBorder),interior.getHeight()-(2*cursorBorder));
                if ( r.contains(e.getPoint()))
                {
                    parent.setCursor(cursors[8]);
                    mouseCursorShown = false;
                }
                r = new Rectangle(interior.getX()-cursorBorder,interior.getY()-cursorBorder,interior.getWidth()+(2*cursorBorder),interior.getHeight()+(2*cursorBorder));
                if ( !r.contains(e.getPoint()))
                {
                    parent.setCursor(cursors[8]);
                    mouseCursorShown = false;
                }
            }
            return true;
        }
        return false;
    }
    
    

}
```


----------



## Marco13 (18. Dez 2008)

Ach ja, noch zum Cursor selbst: Mit http://java.sun.com/j2se/1.4.2/docs/api/java/awt/Cursor.html#getPredefinedCursor(int) fällt die Frage nach der Erstellung der Cursor-Objektes vermutlich auch weg...


----------



## Antimon (3. Jan 2009)

So nach längerer Zeit Abwesenheit (Weihnachts-Stress, Krankheit, ...) melde ich mich mal wieder.

Ich habe mittlerweile das Programm so umgestrickt, dass keine Components zum Panel hinzugefügt werden, sondern eigene Items - schlanke Objekte, die in einer paintItem()-Methode das Graphics-Objekt erhalten und dann ihr Symbol an die entsprechende Stelle zeichnen (die Informationen dazu gibts aus dem ItemModel).

Verschieben und so funktioniert auch, allerdings bin ich nicht glücklich damit - ehrlich gesagt hat sich von der Performance her nix verbessert. Wenn ich ein Objekt verschiebe, verhält sich das wie die Mausspur, die man beim Windows einschalten kann - das Objekt hinkt den Mausbewegungen deutlich hinterher.

Vielleicht ist das Ganze ja ein Verständnisproblem... denn wie ich es verstanden habe, muss ich die repaint()-Methode vom Panel, auf das alles gezeichnet wird, aufrufen, damit die Änderungen dargestellt werden. Allerdings wird die Methode ja nicht unbedingt sofort aufgerufen, sondern wenn die VM "es für richtig erachtet", oder? Kann das für die Verzögerung verantwortlich sein?

Das nächste wäre das Neuzeichnen generell - normalerweise soll man ja nicht das ganze Panel neu zeichnen lassen, sondern nur die Bereiche, die sich geändert haben. Was für mich beim Verschieben bedeuten würde, ich zeichne die alte Position des Objekts neu als auch die neue - der Rest des Panels hat sich ja nicht verändert...

Nur wenn ich die repaint()-Methode für diese Stellen aufrufe - woher soll dann bekannt sein was an den Stellen neu gezeichnet wird? Denn die Items zeichnen sich ja quasi "selbst"...

Irgendwie bin ich aus dem Paint-Howto bei Sun noch nicht wirklich schlau geworden, vielleicht habt Ihr ja ein paar Infos für mich, die das verständlicher werden lassen?


Ach ja und nochwas: Zu Debug-Zwecken lasse ich mir einige Ausgaben per System.out.println() auf die Konsole ausgeben - kann es sein dass das an den Performance-Einbrüchen mit schuld ist? Ich habe mir auch schon nen kleinen Timer gebaut, mit dem ich die Zeit für bestimmte Code-Fragmente stoppen, aber so wirklich geholfen hat mir das bis jetzt auch nicht, es laggt nach wie vor... :-/


----------



## SegFault (5. Jan 2009)

Nach dem was du beschreibst wirds etwas komplexer. 
Da musst du AFAIK eine andere Paint methode überladen und dich komplett selber um das zeichnen kümmern. 
müsste repaint sein. Dort wird ein bereich übergeben der neu gezeichnet werden soll. 
Du musst also selbst auswerten welche komponenten sich dort drinnen befinden und nur diese neu zeichnen (vorher diesen bereich natürlich mit der hintergrundfarbe neu füllen) dies ist insofern komplex weil es ja sein kann das du nur einen kleine überlappung neu zeichnest aber dadurch andere größere Objekte ausserhalb der Redraw Area trotzdem neu zeichnen musst. Sofern du viele objekte neu zeichnest sollte aber der rechenaufwand um heraus zu finden was neu zu zeichnen ist weitaus weniger sein als das neuzeichnen der ganzen Objekte. 

Benutzt du eigentlich dubble buffering um das ganze neu zu zeichnen? Bei mir hats auch ein klein wenig gehakt (ca 100-200 verschiedene Objekte) wenn ich Objekte verschoben hab. Aber das fand ich jetzt weniger schlimm. Kommt halt auch auf die Maschine an auf der das Proggie letztendlich läuft.


----------



## Marco13 (5. Jan 2009)

Bist du sicher, dass es am Zeichnen selbst liegt? Wird (bei Mausbewegungen bzw. beim Verschieben) noch irgendwas gemacht, was potentiell aufwändig ist? VIELE System.out.printlns können schon Zeit kosten... ist so aus der Ferne schwer zu sagen...


----------



## Antimon (6. Mrz 2009)

Soderlü - da bin ich wieder... hab jetzt einiges am Programm umgeschrieben und versucht, es zu optimieren, dabei bin ich allerdings auf ein seltsames Phänomen gestoßen:

Ich versuche, nach dem Verschieben eines Objekts nur dessen alte und neue Position neu zu zeichnen. Laut dem Painting-Tutorial von Sun ist Swing sogar so intelligent, dass es zwei kurz aufeinander folgende repaints zu einer Aktion zusammenfasst. Allerdings wird mir Swing zu intelligent... es zeichnet nämlich viel zu viel neu.

Testhalber habe ich nur ein Testrechteck mit festen Koordinaten neu zeichnen lassen (repaint(50, 10, 5, 5)), wenn ich in der paint()-Methode mir aber das Clipping mit getClipBounds() ausgeben lasse, wird da sowas draus: 
java.awt.Rectangle[x=-86,y=-101,width=395,height=545]

Nicht nur dass da negative Koordinaten bei rauskommen, das Ganze ist für mich auch absolut nicht nachvollziehbar. Denn wenn ich der repaint-Methode andere Koordinaten übergebe, kommt doch wieder der gleiche Clipping-Bereich dabei raus.

Hat jemand eine Idee wie das zustandekommen könnte bzw. wie ich der Ursache auf die Schliche kommen könnte? Denn erstens zeichnet er mir natürlich keine Objekte ausserhalb des Bereiches beim Verschieben neu, zweitens ist die Performance durch die große Fläche nicht wirklich berauschend...


----------

