I have a View, Model, Controller classes. The View and Model are passed to the Controller constructor and all three are initialized with EventQueue.invokeLater() one after another.
The view has a JList component. My application requires to run a stream of text lines and to put each line in the JList component as they arrive. The stream architecture is done with Listeners, so as soon as an element arrives from the stream a StreamListener is notified. I need to start or stop the stream.
Now my problem is that I'm not sure how to model such a scenario. Of course setting the Controller as the StreamListener and then using a SwingWorker inside the Controller will solve the problem. However I thought it is the jobs of the Model to handle the streaming and business logic because the role of the Controller is just to facilitate the communications between the View and the Model.
One way I'm thinking to solve this is by defining a SwingPropertyChangeSupport in the Model and let the Controller implement PropertyChangeListener. Then in the Model I create a SwingWorker and call publish() every time an element from the stream arrives. Then from process() I call SwingPropertyChangeSupport.firePropertyChange(propertyName, oldValue, newValue) which then will cause the Controller to invoke its implemented method propertyChange(PropertyChangeEvent evt) which will finally cause the JList to add the element.
However I'm not sure if my approach is correct nor if the code will run on the Event Dispatching Thread. I hope if you can help.
public class Model {
SwingPropertyChangeSupport changeFirer;
Task task;
class Task extends SwingWorker<Void, Element> implements StreamListener {
Stream stream;
protected Void doInBackground() {
stream = new Stream();
stream.start();
}
public void onRecieveing(Element element) {
public(element);
}
protected void process(List<Element> elements) {
Element element = elements.get(elements.size()-1);
changeFirer.firePropertyChange("updateList", null, element);
}
}
}
The Controller:
public class Controller implements PropertyChangeListener {
Model model;
View view;
public Controller(View view, Model model) {
this.view = view;
this.model = model;
model.setChangeFirer(this);
}
#Override
public void propertyChange(PropertyChangeEvent evt) {
Element element = evt.getNewValue();
view.updateList(element);
}
}
Main method:
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
public void run() {
try {
View view = new View();
Model model = new Model();
Controller controller =
new Controller(view, model);
view.setVisible(true);
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
My solution is correct, propertyChange() runs on EDT and fires correctly.
Related
There's a nice discussion on EventQueue.invokeLater() here.
I have a controller class, Master() that loads two UI windows in my application. For example:
public class Master(){
public Master(){
aView = new subView();
bView = new subView();
Where subView extends JFrame and has the following main method:
public class SubView extends JFrame{
....
public static void main(String args[]) {
java.awt.EventQueue.invokeLater(new Runnable() {
public void run() {
new SubView();
}
});}
}
Notice that SubView.main() uses the invokeLater(). My question is how can I invokeLater() within master? Something like:
public class Master(){
public Master(){
aView = EventQueue.invokeLater(new subView);
bView = EventQueue.invokeLater(new subView);
It's not this simple because invokeLater does not return anything. Furthermore, because it's "invoked later", the values of aView and bView remain null in Master. Is there anyway to invoke both in the same manner that main() would invoke one of them in the runLater thread?
I would use invokeAndWait as you need to wait for the outcome.
SubView aView, bView;
public Master() {
java.awt.EventQueue.invokeAndWait(new Runnable() {
public void run() {
aView = new SubView();
bView = new SubView();
}
});
// aView and bView will be initialised.
}
It may prove awkward to solve this problem by invoking instances of Runnable. As an alternative use a SwingWorker to update the table models of both the master and detail views. This example may be a useful staring point.
Following Peter's suggestion, I did the following:
I created an intermediate Runnable called RunUIThread that returns that exposes the objects aView and bView, so I can return them to my Master program. Peter, do you think this is valid?
RunUIThread uiThread = new RunUIThread();
try {
java.awt.EventQueue.invokeAndWait(uiThread);
} catch (InterruptedException | InvocationTargetException ex) {
LOG.error("MasterView interrupted or failed to invoke RunUIThread");
}
aView = uiThread.getaView();
bView = uiThread.getbView();
Can you tell me, where I should declare my event listeners for Nodes, which are added out side of my controller class?
The best way is to explain it with this example:
I have my controller:
public class FXMLDocumentController implements Initializable {
#FXML
private AnchorPane root;
#Override
public void initialize(URL url, ResourceBundle rb) {
TestTask test = new TestTask(root);
Thread th = new Thread(test);
th.start();
}
}
And then I have the Task, which is started in the initialize method:
public class TestTask extends Task<Void>{
private AnchorPane root;
public TestTask(AnchorPane root){
this.root = root;
}
#Override
protected Void call() throws Exception {
Button btn = new Button("TestButton");
Platform.runLater(() -> { root.getChildren().add(btn); });
return null;
}
}
What I'm doing here? I have a FXML with an AnchorPane as root element. It has the id root. So now I start the Task in which I add one Button to my root node. Now I want to register an action event to the button. My question is now, where can/should I register the listener. Normally I register them in the controller, but here I can't do this because the Button only exists in the Task class. I could register it in the Task class but I think that it not scales good with large applications. The other way would to return the node back, so that I can access it in the controller class, but here I have to check if it is already added (to do this I have to call task.get(), which stops my application. So now could you tell me: what is the best way to register the listener for the node?
Don't create the UI in the background thread. There is (at best) very rarely a need to do this. If you need to perform some long-running task that retrieves data you need in order to create your UI, return the data from the task, and create the UI in the task's onSucceeded handler:
public class SomeControllerClass {
#FXML
private AnchorPane root ;
public void initialize() {
Task<SomeDataType> task = new MyTask();
task.setOnSucceeded(e -> {
// this method executed on FX Application thread.
SomeDataType result = task.getValue();
// now create UI and update root, using the data retrieved
});
Thread thread = new Thread(task);
thread.start();
}
}
and
public class MyTask extends Task<SomeDataType> {
#Override
public SomeDataType call() {
SomeDataType result = longRunningProcess();
return result ;
}
}
This question may be duplicated because I found a lot of similar question, but not the answer to my problem: I need to update the view of my SWING application from different SwingWorker.
I have a View class with a JTextArea and a JTable that i need to update during the execution of the Threads. The view also has a Start button that launch all the threads.
The controller listens for the button to be clicked then launch the threads:
public class MonitorPageController {
private MonitorPage monitorPage;
private List<Mission> missions;
class StartButtonListener implements ActionListener {
#Override
public void actionPerformed(ActionEvent e) {
for (int i = 0; i < missions.size(); i++) {
MyWorker worker = new MyWorker(missions.get(i));
worker.execute();
}
}
}
}
Then I have MyWorker class that manage the model:
public class MyWorker extends SwingWorker<Integer, String> {
private Mission m;
//<dec>
Block block1 = new Block();
Block block2 = new block();
Block block3 = new Block();
Block block4 = new Block();
public MyWorker(Mission mission) {
this.m = mission;
}
#Override
protected Integer doInBackground() throws Exception {
//<exe>
block1.addObserver(block2);
block2.addObserver(block3);
block3.addObserver(block4);
block4.addObserver(block2);
block1.update(null, m);
return 4;
}
}
In the end I have the Block class that is where I need to update the GUI (JTable and JTextArea):
public class Block extends Node implements Observer {
public Mission run(Mission m) {
m.setStatus(Mission.UNEXECUTED);
// HERE I WANT TO NOTIGY THE VIEW OF THE CHANGE OF STATUS OF THE MISSION
return m;
}
#Override
public void update(Observable o, Object arg) {
Mission m = this.run((Mission) arg);
setChanged();
notifyObservers(m);
}
}
EDIT: Mission is a simple class with the attribute: int status
I already tried with another observer pattern: I setted the mission as observable and the MonitorPageController as the observer. Then in the setter method of the status in class Mission I added the setChanged() and the notifyObservers() methods. In the end in the Observer (MonitoPageController) I implemented the update() method to call the view and update the gui.
I liked this way because it's clean and easy to implement, but I don't now why, after calling the notifyObserver() nothing was happening, so I discarded this solution, even if it seems to be the right one
Update the UI calling SwingUtilities.invokeLater() :
public class Block extends Node implements Observer {
public Mission run(Mission m) {
m.setStatus(Mission.UNEXECUTED);
SwingUtilities.invokeLater(new Runnable(){
#Override
public void run(){
//UPDATE UI HERE
}
});
return m;
}
#Override
public void update(Observable o, Object arg) {
Mission m = this.run((Mission) arg);
setChanged();
notifyObservers(m);
}
}
I found a possible solution, maybe there better ideas, but this works for now:
Creating the SwingWorker in MonitorPageController I changed the constructor passing the istance of the MonitorPageController too.
MyWorker worker = new MyWorker(misssions.get(i), this);
Then in MyWorker class when I create Block1, Block2, ...I pass them the istance of the MyWorker:
Block block1 = new Block(this);
Block block2 = new Block(this);
....
In the same class (MyWorker) I created a method:
public void log(Mission m, String s) {
controller.log(m, s);
}
Controller is the istance of the MonitorPageController that created the worker.
Now the inside block class, when I want to notify the change of status, I can call:
parentWorker.log(mission, "some string");
In the end the log() method in the MonitorPageController calls the view method to update the components...by now it seems to work...
I want JList to be populated with multiple threads.
I tried this way but jlist is empty.
It would be good if jlist was updated on the fly
There are two threads, the other one loads in anouther direction
new Thread(new Runnable() {
#Override
public void run() {
for(i=0; i<cells.size()/2; i++){
System.out.println("thread");
try{
HtmlPage p = client.getPage("https://tbilisi.embassytools.com/en/slotsReserve?slot="+cells.get(i).getAttribute("data-slotid"));
pages.add(p);
if(!p.getUrl().toString().contains("slotsReserve"))
model.add(i,p.getUrl().toString());
}
catch (Exception e){
e.printStackTrace();
}
}
}
});
list1.setModel(model)
Thanks in advance
UPDATE*
So I fixed by using SwingWorker
Swing is a single threaded framework, that is, it is expected that all updates and modifications to the UI are done from within the context of the Event Dispatching Thread.
Equally, you should do nothing in the EDT that might block or otherwise prevent it from processing the Event Queue (like downloading content from the web).
This raise a conundrum. Can't update the UI outside the EDT, need to use some kind of background process to execute time consuming/blocking tasks...
So long as the order of items is unimportant, you would use multiple SwingWorkers in place o of the Threads, for example...
DefaultListModel model = new DefaultListModel();
/*...*/
LoadWorker worker = new LoadWorker(model);
worker.execute();
/*...*/
public class LoaderWorker extends SwingWorker<List<URL>, String> {
private DefaultListModel model;
public LoaderWorker(DefaultListModel model) {
this.model = model;
}
protected void process(List<String> pages) {
for (String page : pages) {
model.add(page);
}
}
protected List<URL> doInBackground() throws Exception {
List<URL> urls = new ArrayList<URL>(25);
for(i=0; i<cells.size()/2; i++){
try{
HtmlPage p = client.getPage("https://tbilisi.embassytools.com/en/slotsReserve?slot="+cells.get(i).getAttribute("data-slotid"));
pages.add(p);
if(!p.getUrl().toString().contains("slotsReserve")) {
publish(p.getUrl().toString());
urls.add(p.getUrl());
}
}
catch (Exception e){
e.printStackTrace();
}
}
return urls;
}
}
This allows you execute your blocking/long running in the backround (doInBackground) and publish the results of this method which are then processed within the context of the EDT...
See Concurrency in Swing for more details
Swing is not thread safe you should use SwingUtilities to run multiple threads updating swing.
javax.swing.SwingUtilities.invokeLater(new Runnable() {
public void run() {
doWhateverYouWant();
}
});
read more
I have a view that I want to react to what happens in the editor. Right now I have a button that I want it so that when clicked it updates the data in the view to some new set of information. Where do I start, I have my selection event but no idea on how to communicate between the two. I'm looking for a loose coupling solution.
I'm sure there are many ways of doing this, but I've used the JFace IPropertyChangeListener interface in the past for simple event propagation.
Make your view implement IPropertyChangeListener. Create a Singleton class that you can register your IPropertyChangeListener with, and send a PropertyChangeEvent to. Then in the constructor of your view, register it with your Singleton.
Now you can get hold of your Singleton in your editor and fire off an event that will get picked up in your view.
Example code for the Singleton:
public class PropertyChangeEventBus {
private static PropertyChangeEventBus s_instance = new PropertyChangeEventBus();
public static PropertyChangeEventBus instance()
{
return s_instance;
}
private Set<IPropertyChangeListener> m_listeners;
private PropertyChangeEventBus()
{
// use CopyOnWriteArraySet to prevent ConcurrentModificationExceptions
m_listeners = new CopyOnWriteArraySet<IPropertyChangeListener>();
}
public void addListener(IPropertyChangeListener listener)
{
m_listeners.add(listener);
}
public void removeListener(IPropertyChangeListener listener)
{
m_listeners.remove(listener);
}
public void fire(final PropertyChangeEvent event)
{
// run property change events in UI thread to prevent having to have lots of syncExecs in the listener methods
ViewUtils.syncExec(new Runnable()
{
#Override
public void run()
{
for (IPropertyChangeListener listener : m_listeners)
{
try
{
listener.propertyChange(event);
}
catch(Exception e)
{
//log it, present error message
}
}
}
});
}
}
Example Code for the View:
//The constructor
public MyView()
{
PropertyChangeEventBus.instance().addListener(this);
}
#Override
public void propertyChange(PropertyChangeEvent event)
{
if(event.getProperty().equals(SOME_CONSTANT))
{
// Refresh View
}
}