Handle long-running EDT tasks (f.i. TreeModel searching) - java

Trigger is a recently re-detected SwingX issue: support deep - that is under collapsed nodes as opposed to visible nodes only, which is the current behaviour - node searching.
"Nichts leichter als das" with all my current exposure to SwingWorker: walk the TreeModel in the background thread and update the ui in process, like shown in a crude snippet below. Fest's EDT checker is happy enough, but then it only checks on repaint (which is nicely happening on the EDT here)
Only ... strictly speaking, that background thread must be the EDT as it is accessing (by reading) the model. So, the questions are:
how to implement the search thread-correctly?
or can we live with that risk (heavily documented, of course)
One possibility for a special case solution would be to have a second (cloned or otherwise "same"-made) model for searching and then find the corresponding matches in the "real" model. That doesn't play overly nicely with a general searching support, as that can't know anything about any particular model, that is can't create a clone even if it wanted. Plus it would have to apply all the view sorting/filtering (in future) ...
// a crude worker (match hard-coded and directly coupled to the ui)
public static class SearchWorker extends SwingWorker<Void, File> {
private Enumeration enumer;
private JXList list;
private JXTree tree;
public SearchWorker(Enumeration enumer, JXList list, JXTree tree) {
this.enumer = enumer;
this.list = list;
this.tree = tree;
}
#Override
protected Void doInBackground() throws Exception {
int count = 0;
while (enumer.hasMoreElements()) {
count++;
File file = (File) enumer.nextElement();
if (match(file)) {
publish(file);
}
if (count > 100){
count = 0;
Thread.sleep(50);
}
}
return null;
}
#Override
protected void process(List<File> chunks) {
for (File file : chunks) {
((DefaultListModel) list.getModel()).addElement(file);
TreePath path = createPathToRoot(file);
tree.addSelectionPath(path);
tree.scrollPathToVisible(path);
}
}
private TreePath createPathToRoot(File file) {
boolean result = false;
List<File> path = new LinkedList<File>();
while(!result && file != null) {
result = file.equals(tree.getModel().getRoot());
path.add(0, file);
file = file.getParentFile();
}
return new TreePath(path.toArray());
}
private boolean match(File file) {
return file.getName().startsWith("c");
}
}
// its usage in terms of SwingX test support
public void interactiveDeepSearch() {
final FileSystemModel files = new FileSystemModel(new File("."));
final JXTree tree = new JXTree(files);
tree.setCellRenderer(new DefaultTreeRenderer(IconValues.FILE_ICON, StringValues.FILE_NAME));
final JXList list = new JXList(new DefaultListModel());
list.setCellRenderer(new DefaultListRenderer(StringValues.FILE_NAME));
list.setVisibleRowCount(20);
JXFrame frame = wrapWithScrollingInFrame(tree, "search files");
frame.add(new JScrollPane(list), BorderLayout.SOUTH);
Action traverse = new AbstractAction("worker") {
#Override
public void actionPerformed(ActionEvent e) {
setEnabled(false);
Enumeration fileEnum = new PreorderModelEnumeration(files);
SwingWorker worker = new SearchWorker(fileEnum, list, tree);
PropertyChangeListener l = new PropertyChangeListener() {
#Override
public void propertyChange(PropertyChangeEvent evt) {
if (evt.getNewValue() == SwingWorker.StateValue.DONE) {
//T.imeOut("search end ");
setEnabled(true);
((SwingWorker) evt.getSource()).removePropertyChangeListener(this);
}
}
};
worker.addPropertyChangeListener(l);
// T.imeOn("starting search ... ");
worker.execute();
}
};
addAction(frame, traverse);
show(frame)
}
FYI: cross-posted to OTN's Swing forum and SwingLabs forum - will try to post a summary of all input at the end (if there is any :-)
Addendum
At the end of the day, it turned out that I asked the wrong question (or right question in a wrong context ;-): the "problem" arose by an assumed solution, the real task to solve is to support a hierarchical search algorithm (right now the AbstractSearchable is heavily skewed on linear search).
Once that will solved, the next question might be how much a framework can do to support concrete hierarchical searchables. Given the variety of custom implementations of TreeModels, that's most probably possible only for the most simple.
Some thoughts that came up in the discussions here and the other forums. In a concrete context, first measure if the traversal is slow: most in-memory models are lightning fast to traverse, nothing needs to be done except using the basic support.
Only if the traversal is the bottleneck (as f.i. in the FileSystemModel implementations of SwingX) additional work is needed:
in a truly immutable and unmodifiable TreeModel we might get away with read-only access in a SwingWorker's background thread
the unmodifiable precondition is violated in lazy loading/deleting scenarios
there might be a natural custom data structure which backs the model, which is effectively kind-of "detached" from the actual model which allows synchronization to that backing model (in both traversal and view model)
pass the actual search back to the database
use an wrapper on top of a given TreeModel which guarantees to access the underlying model on the EDT
"fake" background searching: actually do so in small-enough blocks on the EDT (f.i. in a Timer) so that the user doesn't notice any delay
Whatever the technical option to a slow search, there's the same usability problem to solve: how to present the delay to the end user? And that's an entirely different story, probably even more context/requirement dependent :-)

Both SwingWorker and TreeModel should synchronize access to a common, underlying DataModel. Making the shared quanta of data (effectively) immutable can minimize the overhead. As this is highly application-dependent, I'm not sure how much support the view can offer for searching non-visible nodes.

Related

Updating entries of virtual TableViewer on model change

I'm currently recoding a TableViewer to work fully virtual. So far I'm pretty content with the results, but I still have a problem, that all visible elements in the table are refreshed on a fixed timer. The model changes continously though. This means, that if I click on an entry before a periodic update happens, the table loads in the actual value for that position, but leaves all other elements untouched. Since this is how the LazyContentProvider works that is set for the TableViewer this is not much of a problem.
Since my TableViewer is a Live-Viewer of incoming events, with the newest entry shifting all other items one down, I'd like to refresh all visible elements on adding a new event.
I've tried to use TableViewer.refresh() on adding a new item, but that does not seem to do anything.
Since the full code is pretty complex, and part of a bigger piece of code I'll provide a basic representation of the code:
public class MyClass{
public TableViewer liveViewer;
public List<String> myItems=new ArrayList<>();
void init(){
liveViewer = new TableViewer(liveComp, SWT.BORDER | SWT.FULL_SELECTION | SWT.VIRTUAL);
liveViewer.setContentProvider(new LiveViewerContentProvider(liveViewer));
liveViewer.setLabelProvider(someLabelProvider);
liveViewer.setUseHashlookup(true);
ClassThatProvidesItems.addListener(new ItemAddedListener(){
#Override
void itemAdded(String item){
myItems.add(0,item);
}
}
}
}
public class LiveViewerContentProvider implements ILazyContentProvider{
private TableViewer viewer;
private List<String> input;
public LiveViewerContentProvider(TableViewer viewer) {
this.viewer = viewer;
}
#Override
public void dispose() {
}
#Override
public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
this.input = (List<String>) newInput;
}
#Override
public void updateElement(int index) {
viewer.replace(input.get(index), index);
}
}
I'm aware, that an ArrayList is probably not the best choice, for always adding an element at the head, but please ignore it for now. I've tried to perform a liveViewer.refresh(); at the end of the listener callback, but it didn't seem to refresh my elements. What could I do to force a refresh for all visible items on adding a new one?
Thanks in advance.
I've just noticed, that my solution was almost working. The problem was, that the whole code was somewhere within a weird try-catch-block that just silently swallowed Exceptions, and didn't give me the invalid-Thread-access exception that I should have gotten for not performing the liveViewer.refresh within the Display-Thread. Wrapping the line like this fixed the issue:
Display.getDefault().asyncExec(new Runnable() {
#Override
public void run() {
liveViewer.refresh();
}
});

Java - Doing large scale GUI projects

To get right directly to my question.
How do you do large scale GUI projects. I have not done any larger GUI projects in java so far but what i am working on now grew pretty fast and pretty big and now i am stuck whit a huge pile of code that is really annoying and messy.
Since i come from field of web development i am used to MVC frameworks so i have 3 packages in my projects Model where i keep classes that interact whit files or db, Views where i keep my classes for Forms or GUI and Controller package where i keep the majority of my logic.
I have been told to separate my logic as well keep actions in one class and listeners in another class but i have no idea how to link all that up.
So far i only have 1 Controller class where i execute all the methods regarding whats happening on the GUI once its invoked.
package pft.controller;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JLabel;
import javax.swing.JComboBox;
import javax.swing.JTree;
import java.awt.event.*;
import javax.swing.JProgressBar;
import pft.view.Invoke_GUI;
import pft.model.Events;
import pft.model.Parse;
public class Tower_Controller {
public Tower_Controller() {
}
//Global variables
String isSelected = null;
int hasModules = 0;
int cap = 0;
int cpu = 0;
int shield = 0;
int armor = 0;
public void setName(String name){
this.isSelected = name;
}
public String getName(){
return this.isSelected;
}
public void setCap(int cap){
this.cap = cap;
}
public int getCap(){
return this.cap;
}
public void setCpu(int cpu){
this.cpu = cpu;
}
public int getCpu(){
return this.cpu;
}
public void setShield(int shield){
this.shield = shield;
}
public int getShield(){
return this.shield;
}
public void setArmor(int armor){
this.armor = armor;
}
public int getArmor(){
return this.armor;
}
public void invoke() throws IOException {
Invoke_GUI runnable = new Invoke_GUI();
final JLabel tower_name = runnable.tower_name;
final JComboBox tower_select = runnable.tower_select;
final JTree module_browser = runnable.module_browser;
final JTree selected_modules = runnable.selected_modules;
final JProgressBar cap_bar = runnable.cap_bar;
final JProgressBar cpu_bar = runnable.cpu_bar;
final JLabel em_res = runnable.em;
final JLabel th_res = runnable.thermic;
final JLabel ki_res = runnable.kinetic;
final JLabel ex_res = runnable.explosive;
setTowerName(tower_name, tower_select);
removeTower(tower_name);
runnable.setVisible(true);
}
public void removeTower(final JLabel tower_name) {
tower_name.addMouseListener(new MouseListener() {
#Override
public void mouseClicked(MouseEvent e) {
if (hasModules == 1 & isSelected != null) {
Events evt = new Events();
evt.towerHasModules();
} else if (isSelected == null) {
} else {
tower_name.setText("No Control Tower selected");
isSelected = null;
}
}
#Override
public void mousePressed(MouseEvent e) {
}
#Override
public void mouseReleased(MouseEvent e) {
}
#Override
public void mouseEntered(MouseEvent e) {
}
#Override
public void mouseExited(MouseEvent e) {
}
});
}
public void updateVariables(String name) throws IOException{
Parse tower = new Parse();
String data[] = tower.towerData(name);
Integer x = Integer.valueOf(data[1]).intValue();
setCap(x);
}
public void setTowerName(final JLabel tower_name, final JComboBox tower_select) {
tower_select.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
if (isSelected != null) {
Events evt = new Events();
evt.towerSelected(isSelected);
} else {
tower_name.setText(tower_select.getSelectedItem().toString());
setName(tower_name.toString());
try {
updateVariables(tower_name.toString());
} catch (IOException ex) {
Logger.getLogger(Tower_Controller.class.getName()).log(Level.SEVERE, null, ex);
}
}
}
});
}
}
There are a lot of tutorials and examples how to do small usually single class Java GUI but no tutorials or examples on how to do projects that are bit larger than a single class.
Thanks in advance for all the help and
advice.
Here is my advice for Swing development in general. It does discuss the importance of using Controllers to bridge the needs of the view and the interace of the model.
GUI guidelines for swing
Last Swing project I did I designed a MVC framework that used Spring for defining the model of the program and Controllers, then used annotations in the Controller to wire up events dispatched by the view onto methods in the controller. The view had access to the event dispatcher which was an event bus, and events sent over the bus called methods on the Controller through the annotations. This allowed any Controller to respond to events from the View. So as a Controller got too large it was super simple to refactor each set of methods into another Controller, and the view or model didn't have to change.
The beauty of the event bus was it could be shared with the model as well so the model could dispatch asynchronous events the Controller could register for as well. It looked something like:
public class SomeController {
private AuthenticationModel authenticationModel;
private LoginService loginService;
private MyApp view;
#Listener( event = "login" )
public void login( LoginEvent event ) {
view.showWaitDialog();
loginService.login( event.getUserName(), event.getPassword() )
.onResult( new Callback<User>() {
public void onResult( User user ) {
authenticationModel.setUser( user );
view.hideWaitDialog();
view.showStartScreen(user);
}
});
}
}
Once this framework was in place it was amazing how fast we could get things done. And it held up pretty well as we added features. I've done my fair share of large Swing projects (3 to date), and this architecture made a huge difference.
The easiest way to scale a GUI is to make everything loosely coupled. Events (Swing's and your own) are the best way to do this. Unless a class is directly creating or showing a GUI element, it shouldn't know or care about anything else in the UI.
The Controller should continue doing what it's supposed to do - firing events in response to other events. But, these events should be application level events defined by the needs of your app. The Controller shouldn't directly manipulate GUI elements. Instead, you should create components (maybe just subclasses of JWhatever) that register themselves with the Controller as interested in events.
For example, create an TowerEventListener interface with a nameChanged() function. The Controller also has a changeTowerName() function, which when called, updates the model (a Tower class) then calls nameChanged() on all registered TowerEventListeners.
Then create a TowerRenamer class that, for example, subclasses JDialog (i.e. a popup window) that includes a text box and and OK button along with a reference to the Controller. When the user clicks OK, Controller.changeTowerName() is called. Other parts of your GUI that register as TowerEventListeners will receive the event and update as needed (maybe by updating a JLabel on the UI).
Note, if your needs are simple enough, you can just use PropertyChangeEvents and not worry about a whole event interface structure. PropertyChangeSupport can be used by the Controller to fire event notifications.
In addition to the great advice already given, I would recommend reading some of what Trygve Reenskaug has written and/or recorded on his MVC page. He was there during the development of this architectural style in the late 70's. His two page technical report entitled Models - Views - Controllers from December of 1979 presents the most concise description of the model, view, and controller.
Of particular note, views are both observers and manipulators of the model. The controller is primarily concerned with arranging (wiring) the views and translating user input into interactions with the model. Several of the MVC frameworks out there have the controller relaying data from the model to the view - this is simply wrong. A paper from earlier in 1979 included the concept of an editor as a composite of related views. The editor was discarded; its functionality was moved into both the controller and the view.
Another article that is good at describing how to apply this guideline is Burbeck's How to use Model-View-Controller. It is written with Smalltalk in mind so it might not translate to Java easily, but it is a good description of how to apply the guideline.
I think that the most important thing to consider is that the original MVC style was created for user interfaces that included more than one view (representation) of the same model. This really works well for user interfaces but does not translate exceptionally well to the web service world. Using MVC for a GUI lets you really see and understand the power of this style.

How do I access the source of an ActionEvent when the ActionListener is located in a different class?

I can't get my head round this one. I've tried to adhere to the MVC pattern for the first time and now have difficulties accessing the source of an ActionEvent because the ActionListener is located in a different class. But let the code do the talking...
In the "view":
// ControlForms.java
...
private JPanel createSearchPanel() throws SQLException {
...
comboBoxCode = new JComboBox(); // Field comboBoxCode -> JComboBox()
SwingUtilities.invokeLater(new Runnable() {
public void run() {
AutoCompleteSupport<Object> support = AutoCompleteSupport.install(
comboBoxCode, GlazedLists.eventListOf(jnlCodeArray));
}
}); // Auto-Complete comboBox from GlazedLists
...
public void setComboListener(ComboListener comboListener) {
comboBoxCode.addActionListener(comboListener);
}
...
}
Then, in what I term the controller, I have two different classes:
// Controller.java
public MyController() throws SQLException {
...
addListeners();
}
...
private void addListeners(){
View view = getView();
getView().getControlForm().setComboListener(new ComboListener());
}
and
public class ComboListener implements ActionListener {
public void actionPerformed(ActionEvent e) {
System.out.println("ComboBox listened to! e = " + e.toString());
}
}
Now, e obviously doesn't give the name of the variable (which at the moment I wish it would), so I cannot if test for e.getSource().
My question is thus: is there either a) a way to query (via if for example) the source of e, or b) a less complicated way to get to the variable name?
Many, many thanks in advance for your insights and tips!
Why do you need the name of the variable? Why can't you do the event handling like this
public class ComboListener implements ActionListener
{
public void actionPerformed(ActionEvent e)
{
JComboBox source = (JComboBox)e.getSource();
//do processing here
}
}
I'd think that if you need to do processing according the variable name, obviously you need different listeners for different combo boxes.
Generally, there are only two situations in which you should use a listener like that: a) you're going to handle a certain event the same way for a bunch of objects, or b) you're only going to use the listener for one object. In the latter case, I'd prefer handling the event locally anyway.
That said, the direct answer to your question is: you shouldn't have to check inside your ActionListener implementation to see whether the appropriate object is the source of the event; you should simply only add the ActionListener to that one object.
One final note: without knowing the specifics of your architecture... generally, MVC will treat all event handling as part of the View (it reduces coupling) and the View will pass commands or method calls or your own events (i.e., not Swing's) to the Controller.

How to setSelectionPaths() for a JIDE CheckBoxTree using the CheckBoxTreeSelectionModel?

I'm using the CheckBoxTree class which is part of the JIDE Common Layer package (http://jidesoft.com/products/oss.htm). What I'd like to be able to do is save and load the state of the CheckBoxTreeSelectionModel which is what tracks what boxes are checked or not. I can save it by just saving selectionModel.getSelectionPaths(), but my problem is with loading it. When I selectionModel.setSelectionPaths() it only checks the boxes for the root and the leaf of the path, but nothing in between. Strangely enough, this also happens when I save the results of getSelectionPaths() then feed it directly into setSelectionPaths().
For the FileSystemModel, I'm using some code I found which likes to use File objects instead of TreeNodes. I have tried different combinations of FileSystemModels and CheckBoxTrees that I've found in various places on the Net, and the results are always the same. I've probably put close to 20 hours in on this issue... which is a bit embarrassing to admit. Any help is appreciated!
My code is as follows. This creates the CheckBoxTree and attempts to load it with "/Documents and Settings/Administrator" which results in "/" and "Administrator" and all it's children being checked, but not "Documents and Settings".
public class CheckBoxTreeFrame {
private FileSystemModel fileSystemModel = null;
private CheckBoxTree checkBoxTree = null;
private JFrame main_frame = null;
private CheckBoxTreeSelectionModel selectionModel = null;
public CheckBoxTreeFrame(){
// create the model
fileSystemModel = new FileSystemModel(new File(File.separator));
// use the model for the Tree
checkBoxTree = new CheckBoxTree(fileSystemModel);
checkBoxTree.setEditable(false);
// model for the checkboxes (not the directory structure)
selectionModel = checkBoxTree.getCheckBoxTreeSelectionModel();
// event listener
checkBoxTree.getCheckBoxTreeSelectionModel().addTreeSelectionListener(new TreeSelectionListener() {
public void valueChanged(TreeSelectionEvent e) {
System.out.println(selectionModel.getSelectionPath());
}
});
// setup a little UI window for the tree.
main_frame = new JFrame("Frame Title");
main_frame.add(checkBoxTree);
main_frame.setSize(400, 400);
main_frame.setVisible(true);
// run the loading test
runTest();
}
public void runTest(){
File[] finalPath = new File[3];
finalPath[0] = (File)selectionModel.getModel().getRoot();
finalPath[1] = new File(finalPath[0],"Documents and Settings");
finalPath[2] = new File(finalPath[1],"Administrator");
selectionModel.setSelectionPath(new TreePath(finalPath));
}
}
Thanks!!
The CheckBoxTreeSelectionModel is basically a DefaultTreeSelectionModel (as in Swing). The trick the tree path has to exist in the TreeModel. I don't think the way you create a TreePath in runTest will create the same tree path. It'd better to get the tree path from the tree. Try this, it will work.
checkBoxTree.getCheckBoxTreeSelectionModel().addSelectionPath(checkBoxTree.getPathForRow(2));

progress bars + MVC in Java =?

So I have this nice spiffy MVC-architected application in Java Swing, and now I want to add a progress bar, and I'm confused about Good Design Methods to incorporate a JProgressBar into my view. Should I:
add a DefaultBoundedRangeModel to my controller's state, and export it?
class Model {
final private DefaultBoundedRangeModel progress
= new DefaultBoundedRangeModel();
public void getProgressModel() { return progress; }
public void setProgressCount(int i) { progress.setValue(i); }
}
class Controller {
Model model;
int progressCount;
void doSomething()
{
model.setProgressCount(++progressCount);
}
}
class View {
void setup(Model m)
{
JProgressBar progressBar = /* get or create progress bar */ ;
progressBar.setModel(m.getProgressModel());
}
}
/* dilemma: Model allows progress to be exported so technically
all of the progress state could be set by someone else; should it be put
into a read-only wrapper? */
use JGoodies Binding to try to connect the JProgressBar's visual state to my model's state?
class Model {
private int progress;
public void getProgressCount() { return progress; }
public void setProgressCount(int i) { progress = i; }
}
class View {
void setup(Model m)
{
ProgressBar progressBar = /* get or create progress bar */ ;
CallSomeMagicMethodToConnect(m, "progressCount", progressBar, "value");
// is there something that works like the above?
// how do I get it to automatically update???
}
}
or something else???
edit: more specifically: could someone point me to a Good Example of realistic source for an application in Java that has a status bar that includes a progress bar, and has a decent MVC implementation of it?
No (to 1) and NOOOO (to 2). At least in my opinion.
No (to 1): First, DefaultBoundedRangeModel is a javax.swing class. In my opinion, these classes have no place in models. For example, think about the model living on the server, being accessed via RMI - All of the sudden putting a javax.swing class there seems "not right".
However, the real problem is that you're giving a part of your model (the bounded model) to someone else, with no control over events fired or queries made.
No (to 2): Ugh. Binding is fun but (at least in my opinion) should be used to synchronize between UI model and UI components, not between data model and UI model. Again, think what would happen if your data model lived on a remote server, accessed by RMI.
So what? Well, this is only a suggestion, but I'd add an event listener interface and add the standard event listener subscription methods (addListner(...), removeListener(...)). I'd call these listeners from within my model when I have updates going on. Of course, I'd make sure to document the calling thread (or say it cannot be determined) in order for the client (the UI in this case) to be able to synchronize correctly (invokeLater and friends). Since the listener service will be exposed by the controller, this will allow the model to live anywhere (even allowing for listeners to be remotely invoked or pooled). Also, this would decouple the model from the UI, making it possible to build more models containing it (translators / decorators / depending models).
Hope this helps.
I would say, something else.
The problem I have had with MVC, is to define the level of abstraction of the model.
Model could be some sort of objects for the UI components
Model could also be some other sort of objects for the program it self.
and
Model could be as high as business models.
In this case I would have separated model/component pairs for the progress bar and handle them in a separate controller class.
This article describes swing architecture and might clarify the way it uses models inside.
In our app (MVC, about 100 KLOC) we have it like that (pattern Observer, actually):
/**
* Observer on progress changes
*/
public interface IProgressListener {
public void setProgress(ProgressEvent e);
}
public class ProgressEvent extends ... {
private int progressCount;
// setter + getter
...
}
class Model {
public void addProgressListener(IProgressListener l);
protected void fireProgressChange(ProgressEvent e); // call .setProgress() on listeners
}
class Controller {
private Model model;
}
class View extends ProgressBar implements IProgressListener {
...
// IProgressListener implementation
public void setProgress(ProgressEvent e) {
this.setValue(e.getProgress());
}
...
}

Categories