3

My project is built upon Java's Swing library. It spawns the EDT which displays my GUI (which works correctly).

The entrance to the program, which initializes the EDT:

public final class Main {

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Start());
    }

    class Start implements Runnable {
        private Model model = new Model();
        private Controller controller = new Controller(model);
        private View view = new View(controller);

        @Override
        public void run() {
            // Initialize the view and display its JFrame...
        }
    }
}

}

However, when a button / radio box / etc. is clicked within my GUI, the Controller class must perform an action on the model.

My questions are the following:

  • Should I wrap the controller's code in a new SwingWorker?
    • If no, should I wrap my model's code in a new SwingWorker?
  • If I wrap the controller's code with threads, do I need to synchronize the shared state variables within my model?
  • If my model, running on a new thread, notifies my GUI of changes, will this occur on the EDT or on the new thread?

For example:

public class Controller {
    public void updateModel() {
        new SwingWorker<Void, Void>() {
            @Override
            protected Void doInBackground() throws Exception {
                model.somethingSomethingSomething();
            }
        }.execute();
    }
}

public class Model {
    public void somethingSomethingSomething() {
        notifyListeners(); // This is going to notify whichever GUI 
                           // is listening to the model.
                           // Does it have to be wrapped with Swing.invokeLater?
    }
}

public class View {
    // This function is called when the model notifies its listeners.
    public void modelChangedNotifier() {
        button.setText("THE MODEL HAS CHANGED"); // Does this occur on the EDT?
    }
}
mKorbel
  • 109,525
  • 20
  • 134
  • 319
sdasdadas
  • 23,917
  • 20
  • 63
  • 148
  • It appears this may be similar to this question: http://stackoverflow.com/questions/12759619/mvc-swingworker-with-a-long-running-process-that-should-update-the-view where the answer seemed to be to have the Model notify the SwingWorker, which in turn notifies the View. I am still looking for suggestions, though... – sdasdadas Feb 05 '13 at 22:02

3 Answers3

4

You can read about it here: Improve Application Performance With SwingWorker in Java SE 6. In short: all time consuming operations, which are not affected UI must be done in another thread. To show results of operation you must go back to EDT. For example, if you make database search, you should show a progress bar (usually infinite) and start the search using SwingWorker. To show search results in a table, you must be in EDT. Alternatively you can use foxtrot lib (it allows to make your code more Swing convenient without to redesign it). If your controller code permanently updates the swing widgets you should execute it in EDT or at least perform these updates of UI in EDT (using SwingUtilities.invokeLater, chunk processing in SwingWorker or swing.Timer). So your sample is wrong: model update should be up-to-date in EDT.

Sergiy Medvynskyy
  • 11,160
  • 1
  • 32
  • 48
  • If my view is a representation of my model, then my model code is notifying my view of its changes. The controller doesn't actually affect my buttons / labels / etc. My main question is: when my model is updated in a different thread, and notifies my view, is this done on the model's thread or in the EDT? (Or maybe I'm confused as to how threads work with the observer pattern...) – sdasdadas Feb 05 '13 at 21:47
  • In EDT if your view contains (modifies) Swing widgets. – Sergiy Medvynskyy Feb 05 '13 at 21:56
  • I'm still a bit confused, are you saying that: i) my view SHOULD be on the EDT if it modifies widgets, or ii) my view is AUTOMATICALLY on the EDT because it modifies widgets? – sdasdadas Feb 05 '13 at 21:59
  • You can modify widgets in any thread, but only modifications in EDT works properly. So if you modify a Swing widget in another thread you can get strange behavior of your UI (exceptions, painting problems etc.) – Sergiy Medvynskyy Feb 05 '13 at 22:05
  • I understand that, though - my question is, "How do I notify the view that my model has changed, if my model is changing in a SwingWorker thread?" – sdasdadas Feb 05 '13 at 22:07
  • 2
    Swing worker usually used to perform 1..n same operations. If it so - you should override done or process method (both are executed in EDT). If no - you should update your view using SwingUtilities.invokeLater method (if these changes are seldom). If widgets are modified frequently (more than 5 times per second) you should use the Swing timer (to get from the EDT all changes in your model). – Sergiy Medvynskyy Feb 05 '13 at 22:21
  • 2
    @sdasdadas *"when my model is updated in a different thread"* this is where I would stop and think. Isolate the thread interactions to the controller. All interactions with the model and view should be made only within the EDT - IMHO – MadProgrammer Feb 05 '13 at 22:58
  • Thanks, both of you. I'm going to work on moving my updating logic into my controller. – sdasdadas Feb 05 '13 at 23:05
  • +1 for the [article](http://www.oracle.com/technetwork/articles/javase/swingworker-137249.html); I agree with @MadP; a related example is cited [here](http://stackoverflow.com/a/14719936/230513). – trashgod Feb 06 '13 at 01:15
  • Not sure I agree with @MadProgrammer about doing _all_ model stuff on EDT. You might want the Model to have a `doLongCalculationAndFastFourierTransform()` method, where that clearly doesn't work. So, to follow his advice, you'd have to put that into another class. Which may or may not be appropriate. Depends on how much logic you like to put into the model. I tend to put a _lot_ there, more than most. YMMV. – user949300 Feb 06 '13 at 01:51
  • @user949300 You have a valid point, but my concern is modify the model from the UI and thread simultaneously could cause unwanted results and would be difficult to debug. Best to place long running tasks (regardless of where they come from) in a separate thread and re-sync back to the model under a common, agreed thread (the EDT is convenient as it has re-sync methods and is the lowest common requirement) - IMHO – MadProgrammer Feb 06 '13 at 01:55
4

Instead of updating your model from doInBackground(), publish() interim results and update your model from process(), which executes on the EDT. In this example, JTable corresponds to your View and TableModel corresponds to your Model. Note that JTable listens to its own TableModel.

Community
  • 1
  • 1
trashgod
  • 203,806
  • 29
  • 246
  • 1,045
-2

One alternative approach, from Java Concurrency in Practice 9.4.2, uses a "Split" or a "Shared Data Model". You update your Business Model on whatever thread you want, likely the long-running non-EDT thread. But then, instead of directly calling notifyListeners() and worrying about which thread you are on, simply call myComponent.repaint(), which will queue up a repaint request on the EDT.

Then, somewhere in your paintComponent() method, you explicitly grab all new data from the Model, typically in a method called modelToView()

   commentTextArea.setText(myModel.getCommentText());
   fooLabel.setText(myModel.getFooText());
   ...

The upsides are that threading is not an issue, and, at least to some minds, this "makes sense", and the model is nicely decoupled from the view. A downside is that you are resetting all the values every time. So if you have 100 JComponents, that's 100 things getting set. Also, the view is pretty tightly coupled to the model.


Working Code Examples

@MadProgrammer and @kleopatra are correct that, if the view directly contains the components that are being updated, you get an "infinite loop of doom". For proof, see

Demo_14716901_Fails

However, if the view is isolated from the components, you avoid the infinite loop. Normally, the higher level view would contain stuff like JSplitPanes, holding JScrollPanes, holding Boxes or more JPanels, holding the actual low level components. So this requirement, IMO, is not unreasonable.

Working code at Demo_14716901_Works

Some Comments Added for the Downvoters:

Some people want to defeat Swing. They are writing instrument control code or algorithms and just want to get their job done without worrying about the EDT, endless SwingWorkers and invokeLaters. This technique lets them get their job done. With the one important caveat noted, it works. (Personally, I understand and generally like Swing, but many don't).

While Swing components are nicely MVC, they are generally at far too micro a level. The "real" model is not a single String, it is dozens of values. The "real" view is not a single JLabel, it is many JPanels, each with many components, combined with scrollers, splitters, etc. This technique usually fits the real world better, allowing the programmer to think naturally at a higher level.

As far as "bad practice", take it up with Brian Goetz, Josh Bloch, etc. o.k., that's "appeal to authority", but it works for me. :-)

user949300
  • 15,364
  • 7
  • 35
  • 66
  • That's definitely an alternative but it gets pretty ugly with the libraries I have involved. – sdasdadas Feb 05 '13 at 23:32
  • Yeah, you either use this approach most of the time or not at all. – user949300 Feb 06 '13 at 00:30
  • I hope you're not suggesting that people should update UI components from the `paintComponent`? That would cause another repaint request to be raised and hello infinite loop of doom :P – MadProgrammer Feb 06 '13 at 01:56
  • I haven't done Swing coding in a while so I probably have the details wrong but the concept works. – user949300 Feb 06 '13 at 03:55
  • -1 a) defeating the swing mechanism from ground on b) doing updates during the _paint_ cycle – kleopatra Feb 06 '13 at 13:30
  • @MadProgrammer I think it works cause you are overriding paintComponent of a high level container, like the `JPanel`, not the individual widgets. But, in answer to you and @Kleopatra, you could listen for update events, then do an `invokeLater()` around the `modelToView()` and a `repaint()`. – user949300 Feb 06 '13 at 16:28
  • @kleopatra The updates proposed, like JLabel.setText(), are all trivial and fast. If an update were extremely time consuming (not sure of a good example but they probably exist) then I agree with you that it would be better off the paint cycle. – user949300 Feb 06 '13 at 16:31
  • fast or not doesn't matter (and is relative, anyway). The point is that you _must not_ change the state of a component in the paint cycle because those changes (and other changes triggered by those, like f.i. a relayout) might again trigger a repaint. Anyway, your major sin is fighting against Swing in by-passing the internal updates ... – kleopatra Feb 06 '13 at 16:49
  • I'll try to post some working code later. There is one gotcha/trick that is far from elegant. – user949300 Feb 06 '13 at 18:56
  • @Madprogrammer and kleopatra working code examples posted - see edits to my post – user949300 Feb 10 '13 at 16:54
  • @user949300 Bad practice is still bad practice no matter how you dress it up - IMHO – MadProgrammer Feb 10 '13 at 19:38
  • So what do you do when a JTable is visualizing your model? It calls `getRowCount`, and by the time the JTable is willing to render the last row, it doesn't exist anymore due to the model's concurrent nature. You just cannot use a concurrent model to serve Swing views. Brian Goetz says on the Split Data Model, that you should have a separate model that is updated only on the EDT. This model may listen to updates to the concurrent model underneath, but updates itself only on the EDT. Then you notify the views from the EDT model, also on the EDT thread. That is what a Split Data Model is about. – Timmos Feb 16 '16 at 13:37