PDA

View Full Version : [JAVA]Drag & Drop di oggetti fra finestre


Nurring
25-01-2007, 15:56
Dunque...sono due giorni che ci sto sbattendo la capoccia :muro: per fare una cosa che a prima vista sembrava una cavolata mentre ora piango lacrime amare :cry: .
Precondizioni:
Ho una finestra (JFrame) che a sua volta continene un JDesktopPane, per effettuare operazioni di MDI (Multi Document Interface).
Quello che vorrei realizzare è che i documenti aperti dentro il DesktopPane contenessero degli oggetti, visualizzati tramite icone che:
1)Si possano muovere liberamente all'interno di uno spazio delimitato.
2)Si possano copiare/spostare da un internal frame all'altro, portandosi appresso tutte le informazioni che l'oggetto contiene.

Ho letto vari tutorial, ma devo ammettere che non ci ho capito molto :mbe: , e sembra che nessuno si interessi a come far viaggiare "oggetti grafici" da una finestra a un'altra.

:help: Cerco aiuto da chi sia disposto, dandomi quelche dritta e/o consigliandomi altre vie se quella che sto intrapendendo è sbagliata :help: .

(per maggiore chiarezza allego un'immagine di come vorrei realizzare il tutto :read: )

PGI-Bis
25-01-2007, 17:41
Non è complicato tuttavia un esempio significativo richiede qualche articolazione. Se hai tempo vedo di mettere insieme qualcosa entro sera.

Nurring
25-01-2007, 19:30
Non è complicato tuttavia un esempio significativo richiede qualche articolazione. Se hai tempo vedo di mettere insieme qualcosa entro sera.

Si ti prego ne avrei veramente bisogno :cry: ......in PVT ti mando anche i contatti di ICQ & Skype

PGI-Bis
25-01-2007, 22:49
E' un po' rapido ma dovrebbe rendere l'idea.

La storia è questa. C'è un desktop pane con dei JInternalFrame. All'interno di ognuno di questi JInternalFrame c'è un panello. Questo pannello contiene delle cose. Col drag and drop queste passano da un pannello all'altro, tra i JInternalFrame.

Puoi trasferire di tutto. Nell'esempio il trasferendo è Sprite.

import java.awt.*;
import java.awt.image.*;

public class Sprite {
private Point location = new Point();
private BufferedImage image;

public Sprite(BufferedImage image) {
this.image = image;
}

public Point getLocation() {
return (Point)location.clone();
}

public boolean contains(Point p) {
return
p.x >= location.x &&
p.x < location.x + image.getWidth() &&
p.y >= location.y &&
p.y < location.y + image.getHeight();
}

public void setLocation(Point p) {
location.setLocation(p);
}

public void paint(Graphics g) {
g.drawImage(image, location.x, location.y, null);
}
}

E' un'immagine in un punto di uno schermo. Niente di più. Poi tu dovrai scegliere cosa trasferire. Trattandosi di spostamenti all'interno della stessa JVM, puoi metterci tutto quello che vuoi: quello che circola è un riferimento ad un oggetto. Diverso è il caso di spostamenti tra JVM diverse o tra JVM e il window manager del sistema operativo. In questo caso occorre fornire una rappresentazione indipendente da Java e ricostruibile nativamente. Ma noi abbiamo la fortuna di trattare il caso più semplice :D:

Ciò che trasferisci con il Drag And Drop AWT è incapsulato in un oggetto di tipo Transferable (Trasferibile). L'incapsulamento è richiesto anche se si tratti, come nel nostro caso, di cose arcinote e immutabili. Se guardi, l'interfaccia Transferable è molto semplice. Fa due cose: è in grado di dire se quello che contiene è compatibile con un certo DataFlavor (isDataFlavorSupported) e può restituirle quel qualcosa in una forma compatibile con un DataFlavor, se questo sia supportato (getTransferData(DataFlavor)).

Noi vogliamo trasferire uno Sprite (tu vorrai trasferire qualcos'altro, il modus operandi è lo stesso), ci serve una capsula Transferable. E' questione di poche righe.

import java.awt.datatransfer.*;

/** Capsula per il trasferimento di uno Sprite nella stessa JVM */
public class SpriteTransferable implements Transferable {

/* Identifica un Transferable che trasporta uno Sprite */
public static final DataFlavor SPRITE_FLAVOR = new DataFlavor(
Sprite.class , "sprite");

/* Sprite trasferito */
private final Sprite SPRITE;

/** Crea un Transferable che incapsula lo Sprite in
argomento */
public SpriteTransferable(Sprite sprite) {
SPRITE = sprite;
}

/** Restituisce un oggetto SPRITE se la classe di rappresentazione
del DataFlavor in argomento sia compatibile in assegnamento
con Sprite.class */
public Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorException {
if(flavor.getRepresentationClass().isAssignableFrom(Sprite.class)) {
return SPRITE;
} else {
throw new UnsupportedFlavorException(flavor);
}
}

/* True se la classe di rappresentazione del DataFlavor in
argomento sia compatibile in assegnamento con Sprite.class */
public boolean isDataFlavorSupported(DataFlavor flavor) {
return flavor.getRepresentationClass().isAssignableFrom(Sprite.class);
}

public DataFlavor[] getTransferDataFlavors() {
return new DataFlavor[] { SPRITE_FLAVOR };
}
}

Tutto quello che fa SpriteTransferable è immagazzinare un riferimento ad uno Sprite e restituire quel riferimento se qualcuno glielo chieda passandogli SPRITE_FLAVOR come DataFlavor.

A questo punto è tutto fatto. Il Drag And Drop funziona come fosse un MouseListener. In particolare, quello che a noi interessa di più è l'esistenza di un metodo "dragMouseMoved". E' l'alter ego di "mouseDragged" solo che è invocato quando il trascinamento del mouse è quello che corrisponde ad un drag and drop in corso. La cosa comunque funziona così.

Il componente che voglia iniziare un "Drag" viene agganciato ad un DragSource (solitamente il DragSource predefinito della piattaforma) attraverso il metodo createDragGestureRecognizer di quest'ultimo. Al DragSource puoi agganciare un DragSourceListener, che registra in particolare un evento "qualcosa di trascinato sta entrando" e un DragSourceMotionListener, che registra l'evento "qualcosa di trascinato si sta muovendo".

Il componente che vuole ricevere un drop, l'atto terminale del drag and drop, può farlo usando DropTarget. DropTarget ha un costruttore che richiede un Component e un DropTargetListener. Il component è quello su cui avverrà il drop e il DropTargetListener è l'oggetto il cui metodo drop è invocato quando avviene il rilascio.

Riassunto. Per fare di un Component l'attivatore di un Drag:

DragSource source = DragSource.getDefaultDragSource();
DragGestureRecognizer dgr = source.createDragGestureRecognizer(
IL_COMPONENTE,
uno dei valori di DnDConstants, ad esempio MOVE,
l'ascoltatore di eventi DragSourceListener);

Dopo questi due passaggi, quando il sistema rileva un qualcosa che somiglia ad un "drag", invoca il metodo "dragGestureRecognized" dell'ascoltatore di eventi DragSourceListener. Là dentro si fanno i controlli necessari a stabilire se sia il caso di iniziare il drag. Per noi questo significherà capire se nel punto in cui il mouse è stato premuto ci sia uno Sprite (che non a caso ha un metodo contains(Point) :D). Per registrare il movimento del mouse, si prende il DragSource e gli si aggiunge un DragSourceListener (quando un drag entra nell'area di un componente) e un DragSourceMotionListener (quando si sta muovendo qualcosa sul componente).

Per attivare il drop:

DropTarget dropTarget = new DropTarget(COMPONENTE, DropTargetListener);

Quello che segue è il pannello che contiene le icone trascinabili.

import java.awt.*;
import java.awt.dnd.*;
import java.awt.datatransfer.*;
import java.util.*;
import javax.swing.*;
import javax.swing.border.*;

public class FloatingPanel extends JPanel {
private ArrayList<Sprite> sprites = new ArrayList<Sprite>();
private DropTarget dropTarget;
private DragSource dragSource = DragSource.getDefaultDragSource();

public FloatingPanel() {
setBackground(Color.WHITE);

/* Quando si rilascia qualcosa è invocato il metodo drop di
dropManager */
dropTarget = new DropTarget(this, dropManager);

/* Quando un "droppable" entra nella regione di questo componente
è invocato il metodo dragEnter */
dragSource.addDragSourceListener(dragProgressManager);

/* Quando il mouse si muove in corrispondenza di un evento drag and
drop è invocato il metodo dragMouseMoved */
dragSource.addDragSourceMotionListener(dragProgressManager);
DragGestureRecognizer dragStarter = dragSource.createDefaultDragGestureRecognizer(
this, DnDConstants.ACTION_MOVE,
dragManager);
}

/** Aggiunge lo sprite in argomento (non accetta duplicati) */
public void addSprite(Sprite sprite) {
if(sprites.contains(sprite) == false) {
sprites.add(sprite);
repaint();
}
}

/** Rimuove uno sprite */
public void removeSprite(Sprite sprite) {
int index = sprites.indexOf(sprite);
if(index >= 0) {
sprites.remove(index);
repaint();
}
}

/** Restituisce lo sprite in posizione location o null,
se non ci siano sprite in quella posizione */
public Sprite getSpriteAt(Point location) {
for(Sprite s : sprites) {
if(s.contains(location)) {
return s;
}
}
return null;
}

/** Disegna gli sprite */
protected void paintComponent(Graphics g) {
super.paintComponent(g);
for(Sprite s : sprites) {
s.paint(g);
}
}

/** Gestisce l'avvio del drag */
private DragGestureListener dragManager = new DragGestureListener() {
public void dragGestureRecognized(DragGestureEvent dge) {
Point p = dge.getDragOrigin();
Sprite sprite = getSpriteAt(p);
if(sprite != null) {
dge.startDrag(DragSource.DefaultMoveDrop, new SpriteTransferable(sprite));
}
}
};

/** Gestisce il drop */
private DropTargetAdapter dropManager = new DropTargetAdapter() {
public void drop(DropTargetDropEvent e) {
if(e.isDataFlavorSupported(SpriteTransferable.SPRITE_FLAVOR)) {
e.acceptDrop(DnDConstants.ACTION_MOVE);
dropSprite(e);
} else {
e.rejectDrop();
}
}
};

/** Gestisce il movimento in drag */
private DragSourceAdapter dragProgressManager = new DragSourceAdapter() {
public void dragMouseMoved(DragSourceDragEvent e) {
DragSourceContext context = e.getDragSourceContext();
Transferable t = context.getTransferable();
if(t.isDataFlavorSupported(SpriteTransferable.SPRITE_FLAVOR)) {
moveSprite(t, e.getLocation());
}
}

public void dragEnter(DragSourceDragEvent e) {
DragSourceContext context = e.getDragSourceContext();
Transferable t = context.getTransferable();
if(t.isDataFlavorSupported(SpriteTransferable.SPRITE_FLAVOR)) {
acquireSprite(t, e.getLocation());
}
}
};

/* Invocato quando lo sprite trascinato entra nel pannello */
private void acquireSprite(Transferable t, Point p) {
try {
SwingUtilities.convertPointFromScreen(p, this);
Sprite sprite = (Sprite)t.getTransferData(SpriteTransferable.SPRITE_FLAVOR);
sprite.setLocation(p);
addSprite(sprite);
} catch(Exception ex) {
System.err.println(ex.getMessage());
}
}

/* Invocato quando lo sprite trascinato esce dal pannello */
private void releaseSprite(Transferable t) {
try {
Sprite sprite = (Sprite)t.getTransferData(SpriteTransferable.SPRITE_FLAVOR);
removeSprite(sprite);
} catch(Exception ex) {
System.err.println(ex.getMessage());
}
}

/* Invocato durante il trascinamento di uno sprite */
private void moveSprite(Transferable t, Point p) {
try {
Rectangle bounds = getBounds(); //JComponent
Point onScreen = getLocationOnScreen(); //JComponent
bounds.x += onScreen.x;
bounds.y += onScreen.y;
Sprite sprite = (Sprite)t.getTransferData(SpriteTransferable.SPRITE_FLAVOR);

/* Se il movimento avviene all'interno del componente, muove lo sprite */
if(bounds.contains(p)) {
p.x -= onScreen.x;
p.y -= onScreen.y;
sprite.setLocation(p);
repaint();
} else {
/* Se il movimento avviene al di fuori del componente, lo sprite
viene rimosso */
removeSprite(sprite);
}
} catch(Exception ex) {
System.err.println(ex.getMessage());
}
}

/* Azioni conseguenti ad una richiesta di rilascio */
private void dropSprite(DropTargetDropEvent e) {
try {
Transferable t = e.getTransferable();
Sprite sprite = (Sprite)t.getTransferData(SpriteTransferable.SPRITE_FLAVOR);
addSprite(sprite);
repaint();
e.dropComplete(true);
} catch(Exception ex) {
e.dropComplete(false);
}
}
}

Quando trascini il mouse su un'icona di questo pannello (uno sprite) quello sprite si muove all'interno del pannello. Quando, per effetto del trascinamento, lo sprite esce dal pannello, lo sprite viene rimosso. Quando uno sprite viene trascinato all'interno del pannello, lo sprite viene acquisito e si muove.

Per provare si possono creare due pannelli, infilarli in altrettanti JInternalFrame, creare uno sprite e infilarlo in uno dei due pannelli. Apri un finestrone e potrai trascinare l'icona da un pannello all'altro. Oltre che sperimentare un tot di problemi.

import java.awt.*;
import java.awt.image.*;
import javax.swing.*;
import javax.imageio.*;
import java.util.*;
import java.io.*;

public class Main implements Runnable {
public static void main(String[] args) {
SwingUtilities.invokeLater(new Main());
}

private final String IMAGE_PATH = "/icon.png";

public void run() {
Dimension panelSize = new Dimension(200, 200);
FloatingPanel firstPanel = new FloatingPanel();
firstPanel.setPreferredSize(panelSize);
JInternalFrame firstFrame = new JInternalFrame(
"First Frame", true, false, true, false);
firstFrame.add(firstPanel, BorderLayout.CENTER);

FloatingPanel secondPanel = new FloatingPanel();
secondPanel.setPreferredSize(panelSize);
JInternalFrame secondFrame = new JInternalFrame(
"Second Frame", true, false, true, false);
secondFrame.add(secondPanel, BorderLayout.CENTER);

JDesktopPane desktop = new JDesktopPane();
desktop.add(firstFrame);
desktop.add(secondFrame);

JFrame window = new JFrame("Sample");
window.add(desktop, BorderLayout.CENTER);
window.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
window.pack();

/* Carica uno sprite e lo introduce nel primo pannello */
GraphicsConfiguration screenConfig = window.getGraphicsConfiguration();
Sprite sprite = loadSprite(screenConfig, IMAGE_PATH);
sprite.setLocation(new Point(5, 5));
firstPanel.addSprite(sprite);

window.setSize(640, 480);
window.setVisible(true);

firstFrame.pack();
firstFrame.setLocation(new Point(300, 50));
firstFrame.setVisible(true);
secondFrame.pack();
secondFrame.setLocation(new Point(20, 50));
secondFrame.setVisible(true);
}

private Sprite loadSprite(GraphicsConfiguration cfg, String iconPath) {
try {
InputStream in = getClass().getResourceAsStream(iconPath);
BufferedImage image = ImageIO.read(in);
BufferedImage fastImage = cfg.createCompatibleImage(
image.getWidth(), image.getHeight());
fastImage.getGraphics().drawImage(image, 0, 0, null);
return new Sprite(fastImage);
} catch(IOException ex) {
throw new RuntimeException(ex);
}
}
}

La prima cosa da notare è che si tratta di un rapido esperimento. Ciò che certamente non va è il fatto che se l'oggetto trascinato può perdersi per strada se il drop non avviene all'interno dell'altro JInternalFrame. Considerando questa possibilità si dovrebbe rimuovere lo sprite dal pannello di origine se e solo se esso abbia effettivamente raggiunto un altro pannello. Poi se sovrapponi parzialmente i due JInternalFrame noti uno spostamento strano dell'immagine che deriva dall'uso del metodo dragEnter per gestire l'acquisizione dello sprite. Sarebbe preferibile fare tutto con il solo movimento. C'è da ricamare un bel po'.

La cosa un po' meno agevole da risolvere è il fatto che nel passaggio tra un frame e l'altro l'icona scompare. Deriva da ciò che la proiezione dell'icona avviene sui pannelli e non è associata al cursore del trascinamento. C'è un metodo di DragGestureEvent che potrebbe farlo ma non lo fa in tutte le piattaforme dunque è da scartare.

C'è più di una soluzione all'ultimo e più consistente problema. Si può usare il DesktopPane come ulteriore oggetto bersaglio del drop e disegnare su di esso l'icona quando si trovi a svolazzare tra due pannelli oppure si può passare per il glass pane della finestra. A dirla tutta l'opzione GlassPane è appettibile per gestire tutto l'ambaradan perchè ha notevoli potenzialità "artistiche". Tuttavia non saremmo più nel campo del drag and drop, almeno non in senso comune. Ma, se vuoi, possiamo provare a vedere cosa si cava da questa seconda opzione.

Nurring
25-01-2007, 23:12
Innanzitutto grazie davvero di cuore.
Ammetto di essere troppo stanco ora per leggere il tutto, ma domani con mente lucida mi ci metto e mi studio tutto per bene.
Inoltre sono strabiliato con la facilità con la quale hai scritto (e descritto) questo codice, per me ancora oscuro, ma per te contenente concetti ormai chiari. Beato te!
Sinceramente la storia del GlassPane mi incuriosisce, visto che gli oggetti che porti da un frame all'altro dovranno anche avere un collegamento fra di loro tramite linee definite in una certa maniera......ma questa è un'altra storia, per ora vedo di riuscire a risolvere questa.

Ancora grazie :vicini:

PGI-Bis
26-01-2007, 17:44
Ho provato a farlo in versione "glass pane" e viene molto più carino e direi anche meno complicato. Se ti interessa incollo il codice in un messaggio. Prima devo dargli una sistemata perchè è 'na schifezza :D.

Nurring
27-01-2007, 00:21
Ho provato a farlo in versione "glass pane" e viene molto più carino e direi anche meno complicato. Se ti interessa incollo il codice in un messaggio. Prima devo dargli una sistemata perchè è 'na schifezza :D.

Incolla incolla!!! Intanto io mi sono studiato il tuo codice e l'ho provato in parte, ho ridefinito il JPanel exstendendolo con la classe che mi avevi dato, l'ho compilato, ho agginuto il pannello nei posti dove mi interessava e funziona :sofico: .
Solo che non sono riuscito a provare a mettterci un oggetto dentro perché non ho avuto tanto tempo oggi. :( ...

PS Io uso NetBeans 5.0.....mi ci trovo molto bene, tu che hai sicuramente piu' esperienza di me , se lo hai usato,come lo valuti?

PGI-Bis
27-01-2007, 17:54
Conosco NetBeans e mi piace tuttavia io programmo con un editor che una sorta di Notepad con le schede, senza evidenziazione del codice, indentazione, compilazione o ammennicoli del genere. Preferisco così, non lo consiglio a nessuno. Capirai che io non sia la persona più indicata per valutare la bontà di un IDE :D.

Ho tradotto in qualcosa di usabile il drag and drop tramite glass pane. Funziona e apre un tot di possibilità molto simpatiche, graficamente parlando. Qui sotto c'è il minimo sindacale. Non si vede il cursore del mouse perchè "stamp" non lo prende, ma c'è.

Partenza
http://www.tukano.it/partenza.png

Trascinamento
http://www.tukano.it/transito.png

Arrivo
http://www.tukano.it/arrivo.png

L'ambaradan si appoggia a tre elementi. Due interfacce e un JComponent da usare come pannello trasparente.

L'interfaccia DragData dichiara le capacità di qualcosa che può essere "trascinato".

DragData.java
package softdnd;

/** Dati trasferiti attraverso il soft drag and drop */
public interface DragData {

/** Restituisce la sorgente dei dati trasferiti o null */
<T> T getSource(Class<T> type);

/** Restituisce i dati trasferiti o null */
<T> T getData(Class<T> type);

/** Restituisce il punto di origine dei dati nel
sistema di coordinate dello schermo */
java.awt.Point getOriginOnScreen();

/** Restituisce l'icona dei dati trasferiti */
java.awt.Image getImage();
}

Serve a questo. Quando sul pannello è rilevata la pressione del mouse, il pannello richiede ad un certo oggetto se in quel punto ci sia qualcosa di trascinabile. Se c'è, allora il glasspane imposta un campo DragData sul valore restituito da quell'oggetto. Durante il trascinamento del mouse, il glass pane disegna lungo il percorso del cursore l'immagine restituita dal metodo getImage di quel dragData. Nulla di più.

Le proprietà di chi può fornire e ricevere un DragData sono dichiarare nell'interfaccia che segue.

DDUnit.java
package softdnd;

import java.awt.Point;

/** Sorgente, bersaglio e destinazione del "soft"
drag and drop */
public interface DDUnit {

/** Estrae i dati trasferibili dal punto screenPoint.
Il sistema di coordinate di screenPoint è quello
dello schermo. Restituisce null se non ci siano dati
da trasferire. */
DragData carve(Point screenPoint);

/** Riceve una notifica di trascinamento dei dati
trasferiti. Il punto screenPoint è la posizione
attuale del trascinamento nel sistema di coordinate
dello schermo. data sono i dati in trasferimento. */
void drag(Point screenPoint, DragData data);

/* Richiesta di rilascio dei dati data nel punto
screenPoint. Il punto screenPoint è relativo al
sistema di coordinate dello schermo. Restituisce true
se il drop sia avvenuto con successo. */
boolean drop(Point screenPoint, DragData data);
}

Anche qui il funzionamento dovrebbe essere evidente. Il glass pane ha un riferimento ad uno DDUnit. Quando il mouse è premuto, il glass pane preleva dal suo ddUnit - carve(Point) - un DragData. Se lo ddUnit risponde con qualcosa allora il glasspane considera iniziato il draganddrop. Durante il trascinamento, che per il glass pane corrisponde ad un evento "MOUSE_DRAGGED" in presenza di un dragData diverso da null, il glass pane notifica al suo ddUnit gli spostamenti invocandone il metodo drag. Nel concreto esempio non faccio uso di questa notifica ma può servire nel caso in cui, ad esempio, il componente che vede passarsi sopra un DragData voglia in qualche modo reagire - lampeggiando, gridando "a me, a me" o quant'altro.

La classe che segue concretizza DragData. Lo fa semplicemente fornendo un get ed un set per ogni proprietà che eredita.

DefaultMutableDragData.java
package softdnd;

import java.awt.*;

public class DefaultMutableDragData implements DragData {
private Object source;
private Object data;
private Image image;
private Point originOnScreen;

public Image getImage() {
return image;
}

public void setImage(Image image) {
this.image = image;
}

public void setOriginOnScreen(Point p) {
originOnScreen = (Point)p.clone();
}

public Point getOriginOnScreen() {
return originOnScreen;
}

public void setSource(Object source) {
this.source = source;
}

public void setData(Object data) {
this.data = data;
}

public <T> T getSource(Class<T> type) {
if(source != null && type.isAssignableFrom(source.getClass())) {
return type.cast(source);
} else {
return null;
}
}

public <T> T getData(Class<T> type) {
if(data != null && type.isAssignableFrom(data.getClass())) {
return type.cast(data);
} else {
return null;
}
}
}

C'è poi una classe che contiene due metodi di servizio per la conversione di coordinate da un componente allo schermo e viceversa.

PointService.java
package softdnd;

import java.awt.*;

public class PointService {

/* Restituisce la conversione di un punto dal sistema di
coordinate del componente source a quello dello schermo */
public static Point convertPointToScreen(Point p, Component source) {
Point location = source.getLocationOnScreen();
return new Point(p.x + location.x, p.y + location.y);
}

/* Restituisce la conversione di un punto dal sistema di
coordinate dello schermo a quello di questo componente */
public static Point convertPointFromScreen(Point p, Component source) {
Point location = source.getLocationOnScreen();
return new Point(p.x - location.x, p.y - location.y);
}
}

Ci sono dei metodi analoghi in SwingUtilities ma fanno qualcosa in più che ho preferito evitare.

La classe UnitPane è un JPanel che concretizza l'interfaccia DDUnit. Cioè un JPanel da cui si può estrarre e su cui si può rilasciare un certo DragData. Il DragData prodotto e acquisibile è un DragData che contenga un Component.

UnitPane.java
package softdnd;

import java.awt.*;
import java.awt.image.*;
import javax.swing.*;
import static softdnd.PointService.convertPointToScreen;
import static softdnd.PointService.convertPointFromScreen;

/** Unità soft drag and drop JPanel */
public class UnitPane extends JPanel implements DDUnit {
public UnitPane() {}

public UnitPane(LayoutManager layout) {
super(layout);
}

/** @inheritDoc */
public DragData carve(Point screenPoint) {
/* Cerca un Component nel punto corrispondente a
screenPoint. Se lo trova lo impacchetta in un
DefaultMutableDragData e lo restituisce. Elimina
da sè il componente trasferito */
DragData answer = null;
Point location = convertPointFromScreen(screenPoint, this);
Component component = getComponentAt(location);
if(component != null && component != this) {
answer = packComponent(component);
remove(component);
revalidate();
}
return answer;
}

/** Inerte */
public void drag(Point screenPoint, DragData data) {}

/** @inheritDoc */
public boolean drop(Point screenPoint, DragData data) {
/* Se data contiene un Component allora questo
pannello ingloba quel componente. */
boolean answer = false;
Component component = data.getData(Component.class);
if(component != null) {
add(component);
revalidate();
answer = true;
}
return answer;
}

/* Incapsula un componente in un DefaultMutableDragData.
Crea uno "screenshot" del componente come immagine da
usare per il trasferimento */
private DragData packComponent(Component c) {
BufferedImage image = newBuffer(c.getWidth(), c.getHeight());
c.print(image.getGraphics());
DefaultMutableDragData data = new DefaultMutableDragData();
data.setSource(this);
data.setData(c);
data.setOriginOnScreen(c.getLocationOnScreen());
data.setImage(image);
return data;
}

/* Crea un buffer di formato compatibile con lo schermo */
private BufferedImage newBuffer(int width, int height) {
GraphicsConfiguration config = getGraphicsConfiguration();
BufferedImage image = config.createCompatibleImage(
width, height, Transparency.TRANSLUCENT);
return image;
}
}

Gli UnitPane sono usati nell'esempio come pannelli del contenuto per i due JInternalFrame. Tra questi UnitPane e il glass pane c'è uno strato intermedio. Questo strato intermedio è ciò che è usato come DDUnit dal glass pane ed è definito nella classe DesktopUnit.

DesktopUnit.java
package softdnd;

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import static softdnd.PointService.convertPointToScreen;
import static softdnd.PointService.convertPointFromScreen;

/** Unità drag and drop associata ad un JDesktopPane */
public class DesktopUnit implements DDUnit {
private JDesktopPane desktopPane = new JDesktopPane();

/** Restituisce il DesktopPane associato a questo
DesktopUnit */
public JDesktopPane getDesktopPane() {
return desktopPane;
}

/** @inheritDoc */
public DragData carve(Point screenPoint) {
/* Per estrarre un DragData questo DesktopUnit
interroga i JInternalFrame contenuti nel suo
desktopPane. */

DragData answer = null;
DDUnit unit = ddUnitAt(screenPoint);
if(unit != null) {
answer = unit.carve(screenPoint);
}
return answer;
}

/** Inerte. */
public void drag(Point screenPoint, DragData data) {}

/** @inheritDoc */
public boolean drop(Point screenPoint, DragData data) {
/* Se c'è un DDUnit che può ricevere il drop allora
lascia quell'unità il compito di gestire l'acquisizione.
Altrimenti annulla il trasferimento. Per farlo usa
la proprietà "source" del DragData (sorgente del
trasferimento). */

DDUnit unit = ddUnitAt(screenPoint);
if(unit == null || !unit.drop(screenPoint, data)) {
undoCarving(data);
}
return true;
}

/** Annulla, se possibile, l'operazione di drag and drop
rappresentata dal DragData in argomento */
private void undoCarving(DragData data) {
/* Se il DragData ha un contentire di origine e
trasporta un Component allora aggiunge il componente
al contenitore originale. */
Container container = data.getSource(Container.class);
Component component = data.getData(Component.class);
if(component != null && container != null) {
container.add(component);
container.validate();
}
}

/** Restituisce l'unità drag and drop che si trova nel punto
screenPoint o null se non ve ne sia una. */
private DDUnit ddUnitAt(Point screenPoint) {
/* Tutto si fonda sull'assunto che il pannello del
contenuto di ogni JInternalFrame sia un DDUnit. */
DDUnit answer = null;
Point point = convertPointFromScreen(screenPoint, desktopPane);
Component component = desktopPane.getComponentAt(point);
if(component instanceof JInternalFrame) {
JInternalFrame frame = (JInternalFrame)component;
Container contentPane = frame.getContentPane();
if(contentPane instanceof DDUnit) {
answer = (DDUnit)contentPane;
}
}
return answer;
}
}

DesktopUnit gestisce un JDesktopPane. In questo JDesktopPane possono esseri un toto di JInternalFrame. Quando qualcuno (il glass pane) richiede al DesktopUnit di compiere l'estrazione di un DragData in un certo punto, DesktopUnit controlla se in quel punto ci sia un JInternalFrame e, in caso affermativo, se il suo pannello del contenuto sia un DDUnit. Se il pannello del contenuto è un DDUnit gli passa la palla. Nell'esempio, il pannello del contenuto sarà uno UnitPane.

L'ultimo partecipante al magheggio è DDGlass. DDGlass è un JComponent che sarà usato come glass pane di una finestra. Un glass pane è un componente idealmente trasparente che viene proiettato sopra al pannello del contenuto della finestra.

DDGlass.java
package softdnd;

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import static java.awt.AWTEvent.*;
import static java.awt.event.MouseEvent.*;
import static softdnd.PointService.convertPointToScreen;
import static softdnd.PointService.convertPointFromScreen;

/** Pannello trasparente che gestisce il "soft" drag and drop */
public class DDGlass extends JComponent implements AWTEventListener {
/* Punto di trascinamento corrente */
private Point currentDataPoint;

/* Dati in trasferimento */
private DragData dragData;

/* Sorgente e destinazione dei dati trasferibili */
private DDUnit ddUnit;

/* May throw a SecurityException */
public DDGlass(DDUnit ddUnit) {
this.ddUnit = ddUnit;
setOpaque(false);
Toolkit.getDefaultToolkit().addAWTEventListener(
this,
MOUSE_EVENT_MASK | MOUSE_MOTION_EVENT_MASK);
}

/** Called by constructor */
public void setOpaque(boolean val) {
super.setOpaque(val);
}

/** Riceve gli eventi AWT ovunque siano generati */
public void eventDispatched(AWTEvent e) {
if(e.getID() == MOUSE_PRESSED) {
handleMousePressed((MouseEvent)e);
} else if(dragData != null && e.getID() == MOUSE_DRAGGED) {
handleMouseDragged((MouseEvent)e);
} else if(dragData != null && e.getID() == MOUSE_RELEASED) {
handleMouseReleased((MouseEvent)e);
}
}

/* Gestisce la pressione del mouse estraendo da ddUnit i dati
da trascinare */
private void handleMousePressed(MouseEvent e) {
Point screenPoint = convertPointToScreen(
e.getPoint(), e.getComponent());
dragData = ddUnit.carve(screenPoint);
if(dragData != null) {
setVisible(true);
currentDataPoint = convertPointFromScreen(screenPoint, this);
repaint();
}
}

/* Gestice il trascinamento del mouse spostando i dati
estratti */
private void handleMouseDragged(MouseEvent e) {
Point screenPoint = convertPointToScreen(
e.getPoint(), e.getComponent());
ddUnit.drag(screenPoint, dragData);
currentDataPoint = convertPointFromScreen(screenPoint, this);
repaint();
}

/** Gestice il rilascio del mouse rilasciando i dati estratti
e spostati */
private void handleMouseReleased(MouseEvent e) {
Point screenPoint = convertPointToScreen(
e.getPoint(), e.getComponent());
ddUnit.drop(screenPoint, dragData);
dragData = null;
repaint();
}

@Override
protected void paintComponent(Graphics g) {
if(dragData != null) {
paintDragData(g);
}
}

/** Disegna l'immagine associata a dragData nel punto
di trascinamento corrente */
private void paintDragData(Graphics g) {
g.drawImage(
dragData.getImage(),
currentDataPoint.x,
currentDataPoint.y,
null);
}
}

Praticamente ci sono solo due cose su cui fare mente locale. Una è il fatto che la gestione degli eventi passa per un AWTEventListener associato al Toolkit. Si fa per evitare che il glass pane consumi gli eventi che intercetta, come avverrebbe con un normale addMouseListener o con un enableEvents. La seconda è quel "setVisible" in handleMousePressed. Non chiedermi perchè ma pare che se si ridimensiona un JInternalFrame allora il glass pane della finestra perde lo stato "visible". Tale stato è necessario per determinare la posizione del mouse sul glass pane. Probabilmente ha a che fare con la strategia di aggiornamento del contesto grafico da parte di JDesktopPane ma non ci metterei la mano sul fuoco. La successione di eventi che capita al glass pane è questa. Quando si preme il mouse, se il DDUnit associato dice che c'è qualcosa di trascinabile DDGlass lo imposta come valore del campo "dragData" e si ridisegna. Quando il mouse viene trascinato e se esiste un dragData allora DDGlass aggiorna la posizione in cui deve essere disegnata l'immagine del dragData e si ridisegna. Quanto il mouse viene rilasciato e se esiste un dragData allora DDGlass semplicemente rilascia i dati trascinati sul suo DDUnit. Spetta a questo sapere cosa fare sia nel caso in cui ci sia un qualcosa disposto ad accettare il drop sia nel caso in cui questo qualcosa non ci sia.

La classe Main che mette insieme il tutto in un esempio è molto semplice. Crea un'etichetta con un'immagine come icona. Poi crea due JInternalFrame e imposta per ognuno di essi uno UnitPane come pannello del contenuto. Così facendo DesktopUnit sarà il grando di prelevare e rilasciare oggetti nelle finestre. Main crea uno DesktopUnit, gli aggiunge i due JInternalFrame, crea una finestra, crea un DDGlass lo imposta come glass pane della finestra, si apre e via.

Main.java
package softdnd;

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

public class Main implements Runnable {
public static void main(String...args) {
SwingUtilities.invokeLater(new Main());
}

private final String ICON_PATH = "/icons/icon.png";
private final Dimension PANEL_SIZE = new Dimension(200, 200);

public void run() {
/* Icona usata per l'etichetta trascinabile */
ImageIcon icon =
new ImageIcon(getClass().getResource(ICON_PATH));

/* Etichetta trascinabile */
JLabel label = new JLabel(icon);

/* Pannello del contenuto del primo frame interno */
UnitPane firstPane = new UnitPane(new FlowLayout());
/* che contiene l'etichetta trascinabile */
firstPane.add(label);

/* Pannello del contenuto del secondo frame interno */
UnitPane secondPane = new UnitPane(new FlowLayout());

/* Assegna una dimensione predefinita ai due pannelli
del contenuto. */
firstPane.setPreferredSize(PANEL_SIZE);
secondPane.setPreferredSize(PANEL_SIZE);

/* DesktopUnit il cui desktop pane contiene i due
frame interni e che sarà usato come DDUnit per il
glass pane DDGlass */
DesktopUnit desktop = new DesktopUnit();

/* Frame interno che contiene il primo UnitPane */
JInternalFrame firstFrame = new JInternalFrame(
"First frame", true, false, true);

/* Frame interno che contiene il secondo UnitPane */
JInternalFrame secondFrame = new JInternalFrame(
"Second frame", true, false, true);

/* Imposta i pannelli del contenuto per i due
JInternalFrame */
firstFrame.setContentPane(firstPane);
secondFrame.setContentPane(secondPane);

/* Aggiunge i due frame interni al desktop dello
DesktopUnit */
desktop.getDesktopPane().add(firstFrame);
desktop.getDesktopPane().add(secondFrame);

/* Assegna una dimensione ai due frame interni e
li rende visibili */
firstFrame.pack();
firstFrame.setVisible(true);
secondFrame.pack();
secondFrame.setVisible(true);

/* Crea il glass pane usando il desktop come
sua DDUnit */
DDGlass glass = new DDGlass(desktop);

/* Crea una finestra */
JFrame window = new JFrame();

/* le assegna un glass pane e lo rende visibile */
window.setGlassPane(glass);
glass.setVisible(true);

/* apre la finestra sullo schermo */
window.add(desktop.getDesktopPane(), BorderLayout.CENTER);
window.setSize(640, 480);
window.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
window.setVisible(true);
}
}

Può darsi che ci sia qualcosa da sistemare perchè, in fondo, è solo uno showcase. Però ha l'aria di funzionare.

Nurring
28-01-2007, 11:37
Ancora una volta grazie;
devo ammettere pero' che, al momento, trovo piu' facile l'altro codice, mi da l'idea di riuscire a gestirlo meglio.

Questo ultimo l'ho appena letto, ma non ti nascondo che ho trovato notevoli difficoltà anche solo nel leggere il codice (I feel noob! :nera: :muro: ).

Ancora una volta grazie per la tua disponibilità!http://forum.igz.it/images/smilies/hail.gif

franksisca
28-01-2007, 12:51
mi inserisco solo per dire
:ave: :ave: :ave: :ave: :ave: PGI :ave: :ave: :ave: :ave: :ave:

nucarote
20-08-2010, 01:33
Riporto in alto tale topic per chiedere come si deve procedere in tal caso:

... Diverso è il caso di spostamenti tra JVM diverse o tra JVM e il window manager del sistema operativo. In questo caso occorre fornire una rappresentazione indipendente da Java e ricostruibile nativamente. Ma noi abbiamo la fortuna di trattare il caso più semplice :D...


Grazie fin da ora per la vostre risposte. ;)

ndakota
20-08-2010, 11:28
Wow. Questo me l'ero perso, ai tempi non seguivo. PGI :ave: :ave:

Nurring
20-08-2010, 18:05
Wow. Questo me l'ero perso, ai tempi non seguivo. PGI :ave: :ave:

Eccomi...il "destinatario" del codice tre anni dopo....

Ancora oggi, dopo tre anni di lavoro pieno su Java e WebTechnology ,lo ringrazio :ave: :ave:
Grazie a lui la mia tesi è letteralmente volata ed è stata valutata con il massimo dei punti...:read:

banryu79
20-08-2010, 19:10
Riporto in alto tale topic per chiedere come si deve procedere in tal caso:

... Diverso è il caso di spostamenti tra JVM diverse o tra JVM e il window manager del sistema operativo. In questo caso occorre fornire una rappresentazione indipendente da Java e ricostruibile nativamente. Ma noi abbiamo la fortuna di trattare il caso più semplice ...

Grazie fin da ora per la vostre risposte. ;)
Ciao nucaronte, ti aiuterei volentieri ma non mi sono mai cimentato con il Drag&Drop, percui non saprei, anche se sento puzza di API del window manage del SO specifico e JNI, almeno per quanto riguarda il secondo scenario tra i due indicati da PGI.

Comunque pare che PGI sia in vacanza o comunque lontano dal forum, almeno per un po'.

[FAN MODE ON]
Mitico questo thread, non l'avevo ancora letto. :sbav:
E grande PGI!

nucarote
20-08-2010, 19:51
Ciao nucaronte, ti aiuterei volentieri ma non mi sono mai cimentato con il Drag&Drop, percui non saprei, anche se sento puzza di API del window manage del SO specifico e JNI...

E' quello che penso pure io. :(
Mi aggiungo al fatto che questo sia uno di quei topic da mettere tra le guide.

ndakota
20-08-2010, 19:55
La cosa che più stupisce è la sua incredibile conoscenza della libreria standard di Java. Non pensavo nemmeno si potesse fare qualcosa del genere :mbe:

banryu79
23-08-2010, 11:39
La cosa che più stupisce è la sua incredibile conoscenza della libreria standard di Java.
Bhe, quello è il meno: "basta" studiare, con dedizione (il che implica un bel po' di tempo) :)

banryu79
07-09-2010, 18:29
@nucarote: piccolo update.

Ovviamente non so se nel frattempo hai già risolto oppure abbandonato. Oggi mentre cercavo delle info nella bug parade, ho buttato l'occhio e ho visto che tra le 25 più votate, in prima posizione c'era una di queste due entry; contengo informazioni che potrebbero esserti utili (butta un occhio sui work-around):
- No way to set drag icon: TransferHandler.getVisualRepresentation() is not used (http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4816922)
- invoking DragSource's startDrag with an Image renders no image on drag (http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4874070)

nucarote
08-09-2010, 13:41
Per il momento tutto il "progettino" è fermo, comunque è molto più pulita della "soluzione" (a base di Observer) che avevo cominciato a pensare, ti ringrazio molto, ho provato il workaround proposto e mi sembra una buona soluzione, appena avrò un pò di tempo cercherò di svilupparlo meglio.
Comunque sarei nucarote :D

banryu79
08-09-2010, 14:24
Comunque sarei nucarote :D

Facciamo caronte e non se ne parli più :O
(spiacente per il typo, ho corretto :D)