am i using actionlistener properly - java

Im trying to understand why this program does not work. Am i using the actionlistener properly why does it not show the time elapsed from when the thread is running but the thread is not running and i see nothing on the screen.
what am i doing wrong or this is the wrong way of using the ActionListener class
or am i not using it properly, here is the code for all the files.
import java.awt.event.*;
import java.util.TooManyListenersException;
public class Timer extends Thread {
private int iTime = 0;
private ActionListener itsListener = null;
public Timer() {
super(); // allocates a new thread object
iTime = 0; // on creation of this object set the time to zero
itsListener = null; // on creation of this object set the actionlistener
// object to null reference
}
public synchronized void resetTime() {
iTime = 0;
}
public void addActionListener(ActionListener event) throws TooManyListenersException {
if (event == null)
itsListener = event;
else
throw new TooManyListenersException();
}
public void removeActionListener(ActionListener event) {
if (itsListener == event)
itsListener = null;
}
public void run() {
while (true) {
try {
this.sleep(100);
}
catch (InterruptedException exception) {
// do nothing
}
iTime++;
if (itsListener != null) {
String timeString = new String(new Integer(iTime).toString());
ActionEvent theEvent = new ActionEvent(this,
ActionEvent.ACTION_PERFORMED, timeString);
itsListener.actionPerformed(theEvent);
}
}
}
}
next file
import java.awt.event.*;
import java.util.TooManyListenersException;
public class TextTimeDemonStration extends Object implements ActionListener {
private Timer aTimer = null; // null reference for this object
public TextTimeDemonStration() {
super();
aTimer = new Timer();
try {
aTimer.addActionListener(this);
}
catch (TooManyListenersException exception) {
// do nothing
}
aTimer.start();
}
public void actionPerformed(ActionEvent e) {
String theCommand = e.getActionCommand();
int timeElapsed = Integer.parseInt(theCommand, 10);
if (timeElapsed < 10) {
System.out.println(timeElapsed);
}
else
System.exit(0);
}
}
the last file the main class running the program from
public class MainTest {
public static void main(String[] args) {
TextTimeDemonStration test = new TextTimeDemonStration();
}
}

In TextTimeDemonStration class, you called aTimer.addActionListener(this); to assign an action listener
But in your Timer.addActionListener() method, in the if else block, you wrote
if (event == null)
itsListener = event;
else
throw new TooManyListenersException();
Since this is not null, it will throw TooManyListenersException exception which will be caught.
After which you start the timer. But, since itsListener is null after the initialisation, in your Timer.run() method, the if block will never be executed. Therefore, nothing is done.
Fix up the logic at Timer.addActionListener() method and it should work just fine.
Hope this helps.

Related

How do I run loops while keeping events running?

I have a question about using events to run loops since doing so seems to lock down the thread. For example I have an nativeMousePressed and nativeMouseReleased event and I am trying to execute some code continuously while the mouse is pressed and then stop when its released. I tried to do this by creating a static boolean variable in another manager class and then setting it to true when the mouse is being pressed and false when the mouse is released. Then I decided to make a while loop that gets called from inside that nativeMousePressed event that uses the boolean value I talked about earlier. The issue is that no events can be called while that while loop is running which means the boolean value when never become false creating an infinite loop. How can I run the while loop while keeping the events running as well?
I assume this has to do with the thread being locked down but I have not worked with stuff like this much and would like some help figuring out how to run both these things in parallel.
public class NativeMouseEvent implements NativeMouseListener {
Program program = new Program();
#Override
public void nativeMouseClicked(org.jnativehook.mouse.NativeMouseEvent e) {
}
#Override
public void nativeMousePressed(org.jnativehook.mouse.NativeMouseEvent e) {
if(e.getButton() == 1 && Controller.threeClicked) {
Controller.fixAim = true;
program.start();
}
}
#Override
public void nativeMouseReleased(org.jnativehook.mouse.NativeMouseEvent e) {
program.interrupt();
Controller.fixAim = false;
}
}
Here is what my second thread is running...
public class Program extends Thread {
public void run() {
while(Controller.fixAim) {
System.out.println("test");
}
}
Here my second attempt which also gives me an error saying that this.program is null.
public class NativeMouseEvent implements NativeMouseListener {
Program program;
#Override
public void nativeMouseClicked(org.jnativehook.mouse.NativeMouseEvent e) {
}
#Override
public void nativeMousePressed(org.jnativehook.mouse.NativeMouseEvent e) {
if(e.getButton() == 1 && Controller.threeClicked) {
Controller.fixAim = true;
if(program != null) {
program = new Program();
program.start();
}
}
}
#Override
public void nativeMouseReleased(org.jnativehook.mouse.NativeMouseEvent e) {
program.interrupt();
program = null;
Controller.fixAim = false;
}
}
Start a tread on mouse down and stop the tread on mouse up. In the thread do circle drawing.
Something like below java code. Note: it is just an example. You need to make changes to make it work in your android environment.
public class Test {
Thread drawTask;
public void mouseDown() {
drawTask = new Thread(()-> {
int i = 0;
try {
for(;;) {
System.out.print("\rDrawing circle " + i++);
Thread.sleep(500);
}
} catch(InterruptedException e) {
System.out.println("finished drawing circle.");
}
});
drawTask.start();
}
public void mouseUp() {
if(drawTask != null) {
drawTask.interrupt();
drawTask = null; //<--- make sure you do this
}
}
public static void main(String[] args) {
Test test = new Test();
Scanner in = new Scanner(System.in);
System.out.println("type anything and press Enter to simulate mouse down/up");
in.next();
test.mouseDown();
in.next();
test.mouseUp();
in.next();
in.close();
}
}

Notify PropertyChangeListener faster

So I'm creating a JProgressBar that displays the progress of a CSV manipulation, where every line is read and checked if there are no null values in obligatory (NOT NULL) columns. For that, I've created a SwingWorker Task that handles converting the number of lines in the file to 100% on the maximum progress value, and adding up on the progress on the correct rate.
That's the SwingWorker:
public static class Task extends SwingWorker<String, Object> {
private int counter;
private double rate;
public Task(int max) {
// Adds the PropertyChangeListener to the ProgressBar
addPropertyChangeListener(
ViewHandler.getExportDialog().getProgressBar());
rate = (float)100/max;
setProgress(0);
counter = 0;
}
/** Increments the progress in 1 times the rate based on maximum */
public void step() {
counter++;
setProgress((int)Math.round(counter*rate));
}
#Override
public String doInBackground() throws IOException {
return null;
}
#Override
public void done() {
Toolkit.getDefaultToolkit().beep();
System.out.println("Progress done.");
}
}
My PropertyChangeListener, which is implemented by the JProgressBar wrapper:
#Override
public void propertyChange(PropertyChangeEvent evt) {
if ("progress".equals(evt.getPropertyName())) {
progressBar.setIndeterminate(false);
progressBar.setValue((Integer) evt.getNewValue());
}
}
Then, where I actually use it, I override the doInBackground() method with the processing I need, calling step() on every iteration.
Task read = new Task(lines) {
#Override
public String doInBackground() throws IOException {
while(content.hasNextValue()) {
step();
// Processing
}
return output.toString();
}
};
read.execute();
return read.get();
So what is happening: the processing works and succeeds, then done() is called, and just after that the propertyChange() registers two 'state' events and one 'progress' event, setting the ProgressBar's progress from 0% to 100%.
What is happening What I thought was happening (check Hovercraft's answer for clarification) is described in the JavaDocs:
Because PropertyChangeListeners are notified asynchronously on the Event Dispatch Thread multiple invocations to the setProgress method might occur before any PropertyChangeListeners are invoked. For performance purposes all these invocations are coalesced into one invocation with the last invocation argument only.
So, after all that, my question is: am I doing something wrong? If not, is there a way for me to make the Event Dispatch Thread notify the PropertyChangeListeners as the onProgress() happens, or at least from time to time?
Obs.: the processing I'm testing takes from 3~5s.
Your problem is here:
read.execute();
return read.get();
get() is a blocking call, and so calling it from the event thread immediately after executing your worker will block the event thread and your GUI.
Instead, it should be called from a call-back method such as the done() method or from the property change listener after the worker has changed its state property to SwingWorker.StateValue.DONE.
For example
import java.awt.*;
import java.awt.event.*;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import javax.swing.*;
#SuppressWarnings("serial")
public class TestSwingWorkerGui extends JPanel {
private JProgressBar progressBar = new JProgressBar(0, 100);
private Action myAction = new MyAction("Do It!");
public TestSwingWorkerGui() {
progressBar.setStringPainted(true);
add(progressBar);
add(new JButton(myAction));
}
private class MyAction extends AbstractAction {
public MyAction(String name) {
super(name);
}
#Override
public void actionPerformed(ActionEvent e) {
myAction.setEnabled(false);
Task read = new Task(30) {
#Override
public String doInBackground() throws Exception {
int counter = getCounter();
int max = getMax();
while (counter < max) {
counter = getCounter();
step();
TimeUnit.MILLISECONDS.sleep(200);
}
return "Worker is Done";
}
};
read.addPropertyChangeListener(new MyPropListener());
read.execute();
}
}
private class MyPropListener implements PropertyChangeListener {
#Override
public void propertyChange(PropertyChangeEvent evt) {
String name = evt.getPropertyName();
if ("progress".equals(name)) {
progressBar.setIndeterminate(false);
progressBar.setValue((Integer) evt.getNewValue());
} else if ("state".equals(name)) {
if (evt.getNewValue() == SwingWorker.StateValue.DONE) {
myAction.setEnabled(true);
#SuppressWarnings("unchecked")
SwingWorker<String, Void> worker = (SwingWorker<String, Void>) evt.getSource();
try {
String text = worker.get();
System.out.println("worker returns: " + text);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
}
}
private static void createAndShowGui() {
TestSwingWorkerGui mainPanel = new TestSwingWorkerGui();
JFrame frame = new JFrame("GUI");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(mainPanel);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> createAndShowGui());
}
}
class Task extends SwingWorker<String, Void> {
private int counter;
// private double rate;
private int max;
public Task(int max) {
// Adds the PropertyChangeListener to the ProgressBar
// addPropertyChangeListener(gui);
// !!rate = (float)100/max;
this.max = max;
setProgress(0);
counter = 0;
}
/** Increments the progress in 1 times the rate based on maximum */
public void step() {
counter++;
int progress = (100 * counter) / max;
progress = Math.min(100, progress);
setProgress(progress);
// setProgress((int)Math.round(counter*rate));
}
public int getCounter() {
return counter;
}
public int getMax() {
return max;
}
#Override
public String doInBackground() throws Exception {
return null;
}
#Override
public void done() {
Toolkit.getDefaultToolkit().beep();
System.out.println("Progress done.");
}
}

Java async problems

So my code works just the way I want it the only issue I'm having is this.. Basically I am having a main class which controls gates on a railroad track, when a train is approaching or crossing the track from either 1 of two tracks the gates should close. The only issue I'm having is the statements for when a gate opens or closes spam like 3-5 times everytime it does something so if the gate is closing it will go..
GATE: Closing
GATE: Closing
GATE: Closing
GATE: Closing
GATE: Closing
GATE: Closed
I'm wondering why this is occuring, here is my code for the Gate class and Main class
public class Gate {
private boolean isClosed = false;
private boolean closing = false;
private boolean opening = false;
public Gate(){
}
public void close(){
if(!(isClosing() == true)){
Runnable task = new Runnable() {
public void run() {
try {
setClosing(true);
setOpening(false);
System.out.println("GATE: Closing");
Thread.sleep(400);
System.out.println("GATE: Closed");
setClosed(true);
setClosing(false);
}catch(Exception ex){
}
}
};
new Thread(task, "closeThread").start();
}
}
public void open(){
if(!(isOpening() == true)){
Runnable task = new Runnable() {
public void run() {
try {
setOpening(true);
System.out.println("GATE: Opening");
Thread.sleep(400);
setOpening(false);
if(closing == false){
setClosed(false);
System.out.println("GATE: Opened");
}
}catch(Exception ex){
}
}
};
new Thread(task, "openThread").start();
}
}
public boolean isClosed(){
return isClosed;
}
public boolean isClosing(){
return closing;
}
public boolean isOpening(){
return opening;
}
public synchronized void setClosing(boolean t){
closing = t;
}
public synchronized void setOpening(boolean t){
opening = t;
}
public synchronized void setClosed(boolean t){
isClosed = t;
}
}
public class Controller {
public static void main(String[] args){
Track t1 = new Track("Track 1");
Track t2 = new Track("Track 2");
Gate g = new Gate();
t1.simulateTrack();
t2.simulateTrack();
do{
System.out.print("");
if((t1.isApproaching() || t1.isCrossing()) || (t2.isApproaching() || t2.isCrossing())){
if(!g.isClosed() && !g.isClosing()){
g.close();
}
}else if(g.isClosed() && !g.isOpening()){
g.open();
}
}while((t1.isSimulating() || t2.isSimulating()));
}
}
Also the code for Track
import java.security.SecureRandom;
public class Track {
private static final SecureRandom gen = new SecureRandom() ;
private boolean approaching = false;
private boolean atCrossing = false;
private boolean simulating = false;
private String trackName = "";
public Track(String n){
trackName = n;
}
public void simulateTrack(){
Runnable task = new Runnable() {
public void run() {
try {
setSimulating(true);
for(int i = 0; i < 10; i++){
Thread.sleep((gen.nextInt(5000) + 2500));
setApproaching(true);
System.out.println(trackName + ": Train is now approaching.");
Thread.sleep((gen.nextInt(5000) + 3500));
setCrossing(true);
setApproaching(false);
System.out.println(trackName + ": Train is now crossing.");
Thread.sleep((gen.nextInt(1000) + 1000));
setCrossing(false);
System.out.println(trackName + ": Train has left.");
}
setSimulating(false);
} catch (Exception ex) {
}
}
};
new Thread(task, "simulationThread").start();
}
public boolean isApproaching(){
return approaching;
}
public boolean isCrossing(){
return atCrossing;
}
public boolean isSimulating(){
return simulating;
}
public synchronized void setSimulating(boolean t){
simulating = t;
}
public synchronized void setApproaching(boolean t){
approaching = t;
}
public synchronized void setCrossing(boolean t){
atCrossing = t;
}
}
This is just an idea:
By shooting the close() logic on a background thread you lose the atomicity. The main's do loop can go around 5 times before it gives up the control of the main thread and one of the "closeThread"s start executing. Don't you see multiple "GATE: Closed"s as well?
Try this (not tested, sorry):
public synchronized void close() { // added synchornized
if (!isClosing()) { // read: "if not closing"
setClosing(true); // set closing so next time close() is called it is a no op
setOpening(false); // close other loopholes so the state is correct
System.out.println("GATE: Closing");
// we're in closing state now, because the close method is almost finished
// start the actual closing sequence
Runnable task = new Runnable() {
public void run() {
try {
Thread.sleep(400);
System.out.println("GATE: Closed");
setClosed(true);
setClosing(false);
}catch(Exception ex){
}
}
};
new Thread(task, "closeThread").start();
}
}
You'll need to modify open() the same way, so that the invariants are always kept. Checking and setting the closing and opening flags are mutually exclusive, that's what you get by placing synchronized on both of them.

Calling super.approveSelection() within a SwingWorker

I have a customized JFileChooser
Its approveSelection() method is slightly modified:
public void approveSelection()
{
File file = getSelectedFile();
changeGui();
final Object a = makeALongCalcualtion(file);
if (a != null)
{
super.approveSelection();
SwingWorker<Document, Void> open = new SwingWorker<Document, Void>()
{
#Override
protected Document doInBackground() throws Exception
{
return createADocument(a);
}
#Override
protected void done()
{
try
{
if(get() != null)
{
changeGui();
}
else
{
//TODO error message
changeGui();
}
}
catch (InterruptedException | ExecutionException e)
{
//TODO error message
changeGui();
}
}
};
open.execute();
}
else
{
//TODO error message
changeGui();
}
}
The changeGui() method sets a JProgressBar to indeterminate and updates a JLabel with a new string.
If file provided to makeALongCalcualtion(file) is of invalid type, it will return null, otherwise it returns info that is passed to SwingWorker which can use it to acutally create the representation of a file in the program (the Document object).
However, this doesn't work as it should because makeALongCalcualtion(file) isn't called within SwingWorker method, and that blocks the EDT.
In order to fix the problem, I would have to call makeALongCalcualtion(file) within a SwingWorker. I could move that part of the code into the SwingWorker without any problems, but then I would have to (due to my code logic) move super.approveSelection() along with it.
So the bottom line is, how do I call super.approveSelection() from within doInBackground() for this specific case?
//More info
What is supposed to happen:
User selects and opens a file
JLabel and JProgressBar are updated, indeterminate progress starts to play.
If makeALongCalcualtion(file) return null user is warned with an error window, but the JFileChooser stys open, making it possible to choose again when the error window is closed.
Otherwise, super.approveSelection() is called, allowing the chooser to close.
A document is created (but the method that creates the document return null if something goes wrong).
If everything is fine, JLabel updates and progressBar animation is stopped (indeterminate is set to false).
If something goes wrong same thing happens as in step 6, only with different message in JLabel.
What happens:
same
same
same, but when makeALongCalculation(file); begins, progressBar freezes.
same
same
same, but the animation isn't stopped (since the progressbar is frozen), only the frozen "picture" is removed and progressbar returns to it's previous state.
same
EDIT
I have made some alterations to my program and I now have this:
approveSelection():
public void approveSelection()
{
File file = getSelectedFile();
Main.getStatusBar().startOpen();
final WorkOpen open = new WorkOpen(file);
open.execute();
open.addPropertyChangeListener(new PropertyChangeListener()
{
#Override
public void propertyChange(PropertyChangeEvent evt) {
if ("state".equals(evt.getPropertyName())) {
if (evt.getNewValue().equals("DONE"))
{
if (open.failed())
{
//TODO error message
Main.getStatusBar().endOpen(false);
}
else
{
Main.getStatusBar().endOpen(true);
}
}
}
}
});
}
SwingWorker:
class WorkOpen extends SwingWorker<Document, Void>
{
boolean failed = false;
File file;
public boolean failed()
{
return failed;
}
#Override
protected Document doInBackground() throws Exception
{
ArrayList<String> data = Opener.extractData(file);
if (data != null)
{
//My little path/name/similar managing system
FileComplex fullPath = new FileComplex(file.toString());
return Opener.createDocument(fullPath.getFullName(), fullPath.getFullPath(), data);
}
else
{
failed = true;
return null;
}
}
#Override
protected void done()
{
try
{
if(get() != null)
{
Main.addDocument(get());
}
}
catch (InterruptedException | ExecutionException e)
{
failed = true;
}
}
WorkOpen(File file)
{
this.file = file;
}
}
The problem now is where to call super.approveSelection(). It has to wait for the worker to finish executing, yet I can't call it from the property change listener.
What to do here?
EDIT 2
As HovercraftFullOfEels suggested, I fixed my code and it compiled and ran. But the problem of JProgressBar freezeing remained. Also, I had to introduce something I don't know if I should have:
private void superApproveSelection()
{
super.approveSelection();
}
public void approveSelection()
{
final File file = getSelectedFile();
class OpenWorker extends SwingWorker<Boolean, Void>
{
Document document;
Document getDocument()
{
return document;
}
#Override
protected Boolean doInBackground() throws Exception
{
ArrayList<String> data = Opener.extractData(file);
if (data != null)
{
//I had to start the progressBar here, because if invalid
//file was selected (extractData(file) returns null if it was),
//nothing should happen (maybe an error
//window later, handled with a new Runnable() same as this:
SwingUtilities.invokeLater(new Runnable()
{
#Override
public void run()
{
Main.getStatusBar().startOpen();
}
});
FileComplex fullPath = new FileComplex(file.toString());
document = Opener.createDocument(fullPath.getFullName(), fullPath.getFullPath(), data);
return true;
}
else
{
return false;
}
}
};
final OpenWorker opener = new OpenWorker();
opener.addPropertyChangeListener(new PropertyChangeListener()
{
#Override
public void propertyChange(PropertyChangeEvent evt)
{
if ("state".equals(evt.getPropertyName()))
{
if (evt.getNewValue() == SwingWorker.StateValue.DONE)
{
if(opener.getDocument() != null)
{
superApproveSelection();
Main.addDocument(opener.getDocument());
Main.getStatusBar().endOpen(true);
}
else
{
try
{
//I'm retrieveing doInBackground()'s value to see if
//progressBar needs stoping (it also displays some
//text, so it must not be called unless the
//progressBar was started).
if (opener.get())
{
Main.getStatusBar().endOpen(false);
}
}
catch (InterruptedException | ExecutionException e)
{
//TODO error something went wrong
}
}
}
}
}
});
opener.execute();
}
"In order to fix the problem, I would have to call makeALongCalcualtion(file) within a SwingWorker. I could move that part of the code into the SwingWorker without any problems, but then I would have to (due to my code logic) move super.approveSelection() along with it."
No, not true at all. super.approveSelection() would not have to be called inside of the SwingWorker.
Why not simply create a SwingWorker, add a PropertyChangeListener to it, and when the SwingWorker's state is done, call super.approveSelection() if indicated?
OK, here is my example below. Explanation to follow:
import java.awt.*;
import java.awt.Dialog.ModalityType;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.File;
import java.util.Scanner;
import java.util.concurrent.ExecutionException;
import javax.swing.*;
import javax.swing.text.*;
#SuppressWarnings("serial")
public class ApproveSelectionTest extends JPanel {
private JTextArea textArea = new JTextArea(30, 60);
public ApproveSelectionTest() {
textArea.setEditable(false);
textArea.setFocusable(false);
JPanel btnPanel = new JPanel();
btnPanel.add(new JButton(new MyGetFileAction("Get Text File Text")));
setLayout(new BorderLayout());
add(new JScrollPane(textArea), BorderLayout.CENTER);
add(btnPanel, BorderLayout.PAGE_END);
}
private class MyGetFileAction extends AbstractAction {
public MyGetFileAction(String text) {
super(text);
}
public void actionPerformed(java.awt.event.ActionEvent arg0) {
MyFileChooser myFileChooser = new MyFileChooser();
int result = myFileChooser.showOpenDialog(ApproveSelectionTest.this);
if (result == JFileChooser.APPROVE_OPTION) {
Document doc = myFileChooser.getDocument();
textArea.setDocument(doc);
}
};
}
private static void createAndShowGui() {
ApproveSelectionTest mainPanel = new ApproveSelectionTest();
JFrame frame = new JFrame("ApproveSelectionTest");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(mainPanel);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGui();
}
});
}
}
#SuppressWarnings("serial")
class MyFileChooser extends JFileChooser {
private WorkOpen workOpen = null;
private JDialog progressDialog = null;
public MyFileChooser() {
}
#Override
public void approveSelection() {
JProgressBar progBar = new JProgressBar();
progBar.setIndeterminate(true);
Window win = SwingUtilities.getWindowAncestor(this);
progressDialog = new JDialog(win, "Checking File", ModalityType.APPLICATION_MODAL);
progressDialog.getContentPane().add(progBar);
progressDialog.pack();
progressDialog.setLocationRelativeTo(null);
File file = getSelectedFile();
workOpen = new WorkOpen(file);
workOpen.addPropertyChangeListener(new PropertyChangeListener() {
#Override
public void propertyChange(PropertyChangeEvent pcEvt) {
if (SwingWorker.StateValue.DONE == pcEvt.getNewValue()) {
if (progressDialog != null) {
progressDialog.dispose();
}
try {
boolean bool = workOpen.get().booleanValue();
if (bool) {
superApproveSelection();
} else {
JOptionPane.showMessageDialog(MyFileChooser.this, "Invalid File Chosen");
}
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
});
workOpen.execute();
progressDialog.setVisible(true);
}
// ****** this is key *****
private void superApproveSelection() {
super.approveSelection();
}
public Document getDocument() {
if (workOpen == null) {
return null;
} else {
return workOpen.getDocument();
}
}
}
class WorkOpen extends SwingWorker<Boolean, Void> {
private static final long SLEEP_TIME = 4 * 1000;
private Document document = null;
private File file = null;
public WorkOpen(File file) {
this.file = file;
}
#Override
protected Boolean doInBackground() throws Exception {
if (file == null || !file.exists()) {
return Boolean.FALSE;
}
Thread.sleep(SLEEP_TIME);
String fileName = file.getName();
if (fileName.contains(".txt")) {
Scanner scan = new Scanner(file);
StringBuilder stringBuilder = new StringBuilder();
while (scan.hasNextLine()) {
stringBuilder.append(scan.nextLine() + "\n");
}
document = new PlainDocument();
document.insertString(0, stringBuilder.toString(), null);
return Boolean.TRUE;
}
return Boolean.FALSE;
}
public Document getDocument() {
return document;
}
}
Explanation and key points from my example:
This example behaves very simply. You choose a file, and if the file exists and contains ".txt" in its name, then it reads in the document and displays the text in a JTextField.
Else it displays a warning message but leaves the JFileChooser displayed.
Probably the key point: I've given my MyFileChooser class a private superApproveSelection() method that can be called by my PropertyChangeListener. This exposes the super's approveSelection() method to the inner class, one of the problems you were having.
The order of calling code is important in my approveSelection() override.
In this method I first create my JProgressBar and its dialog but don't yet display it immediately. It really doesn't have to be created first, but it sure needs to be displayed last.
I create my SwingWorker, workOpen, but don't yet execute it.
I add my PropertyChangeListener to the SwingWorker before executing the SwingWorker.
I then execute my SwingWorker
I then display my modal JDialog with the indeterminate JProgressBar.
My SwingWorker is structured so that its doInBackground returns a Boolean, not a Document.
I have it create a (very simple) Document if all works out OK that holds the content of the text file, and set a private "doc" field obtainable by a getter method, and then have doInBackground return Boolean.TRUE if all works well.
I've given my doInBackground a Thread.sleep(4000) just to pretend that its action takes a lot of time. Yours of course won't have this.
In the PropertyChangeListener if the SwingWorker is DONE, I'll dispose of the progress bar dialog and then call get() on the SW to get the Boolean result.
If it's Boolean.TRUE, then call the superApproveSelection() method described above.
Else show an error message. Note that since the super's approveSelection() isn't called, the file chooser dialog remains displayed.
If the approveSelection is called then the code that displays my file chooser dialog will get the appropriate return value, will extract the Document from the file chooser and displays the Document in a JTextArea.

Disabling or Intercepting a Drop Outside Of A Window

I've implemented a set of draggable tabs, following the form of this example:
How to implement draggable tab using Java Swing?
Everything appears to work as I desire, however,when I drag outside of the main panel, the desktop will become a valid drop target (the resulting drop is accepted and marked successful).
Is there a way to intercept this drop to react to dropping outside of our root pane? It's simple enough to detect, but it's not clear to me how to actually capture the drop before the outside world does.
By the time DragSourceListener's dragDropEnd is called, the drop is already executed and there doesn't appear to be a good way to end dragging in dragOver/Exit/Whatever.
Gee, it'd be nice if something like this worked:
#Override
public void dragOver(DragSourceDragEvent dragEvent)
{
DragEnabledTabTransferData data = getTabTransferData(dragEvent);
DragSourceContext dragSourceContext = dragEvent.getDragSourceContext();
if (data == null)
{
dragSourceContext.setCursor(DragSource.DefaultMoveNoDrop);
return;
}
if (!data.getTabbedPane().getRootPane().getBounds().contains(dragEvent.getLocation()))
{
dragSourceContext.dragDropEnd(new DragSourceDropEvent(dragSourceContext, 999, true));
}
}
Instead the drag continues dragging along. I do, however get a dragDropEnd for my troubles.
Any ideas? I'd be pretty sad to hear that the only solution would be to have some hidden maximized global pane that acted only as a drop target to capture out-of-window events.
Here is a working example. If you drag a tab out to, say, the desktop in Linux, it'll try to cast the transfer data into a Serializable and not be happy. The drag over I was playing with is commented with "This is where I'd assume we'd be able to intercept stuff" if you want to jump straight to what I'd pointed to above.
/** "Simple" example of DnD tabbed panes. Sourced from Eugene Yokota:
* http:stackoverflow.com/questions/60269/how-to-implement-draggable-tab-using-java-swing */
import java.awt.*;
import java.awt.datatransfer.*;
import java.awt.dnd.*;
import javax.swing.*;
public class DnDTabbedPane extends JTabbedPane {
private static final String NAME = "TabTransferData";
private final DataFlavor FLAVOR = new DataFlavor(DataFlavor.javaJVMLocalObjectMimeType, NAME);
public DnDTabbedPane() {
super();
final DragSourceListener dsl = new DragSourceListener() {
public void dragEnter(DragSourceDragEvent e) {
e.getDragSourceContext().setCursor(DragSource.DefaultMoveDrop);
}
public void dragExit(DragSourceEvent e) {
e.getDragSourceContext().setCursor(DragSource.DefaultMoveNoDrop);
}
/**
* This is where I'd assume we'd be able to intercept stuff
* so drops don't happen where we don't want them to.
*/
public void dragOver(DragSourceDragEvent e) {
TabTransferData data = getTabTransferData(e);
if (data == null) {
e.getDragSourceContext().setCursor(DragSource.DefaultMoveNoDrop);
return;
}
//This is where I ended up robokilling the drag via hackery
e.getDragSourceContext().setCursor(DragSource.DefaultMoveDrop);
}
public void dragDropEnd(DragSourceDropEvent e) {}
public void dropActionChanged(DragSourceDragEvent e) {}
};
final DragGestureListener dgl = new DragGestureListener() {
public void dragGestureRecognized(DragGestureEvent e) {
Point tabPt = e.getDragOrigin();
int dragTabIndex = indexAtLocation(tabPt.x, tabPt.y);
if (dragTabIndex < 0) {
return;
}
e.startDrag(DragSource.DefaultMoveDrop,new TabTransferable(DnDTabbedPane.this, dragTabIndex), dsl);
}
};
new DropTarget(this, DnDConstants.ACTION_COPY_OR_MOVE, new CDropTargetListener(), true);
new DragSource().createDefaultDragGestureRecognizer(this, DnDConstants.ACTION_COPY_OR_MOVE, dgl);
}
private TabTransferData getTabTransferData(DropTargetDropEvent a_event) {
try {
return (TabTransferData) a_event.getTransferable().getTransferData(FLAVOR);
} catch (Exception e) {}
return null;
}
private TabTransferData getTabTransferData(DropTargetDragEvent a_event) {
try {
return (TabTransferData) a_event.getTransferable().getTransferData(FLAVOR);
} catch (Exception e) {}
return null;
}
private TabTransferData getTabTransferData(DragSourceDragEvent a_event) {
try {
return (TabTransferData) a_event.getDragSourceContext().getTransferable().getTransferData(FLAVOR);
} catch (Exception e) {}
return null;
}
class TabTransferable implements Transferable {
private TabTransferData m_data = null;
private DataFlavor[] flavors = {FLAVOR};
public TabTransferable(DnDTabbedPane a_tabbedPane, int a_tabIndex) {
m_data = new TabTransferData(DnDTabbedPane.this, a_tabIndex);
}
public Object getTransferData(DataFlavor flavor) {
return m_data;
}
public DataFlavor[] getTransferDataFlavors() {
return flavors;
}
public boolean isDataFlavorSupported(DataFlavor flavor) {
return flavor.getHumanPresentableName().equals(NAME);
}
}
class TabTransferData {
DnDTabbedPane m_tabbedPane = null;
int m_tabIndex = -1;
public TabTransferData(DnDTabbedPane a_tabbedPane, int a_tabIndex) {
m_tabbedPane = a_tabbedPane;
m_tabIndex = a_tabIndex;
}
}
class CDropTargetListener implements DropTargetListener {
public void dragEnter(DropTargetDragEvent e) {
if (isDragAcceptable(e)) {
e.acceptDrag(e.getDropAction());
} else {
e.rejectDrag();
}
}
public void drop(DropTargetDropEvent a_event) {
if (isDropAcceptable(a_event)) {
convertTab(getTabTransferData(a_event),
getTargetTabIndex(a_event.getLocation()));
a_event.dropComplete(true);
} else {
a_event.dropComplete(false);
}
}
private boolean isTransferableGood(Transferable t, DataFlavor flavor)
{
return t == null || t.isDataFlavorSupported(flavor);
}
private boolean isDataGood(TabTransferData data)
{
if (DnDTabbedPane.this == data.m_tabbedPane && data.m_tabIndex >= 0) {
return true;
}
return false;
}
public boolean isDragAcceptable(DropTargetDragEvent e) {
Transferable t = e.getTransferable();
if (!isTransferableGood(t, e.getCurrentDataFlavors()[0])) {
return false;
}
return isDataGood(getTabTransferData(e));
}
public boolean isDropAcceptable(DropTargetDropEvent e) {
Transferable t = e.getTransferable();
if (!isTransferableGood(t, e.getCurrentDataFlavors()[0])) {
return false;
}
return isDataGood(getTabTransferData(e));
}
public void dragExit(DropTargetEvent e) {}
public void dropActionChanged(DropTargetDragEvent e) {}
public void dragOver(final DropTargetDragEvent e) {}
}
private int getTargetTabIndex(Point a_point) {
for (int i = 0; i < getTabCount(); i++) {
Rectangle r = getBoundsAt(i);
r.setRect(r.x - r.width / 2, r.y, r.width, r.height);
if (r.contains(a_point)) {
return i;
}
}
return -1;
}
private void convertTab(TabTransferData a_data, int a_targetIndex) {
DnDTabbedPane source = a_data.m_tabbedPane;
int sourceIndex = a_data.m_tabIndex;
if (sourceIndex < 0) {
return;
}
Component cmp = source.getComponentAt(sourceIndex);
String str = source.getTitleAt(sourceIndex);
if (a_targetIndex < 0 || sourceIndex == a_targetIndex) {
return;
}
source.remove(sourceIndex);
if (a_targetIndex == getTabCount()) {
addTab(str, cmp);
} else if (sourceIndex > a_targetIndex) {
insertTab(str, null, cmp, null, a_targetIndex);
} else {
insertTab(str, null, cmp, null, a_targetIndex - 1);
}
}
public static void main(String[] args)
{
JFrame window = new JFrame();
DnDTabbedPane tabbedPane = new DnDTabbedPane();
for(int i=0; i< 5; i++)
{
tabbedPane.addTab("I'm tab "+i, new JLabel("I'm tab "+i));
}
window.add(tabbedPane);
window.setSize(400, 200);
window.setVisible(true);
}
}
Thus far, the best I can do is call something to this effect when we hop out of the parent.
Component rootPane = SwingUtilities.getRoot(component);
Rectangle bounds = rootPane.getBounds();
if (!bounds.contains(location))
{
Robot robot = null;
try
{
robot = new Robot();
} catch (AWTException e)
{
return;
}
robot.keyPress(KeyEvent.VK_ESCAPE);
robot.keyRelease(KeyEvent.VK_ESCAPE);
}
It's a total hack, and doesn't solve my issue. I'd like to intercept the final drop event, see if it was outside of the frame and spawn the tab in its own JFrame.
If I was using the NetBeans, MyDoggy, or Eclipse frameworks, I guess this would all be magically handled for me. Alas.
There is no Way to Cancel the Drag directly. see http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4502185
I would prefer to show the User that Drop on Desktop is not allowed, by changing the Cursor.
Your DragSourceListener dsl has in the dragOver method a DragSourceDragEvent which tells you
that the target action is NONE over the Desktop.
Change to this:
public void dragOver(DragSourceDragEvent e) {
TabTransferData data = getTabTransferData(e);
if( data == null || e.getTargetActions() == DnDConstants.ACTION_NONE ) {
e.getDragSourceContext().setCursor( DragSource.DefaultMoveNoDrop );
return;
}
e.getDragSourceContext().setCursor( DragSource.DefaultMoveDrop);
}
If you really want to Cancel, than you have to use your ESC solution or something like that:
try {
new Robot().mouseRelease( InputEvent.BUTTON1_MASK ); // if Button1 was the only Button to start a Drag
} catch( AWTException e1 ) {
}
As confirmed by #oliholz, there just isn't a way to do it without having to force a cancel via a keystroke.
However, for my needs of creating a tear-off tab, I found that creating a floating pane that was, itself, a drop target listener felt like the cleanest solution:
package com.amish.whatever;
import java.awt.MouseInfo;
import java.awt.Point;
import java.awt.dnd.DnDConstants;
import java.awt.dnd.DropTarget;
import java.awt.dnd.DropTargetDragEvent;
import java.awt.dnd.DropTargetDropEvent;
import java.awt.dnd.DropTargetEvent;
import java.awt.dnd.DropTargetListener;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JLabel;
import javax.swing.JWindow;
import javax.swing.Timer;
public class TearAwayTab extends JWindow {
MousePoller mousePoller = new MousePoller();
public TearAwayTab() {
this.add(new JLabel("FLONT"));
this.pack();
new DropTarget(this, DnDConstants.ACTION_COPY_OR_MOVE, new EasyDropTarget(), true);
this.setVisible(false);
}
private void center(Point location)
{
Point center = new Point();
center.setLocation(location.x-this.getWidth()/2, location.y-this.getHeight()/2);
TearAwayTab.this.setLocation(center);
}
public void attach(Point location)
{
center(location);
mousePoller.start();
this.setVisible(true);
}
public void detach()
{
mousePoller.stop();
this.setVisible(false);
}
private int DELAY = 10;
private class MousePoller extends Timer{
public MousePoller(){
super(DELAY, new ActionListener() {
private Point lastPoint = MouseInfo.getPointerInfo().getLocation();
#Override
public void actionPerformed(ActionEvent e) {
Point point = MouseInfo.getPointerInfo().getLocation();
if (!point.equals(lastPoint)) {
center(point);
}
lastPoint = point;
}
});
}
}
private class EasyDropTarget implements DropTargetListener
{
#Override
public void dragEnter(DropTargetDragEvent dtde) {
dtde.acceptDrag(dtde.getDropAction());
}
#Override
public void dragOver(DropTargetDragEvent dtde) {}
#Override
public void dropActionChanged(DropTargetDragEvent dtde) {}
#Override
public void dragExit(DropTargetEvent dte) {}
#Override
public void drop(DropTargetDropEvent dtde) {
dtde.dropComplete(true);
detach();
System.out.println("DROP Intercepted");
}
}
}
The bit with the MousePoller works around scrubbing the mouse too fast for mouse listeners to reliably update the location. I'd tried with a motion listener and was able to escape the bounds of the floater quite easily.
Back in the first example, I now include the tear away tab as a private member of the tabbed pane, and call attach and detach when exiting or entering my drop areas:
final DragSourceListener dsl = new DragSourceListener() {
public void dragEnter(DragSourceDragEvent e) {
e.getDragSourceContext().setCursor(DragSource.DefaultMoveDrop);
Rectangle bounds = SwingUtilities.getRoot(DnDTabbedPane.this).getBounds();
if(bounds.contains(e.getLocation())){
tearTab.detach();
}
}
public void dragExit(DragSourceEvent e) {
e.getDragSourceContext().setCursor(DragSource.DefaultMoveNoDrop);
tearTab.attach(e.getLocation());
}
...
This also has the added benefit of preserving the DnD operation in the case of dragging out, and then back in.
Thanks for the input. If you have any other ideas/comments, I'm all ears.
This doesn't directly relate to tabs, but one way to stop drags from being able to be dragged to the desktop is to wrap whatever you're dragging in a custom wrapper class. Then, when you make your TransferHandler, make a DataFlavor localFlavor = new ActivationDataFlavor(YourWrapperClass.class, DataFlavor.javaJVMLocalObjectMimeType, "description"); Next, override the createTransferable method to have new DataHandler(passedInComponent, localFlavor.getMimeType()); and return a new Transferable in which you've overridden all the methods to only have your localFlavor. Finally, in the importData method, make sure to import your data as your localFlavor type. This will prevent dragging to the deaktop as the flavor you defined is local to the JVM.

Categories