1

Such a piece of code:

private void log(String message) {
    LogBox.append(message + "\n");
}

private void log(Exception e) {
    log(e.getMessage());
}

private void ConvertButtonActionPerformed(java.awt.event.ActionEvent evt) {
    String url = UrlBox.getText();
    if (url.isEmpty()) {
        log("Empty URL");
        return;
    }
    LogBox.setText("");
    try {
        log("URL "+url+" accepted. Trying to download...");
        String content = URLConnectionReader.getText(url);
        log("Downloaded. Parsing the content...");
        //
    } catch (Exception e) {
        log(e);
    }
}

should output each message to the LogBox (JTextArea) immediately after each log call, but outputs URL ... accepted only when URLConnectionReader.getText(url) finishes.

There were several ways do do an immediate output:

  • Application.DoEvents in Visual Basic 6 and .NET
  • Application.ProcessMessages in Delphi

Is there some simple way to do an immediate output? I was studying questions about the DoEvents and how to do this in Java, but I think that starting to learn Java from multi-threading isn't a right approach.

Paul
  • 25,812
  • 38
  • 124
  • 247
  • **In a nutshell: Don't block EDT (Event Dispatch Thread).** Multi-threading is absolutely a right approach. In fact I learned multi-threading before I learned GUI. – johnchen902 Jul 01 '13 at 15:31
  • @johnchen902: I do not know which of my lines blocks EDT and how to block it at all. – Paul Jul 01 '13 at 15:34
  • 1
    `ConvertButtonActionPerformed` from the name of it I guess it must be invoked by an `ActionListener`, which is invoked by EDT. And `URLConnectionReader.getText` takes some time, hence blocking the EDT. – johnchen902 Jul 01 '13 at 15:37
  • Add `while(true);` to `ConvertButtonActionPerformed` and probably you'll understand how is your code blocking EDT. (You'll find that your app is not responding) – johnchen902 Jul 01 '13 at 15:42
  • @johnchen902: `And URLConnectionReader.getText takes some time, hence blocking the EDT`. For many Windows app developers it wasn't a problem during many years :) :) :) But you are right. – Paul Jul 01 '13 at 15:51

2 Answers2

5

Create a SwingWorker to do the download: http://docs.oracle.com/javase/tutorial/uiswing/concurrency/worker.html


The role of an ActionListener is just that: to listen for user action, and initiate a response to that action by the program.

Sometimes, the program's response is very quick, and only involves the GUI. For example, in a calculator app, you could have a listener attached to an "equals" button that calculates the result of the current expression and writes it to a textbox. This can all be done within the listener method (although you might want to separate behavior for testing).

If the response to an user action is to initiate some long-running process, like downloading and parsing the file, then you don't want to do this within the listener body, because it will freeze the UI. Instead, gather any information (in your case, the URL value) from within the listener, and spin up a SwingWorker to handle the program's response.

In my comment, I suggested moving everything after the getText() into a SwingWorker. This is because, to me, the response is "download a file if you have a valid URL, and log the progress." And as I see it, testing for an empty string is part of that response. If you want to leave the empty-string test inside the listener, that's fine, but imo it's less testable.

You must leave the call to getText() inside the body of the listener, because you are only allowed to access Swing components from the event dispatch thread. If you moved that call into the worker, then it might access the textbox at the same time the textbox is updating itself, resulting in corrupt data.

parsifal
  • 336
  • 2
  • 4
  • So, if I understood right, the `String content = URLConnectionReader.getText(url);` call should be wrapped into SwingWorker or URLConnectionReader should be derived from SwingWorker? – Paul Jul 01 '13 at 15:31
  • @Paul - I'd create a `SwingWorker` subclass that contains all the code after `getText()`. – parsifal Jul 01 '13 at 15:55
  • I thought that understood you, but now I am confused - you suggest the reverse of what I understood. After `getText()` or including `getText()` too? – Paul Jul 01 '13 at 15:58
  • `You must leave the call to getText() inside the body of the listener, because you are only allowed to access Swing components from the event dispatch thread`. The URLConnectionReader and its getText() method do not belong to Swing UI. This class is entirely my own class. BTW: what if there are other long-running methods after getText() ? – Paul Jul 01 '13 at 17:04
  • 1
    @Paul - I was referring to `UrlBox.getText()`; I assumed `UrlBox` was a Swing component. The general rule is that things that *just* interact with Swing happen on the event dispatch thread (the thread that runs listeners), while things that interact with the outside world happen in `SwingWorker`s. – parsifal Jul 01 '13 at 17:11
3

Read up on Concurrency.

You should probably use a SwingWorker for the long running task, then you can publish results to the GUI as they become available.

camickr
  • 321,443
  • 19
  • 166
  • 288