Obsah
zápočet -- zapíšeme před zkouškou, nechoďte za RL
zkouška -- termín 2. týden ve zkouškovém období -- který den?, (RL můze kdykoliv)
http://java.sun.com/docs/books/tutorial/uiswing/dnd/index.html
používaji se zkratky:
DnD = Drag and Drop
CCP = Copy Cut Paste
DnD a CCP umožňují přednášet data mezi aplikacemi (i jinak než pomocí souborů)
Následující obrázek ukazuje co se děje při přetáhnutí položky z JList do JTextField.
šipky ukazují data-flow
co přesně se během DnD děje:
uživatel stiskne tlačítko myši nad položkou v JList (source) a pohne myší -- tím iniciuje táhnutí (drag gesture)
na začátku drag gesture
JList
připraví data (táhnutou pložku) na
export a deklaruje jaké
source actions
JList
(coby původce dat) podporuje -- například
COPY
, MOVE
či
LINK
během drag gesture
uživatel může mačkat Shift a/nebo Control klávesu a tím
měnit user action, která je
také součástí drag gesture;
typicky je sámantika táhnutí MOVE
a při stisku
Control klávesy se mění na COPY
jak je táhnuto přes komponenty, komponenty (target) jsou neustále dotazovány, zda přijímají táhnutá data nebo ne a zobrazují odpovídající vizuální zpětnou vazbu -- například zobrazují kurzor na místě kam by data mohla být vložena
po uvolnění myši cílová komponenta zjistí jaké byly deklarovány source actions a user actions a vybere si z nabízených možností
nakonec cílová komponenta importuje data a případně se provede akce nad source komponentou
to všechno za nás udělá Swing, programátoři mohou tu a tam něco pozměnit, pokud chtějí, ale DnD jako celek prostě funguje "sám od sebe"
Přenášení dat přes clipboard (česky: schránku)
řada komponent podporuje DnD přímo
metody pro přenos dat jsou deklarovány v JComponent
následující komponenty mají defaultní podporu pro
drag gesture a není třeba nic
doprogramovat; stačí podporu aktivovat zavoláním metody
setDragEnabled(true)
JColorChooser
, JEditorPane
,
JFileChooser
, JFormattedTextField
,
JList
, JTable
, JTextArea
,
JTextField
, JTextPane
,
JTree
následující komponenty mají defaultní podporu pro drop a není třeba nic doprogramovat
JEditorPane
,
JFormattedTextField
, JPasswordField
,
JTextArea
, JTextField
,
JTextPane
, JColorChooser
u třech komponent se musí dopsat něco málo kódu:
JList
, JTable
, JTree
přenos dat zprostředkovává třída TransferHandler
a její pomocné třídy viz http://java.sun.com/docs/books/tutorial/uiswing/dnd/transferhandler.html
výše uvedené komponety mají již implementovaný TransferHandler
na přiřazení/aktivaci TransferHandler
u se užívaji
tři metody
setDragEnabled(boolean)—
viz výše
setDropMode(DropMode)
— viz níže
setTransferHandler(TransferHandler)—
pro
nastavení vlastní implementace exportu a importu
trojice metod
getSourceActions(JComponent)
— vrací akce
které komponenta podporuje -- nějakou kombinaci z
COPY
, MOVE
a
LINK
createTransferable(JComponent)
— vytvoří
objekt s daty typovaný na Transferable
rozhraní
exportDone(JComponent, Transferable, int)
—
definuje co se má udělat po skončení přesunu -- např. mazání
položky JList
u při MOVE
příklad implementace:
int getSourceActions(JComponent c) { return COPY_OR_MOVE; } Transferable createTransferable(JComponent c) { return new StringSelection(c.getSelection()); } void exportDone(JComponent c, Transferable t, int action) { if (action == MOVE) { c.removeSelection(); } }
řada pomocných metod pro získání informací z
TransferHandler
, cílové komponety a
Transferable
dat
http://java.sun.com/docs/books/tutorial/uiswing/dnd/transfersupport.html
dvě metody
canImport(TransferHandler.TransferSupport)
Swing cyklicky volá tuto metodu komponenty nad kterou je právě prováděněno táhnutí
vrací true/false podle toho, zda komponenta pod
kurzorem je schopna přijmout Transferable
data
pokud vraci true, komponenta poskutuje vizuální zpětnou vazbu — jaká ta zpětná vazba je zavisí na komponentě a na táhnutých datech — například ukazuje kurzor v textu tam kam se vkopíruje táhnutý text
příklad pro tabulku: http://java.sun.com/docs/books/tutorial/uiswing/dnd/locsensitivedrop.html
public boolean canImport(TransferHandler.TransferSupport info) {
// ...
JTable.DropLocation dl = (JTable.DropLocation)info.getDropLocation();
int column = dl.getColumn();
// we do not support invalid columns or the first column of table
if (column == -1 || column == 0) {
return false;
}
return true;
}
a demo s kódem: http://java.sun.com/docs/books/tutorial/uiswing/dnd/locsensitivedemo.html
importData(TransferHandler.TransferSupport)
— voláno po uvolnění myši, zahájí přenos dat
příklad
public boolean canImport(TransferSupport supp) { // Check for String flavor if (!supp.isDataFlavorSupported(stringFlavor)) { return false; } // Fetch the drop location DropLocation loc = supp.getDropLocation(); // Return whether we accept the location return shouldAcceptDropLocation(loc); } public boolean importData(TransferSupport supp) { if (!canImport(sup)) { return false; } // Fetch the Transferable and its data Transferable t = supp.getTransferable(); String data = t.getTransferData(stringFlavor); // Fetch the drop location DropLocation loc = supp.getDropLocation(); // Insert the data at this location insertAt(loc, data); return true; }
co dělat s daty po přetažení na komponentu?
konstanty USE_SELECTION
, ON
,
INSERT
a ON_OR_INSERT
http://java.sun.com/docs/books/tutorial/uiswing/dnd/dropmodes.html
demo: http://java.sun.com/docs/books/tutorial/uiswing/dnd/dropmodedemo.html
určuje kam přesně se mají vložit/nakopírovat data, což závisí na cílové komponentě
Tabulka 1.1. DropLocation vnitřní třídy pro JList, JTree, JTable a JTextComponent třídy
JList | JTree | JTable | JTextComponent |
isInsert | getChildIndex | isInsertRow | getIndex |
getIndex | getPath | isInsertColumn | getBias |
getRow | |||
getColumn |
TRIK: Pro přetáhnutí dat do
prázdné tabulky je třeba použít trik s
JTable.setFillsViewportHeight(true)
. Tento trik není
třeba používat pro prázdný JList
nebo
JTree
kde vše funguje tak jak má.
přidání CCP je triviální
je potřeba
ujistit se, že komponenta má aktivní TransferHandler (opakuji, že řada komponent má nějaký TransferHandler defaultně)
rozhodnout se jak se CPP bude ovládat -- většinou pomocí klávesových zkratek
(volitelně) lze připravit menu a/nebo tlačítka pro CCP
rozhoudnout se kam se mají data vkopírovat
(paste) -- popíšeme to kódem v
importData()
textové komponenty používají DefaultEditorKit
s
vestavěnou podporou CCP
takže pro textové komponenty není třeba dělat vůbec nic
pokud chcete tlačítka (viz bod 3 výše), pak takhle:
/** * Create an Edit menu to support cut/copy/paste. */ public JMenuBar createMenuBar () { JMenuItem menuItem = null; JMenuBar menuBar = new JMenuBar(); JMenu mainMenu = new JMenu("Edit"); mainMenu.setMnemonic(KeyEvent.VK_E); menuItem = new JMenuItem(new DefaultEditorKit.CutAction()); menuItem.setText("Cut"); menuItem.setMnemonic(KeyEvent.VK_T); mainMenu.add(menuItem); menuItem = new JMenuItem(new DefaultEditorKit.CopyAction()); menuItem.setText("Copy"); menuItem.setMnemonic(KeyEvent.VK_C); mainMenu.add(menuItem); menuItem = new JMenuItem(new DefaultEditorKit.PasteAction()); menuItem.setText("Paste"); menuItem.setMnemonic(KeyEvent.VK_P); mainMenu.add(menuItem); menuBar.add(mainMenu); return menuBar; }
netextové komponenty nemají DefaultEditorKit
s
vestavěnou podporou CCP a proto je s nimi víc práce
je třeba přidat CCP akce do action map
private void setMappings(JList list) { ActionMap map = list.getActionMap(); map.put(TransferHandler.getCutAction().getValue(Action.NAME), TransferHandler.getCutAction()); map.put(TransferHandler.getCopyAction().getValue(Action.NAME), TransferHandler.getCopyAction()); map.put(TransferHandler.getPasteAction().getValue(Action.NAME), TransferHandler.getPasteAction()); }
pokud implementujeme Edit menu, nezapomeneme na klávesvé zkratky (nepovinně)
menuItem = new JMenuItem("Cut");
menuItem.setActionCommand((String)TransferHandler.getCutAction().
getValue(Action.NAME));
menuItem.addActionListener(actionListener);
menuItem.setAccelerator(
KeyStroke.getKeyStroke(KeyEvent.VK_X, ActionEvent.CTRL_MASK));
menuItem.setMnemonic(KeyEvent.VK_T);
mainMenu.add(menuItem);
je třeba zaregistrovat klavesové zkratky (tento krok přeskočíte, pokud jste registrovali menu akcelerátor v nepovinném kroku 2)
InputMap imap = this.getInputMap(); imap.put(KeyStroke.getKeyStroke("ctrl X"), TransferHandler.getCutAction().getValue(Action.NAME)); imap.put(KeyStroke.getKeyStroke("ctrl C"), TransferHandler.getCopyAction().getValue(Action.NAME)); imap.put(KeyStroke.getKeyStroke("ctrl V"), TransferHandler.getPasteAction().getValue(Action.NAME));
je třeba definovat, která komponenta by měla reagovat na CCP, když uživatel stiskne Ctrl+něco
Pro textové komponenty si
DefaultEditorKit
pamatuje, která komponenta
měla naposledy aktivní (měla
focus) a tato komponentu
zareaguje
Pro netextové komponenty lze použít následující třídu, která dělé to samé:
public class TransferActionListener implements ActionListener, PropertyChangeListener { private JComponent focusOwner = null; public TransferActionListener() { KeyboardFocusManager manager = KeyboardFocusManager. getCurrentKeyboardFocusManager(); manager.addPropertyChangeListener("permanentFocusOwner", this); } public void propertyChange(PropertyChangeEvent e) { Object o = e.getNewValue(); if (o instanceof JComponent) focusOwner = (JComponent)o; else focusOwner = null; } public void actionPerformed(ActionEvent e) { if (focusOwner == null) return; String action = (String)e.getActionCommand(); Action a = focusOwner.getActionMap().get(action); if (a != null) { a.actionPerformed(new ActionEvent(focusOwner, ActionEvent.ACTION_PERFORMED, null)); } } }
konečně, je třeba definovat jak se má zpracovat vložení (paste) operace
v případě drop operace se místo vložení odvozovalo od pozice myši
v případě paste tato
infomace chybí a musíme vymyslet něco co dává smysl v
kontextu konkrétní aplikace -- viz
else větev v následujícím
kódu importData
zde například vložená položka vsune za vybraou
položku JList
u nebo nakonec, pokud žádná
položka není vybrána:
public boolean importData(TransferHandler.TransferSupport info) {
String data = null;
...
if (info.isDrop()) { //This is a drop
JList.DropLocation dl =
(JList.DropLocation)info.getDropLocation();
int index = dl.getIndex();
if (dl.isInsert()) {
model.add(index, data);
return true;
} else {
model.set(index, data);
return true;
}
} else { // This is a paste
int index = list.getSelectedIndex();
// if there is a valid selection,
// insert data after the selection
if (index >= 0) {
model.add(list.getSelectedIndex()+1, data);
// else append to the end of the list
} else {
model.addElement(data);
}
return true;
}
}
toto bylo jediné místo v programu, kde je třeba
napsat if-else
kód pro rozlišení DnD a CCP (a
i to je v princupu nutné dělat jen tehdy, když program
podporuje jak DnD tak CCP)
třída která určuje jakého typu jsou data
používá se např. v importData()
tři nejčastěji používané jsou již předdefinované
imageFlavor
-- reprezentuje data v
java.awt.Image
formátu.
stringFlavor
-- reprezentuje textová data
java.lang.String
javaFileListFlavor
representuje
java.io.File
objekty v java.util.List
formátu; užitečné v aplikacích kam chcete nechat přetáhnout
soubory s nějakého souborového mamžeru nebo např. z pracovní
plochy
výše uvedené většinou stačí a tak se numusíte o
DataFlavor
více starat
když nestačí, protože například používáte nějakou speciální
třídu, použijete vlastní DataFlavor
konstrutor je DataFlavor(Class,
String)
použití pro Vaši třídu Osoba: new
DataFlavor(Osoba.class, "Osoba");
pro pole celých čísel: new DataFlavor(int[].class,
"Interger Array");
pro přenos dat se vnitřně používá
serializace a proto přenášená data (např.
Osoba
) musí implementovat rozhraní http://java.sun.com/javase/6/docs/api/java/io/Serializable.html