====== Drag and Drop ====== Vytvoříme nový projekt **Designer**, přidáme okno **DesignerWindow** typu **JFrame** * do okna vložíme prvek se záložkami **JTabbedPane**, proměnnou pojmenujeme **toolbarTabs** * jako první záložku umístíme **JToolBar** pojmenovaný **componentToolbar** (pravou myší na prázdné záložky, "Add From Palette", "Swing Containers", "Toolbar") * vlastost **Tab Title** nastavíme na **Components** * jako druhou záložku opět **JToolBar** pojmenovaný **colorToolbar** * vlastost **Tab Title** nastavíme na **Colors** * do dolní části vložíme **JPanel**, pojmenovaný **designer** {{dnd_window.png}} ===== Toolbar se jmény jednotlivých grafických prvků ===== Do "toolbaru" na první záložce přidáme tlačítka se jmény tříd, které chceme pomocí myši "táhnout a vhazovat" do našeho pabelu //designer//. * Funkce **addComponent** vytvoří nové tlačítko * Tlačítku nastaví zobrazovaný text * Vytvoří **TransferHandler** a předá ho tlačítku * parametr **"text"** určuje, že během //drag and drop// bude předávána vlastnost (//property//) //text// tvořená metodami tlačítka **getText** a **setText** * Tlačítku ještě předáme **MouseMotionListener** reagující na "tažení myši", který bude vysvětlen později * Přidáme tlačítko na toolbar private void addComponent (String name) { JButton btn = new JButton (); btn.setText (name); btn.setTransferHandler (new TransferHandler ("text")); btn.addMouseMotionListener (new LocalMouseMotion ()); componentToolbar.add (btn); } V konstruktoru okna zaloláme funkci **addComponents**, která přidá několik tlačítek do toolbaru private void addComponents () { addComponent ("JButton"); addComponent ("JTree"); addComponent ("JTable"); addComponent ("JTextArea"); } ==== Reakce na tažení knoflíku pomocí myši ==== Funkce **btn.addMouseMotionListener** přiřadí tlačítku reakci na pohyb a "tažení" myši. \\ Funkce je deklarována ve třídě **Component**, ze které je odvozena třída **JButton** class Component { ... public synchronized void addMouseMotionListener (MouseMotionListener ml) { ... } ... } class JButton extends Component { ... } Každé volání metody //addMouseMotionListener// přidá tlačítku dalšího //posluchače//, \\ který bude informován o pohybech myši nad naším tlačítkem. === synchronizované metody === Klíčové slovo **synchronized** zajišťuje, že pokud zmíněná metoda našeho tlačítka bude v jeden okamžik volána z více //vláken//, \\ budou volání zpracována postupně jedno po druhém, aby v seznamu osluchačů nevznikly chyby. \\ * ( Pro různá tlačítka může být metoda zavolána současně. ) * ( Pro jedno tlačítko nemohou být současně zavolány dvě //synchronized// metody. ) * ( Výjimkou je situace kdy jedna //synchronized// metoda volá jinou //synchronized// metodu. ) ==== Rozhraní (interface) ==== Parametrem metody //addMouseMotionListener// je odkaz na rozhraní **MouseMotionListener** Rozhraní připomíná třídu, \\ smí obsahovat jen deklarace funcí bez implementace \\ ( a případně deklarace konstant ). Všechny deklarované metody jsou automaticky verřejně dostupné, nemusíme psát //public// před metodami. * V Javě jsou všechny metody (tříd i rozhraní) automaticky //virtuální//, pokud nepoužijeme klíčové slovo **final**. * U deklarací proměnných //final// znamená, že proměnná po inicializaci již nepůjde změnit. * Proměnné označená //static// //final// jsou konstantami. * Z třídy označené //final// není dovoleno odvozovat jiné třídy Klíčové slovo //public// před //interface// znamená, že rozhraní je přístupné i z jiných balíčků (packages). Rozhraní v Javě je obdobou čistě virtuální třídy v C++. Rozhraní //MouseMotionListener// má dvě metody **mouseDragged** a **mouseMoved**. public interface MouseMotionListener { void mouseDragged (MouseEvent me); void mouseMoved (MouseEvent me); } Všechnny metody rozhraní, které je použito jako //listener// grafického prvku, \\ obvykle mají jeden parametr stejného typu, v našem případě **MouseEvent**, \\ který obsahuje detailní informace o události (např. souřadnice myši). ==== Třída implementující rozhraní ==== Rozhraní se používá při deklaraci třídy. \\ Třída může deklarovat, že implementuje rozhraní class MouseMotionAdapter implements MouseMotionListener { ... } V Javě třída může být odvozena jen z jedné třídy class B extends A { ... } Ale třída může implementovat několik rozhraní class B extends A implements I1, I2, I3 { ... } Třídy, které implementují rozhraní musí implementovat jednotlivé metody, třeba jako metody co nic nedělají. public class MouseMotionAdapter implements MouseMotionListener { public MouseMotionAdapter () { } public void mouseDragged (MouseEvent me) { } public void mouseMoved (MouseEvent me) { } } Identifikátor rozhraní můžeme použít jako jméno typu, \\ do kterého můžeme ukládat ukazatele na instance tříd, které rozhraní implementují. MouseMotionListener p = new MouseMotionAdapter (); V našem případě to bude parameter typu //MouseMotionListener// u metody //addMouseMotionListener// === Třída Object z které jsou odvozeny všehnny ostatní třídy === Pokud deklarace třídy neuvádí třídu ze které je odvozena class B { ... } Java použije jako základní třídu **java.lang.Object** \\ (leda, že by se jednalo právě o třídu //java.lang.Object//, která není odvozena z jiné třídy ) Balíček **java.lang** obsahuje základní deklarace, je vždy dostupný a nemusíme psát **import java.lang.* ** Proto stačí napsat identifikátor **Object** bez jména balíčku. Z typu //Object// jsou tedy odvozeny všechy další třídy. \\ Také pole jsou odvozeny z typu //Object//. \\ Proměnné typu //Object// tedy mohou obsahovat ukazatele na libovolný objekt (připomínají **void * ** v C++) === Abstraktní metody === Metodu můžeme označit jako abstraktní klíčovýn slovem **abstract** na začátku deklarace. \\ Taková metoda nesmí obsahovat kód ve složených závorkách, ale za deklarací je jen středník. \\ Metodu potom musí implementovat odvozená třída. public abstract class A { public abstract void func (); } public class B extends A { public void func () { /* implementace funkce */ } } === Abstraktní třídy === Třídu můžeme označit jako abstraktní (klíčové slovo **abstract**) a potom z ní nelze vytvářet instance pomocí **new**. \\ Můžeme vytvářet až instance z odvozených tříd, které implementaci všech metod doplnily. Třídu musíme označit jako //abstraktní//, pokud : * pokud třída obsahuje nějakou abstraktní metodu * nebo je odvozena ze třídy která má abstraktní metody * nebo implementuje rozhraní, ale některou jeho metodu neimplementovala ==== Naše lokální třída rozšiřující výše uvedenou třídu ==== Třídu můžeme deklarovat i uvnitř jiné třídy. \\ Například třídu **LocalMouseMotion** použitou pro náš knoflík z toolbaru btn.addMouseMotionListener (new LocalMouseMotion ()); Třídu //LocalMouseMotion// je rozšířením třídy //MouseMotionAdapter//, která implementuje rozhraní //MouseMotionListener//, \\ proto ji můžeme použít jako parametr metody //addMouseMotionListener//, který je typu //MouseMotionListener//. public class DesignerWindow { ... class LocalMouseMotion extends MouseMotionAdapter { public void mouseDragged (java.awt.event.MouseEvent evt) { JButton button = (JButton) evt.getSource (); TransferHandler handler = button.getTransferHandler (); handler.exportAsDrag (button, evt, TransferHandler.COPY); } } ... } Metoda //mouseDragged// * z parametru //evt// získá odkaz ma grafický prvek, který je zdrojem události * odkaz je obecného typu //Object//, my ale víme, že jde o tlačítko, tak odkaz přetypujeme * pokud by se přetypování **(JButton) výraz** nepovedlo, vznikne výjimka * některé velmi časté výjimky nás Java ani nenutí zachycovat do bloku **try ... catch** * z tlačítka si vyzvedneme **TransferHandler** * zahájíme "drag and drop" ===== Barevné knoflíky ===== Podobně vytvoříme barevná tlačítka, která při "tažení" pomocí myši přenášejí barvu. Barvu zadanou jako parametr funkce **addColor** použijeme pro obarvení podkladu talčítka. \\ Tlačítku nastavíme //TransferHandler// pracující s vlastností **"background"** private void addColor (Color c) { JButton btn = new JButton (); btn.setText (" "); btn.setBackground (c); btn.setTransferHandler (new TransferHandler ("background")); btn.addMouseMotionListener (new java.awt.event.MouseMotionAdapter () { public void mouseDragged(java.awt.event.MouseEvent evt) { JButton button = (JButton) evt.getSource(); TransferHandler handle = button.getTransferHandler(); handle.exportAsDrag (button, evt, TransferHandler.COPY); } } ); colorToolbar.add (btn); } private void addColors () { addColor (new Color (255, 0, 0)); addColor (new Color (0, 255, 0)) ; addColor (new Color (0, 0, 255)) ; addColor (new Color (255, 255, 0)) ; } ==== Anonymní třída ==== Na místo lokální třidy použité v předchozích odstavcích, \\ vytvoříme **anonymní třídu** následující kostrukcí: new MouseMotionAdapter () { kód rošiřující původní třídu o nové metody nebo proměnné } Vzniklá třída nemá ani svůj identifikátor, víme jen, že je odvozena ze třídy //MouseMotionAdapter//. \\ Konstrukce začíná obvyklým **new IdentifikátorTypu ()**. \\ A následují složené závorky s deklaracemi, o které je původní třída (nebo jen rozhraní) rozšířeno. Běžné se //anonymní třída// používá jako parametr jiné metody. btn.addMouseMotionListener ( new java.awt.event.MouseMotionAdapter () { public void mouseDragged (java.awt.event.MouseEvent evt) { // reakce na tažení pomocí myši } } ); ===== Konstruktor ===== Konstruktor okna * umístí tlačítka na obě lišty s nástroji * vytvoří objekty DesignerDropListener a DropTarget, které budou reagovat data vhozená na dolní panel * dolnímu panelu nastaví **null layout**, který dovoluje libovolně umístǒvat grafické prvky na plochu panelu public DesignerWindow () { initComponents (); addComponents (); addColors (); DesignerDropListener listener = new DesignerDropListener (); DropTarget target = new DropTarget (designer, listener); designer.setDropTarget (target); designer.setLayout (null); } ===== Reakce na vhození objektu ====== ==== Lokální třída si pamatuje vňější třídu kam patří ==== Pokud lokální třída není označena klíčovým slovem **static**, tak si pamatuje instanci vnější třídy, ve které byla vytvořena. Například třída **DesignerDropListener** může ve své metodě **drop** zavolat metodu **addDesignerObject** \\ a metoda //addDesignerObject// patří do vnější třídy **DesignerWindow** public class DesignerWindow { class DesignerDropListener implements DropTargetListener { public void drop (DropTargetDropEvent evt) { addDesignerObject (); } public void dragEnter () { } public void dragOver () { } ... } private void addDesignerObject () { ... } } Naše lokální třída implementuje rozhraní **DropTargetListener**, které má několik metod. \\ Musíme všechnny metody implementovat, ačkoliv některé implementace nic nedělají. \\ ( To je také smysl pomocné třídy //MouseMotionAdapter//, která nám usnadňuje implementaci jen jedné metody rozhraní //MouseMotionListener//. ) public interface DropTargetListener extends EventListener { public void dragEnter (DropTargetDragEvent dtde); public void dragOver (DropTargetDragEvent dtde); public void dropActionChanged (DropTargetDragEvent dtde); public void dragExit (DropTargetEvent dte); public void drop (DropTargetDropEvent dtde); } === Ukazatel na vnější třídu ==== Pokud uvnitř metod vnitřní třídy potřebujeme odkaz na vnější třídu, použijeme výraz DesignerWindow.this Tedy uvniř **drop** metody je * obyčejné **this** je ukazatel na instanci __vnitřní__ třídy typu **DesignerDropListener** * **DesignerWindow.this** je ukazatel na instanci __vnější__ třídy typu **DesignerWindow** Pokud zavoláme v metodě //drop// metodu //addDesignerObject// překladač hledá identifikátor * nejprve mezi lokálními proměnnými a parametry * potom ve vniřní třídě * nakonec ve vnější třídě * pokud ani tam nenalezne identifikátor, prohledá aktuální balíček a importované balíčky Pokud je vniřní třída označená klíčovým slovem **static**, tak si vnitřní třída nepamatuje odkaz na vnější třídu === Vytvoření instance vnitřní třídy ==== Uvnitř vnější třídy vytvoříme instanci obvyklým new DesignerDropListener () a nově vzniklá vnitřní třída si uloží odkaz na vnější třídu. Pokud je to v jiném balíčku vnější i vniřní třída musí být **public**, \\ musíme mít připravenu nějakou instanci vnější třídy (**w** v následujícím příkladu) \\ a instanci vytváříme výrazem instance_vnější_třídy.new jméno_vnitřní_třídy ( ) DesignerWindow w = new DesignerWindow (); ... DesignerWindow.DesignerDropListener t = w.new DesignerDropListener (); ==== Informace o typu tažených nebo vhozených dat ==== Parametr **evt** metody **drop** nám poskytne objekt typu **Transferable**. \\ Z tohoto objektu získáme pole typu **DataFlavor []** o přenášených formátech dat. Podobně jako při práci se schránkou (clipboard) můžeme během "drag and drop" přenášet data v několika formátech. \\ Například můžeme přenášet jak text tak i obrázek. Cílové okno si vybere formát dat, který mu vyhovuje. Náš příklad ale bude pracovat jen s jedním formátem. Ověříme si, že pole obsahuje jen jeden prvek. Předdefinovaná třída **Class** (**java.lang.Class**, velké písmeno C na rozdíl od klíčového slova **class**) obsahuje informace o jiných typech v Javě. \\ Do proměnné **cls** typu //Class// si uložíme informace o přenášeném typu dat. Přenášená data uložíme do proměnné **data** obecného typu **Object**. \\ Vyzvednutí dat z objektu typu //Transferable// se nemusí podařit a Java zde vyžaduje //try catch// blok zabycující případné výjimky. Transferable transfer = evt.getTransferable(); DataFlavor [] flavors = transfer.getTransferDataFlavors (); if (flavors.length == 1) { DataFlavor flavor = flavors[0]; Class cls = flavor.getRepresentationClass (); Object data = null; try { data = transfer.getTransferData (flavor); } catch (UnsupportedFlavorException ex) { System.out.println (ex); } catch (IOException ex) { System.out.println (ex); } ==== Vhození barvy nebo jména třídy ==== Otestujeme zda proměnná **cls** obsahuje ukazatel na popis typu //String//. \\ Odkaz na popis typu //String// získáme **String.class**. \\ A případně zavoláme funkci **addDesignerObject** popsanou v následujícím odstavci. Nebo **cls** je rovno **Color.class**. \\ V tom případě přetypujeme //data// na typ //Color//. \\ Získanou barvu použijeme na obarvení grafického prvku, \\ který se nachází právě pod myší (může to být i celý podkladový panel). Point point = evt.getLocation (); if (cls == String.class) { addDesignerObject (point, data.toString ()); } if (cls == Color.class) { Color color = (Color) data; Component comp = designer.getComponentAt (point); comp.setBackground (color); } ==== Celá lokální třída reagující na vhozená data ==== class DesignerDropListener implements DropTargetListener { public void drop (DropTargetDropEvent evt) { // System.out.println ("drop"); Transferable transfer = evt.getTransferable(); DataFlavor [] flavors = transfer.getTransferDataFlavors (); /* for (DataFlavor f : flavors) { System.out.println ("flavor " + f); System.out.println ("class " + f.getRepresentationClass ()); } */ if (flavors.length == 1) { DataFlavor flavor = flavors[0]; Class cls = flavor.getRepresentationClass (); Object data = null; try { data = transfer.getTransferData (flavor); } catch (UnsupportedFlavorException ex) { System.out.println (ex); } catch (IOException ex) { System.out.println (ex); } if (data != null) { Point point = evt.getLocation (); if (cls == String.class) { addDesignerObject (point, data.toString ()); } if (cls == Color.class) { Color color = (Color) data; Component comp = designer.getComponentAt (point); comp.setBackground (color); } } } }; public void dragEnter(DropTargetDragEvent dtde) { } public void dragOver(DropTargetDragEvent dtde) { } public void dropActionChanged(DropTargetDragEvent dtde) { } public void dragExit(DropTargetEvent dte) { } }; ==== Již uvedené instrukce z konstruktoru okna vytvářející instanci lokální třídy ==== DesignerDropListener listener = new DesignerDropListener (); DropTarget target = new DropTarget (designer, listener); designer.setDropTarget (target); ===== Přidání grafického prvku na plochu panelu ===== ==== Vytvoření instance třídy, jméno třídy máme zadáno proměnnou typu string ==== Pokud máme jméno třídy zadané jen jako řetězec znaků, \\ můžeme použít statickou metodu **Class.forName ()**, \\ která se pokusí najít přeloženou třídu zadaného jména a \\ vrátí popis nalezené třídy jako typ **Class**. Ve třídě **Class** je pak metoda **newInstance ()**, \\ která se pokusí vytvořit instanci zadané třídy. \\ ( Pokud zadaná třída má verějně dostupný konstruktor bez parametrů. ) String className = "javax.swing.JButton"; Class cls = Class.forName (className); Object obj = cls.newInstance(); Ještě musíme zachytit případné výjimky. Object obj = null; try { toolName = "javax.swing." + toolName; Class cls = Class.forName (toolName); obj = cls.newInstance(); } catch (ClassNotFoundException ex) { System.out.println (ex); } catch (InstantiationException ex) { System.out.println (ex); } catch (IllegalAccessException ex) { System.out.println (ex); } ==== Vložení grafického prvku na plochu panelu ==== Náš panel má "nulový layout". designer.setLayout (null); Nový prvek přidáme do panelu metodou **add**. Pomocí **setBounds** nastavíme souřadnici a velikost prvku. designer.add (comp); comp.setBounds (point.x, point.y, 100, 100); Ještě otestujeme zda prvek //comp// není např. typu //JButton// \\ a provedeme drobné úpravy aby byl prvek k poznání. if (comp instanceof JButton) ==== Celá funkce addDesignerObject ==== private void addDesignerObject (Point point, String toolName) { Object obj = null; try { toolName = "javax.swing." + toolName; Class cls = Class.forName (toolName); obj = cls.newInstance(); // System.out.println ("new object " + obj); } catch (ClassNotFoundException ex) { System.out.println (ex); } catch (InstantiationException ex) { System.out.println (ex); } catch (IllegalAccessException ex) { System.out.println (ex); } if (obj instanceof Component) { Component comp = (Component) obj; designer.add (comp); comp.setBounds (point.x, point.y, 100, 100); if (comp instanceof JButton) { ((JButton)comp).setText ("text"); // System.out.println ("button"); } if (comp instanceof JTree) { // ((JTree)comp).setMinimumSize(new Dimension (100, 100)); // System.out.println ("tree"); } if (comp instanceof JTable) { JTable table = (JTable) comp; DefaultTableModel m = (DefaultTableModel) table.getModel (); m.setColumnCount (2); m.addRow (new String [] {"1", "abc" }); m.addRow (new String [] {"2", "klm" }); // System.out.println ("table"); } if (comp instanceof JTextArea) { ((JTextArea)comp).setText("some text"); // System.out.println ("edit"); } } } ===== Celý program ===== package designer; import java.awt.*; import java.awt.datatransfer.*; import java.awt.dnd.*; import java.awt.event.*; import java.io.*; import javax.swing.*; import javax.swing.table.*; public class DesignerWindow extends javax.swing.JFrame { public DesignerWindow () { initComponents (); addComponents (); addColors (); DesignerDropListener listener = new DesignerDropListener (); DropTarget target = new DropTarget (designer, listener); designer.setDropTarget (target); designer.setLayout (null); } /* Tooolbar */ class LocalMouseMotion extends MouseMotionAdapter { public void mouseDragged (java.awt.event.MouseEvent evt) { JButton button = (JButton) evt.getSource (); TransferHandler handle = button.getTransferHandler (); handle.exportAsDrag (button, evt, TransferHandler.COPY); } } private void addComponent (String name) { JButton btn = new JButton (); btn.setText (name); btn.setTransferHandler (new TransferHandler ("text")); btn.addMouseMotionListener (new LocalMouseMotion ()); componentToolbar.add (btn); } private void addComponents () { addComponent ("JButton"); addComponent ("JTree"); addComponent ("JTable"); addComponent ("JTextArea"); } private void addColor (Color c) { JButton btn = new JButton (); btn.setText (" "); btn.setBackground (c); btn.setTransferHandler (new TransferHandler ("background")); btn.addMouseMotionListener (new java.awt.event.MouseMotionAdapter () { public void mouseDragged(java.awt.event.MouseEvent evt) { JButton button = (JButton) evt.getSource(); TransferHandler handle = button.getTransferHandler(); handle.exportAsDrag (button, evt, TransferHandler.COPY); } } ); colorToolbar.add (btn); } private void addColors () { addColor (new Color (255, 0, 0)); addColor (new Color (0, 255, 0)) ; addColor (new Color (0, 0, 255)) ; addColor (new Color (255, 255, 0)) ; } /* Designer */ class DesignerDropListener implements DropTargetListener { public void drop (DropTargetDropEvent evt) { // System.out.println ("drop"); Transferable transfer = evt.getTransferable(); DataFlavor [] flavors = transfer.getTransferDataFlavors (); /* for (DataFlavor f : flavors) { System.out.println ("flavor " + f); System.out.println ("class " + f.getRepresentationClass ()); } */ if (flavors.length == 1) { DataFlavor flavor = flavors[0]; Class cls = flavor.getRepresentationClass (); Object data = null; try { data = transfer.getTransferData (flavor); } catch (UnsupportedFlavorException ex) { System.out.println (ex); } catch (IOException ex) { System.out.println (ex); } if (data != null) { Point point = evt.getLocation (); if (cls == String.class) { addDesignerObject (point, data.toString ()); } if (cls == Color.class) { Color color = (Color) data; Component comp = designer.getComponentAt (point); comp.setBackground (color); } } } }; public void dragEnter(DropTargetDragEvent dtde) { } public void dragOver(DropTargetDragEvent dtde) { } public void dropActionChanged(DropTargetDragEvent dtde) { } public void dragExit(DropTargetEvent dte) { } }; private void addDesignerObject (Point point, String toolName) { Object obj = null; try { toolName = "javax.swing." + toolName; Class cls = Class.forName (toolName); obj = cls.newInstance(); // System.out.println ("new object " + obj); } catch (ClassNotFoundException ex) { System.out.println (ex); } catch (InstantiationException ex) { System.out.println (ex); } catch (IllegalAccessException ex) { System.out.println (ex); } if (obj instanceof Component) { Component comp = (Component) obj; designer.add (comp); comp.setBounds (point.x, point.y, 100, 100); if (comp instanceof JButton) { ((JButton)comp).setText ("text"); // System.out.println ("text"); } if (comp instanceof JTree) { // ((JTree)comp).setMinimumSize(new Dimension (100, 100)); // System.out.println ("tree"); } if (comp instanceof JTable) { JTable table = (JTable) comp; DefaultTableModel m = (DefaultTableModel) table.getModel (); m.setColumnCount (2); m.addRow (new String [] {"1", "abc" }); m.addRow (new String [] {"2", "klm" }); // System.out.println ("table"); } if (comp instanceof JTextArea) { ((JTextArea)comp).setText("some text"); // System.out.println ("edit"); } } } private void quitClick(java.awt.event.ActionEvent evt) { System.exit (0); } public static void main (String args[]) { /* Do not use Nimbus look and feel */ ... } } {{dnd.png}} [[https://gitlab.fjfi.cvut.cz/culikzde/java-priklady/-/tree/master/Designer|gitlab]]