Drag and Drop Java with Swing Worker - java

I want to create a DND action from a JList to the OS. My solution for now is to use a TransferHandler. In the method createTransferable I create the Transferable with the files I want to copy. But now there is my Problem: in some cases I have to download the files from a FTP-Server before I can copy the files. The very heavy download operation runs in a JavaSwingWorker (hidden behind the statement d.download(tmpDir);). Now the system trys to copy files which are not downloaded already.
Now i need a mechanism that allows me to create the transferable after I have downloaded the files. Is there a solution for my problem? Please help me!
Thanks!
Here is my method:
public Transferable createTransferable(JComponent c) {
JList list = (JList) c; // we know it's a JList
List<PictureDecorator> selectedPictures = getSelectedValues(list.getModel());
Vector cpFiles = new Vector();
List<Picture> donePictures = new ArrayList<Picture>();
List<Picture> notDonePictures = new ArrayList<Picture>();
String tmpDir = System.getProperty("java.io.tmpdir");
for(PictureDecorator pd : selectedPictures){
if(pd.getPic().getStatus() == PictureStatus.DONE)
donePictures.add(pd.getPic());
else
notDonePictures.add(pd.getPic());
}
Downloader d = new Downloader(parent, loginInformation, sced, donePictures, order);
d.download(tmpDir);
for(Picture p : donePictures){
cpFiles.add(new File(tmpDir + File.separator + p.getPicture().getName()));
}
for(Picture p : notDonePictures) {
cpFiles.add(p.getPicture());
}
TransferableFile tf = new TransferableFile(cpFiles);
return tf;
}
I need something that initiates the drag procedure then I get the path where the drag goes and then I can download the pictures and copy it to the destination path.
EDIT: Or another formulation: How I can find out the drop destination when I drop into the operating system?

To start the drag you need either a TransferHandler on the JList or alternatively a DragSource in combination with a DragGestureListener. Below you can see an example for doing that with a JTextField:
final JTextField textField = new JTextField(50);
DragGestureListener dragListener = new DragGestureListener() {
#Override
public void dragGestureRecognized(DragGestureEvent dge) {
// how the drag cursor should look like
Cursor cursor = Cursor.getPredefinedCursor(Cursor.HAND_CURSOR);
// the component being dragged
JTextField tf = (JTextField) dge.getComponent();
// Here a Transferable is created directly for a single file name
dge.startDrag(cursor, new TransferableFile(tf.getText()));
}
};
final DragSource ds = new DragSource();
ds.createDefaultDragGestureRecognizer(textField, DnDConstants.ACTION_COPY, dragListener);
You can put the above code inside your window creation procedure.
Your resulting transferable (TransferableFile in your case) should support the DataFlavor.javaFileListFlavor and you should return a List of Files from the getTransferData(DataFlavor flavor) method.
I believe this is also the method where the downloading should take place because that's the last point under your control before JVM-OS take over.
Now regarding the SwingWorker problem you can wait inside the method until the download completes. Perhaps modify your Downloader class to expose a boolean flag so you would be able to do something like while (!downloader.isDone()) { Thread.sleep(millisToSleep) };
[Edit: I must admit I don't like the idea of keeping the EventDispath thread busy but if this solves your current problem perhaps you can investigate later a more elegant solution]
A little warning: Since you don't have access to the drop location you cannot know how many times the getTransferData will be called. It is better to take this into account and create a simple cache (a Map sounds reasonable) with the temp files you have downloaded so far. In case you find the file in the cache you return its corresponding temp file directly and don't download it again.
Hope that helps

Related

How to distinguish between copy/move in importData

I need to get action type during drag and drop or copy/cut and paste (copy vs. move). It is a Swing application and there is implemented TransferHandle. I need this information at the end of the action, in importData method.
For drag and drop it seems to be possible test getUserDropAction, like this
#Override
public boolean importData(final TransferSupport support) {
if(support.isDrop() && support.getUserDropAction() == TransferHandler.MOVE) {
// drag and drop, MOVE
}
}
...but how to get this information for cut/copy and paste? Or is there a better, universal way?
Maybe you can get enough tips from the Swing tutorial which contains a working example:
CCP in a non-Text Component (The Java™ Tutorials > Creating a GUI With JFC/Swing > Drag and Drop and Data Transfer)
ListTransferHandler.java
Whether it is drag and drop or copy/cut and paste can be determined by the TransferSupport#isDrop() method.
/**
* Perform the actual data import.
*/
public boolean importData(TransferHandler.TransferSupport info) {
// ...
if (info.isDrop()) { // This is a drop
// ...
} else { // This is a paste
// ...
}
Use the the TransferHandler#exportDone(...) method's int action argument to determine if it is copy and paste or cut and paste.
Since the paste is executed with the TransferHandler#importData(...) method, override the TransferHandler#exportDone(...) method that is called after the paste is completed, and if the action of the argument is TransferHandler.MOVE, cut the transfer source data.
If the action is anything other than TransferHandler.MOVE, there is no need to do anything because it is a copy.
/**
* When the export is complete, remove the old list entry if the
* action was a move.
*/
protected void exportDone(JComponent c, Transferable data, int action) {
if (action != MOVE) {
return;
}
JList list = (JList)c;
DefaultListModel model = (DefaultListModel)list.getModel();
int index = list.getSelectedIndex();
model.remove(index);
}

Repurposing JFileChooser

I need to implement file browsing feature in my app and while I am aware of possibility of making a JList item and doing it manually I had an idea to just implement JFileChooser for this. I managed to reduce the JFileChooser just to list of directories and files but I wasn't able to override some of it's functionalities. I have been going through source code but no luck. My idea is for it to handle as following: On the top of the list to have a /... directory so when clicked on it it returns to parent folder. Also when double clicked on directory it sets it as current directory. When double clicked on file it returns the file as selected.
This is the code I used so far:
final JFileChooser fc = new JFileChooser();
fc.setControlButtonsAreShown(false);
fc.setCurrentDirectory(paths[list.getSelectedIndex()]);
/*remove unwanted components*/
for(int i = 0; i < fc.getComponentCount(); i++) {
fc.getComponent(0).setVisible(false);
fc.getComponent(1).setVisible(false);
fc.getComponent(3).setVisible(false);
}
add(fc, BorderLayout.CENTER);
I tried adding custom MouseListener to the JFileChooser but it didn't work.
This is the result I have so far:
Any idea which classes or listeners to overwrite/replace so I can achieve 2 desired effects?
This is what I am looking for in visual terms:
On the top of the list to have a /... directory so when clicked on i
it returns to parent folder.
JFileChooser has method with name changeToParentDirectory(). So you could simply add a Button and call that method.
JButton toParent = new JButton("/..");
toParent.addActionListener(new ActionListener(){
#Override
public void actionPerformed(ActionEvent a) {
fc.changeToParentDirectory();
}
});
fc.add(toParent, BorderLayout.NORTH);
Also when double clicked on directory it sets it as current directory.
You can set a PropertyChangeListener to listen for JFileChooser.DIRECTORY_CHANGED_PROPERTY property that is fired whenever the current directory has changed using double click or the internal commands.
fc.addPropertyChangeListener(new PropertyChangeListener(){
#Override
public void propertyChange(PropertyChangeEvent e) {
String command = e.getPropertyName();
if (command.equals(JFileChooser.DIRECTORY_CHANGED_PROPERTY)) {
File currentDir = fc.getCurrentDirectory();
System.out.println(currentDir.getAbsolutePath());
}
}
});
When double clicked on file it returns the file as selected.
You can set an ActionListener to listen for JFileChooser.APPROVE_SELECTION action that is fired whenever a file is chosen by double click.
fc.addActionListener(new ActionListener(){
#Override
public void actionPerformed(ActionEvent e) {
String command = e.getActionCommand();
if (command.equals(JFileChooser.APPROVE_SELECTION)) {
File selectedFile = fc.getSelectedFile();
System.out.println(selectedFile.getAbsolutePath());
}
}
});
EDIT:
you misunderstood me for the parent folder action. Here is an image
to describe it.
This could be achieved by using a manipulated FileSystemView with the JFileChooser that is responsible for interacting with the content of the filesystem. My implementation manipulates the getFiles method to smuggle a special File in the list that has a defined name and points to the parent directory.
I'm not quite sure if this is a very good idea, since this is really not meant to be in the JFileChooser Code but here we go.
fc.setFileSystemView(new FileSystemView(){
// this method is abstract but since you don't
// want to create directories here you don't
// need to implement it.
#Override
public File createNewFolder(File f) throws IOException {
return null;
}
// manipulate the default getFiles method that creates
// the list of files in the current directory
#Override
public File[] getFiles(File dir, boolean useFileHiding){
// get the list of files from default implementation
File[] files = super.getFiles(dir,useFileHiding);
// get the parent directory of current
File parent = getParentDirectory(dir);
// skip the next for problematic folders with
// empty names and root folders
if(!dir.getName().isEmpty() && !isRoot(dir)){
// create a new list of files with one extra place
File[] nfiles = new File[files.length + 1];
// add a special file to list that points to parent directory
nfiles[0] = new File(parent.getAbsolutePath()){
// set a special name for that file
#Override
public String getName(){return "...";}
};
// add the rest of files to list
for(int i = 0; i < files.length; i++)
nfiles[i+1] = files[i];
// use the new list
files = nfiles;
}
// return list of files
return files;
}
// some special folders like "c:" gets converted
// in shellfolders.Then our setted name "..." would
// get converted to "local drive (c:)". This garantees
// that our setted name will be used.
#Override
public String getSystemDisplayName(File f) {
return f.getName();
}
});
EDIT2:
is there a quick solution to set scroll from horizontal to vertical
for JFileChooser?
There are two possibilities. The simple one is to change the style of your JFileChooser to the Details View by adding this code:
Action details = fc.getActionMap().get("viewTypeDetails");
details.actionPerformed(null);
The more complex one is to change the LayoutOrientation of the file view JList that is a component of sun.swing.FilePane that is the Component with ID:2 in the JFileChooser.
One problem here is that FilePane is not part of the Java Library but part of the Core Library and not accessible by default. But you can use Reflection to get the field private JList list; in the FilePane and change its LayoutOrientation to JList.VERTICAL with this code:
// get the FilePane Component of JFileChooser
Object filepane = fc.getComponent(2);
// get the list field with reflection
Field field_list = filepane.getClass().getDeclaredField("list");
// get access to this private field
field_list.setAccessible(true);
// read the value of the field
JList<?> list = (JList<?>)field_list.get(filepane);
// change the layout orientation
list.setLayoutOrientation(JList.VERTICAL);

JList only updating on first insertion

This is my first attempt at a decent GUI for a Java app and I needed to use JLists with custom ListModels in order to represent certain structures.
//The 2 below structures implement the ListModel interface, using an internal
//ArrayList, in order to be used as
//a model for 2 different JLists in my GUI.
private PropertyList propertiesList = new PropertyList();
private SelectedProperties selProperties = new SelectedProperties();
//and these are the two JLists they are the models for
private javax.swing.JList Properties_JList;
private javax.swing.JList SelectedProperties_JList;
Here I populate my first JList via a stream:
private void OpenFile_MenuItemActionPerformed(java.awt.event.ActionEvent evt) {
final JFileChooser fc = new JFileChooser();
fc.setCurrentDirectory(null);
int returnVal = fc.showOpenDialog(this);
if (returnVal == JFileChooser.APPROVE_OPTION) {
File file = fc.getSelectedFile();
this.Properties_JList.setModel(propertiesList);
this.propertiesList.AddFromFile(file);
} else {
//...
}
}
which happens to be working perfectly fine. I import a few entries by reading the file and they are all displayed as expected in a .toString() representation.
The problem is the second JList:
private void AddToSelected_JButtonActionPerformed(java.awt.event.ActionEvent evt) {
Property p = (Property) this.Properties_JList.getSelectedValue();
this.SelectedProperties_JList.setModel(selProperties);
this.selProperties.InsertProperty(p);
this.SelectedProperties_JList.revalidate();
}
Which appears to be displaying only the very first item I attempt to add to it through the above button event, and I have no idea why. I considered moving both .setModel(...) calls right after the form's initComponents() call but if I do that none of the lists gets populated, at all.
Logging messages made it clear that the internal structures are getting populated, but even though they are both respective ListModels for my JLists, one of them isn't working as expected.
A sufficient portion of the code is generated by Netbeans and I have spent hours looking up the API but still have trouble finding out what I'm doing wrong. Any ideas?

Java - Updating JTable with fireTableDataChanged();

I have a JTable in my program. I want to update it after clicking JButton.
I wrote this:
DefaultTableModel myTable = new DefaultTableModel(celDatas,celNames);
JTable source = new JTable(myTable){public boolean isCellEditable(int rowIndex, int colIndex) {
return false;}};
JScrollPane pane = new JScrollPane(source);
(...)
start.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e)
{
query = "Select sal FROM EMP";
myTable =(DefaultTableModel)source.getModel();
myTable.fireTableDataChanged();
}
The problem is that id doesn't update my data on JTable.
How to resolve this problem?
EDIT:
JTable is displayed in my guy through JScrollPane.
I make now this:
source = new JTable(myTable){public boolean isCellEditable(int rowIndex, int colIndex) {return false;}};
pane = new JScrollPane(source);
I made also a new void, where is getting datas from database + there I define myTable:
void queryConnection() {
(...)
myTable = new DefaultTableModel(celDatas,celNames);
}
I added a JButton, which update my JTable (when we change the query.
start.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e)
{
query = "Select sal FROM EMP";
queryConnection();
}
}
================================================================================
public class Application2 implements Runnable {
private JTable source;
private JScrollPane pane;
private DefaultTableModel myTable;
private JPanel panel;
private String[][] celDatas = null;
private String[] celNames = null;
public void run() {
(...)
start.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e)
{
if(...) {
query = "Select sal FROM EMP";
queryConnection();
}
}
if(query == null) {
query = "Select * from EMP";
queryConnection();
}
source = new JTable(myTable){public boolean isCellEditable(int rowIndex, int colIndex) {return false;}};
pane = new JScrollPane(source);
pane.setSize(f.getSize().width-60,300);
pane.setLocation(30,20);
panel.add(pane);
f.add(panel);
f.pack();
}
void queryConnection() {
//here is connection and downloading datas
myTable = new DefaultTableModel(celDatas,celNames);
}
I hope that it is right now more convenient? ;)
You don't need to call any fireXXX() methods if you're creating a new TableModel, but rather you mainly need to call these if you're writing your TableModel based on AbstractTableModel (you're not).
Your problem lies elsewhere I think. You seem to be creating a new JTable and adding it to a new JScrollPane. Are you adding these components to your GUI (you don't show)? If so, is the container that you're adding to able to accept new components smoothly? Do you revalidate and repaint the container? Is your newly created JTable being displayed in the GUI?
Or, is there a JTable already displayed in your GUI? If so, perhaps all you want to do is set its model rather create a new JTable. I favor this solution as the easiest.
Edit 1
I would do something like so:
Access Database in a background thread and get information from it.
Create a new DefaultTableModel object filling it with data extracted from the database.
I would not create a new JTable if one already exists. Instead I'd simply call setTableModel(...) on the currently displayed JTable and pass in my newly created DefaultTableModel object.
Then I'd sit back and enjoy the big bucks and other fruits of my success.
Edit 2
Ganjira, for us to be able to best help you, we need to understand the problem better, which is why I've requested either full clarification of your problem or an sscce, of which I've seen neither. My problems with understanding your issues include:
Again, do you have a JTable already displayed in your GUI, and now you're trying to change the data that is displayed in the JTable based on data extracted from a database?
If so, why not simply change the model of the existing JTable rather than create a whole new JTable?
If you don't already have a JTable in the application and now you want to display one, you state that your data is not being displayed but don't show code to help us understand why.
If you can answer the first two questions, we may not need an sscce, but if not, please understand that your current post is no where close to being an sscce, in that we cannot run it, we cannot compile it, it doesn't reproduce your problem for us,... I have to wonder if you've even read the link yet as it explains why all of these conditions are important (that and brevity so as not to drown us in a large amount of unrelated code).
No, it is not required that you post an SSCCE, but if we can't help you based on the text in your question and your code snippets, it does offer a better chance of allowing us to understand the problem and find a solution for you.
Edit 3
You state:
JTable's been already in GUI, because it shows the 'beginning of the program'. After clicking JButton I want to change values in JTable. It doesn't change the model of the existing JTable - it doesn't change nothing.
And this confuses me as the best solution is one I've been suggesting all along -- Don't make a new JTable, but instead create a new DefaultTableModel and change the model for the existing JTable. You've yet to mention why you're not trying this, or if you have tried it, how it's not working for you.

The elements not added to JList

I am trying to add file names to my JList but without success. Here is the piece of the code:
DefaultListModel model = new DefaultListModel();
listLayer.setModel(model);
model.addElement(file.getName());
listLayer is a JList into which I would like to add file name. For information, I am writing my GUI application in netBeans so I can not create a new JList object within this code as it was already created automatically when added JList to my layout. Therefore I can just access it through its methods.
Thanks a lot,
Michal.
-------------------------------------------------------------------------
Ok I will try to extend it more:
private void openActionPerformed(java.awt.event.ActionEvent evt) {
JFileChooser fileChooser = new JFileChooser("C:/");
FileFilter filter1 = new MyCustomFilter();
fileChooser.setFileFilter(filter1);
int returnVal = fileChooser.showOpenDialog(this);
if (returnVal == JFileChooser.APPROVE_OPTION) {
File file = fileChooser.getSelectedFile();
//String[] tokens = file.getName().split(".");
//String name = tokens[0];
DefaultListModel model = new DefaultListModel();
listLayer.setModel(model);
model.addElement(file.getName());
} else {
System.out.println("File access cancelled by user.");
}
}
and yes, my JList called listLayer is declared in non-modifiable section of the code like this:
private javax.swing.JList listLayer;
Thanks again for any help. Michal
Thanks for posting more code. Now quite possibly we can answer your question. A problem I see is that you're recreating a DefaultListModel each time the button is pressed and setting the JList with this new model effectively removing all data that was previously held by the list. A way to avoid doing this is to simply get the model that the JList already has, which should be a DefaultListModel, and add items to it. You will need to cast the object returned by getModel() since per the API, Java only knows this to be a ListModel object, and ListModel doesn't have the addElement(...) method that DefaultListModel does.
Something perhaps like so:
if (returnVal == JFileChooser.APPROVE_OPTION) {
File file = fileChooser.getSelectedFile();
        // note the cast since getModel() only returns a ListModel
DefaultListModel model = (DefaultListModel)listLayer.getModel(); // changed**
model.addElement(file.getName());
}
You recommend me to use not DefaultListModel, but ListModel?
I don't know enough about the rest of your program to say. If you have a single, relatively static JList, DefaultListModel may be perfect. If your program models a constantly changing selection of File instances, then you may want to implement ListModel or even a shared model, as shown here. The latter simply forwards some methods to the default implementation.

Categories

Resources