I have a main GUI See image that is used to load and display (thumbnail) images.
I want to make it possible to drag&drop images on my Gui as well.
So I copied an example and put it in a class
public static class FileDragDropListener implements DropTargetListener {
#Override
public void drop(DropTargetDropEvent event) {
DragDropListener DDL = new DragDropListener();
// Accept copy drops
event.acceptDrop(DnDConstants.ACTION_COPY);
// Get the transfer which can provide the dropped item data
Transferable transferable = event.getTransferable();
// Get the data formats of the dropped item
DataFlavor[] flavors = transferable.getTransferDataFlavors();
// Loop through the flavors
for (DataFlavor flavor : flavors) {
try {
// If the drop items are files
if (flavor.isFlavorJavaFileListType()) {
// Get all of the dropped files
List <File> files = (List) transferable.getTransferData(flavor);
//logger.info("length of list {}", files.size());
File[] imgs = new File[files.size()];
int counter = 0;
// Loop them through
for (File file : files) {
// Print out the file path
logger.info("File path is: {}", file.getPath());
imgs[ counter ] = file;
counter++;
}
MyVariables.setSelectedFiles(imgs);
}
} catch (Exception e) {
// Print out the error stack
e.printStackTrace();
}
}
// Inform that the drop is complete
event.dropComplete(true);
}
}
This drag&drop as such works fine. The code:
// Print out the file path
logger.info("File path is: {}", file.getPath());
lists a row of file paths, which I dragged upon my Gui, in the console so drag&drop as such works fine with the files.
I use a setter/getter MyVariables.setSelectedFiles(imgs); to make this File[] available "everywhere".
My problem is now to get this File[] with image paths back in my Gui main class immediately after the drop, so I can update the left panel in my Gui. For that updating I have a method public void LoadImages that is used from many parts in my program, which also touches multiple Gui elements, so I can't make that one static. For that I created the setter/getter, but how do I listen in my main Gui thread for the event.dropComplete(true); to act on it.
I tried many things like Observers, listeners etc., but I always get the non static method cannot be be referenced from a static context.
I do understand that, but how do I get my Gui notified after the drop event has finished, so that it can pick up the data using the getter?
I finally solved it.
The above mentioned FileDragDropListener in my first post is not necessary at all.
When I start my Gui in the main method, I call the below mentioned method rootPanelDropListener.
Note: rootPanel is the name of my entire main screen JPanel.
MyVariables.setSelectedFiles(droppedFilesArray); is a setter I use to be able, in a later stage, to retrieve the data "everywhere" in my program.
loadImages("dropped files"); is the method that loads the images (obvious) and has 3 options: by directory, by selecting (multiple) files, or by dropping the files onto the Gui. Inside the loadimages I check on the parameter "dropped files", then using the getter for the dropped files like files = MyVariables.getSelectedFiles();
public void rootPanelDropListener() {
//Listen to drop events
rootPanel.setDropTarget(new DropTarget() {
public synchronized void drop(DropTargetDropEvent evt) {
try {
evt.acceptDrop(DnDConstants.ACTION_COPY);
List<File> droppedFiles = (List<File>)
evt.getTransferable().getTransferData(DataFlavor.javaFileListFlavor);
for (File file : droppedFiles) {
logger.debug("File path is: {}", file.getPath());
}
File[] droppedFilesArray = (File[]) droppedFiles.toArray(new File[droppedFiles.size()]);
MyVariables.setSelectedFiles(droppedFilesArray);
loadImages("dropped files");
} catch (Exception ex) {
ex.printStackTrace();
logger.error("Drag drop on rootpanel error {}", ex);
}
}
});
}
Related
I have a ListView containing URLs. When a user click on one of the URL, a video is downloaded. I am calling the video download function within a Task which in turn is called in a Thread. A user can click on multiple video URL and the video would start to download. A separate Task would be created for each of the video. What i want to know is how to uniquely identify Task for each video?
Function to download video:
public void videoFileDownload(){
try {
videoDownloadUrl = lblURL.getText().toString();
IndexOfThisNode = hbox.getId();
String path = "XXXX";
downloadThisVideo = new VGet(new URL(videoDownloadUrl),new File(path));
downloadThisVideo.download();
System.out.println("Download this video: " + videoDownloadUrl + downloadThisVideo.getVideo().getState());
System.out.println("Download complete");
} catch (Exception ex) {
ex.printStackTrace();
System.out.println("Retrying...");
}
}
Function containing Task:
public void showDetailsButton(){
btnSMDetails.addEventHandler(MouseEvent.MOUSE_CLICKED, (e)->{
System.out.println("\n" + "The index is: " + getIndex() + "\n");
showLoader();
//Task created to download videos in background without blocking UI
Task downloadVideoTask = new Task<Void>() {
#Override
public Void call() {
//SIMULATE A FILE DOWNLOAD
videoFileDownload();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return null;
}
};
new Thread(downloadVideoTask).start();
downloadVideoTask.setOnSucceeded(taskFinishEvent ->{showLoader(); /*isButtonClicked="0";*/});
});
}
Listcells don't exist in a one to one relationship with the underlying list. There's only enough listcells instantiated to fill the viewport of the listview plus a couple extra. Data is swapped in and out of the listcells through the updateCell method.
So you can't store data in a listcell, since the cell will get reused for another list item if you scroll the list.
What you need to do is to store a reference to the task in the underlying list item. Modify your updateCell method to bind the visibility and value of your progress bar in the listcell to the task progress property.
I hope someone here can help. I am just trying to wrap my head around the Observer Design Pattern, Threading and how I can use both for a project I am doing.
I currently need to implement the both of them on a Media Player I am building using Java FX.
I need to use both of them to update my listView(Populated by a getNames function of files in my directory. I need any changes to my folder of songs to reflect straight away on the GUI.
Is it possible, to have a running thread constantly calling my getNames function(returns an items variable), and if there are any changes to the items variable then I can use the Observer pattern to notify my GUI class to update its list.
I know it's possible to have a thread constantly hitting the function, but I just need some advice on if its then possible to use the Observer pattern to notify on if the items have changed!
I have no code to show, as I am still trying to figure out how to implement this.
Any ideas?
Appreciate any advice at all! Thanks :)
UPDATE
After quite a long time, Got this working with threads and observer patterm. Didn't need WatchService. Used my thread to constantly call a check for change method, then if method returned through then Observer kicked in to update GUI.
Its possible to use this pattern , you need to run a thread to keep watch on folder for files update and to make this thread safe use eventQueue to run your thread
e.g.
java.awt.EventQueue.invokeLater or invokeAndWait
Once change is detected by the thread, then your observer pattern will update GUI
Hope this helps!!
The best approach to this (IMO) would be:
Consider this Oracle tutorial on the WatchService.
As you are using JavaFX, wrap "the basic steps required to implement a watch service" from that tutorial in a JavaFX Task.
Perhaps following the pattern from the Task javadoc "A Task Which Returns Partial Results" to feedback into your view any changes detected by the watch service.
As you note "unfortunately our lecturer won't let us use WatchService", then you can use a method like in the sample code below which is an active poll of the FileSystem. The use of the WatchService is definitely preferred as it can, internally within the JDK implementation, make use of OS provided file watch services. Those OS services can provide a notification of a file change event, so that the Java code does not need to actively poll the file system for changes. Nevertheless, the following inefficient job will likely suffice to do the job in this case...
What the code does is spawn a JavaFX task on a thread which polls the file system and modifies the observable list backing the ListView to match the files on the file system. The list modification is done within a Platform.runLater call to ensure that the modifications to the list backing the list view occur on the JavaFX application thread, so that the active scene graph is not modified off of the JavaFX application thread.
import javafx.application.*;
import javafx.collections.*;
import javafx.collections.transformation.SortedList;
import javafx.concurrent.Task;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.stage.Stage;
import java.io.File;
import java.nio.file.*;
import java.util.Arrays;
import java.util.Comparator;
public class FileWatcher extends Application {
private static final Path WATCH_DIR = Paths.get(System.getProperty("user.dir"));
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage stage) {
ObservableList<File> songFileList = FXCollections.observableArrayList();
SortedList<File> sortedSongFileList = new SortedList<>(
songFileList,
Comparator.comparing(File::getName)
);
ListView<File> songListView = new ListView<>();
songListView.setItems(sortedSongFileList);
songListView.setCellFactory(param -> new ListCell<File>() {
#Override
protected void updateItem(File item, boolean empty) {
super.updateItem(item, empty);
if (item == null || empty) {
setText(null);
return;
}
setText(item.getName());
}
});
SongWatcher watcher = new SongWatcher(
WATCH_DIR, songFileList
);
Thread watcherThread = new Thread(watcher, "song-watcher");
watcherThread.setDaemon(true);
watcherThread.start();
Scene scene = new Scene(songListView);
stage.setScene(scene);
stage.show();
}
class SongWatcher extends Task<Void> {
private static final String SONG_EXTENSION = "mp3";
private static final long POLL_INTERVAL_MILLIS = 200;
private final Path directory;
private final ObservableList<File> songFiles;
SongWatcher(Path directory, ObservableList<File> songFiles) {
this.directory = directory;
this.songFiles = songFiles;
}
#Override
protected Void call() {
System.out.println("Started watching " + directory + " for song file changes.");
while (!isCancelled()) {
try {
Thread.sleep(POLL_INTERVAL_MILLIS);
} catch (InterruptedException e) {
if (isCancelled()) {
break;
}
Thread.currentThread().interrupt();
}
try {
if (!Files.isDirectory(directory)) {
throw new Exception("Watched directory " + directory + " is not a directory.");
}
File[] foundFiles =
directory
.toFile()
.listFiles(
(dir, name) -> name.endsWith(SONG_EXTENSION)
);
if (foundFiles == null) {
throw new Exception("Watched directory " + directory + " find files returned null (this is not expected).");
}
Platform.runLater(() -> {
// remove files from the song list which are no longer on the disk.
songFiles.removeIf(checkedFile ->
Arrays.binarySearch(foundFiles, checkedFile) < 0
);
// add any files which are on the disk which are not in the song list.
for (File file: foundFiles) {
if (!songFiles.contains(file)) {
songFiles.add(file);
}
}
});
} catch (Exception e) {
e.printStackTrace();
}
}
return null;
}
#Override
protected void succeeded() {
System.out.println("Stopped watching " + directory + " for song file changes.");
}
#Override
protected void cancelled() {
System.out.println("Cancelled watching " + directory + " for song file changes.");
}
#Override
protected void failed() {
System.out.println("Failed watching " + directory + " for song file changes.");
if (getException() != null) {
getException().printStackTrace();
}
}
}
}
I would like to be able to display a list of files showing only the file name not the whole file path.
Currently I have a list of files. When I click one of these files the listener passes this to a method out of scope which loads the file.
This means if I was to just pass it a list of just file names it would no longer work as my listener requires a full file path. I do not have any ideas as to how I would go about both storing a list of filenames whilst simultaneously linking them to the full file path.
Happy to answer any questions you may have. Many thanks,
Note: the small for loop shows how I could potentially extract the filename from the file path, but I am not currently doing anything with it at this time. It's just an example to show you how far I have gotten.
public void GetFilesFromFolder(String dirName) throws IOException {
File dir = new File(dirName);
File[] files = dir.listFiles((File dir1, String filename) -> filename.endsWith(".mp3"));
String[] fileName = new String[files.length];
int x = 0;
for (File file : files) {
String fileTemp = file.toString();
fileTemp = fileTemp.substring(fileTemp.lastIndexOf("\\" + 1));
System.out.println(fileTemp);
fileName[x] = fileTemp;
System.out.println(fileName[x]);
x++;
}
observableList.clear();
observableList.addAll(files);
}
public void SetFileListView() throws IOException {
listView.setItems(null);
}
public VBox listStack() throws IOException {
vbox = new VBox();
vbox.getChildren().add(listView);
listView.setItems(observableList);
listView.setMinHeight(500);
MusicDataModel mdm = MainView.getMainView().musicDataModel;
MusicDataViewController mdv = MainView.getMainView().musicDataViewController;
listView.getSelectionModel().selectedItemProperty().addListener((ObservableValue<? extends File> observable, File oldValue, File newValue) -> {
try {
mdm.load(newValue.toString());
mdv.SetValues();
} catch (UnsupportedTagException | InvalidDataException | IOException | NotSupportedException ex) {
Logger.getLogger(FileListView.class.getName()).log(Level.SEVERE, null, ex);
}
});
return vbox;
}
Populate the list view with Files, as you currently do, and use a cell factory on the list view to change the way the file is displayed:
listView.setCellFactory(lv -> new ListCell<File>() {
#Override
protected void updateItem(File file, boolean empty) {
super.updateItem(file, empty);
setText(file == null ? null : file.getName());
}
});
This will ensure each cell in the list view displays only the file name (the last component of the file's full path), though it still retains the File instance as its data (so you can still get the selected File, etc).
I'm trying to create a button in game where the background color will go from light_gray to dark_gray. However when the application relaunches I have to re select the button to get the color back to dark_gray.
How would I have it so that it saves the color when the application is relaunched?
My code is very simple and is just an action listener on the button which then changes the bg color of selected items.
Ok, I have now had the chance to allow it to create the properties file but one doesn't know how one could store the data. I've seen people have stuff such as 'properties.setProperty("Favorite sport", "Football");'
But how could one have this so that it stores the bg color?
windowDark.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent ae)
{
try {
Properties properties = new Properties();
properties.setProperty();
File file = new File("DarkTheme.properties");
FileOutputStream fileOut = new FileOutputStream(file);
properties.store(fileOut, "Dark theme background colour");
fileOut.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
});
The java.util.prefs Preferences API is well suited for storing persistent preference data for user applications running on the desktop.
Here's an example how you can use it to store and retrieve persistent background color settings:
import java.awt.Color;
import java.util.prefs.Preferences;
public class MyPrefs {
private static Preferences preferences =
Preferences.userRoot().node("myappname.ColorPreferences");
public static void storeBackground(Color background) {
preferences.putInt("background", background.getRGB());
}
public static Color retrieveBackground() {
// Second argument is the default when the setting has not been stored yet
int color = preferences.getInt("background", Color.WHITE.getRGB());
return new Color(color);
}
}
To call it, use something like:
public static void main(String[] args) {
System.out.println("Background: " + retrieveBackground());
storeBackground(Color.RED);
}
You can store the color as an int value in the properties file, as follows:
windowDark.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent ae) {
getProperties().setProperty("color", Integer.toString(getColor().getRGB()));
}
});
Have the properties as a member of the window this button is in, or even better, in some general location of the application (the class with the main() perhaps ?), and access it with getProperties().
When you need to use the color, parse the string:
Color color = new Color(Integer.parseInt(getProperties().getProperty("color")));
Don't save the properties file on each button click, instead, do so when the application is about to exit:
mainWindow.setDefaultCloseOperation(DO_NOTHING_ON_CLOSE);
mainWindow.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
try {
File file = new File("DarkTheme.properties");
FileOutputStream fileOut = new FileOutputStream(file);
getProperties().store(fileOut, "Dark theme background colour");
fileOut.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
mainWindow.dispose();
}
}
});
The change done in memory will be disposed once the application is terminated. If you want to persist some data (in this case, the background color), then you need to store is somewhere, e.g. file, database, etc.
For a simple application, storing your data in a file will be practical.
To do this, you will need to:
- when application starts, read the file, and apply the color specified in the file
- while the application is running and user changes the color, save the color to the same file
To deal with file, you will need to use File, FileReader, and FileWriter classes (all are in java.io package).
I am writing a custom event and would like some help please. Most of what I am about to talk about is based on the help provided at Custom event listener on Android app
So here is my issue. I am writing an app that needs to download updated images from the web, store the images on the phone, then later display those images. Basically, I download any needed images during a splash screen. Then when the images are downloaded and stored, the splash screen clears and any necessary (newly downloaded) images are displayed on the screen. Here is the problem: the download process is done via an asynctask so the part where the images are loaded on to the screen can't be done inside the asynctask. It has to be done on the main UI thread. I would like to create an event and a custom event listener for the main thread to listen for that basically tells the main UI thread that it is safe to start loading the downloaded images from memory.
According to the discussion from the link above, I came up with this so far... a download listener interace
public interface DataDownloadListener {
void onDownloadStarted();
void onDownloadFinished();
}
an event class...
public class DataDownloadEvent {
ArrayList<DataDownloadListener> listeners = new ArrayList<DataDownloadListener>();
public void setOnDownload(DataDownloadListener listener){
this.listeners.add(listener);
}
}
My problem is that I don't understand where to put the last two steps in those instructions. I thought I would have to put the listener and event inside the class that actually initiates the downloads. But where? Here is my function that initiates the download and saves it to the device:
public String download(String sourceLocation) {
String filename = "";
String path = "";
try {
File externalStorageDirectory = Environment
.getExternalStorageDirectory();
URL urlTmp = new URL(sourceLocation);
filename = urlTmp.getFile()
.substring(filename.lastIndexOf("/") + 1);
path = externalStorageDirectory + PATH;
// check if the path exists
File f = new File(path);
if (!f.exists()) {
f.mkdirs();
}
filename = path + filename;
f = new File(filename);
//only perform the download if the file doesn't already exist
if (!f.exists()) {
Bitmap bitmap = BitmapFactory.decodeStream(urlTmp.openStream());
FileOutputStream fileOutputStream = new FileOutputStream(
filename);
if (bitmap != null) {
bitmap.compress(getFormat(filename), 50, fileOutputStream);
Log.d(TAG, "Saved image " + filename);
return filename;
}
}
else{
Log.d(TAG, "Image already exists: " + filename + " Not re-downloading file.");
}
} catch (MalformedURLException e) {
//bad url
} catch (IOException e) {
//save error
}
return null;
}
And the last step about registering the listener, where do I put that? The instructions say to put that somewhere during initialization. Does that mean in the onCreate method of my main activity? outside the class in the import section of the main activity? Never done a custom event before, so any help would be appreciated.
According to the discussion from the link above, I came up with this so far... a download listener interace
public interface DataDownloadListener {
void onDownloadStarted();
void onDownloadFinished();
}
an event class...
public class DataDownloadEvent {
ArrayList<DataDownloadListener> listeners = new ArrayList<DataDownloadListener>();
public void setOnDownload(DataDownloadListener listener){
this.listeners.add(listener);
}
}
Ok...
Now in your download procedure, at the start of the download, cycle all the elements on the listeners ArrayList and invoke the onDownloadStarted event to inform all your listeners that the download is just started (in this event i presume you'll need to open the splashscreen).
Always in your download procedure, at the and of the download, cycle all the elements on the listeners ArrayList and invoke the onDownloadFinished event to inform all your listeners that the download is finished (now close the splashscreen).
How to cycle listeners on download completed
foreach(DataDownloadListener downloadListener: listeners){
downloadListener.onDownloadFinished();
}
How to cycle listeners on download started
foreach(DataDownloadListener downloadListener: listeners){
downloadListener.onDownloadStarted();
}
Don't make it static if possible... In the class that you'll use to download your files, simply add what you put in your DataDownloadEvent class (listeners arrayList and facility methods for adding and removing). You have no immediate need to use a class in that way (static members I mean).
Example
public class DownloadFileClassExample{
private ArrayList<DataDownloadListener> listeners = new ArrayList<DataDownloadListener>();
public DownloadFileClassExample(){
}
public void addDownloadListener(DataDownloadListener listener){
listeners.add(listener);
}
public void removeDownloadListener(DataDownloadListener listener){
listeners.remove(listener);
}
//this is your download procedure
public void downloadFile(){...}
}
Then access you class in this way
DownloadFileClassExample example = new DownloadFileClassExample();
example.addDownloadListener(this); // if your class is implementing the **DataDownloadListener**
or use
example.addDownloadListener( new DataDownloadListener{...})