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.
Related
I am using vaadin grid with push enabled and an asynchronous method to fetch some data. What I would expect is that the data that is already present in the entity is loaded instantly and the data that is loaded using an asynchronous method is loaded using callback. What currently happens is that the outer grid structure is loaded but the content is loaded after a longer period of time all at once. Here is sample code:
private void setupGrid() {
// String grid for illustration
Grid<String> grid = new Grid<>();
grid.addComponentColumn(this::getIconColumn).setHeader("Name");
// other entity items would be displayed here
// grid.addComponentColumn(this::getMainColumn).setHeader("Other");
grid.setItems("2800-player-who-returned-10000-years-later",
"2633-duke-pendragon", "8713-dungeon-reset");
// this allows the outer grid structure to be displayed while not blocking UI
// but content is still loaded "synchronously" - all at once, not one by one
grid.getDataCommunicator().enablePushUpdates(Executors.newCachedThreadPool());
add(grid);
}
private Image getIconColumn(String entityName) {
Image image = new Image("", "");
image.setHeight("150px");
image.setWidth("100px");
UI ui = UI.getCurrent();
asyncLoadIcon(entityName)
.addCallback(
result -> ui.access(() -> image.setSrc(result)),
err -> ui.access(() -> Notification.show("Failed to parse icon for " + entityName))
);
return image;
}
And finally the async method with Thread.sleep(3000) for illustration purposes.
#Async
public ListenableFuture<String> asyncLoadIcon(String entityName) {
try {
// some real code
Thread.sleep(3000);
return AsyncResult.forValue(entityname);
} catch (IOException | InterruptedException e) {
log.error(e);
}
return AsyncResult.forExecutionException(new RuntimeException("Error"));
}
I have found a solution to my problem on the Vaadin documentantion page for Server Push. What I needed to add, was that in the getIconColumn() method the asyncLoadIcon() action with callback is started in a separate Thread each time. This has resulted in me getting the grid outline instantly, along with the easily generated content and some empty image icons. The image icons are then gradually filled in when the async method finishes. Here is the update code:
private Image getIconColumn(MangaEntity entity) {
Image image = new Image("", "");
image.setHeight("150px");
image.setWidth("100px");
UI ui = UI.getCurrent();
new Thread(() -> rsCrawler.asyncLoadIcon(entity.getUrlName())
.addCallback(
result -> ui.access(() -> image.setSrc(result)),
err -> ui.access(() -> Notification.show("Failed to parse icon for " + entity.getName()))
)).start();
return image;
}
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);
}
}
});
}
I have been reading for hours about different multi-threading techniques for JavaFX and cannot seem to find what I'm looking for. The application being worked on is the "Messenger" which part of a bigger application to provide a trading marketplace for a game.
A breakdown of the process I am having trouble with:
A window with a 'Contact Seller' button is displayed
The user clicks 'Contact Seller', and the Messenger window should be displayed
Using the seller's name from the main window, the Messenger should check if a chat already exists with that name
If the chat already exists, get the index of that chat in the Messenger's ListView, and select the chat so the Messenger's text area is populated with the corresponding messages
If the chat doesn't exist, create one
The issue:
Chats are stored server-side
Messages between clients are stored in a message 'cache' on the server when being processed from one user to the other (they are sorted to their respective chats, inserted into the chats, and pushed to the database upon client disconnection)
Messages received on the client are stored locally in the respective chat
When the Messenger is opened, it requests a list of chats for the signed-in user
The server sends an ArrayList of chats, and upon receiving the client builds the Messenger's ListView with these objects
Now when I need to open the Messenger in order to 'Contact Seller', I need to make sure the sync with the server is complete. Without doing so, I won't be able to properly check if a chat already exists with that name since I won't have the most up-to-date list.
The 'RequestWorker' thread that handles incoming server messages is NOT on the JavaFX thread.
The RequestWorker 'gets' the Messenger instance if it is currently open, and populates the ListView with the newly received chat list. (This needs to happen on the JavaFX thread, since I am working in the Messenger GUI)
What I am trying to do is set a static AtomicBoolean syncInProgress to true when the Messenger initiates a sync upon being displayed. When the RequestWorker receives the latest list back from the server and finishes populating the Messengers ListView, it sets syncInProgress to false.
The sync takes longer than opening the Messenger and making it check if a chat exists. Doing it this way it has no items populated yet in the ListView and the method is ineffective.
Calling a while loop to wait until the boolean is changed, blocks the JavaFX thread which means the RequestWorker cannot do what it needs to in the JavaFX thread.
How can I continuously check for this variable to be set false, then continue to 'Contact Seller' once the ListView has been properly populated?
Contact Seller method: The while loop in here causes a block on the JavaFX thread, thus not enabling RequestWorker to properly populate the ListView.
public static void contactSeller(Messenger messenger, String destination, String itemName)
{
while (TarkovTrader.syncInProgress.get())
{
; // Wait until sync is complete to check the latest chat list for an existing chat
}
if (messenger.chatExists(destination))
{
// Chat exists, select the chat for the user
for (Chat openChat : messenger.chatListView.getItems())
{
if (openChat.getName(TarkovTrader.username).equals(destination))
{
messenger.chatListView.getSelectionModel().select(openChat);
messenger.unpackChatMessages(openChat.getMessages());
break;
}
}
}
else
{
messenger.buildNewChat(destination);
}
messenger.chatInput.setText("Hey " + destination + ". Interested in your '" + itemName + "'.");
messenger.chatInput.setOnMouseClicked(e -> messenger.chatInput.clear());
}
RequestWorker process chat block:
switch(receivedFromServer)
case "chatlist":
// Client requested a chat list, results were returned from the server, and now we need to populate the messenger list
ChatListForm chatlistform = (ChatListForm)processedRequest;
if (Messenger.isOpen)
{
FutureTask<Void> updateChatList = new FutureTask(() -> {
Messenger tempMessenger = trader.getMessenger();
int currentIndex = tempMessenger.chatListView.getSelectionModel().getSelectedIndex();
tempMessenger.populate(chatlistform.getChatList());
tempMessenger.chatListView.getSelectionModel().select(currentIndex);
}, null);
Platform.runLater(updateChatList); // RequestWorker needs access to the JavaFX application thread
try {
updateChatList.get(); // Wait until the ListView has been populated before setting 'syncInProgress' to false again
}
catch (InterruptedException e) {
Alert.display(null, "Sync interrupted.");
}
catch (ExecutionException e) {
Alert.display(null, "Sync failed.");
}
TarkovTrader.syncInProgress.compareAndSet(true, false); // The value of syncInProgress should be true, change to false. Sync complete
}
else
{
Platform.runLater(() -> Alert.display(null, "New chat received."));
TarkovTrader.syncInProgress.compareAndSet(true, false);
}
break;
Contact Seller button logic: If a messenger is not open, create it and pass to static contactSeller method to use.
contactButton.setOnAction(e -> {
Messenger messenger;
if (Messenger.isOpen)
{
// Get messenger
messenger = trader.getMessenger();
}
else
{
messenger = new Messenger(worker);
messenger.display();
trader.setMessenger(messenger);
}
Messenger.contactSeller(messenger, item.getUsername(), item.getName());
itemdisplay.close();
});
EDIT:
Partially using Slaw's idea (AtomicBoolean is still being used since I'm not sure how to do this without it), this is what I came up with...
public static void contactSeller(Messenger messenger, String destination, String itemName)
{
Task<Void> waitForSync = new Task<Void>() {
#Override
public Void call()
{
while (TarkovTrader.syncInProgress.get())
{
; // Wait until sync is complete
}
return null;
}
};
waitForSync.setOnSucceeded(e -> {
while (TarkovTrader.syncInProgress.get())
{
; // Wait until sync is complete to check the latest chat list for an existing chat
}
if (messenger.chatExists(destination))
{
// Chat exists, select the chat for the user
for (Chat openChat : messenger.chatListView.getItems())
{
if (openChat.getName(TarkovTrader.username).equals(destination))
{
messenger.chatListView.getSelectionModel().select(openChat);
messenger.unpackChatMessages(openChat.getMessages());
break;
}
}
}
else
{
messenger.buildNewChat(destination);
}
messenger.chatInput.setText("Hey " + destination + ". Interested in your '" + itemName + "'.");
messenger.chatInput.setOnMouseClicked(me -> messenger.chatInput.clear());
});
Thread t = new Thread(waitForSync);
t.setDaemon(true);
t.start();
}
Which does work, but this doesn't seem like a great solution. Is it fine to do something like this or is there a preferred method over this? I feel like the while loop and using a triggered boolean is sloppy..but is this common practice?
Don't try to handle the opened chat in the same method that schedules the chat retrieval. Instead create a queue of handlers that is executed as soon as the chat is ready.
Simplified Example
public class ChatManager {
private final Map<String, Chat> openChats = new HashMap<>();
// only call from application thread
public void openChat(String user, Consumer<Chat> chatReadyHandler) {
Chat chat = openChats.computeIfAbsent(user, this::createNewChat);
chat.addReadyHandler(chatReadyHandler);
}
private Chat createNewChat(String user) {
return new Chat(user);
}
public class Chat {
// list keeping track of handlers any used for synchronisation
private final ArrayList<Consumer<Chat>> readyHandlers = new ArrayList<>(1);
private boolean ready = false;
private final String user;
public String getUser() {
return user;
}
private void addReadyHandler(Consumer<Chat> chatReadyHandler) {
synchronized (readyHandlers) {
// if already ready, immediately execute, otherwise enqueue
if (ready) {
chatReadyHandler.accept(this);
} else {
readyHandlers.add(chatReadyHandler);
}
}
}
private void chatReady() {
synchronized (readyHandlers) {
ready = true;
}
// execute all handlers on the application thread
Platform.runLater(() -> {
synchronized (readyHandlers) {
for (Consumer<Chat> readyHandler : readyHandlers) {
readyHandler.accept(this);
}
readyHandlers.clear();
readyHandlers.trimToSize();
}
});
}
private Chat(String user) {
this.user = user;
new Thread(() -> {
try {
Thread.sleep(10000); // simulate time required to acquire chat
} catch (InterruptedException ex) {
}
chatReady();
}).start();
}
}
}
The following code creates a Chat with a user when Enter is pressed after typing the user name in the TextField and prints a message to the TextArea when the chat is ready.
#Override
public void start(Stage primaryStage) throws Exception {
ChatManager worker = new ChatManager();
TextField userName = new TextField();
TextArea textArea = new TextArea();
textArea.setEditable(false);
userName.setOnAction(evt -> {
String user = userName.getText();
userName.clear();
textArea.appendText("opening chat for " + user + "\n");
worker.openChat(user, chat -> textArea.appendText("chat for " + chat.getUser() + " ready\n"));
});
Scene scene = new Scene(new VBox(10, userName, textArea));
primaryStage.setScene(scene);
primaryStage.show();
}
i am at the moment developing a Softphone with javafx. and i kind of a have problem capturing incoming call to a textfield. an example of my code is here.
an incoming call is with Joptionpane successful bt i had like to have the value appear in call textfield just like telephone.
Thank you.
public void telephoneNumbs(String numbers) {
String replace = numbers.replace("sip:", "").trim().replace(".", ""); // Incoming Call Numbers from Sip UA
if (!replace.isEmpty()) {
List<TelephoneObj> telephons;
telTextField.setText(null); //init it with null
costumDao = new CostumersDao(); // costumers DB
telephons = costumDao.getOrCompareTelfone(numbers);
for (TelephoneObj tmp : telephons) {
System.out.println("Test: " + tmp.getTelephoneNums); // am getting exactle what i need here from my Database
//or
JOptionPane.showMessageDialog(null,"incoming:"+ tmp.getTelephoneNums); // it show it during incoming calls
//here is the problem. it wouldnt show the Value on the Textfield
telTextField.setText(tmp.getTelephoneNums); //try to push that Value(Telephone number) to show in JFXTextfield/it cold be any other Textfields
}
}
Sooo much happy today it went well with after 2days of thinking how to solve this miserable life of not taking time to think.
I finally got the answer by using Task to solve the problem.
Task<Void> task = new Task<Void>() {
{
updateMessage("");
}
#Override
public Void call() throws Exception {
while (true) {
updateMessage(callee);
try {
Thread.sleep(100);
} catch (InterruptedException ex) {
break;
}
}
return null;
}
};
//neuLabel.textProperty().bind(task.messageProperty());
kdAddrTel.textProperty().bind(task.messageProperty());
Thread th = new Thread(task);
th.setDaemon(true);
th.start();
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{...})