# Komponenten auf Zeichenfläche gruppieren u. verschieben



## exhuminator (31. Okt 2007)

Hallo, 
ich steh vor einer zumindest für mich recht komplexen Aufgabe. Ich habe für ein Projekt eine Art einfacher Foto-und Texteditor Komponente entwickelt und brauche nun die Funktionalität wie man sie aus vielen kommerziellen Programmen kennt:

Auf einer Fläche (JLayeredPane) befinden sich verschiende Komponenten die einzeln mit verschiednenen Listenern schon "draggable" sind. Nun soll es die Möglichkeit geben, um diese Komponenten herum ein Rechteck aufzuziehen und diese dann gemeinsam innerhalb der Fläche verschoben werden. 


Ich habe überhaupt keinen Ansatz wie man sowas macht, kann mir jemand helfen wie ich da vorgehen soll ?


----------



## wayne (31. Okt 2007)

hab sowas schon mal gemacht. damals ging es darum dem benutzer einen maskeneditor zur verfügung zu stellen, über den er sich seine eingabemaske selbst zusammeklicken kann. die zur verfügung stehenden Components waren damals in einer datenbank bereits vordefiniert. einzige ausnahme war das JLabel, welches der benutzer frei und so oft er wollte platzieren konnte.

der editor, den ich geschrieben hatte, war so designed, daß die Components selektiert werden konnten, was bedeutet, daß sie ein gestricheltes rechteck außen herum gezeichnet bekamen plus ausgefüllte kleine rechtecke an jeder ecke des rahmens und an den mittelpunkten der rahmenlinien. selektieren konnte man mit der maus, ggf. mit gedrückter STRG-taste und mittels aufziehen eines rechtecks durch gedrückte maustaste. ausserdem konnte man die Components editieren, sprich, es gab ein popup-menü mit unter anderem einem punkt "eigenschaften". dieser öffnete einen dialog in dem die gemeinsamen eigenschaften aller selektierter Components zusammengefasst wurden. sofern diese dann den gleichen wert hatten, wurde dieser angezeigt, ansonsten gab es eine anzeige eines zwischenzustands (z.b. eine JCheckBox-erweiterung, die als wert nicht nur true und false zulässt, sondern auch beides, also beispielsweise ein graues rechteck über die deseletierte JCheckBox zeichnet.

das ganze war bei mir allerdings kein JLayeredPane sondern einfach nur ein JPanel, was aber aufs gleich rausläuft. zunächst mal hab ich meine zeichenoberfläche von JPanel, also bei dir von JLayeredPane erben lassen und überschrieb die paint(Graphics)-methode. darin rief ich zunächst super.paint(Graphics) auf, damit die oberfläche mitsamt aller Components gezeichnet wird. anschließend führte ich dann noch die eigenen zeichenoperationen aus, wie beispielsweise alle selektierten Components zu umrahmen. dafür habe ich mir ein model gemacht, in welchem ich mir die selektionszustände der Components gemerkt hatte. wird eine neue Component hinzugefügt, so fügte ich sie der oberfläche und dem model hinzu und rief anschließend repaint() von der oberfläche auf. um den rahmen für gruppenselektionen zu zeichnen hatte ich das model um ein Rectangle erweitert. ist dieses null, so gibt es gerade keinen rahmen. ein auf die oberfläche eingetragener MouseListener und MouseMotionListener steuerte diese zeichenoperation. sprich, wenn die maustaste gedrückt wird, überprüft er, ob das über einer Component passiert. ist diese nicht selektiert, so setzt er den zustand im model für diese Component auf seletiert bzw. umgekehrt. wird die maustaste über einer freien oberfläche gedrückt, erzeugt mir der controller ein neues Rectangle und gibt dieses dem model (und ruft anschließend repaint() auf). solange nun die maus nur bewegt wird, verändert sich die größe dieses Rectangles entsprechend mit. soweit ich mich erinnere, kann ein Rectangle auch negative breite und höhe haben, ohne zu murren, nur für den fall, daß der user mit der maus unsinnige dinge tut. die klasse Rectangle bietet mit ihren methoden contains(...) und intersects(...) alles um zu überprüfen ob ein Component auf der oberfläche mit seinen Bounds selektiert werden muss oder nicht. wenn nun ein rechtsklick erfolg kam wie gesagt ein popup-menü mit den punkten "ausschneiden", "kopieren", "löschen", "einfügen", "eigenschaften". weiß nicht mehr genau, wie ich das gelöst hatte. kann sein, daß ich dafür ein extra model geschrieben hab. aber da du eh drag 'n' drop schon realisiert hast, sollte das nicht das problem sein, denn du kannst das mit dem kopieren und einfügen ja auch über die zwischenablage von windows realisieren. wenn die eigenschaften aufgerufen wurden, überprüfte ich in meinem model, welche Components gerade selektiert sind und ließ mir für diese eine anzeige generieren. die anzeige war ein JDialog, auf welchem für die verschiedenen eigenschaften verschiedene zeilen eingefügt wurden. dazu hatte ich mir extra zurechtgelegt, welche eigenschaften eigentlich editierbar sein sollen und wie diese anzuzeigen sind (und dann, glaub ich, für jede eigenschaft eine anzeige geschrieben). soweit ich mich erinnere waren die sinnvollerweise editierbaren eigenschaften schriftart, -größe, -farbe, hintergrundfarbe und rahmen (eigenschaftsanzeige kann hier eine JComboBox sein, die einen eigenen ListCellRenderer erhält, welcher einfach nur JLabel mit ein paar leerzeichen füllt und eine entsprechende Border außenherum legt). nachdem ich die zu editierenden Components also ermittelt hatte, überprüfte ich, welcher klasse diese angehören und von dem her, welche eigenschaften sie besitzten und auf welche werte diese eigenschaften gesetzt sind. dazu machte ich mir eine liste aller möglichen eigenschaften und eine ebensogroße liste der werte. wenn ein JLabel selektiert war, gab es beispielsweise noch die eigenschaft ob der hintergrund sichtbar oder unsichtbar sein soll. entsprechend wurde dann auch die eigenschaft für die hintergrundfarbe ausgeblendet. ausserdem haben in meinem editor JLabel keinen rahmen gehabt. befand sich in der selektion ein JLabel so wurde also die eigenschaft rahmen in der zweiten liste mit dem wert "invisible" versehen. waren hingegen nur JTextField, JButton, JComboBox und JCheckBox selektiert, wurde diese eigenschaft unter den selektierten Components verglichen. hatten alle selektieren Components den selben rahmentyp, wurde dieser entsprechend in der zweiten liste vermerkt, ansonsten wurde dort "unknown" reingeschrieben. genau so wurde mit den anderen eigenschaften verfahren. anschließend wurden beide listen an den konstruktor des JDialog übergeben (an den kontruktor meiner klasse, welche von JDialog erbte), die dann daraus die anzuzeigenden eigenschaftsanzeigen mitsamt deren labels erzeugte.

ich hoffe, ich konnte dir etwas weiterhelfen und dich auf den rechten weg zu einer vernünftigen lösung führen :wink:

viel erfolg

wayne


----------



## wayne (31. Okt 2007)

oh ja, grad hab ich's nochmal gelesen. du hast ja auch nach dem verschieben gefragt. dabei ist mir eingefallen, daß ich was falsch beschrieben hab. wenn du in meinem editor auf eine Component draufgeklickt hast, ist sie selektiert worden, umgekehrt aber nicht. um sie zu deselektieren musste man einfach eine andere Component selektieren ohne dabei STRG zu halten oder sie mit gedrückter STRG-taste anklicken. ansonsten habe ich mittels setCursor(...) einen move-cursor für diese Component definiert, damit, wenn die maus über der Component ist, der mauszeiger anzeigt, daß man die Component mittels anklicken und gedrückhalten der maustaste verschieben kann. meine oberfläche selbst war auch als MouseMotionListener eingetragen, damit sie den mauscuror entsprechend ändern kann, wenn man in die nähe eines selektionsrahmens kommt. kam man in die nähe einer ecke, so wurde der mauscursor (aufruf per obefläche.setCursor(...)) zu einem NW-SO oder NO-SW doppelpfeil, ansonsten über den rahmenlinien zu einem doppelkreuz für's verschieben, und in der nähe der mittelpunkte der rahmenlinien zu einem N-S bzw. O-W doppelpfeil (mit den buchstaben NWSO meine ich die himmelsrichtungen). wurde nun die maustaste zum verschieben gedrückt, verschob ich alle selektierten Components um die änderung der mausposition (also beim drücken mausposition global merken und diese variable bei jedem mousemove mit neuen werten überschreiben) mittels aComponent.setLocation(...). wenn hingegen die Components vergrößert oder verkleinert werden sollten, änderte ich die ganzen Bounds der Components (da ja eine veränderung der größe an der linken und/oder oberen kante sich nicht mittels setSize(...) bewerkstelligen lässt).

ach ja, grad fällt mir ein, ausserdem gabs noch in meinem popup-menü die funktionen "horizontal-" bzw. "vertikal ausrichten". damit war gemeint, daß unter den selektierten Components ermittelt wird, welches die höchste und welche die tiefste ist (bei vertikaler ausrichtung) und alle dazwischen liegenden Components entsprechend gleichmäßig dazwischen verteilt wurden (also eine änderung von setLocation(...), in vertikaler richtung aber nur die Y-achse ändern, in horizontaler richtung nur die X-achse).

ausserdem gabs noch sowas wie ein "grid". das war eine unterteilung der oberfläche in ein raster, wodurch bewegungen der Components nur noch in rasterschritten möglich war. dieses raster konnte man ein und ausblenden. gezeichnet wurde es direkt nach dem aufruf von super.paint(...) in der überschriebenen paint(...) methode der oberfläche. realisiert habe ich dies, indem ich bei alle bewegungen, also move und resize, überprüft habe, ob das raster sichtbar oder nicht-sichtbar ist (also ein- oder ausgeschaltet). wenn ja, dann wurde erst eine änderung durchgeführt, wenn die entfernung in x- oder in y-richtung, welche mit der maus zurückgelegt wurde, größer war, als die hälfte meiner rastergröße. dann wurde der wert der entsprechenden achse um die rastergröße erhöht oder erniedrigt. ausserdem brachte das dann noch den menü-punkt in mein popup-menü "am raster ausrichten", wodurch eine frei platzierte Component an den nächsten rasterlinien positioniert und auch entsprechend vergrößert bzw. verkleinert wurde. bei letzterem musst du überigens immer (auch wenn's nicht ums raster geht) aufpassen, daß Components eine minimale größe haben. damit meine ich jetzt nicht aComponent.getMinimumSize() sondern eine von dir festgelegte größe, die sich am inhalt der Component, also dem text eine JLabels oder dem minimalen text eines JTextFields, der schriftart und -größe und dem rahmen errechnet. dafür solltest du dir hilfsweise eine methode schreiben auf die du jederzeit zurückgreifen kannst, denn gerade beim resize musst du dies ständig prüfen.

viel erfolg

wayne


----------



## exhuminator (1. Nov 2007)

uff, danke dir erstmal das du dir die Mühe gemacht hast mit soviel Text. Werds mir mal durchlesen. Hab aber beim Überfliegen schon einige Parallelen erkennen können. 

nochmal vielen Dank für deine Zeit.


----------

