MVC and application state

P

Philipp

Hello,
I just read http://java.sun.com/products/jfc/tsc/articles/architecture/
which was recommended in another thread and a question remains as how to
separate the GUI and data.

Suppose you have a boolean value in your application (for example,
"Check for new messages at startup", in an email app), you will need
this value for the app to work correctly. This value may be set by a
checkbox in some Preferences window, but is usually hidden from the user.

Would you:
- Initialize the GUI without showing it, and get the state by accessing
the GUI component's value (eg. checkbox.isSelected())
- Use directly, or subclass, ButtonModel and use this ButtonModel to
hold this data everywhere in your code, passing it to the JCheckbox upon
construction of the GUI
- Implement your own update mechanism: i.e. storing the data in a simple
boolean somewhere and when the GUI is shown, create a checkbox with the
correct selection state. Update the boolean value only on close of the
Preferences window.
- Any other sensible way I did not think of?

What's your day-to-day way of doing this for GUI apps?

Thanks for your answers
Phil
 
E

Eric Sosman

Philipp said:
Hello,
I just read http://java.sun.com/products/jfc/tsc/articles/architecture/
which was recommended in another thread and a question remains as how to
separate the GUI and data.

Suppose you have a boolean value in your application (for example,
"Check for new messages at startup", in an email app), you will need
this value for the app to work correctly. This value may be set by a
checkbox in some Preferences window, but is usually hidden from the user.

Would you:
- Initialize the GUI without showing it, and get the state by accessing
the GUI component's value (eg. checkbox.isSelected())
- Use directly, or subclass, ButtonModel and use this ButtonModel to
hold this data everywhere in your code, passing it to the JCheckbox upon
construction of the GUI
- Implement your own update mechanism: i.e. storing the data in a simple
boolean somewhere and when the GUI is shown, create a checkbox with the
correct selection state. Update the boolean value only on close of the
Preferences window.
- Any other sensible way I did not think of?

I'd keep the "authoritative" boolean somewhere in the
main part of the app, possibly using a Preferences to preserve
the value from run to run. If the user opens the part of the
GUI that controls this boolean, I'd initialize a checkbox from
the current authoritative value and update that value if the
user changes the checkbox.

I'd try to avoid having the app query the checkbox state
directly, because this would tie the app to the specifics of
the GUI design. Rather, I'd use the GUI as a "knob" to show
and control the real value. This gives me the flexibility to
change the GUI, to implement a non-GUI command-line interface
for use in batch scripts, and so on.

YMMV.
 
S

Stefan Ram

Philipp said:
a question remains as how to separate the GUI and data.

(If replying to this post, please do not quote all of it, but
only small parts you directly refer to [where »you« does not
refer to Philipp, but to anyone replying to this post].)

Usually, the GUI is observing the model via the observer pattern.
checkbox in some Preferences window, but is usually hidden from the user.
- Initialize the GUI without showing it, and get the state by accessing
the GUI component's value (eg. checkbox.isSelected())

Usually, it would not be part of the GUI, but of the
application model. Especially, when it usually is »hidden
from the user«.

Swing does not use the original MVC. For example, it uses
UI-delegates, not controllers and views.

Here is an example code by me to show how to use the original
MVC with Swing in Java as truly as possible. But since this
was my main intention, the code has become larger than
necessary in Java alone, without this intention.

As in your question, it keeps a single-bit state.

The class »Button1Model« is the application model in this
case. Some comments refer to the name of Smalltalk messsages
used in the original MVC, which originated in Smalltalk,
though the correspondence is not always perfect, because
Java+Swing is not Smalltalk. For an explanation, see [Burbeck 87]:

http://st-www.cs.uiuc.edu/users/smarch/st-docs/mvc.html

From reading the code below, you can learn how »Button1Model«
holds a list of its observers and how »Button1View« registers
itself with this model and then observes it.

I am using this program in my class at a point, where control
statements like »if« or »while« have not been introduced yet -
so all is done without a single »if« or »while«.

ButtonModel.java

public class ButtonModel
{ public static void main( final java.lang.String[] args )
{ javax.swing.SwingUtilities.invokeLater( new FrameController() ); }}
class FrameView
{ FrameController controller = null;
Button1View button = null;
javax.swing.JFrame frame = null; /* MVC "subView" */
public FrameView()
{ frame = new javax.swing.JFrame( "MVC" );
frame.setDefaultCloseOperation
( javax.swing.JFrame.DO_NOTHING_ON_CLOSE );
final Button1Model model = new Button1Model();
final Button1Controller controller = new Button1Controller();
controller.setModel( model );
button = new Button1View( frame, model, controller );
frame.pack(); frame.setVisible( true ); }
public void setController // MVC "model:controller:"
( final FrameController controller )
{ this.controller = controller;
this.controller.setView( this );
frame.addWindowListener( this.controller ); }
public void releaseController() /* MVC "release" */
{ frame.removeWindowListener( this.controller );
this.controller.releaseView();
this.controller = null; }
public void dispose() /* MVC "release" */
{ this.button.dispose(); this.button = null;
this.frame.dispose(); this.frame = null; }}
class FrameController // MVC "Top-Level Controller
implements java.lang.Runnable, // MVC "StandardSystemController"
java.awt.event.WindowListener
{ FrameView view = null;
public void setView( final FrameView view ) /* MVC "view" */
{ this.view = view; }
public void releaseView()
{ this.view = null; }
public void run() // MVC "open" (cto)
{ FrameView view = new FrameView();
view.setController( this ); }
public void windowClosing /* MVC "terminate" */
( final java.awt.event.WindowEvent e )
{ FrameView view = this.view;
view.releaseController();
view.dispose(); }
public void windowOpened( final java.awt.event.WindowEvent e ){}
public void windowDeactivated( final java.awt.event.WindowEvent e ){}
public void windowDeiconified( final java.awt.event.WindowEvent e ){}
public void windowIconified( final java.awt.event.WindowEvent e ){}
public void windowActivated( final java.awt.event.WindowEvent e ){}
public void windowClosed( final java.awt.event.WindowEvent e){} }
class Button1View /* MVC "view" */
implements java.beans.PropertyChangeListener
{ javax.swing.JButton button = null;
javax.swing.JFrame frame = null;
Button1Model button1Model = null;
private java.lang.String labelText( final boolean state )
{ return state ? "turn off" : "turn on"; }
public Button1View /* MVC "model:controller" (va3) */
( final javax.swing.JFrame frame,
final Button1Model button1Model,
final java.awt.event.ActionListener controller )
{ assert javax.swing.SwingUtilities.isEventDispatchThread();
this.button1Model = button1Model;
this.button = new javax.swing.JButton( labelText( this.button1Model.isOn() ));
this.button.addActionListener( controller );
this.button1Model.addButton1Listener( this );
this.frame = frame;
this.frame.add( this.button ); }
public void propertyChange
( java.beans.PropertyChangeEvent propertyChangeEvent )
{ java.lang.System.out.println( "changed" );
this.button.setText( labelText( this.button1Model.isOn() )); }
public void dispose()
{ this.button1Model.removeButton1Listener( this );
this.frame.remove( this.button );
this.button = null; }}
class Button1Controller
implements java.awt.event.ActionListener
{ Button1Model button1Model = null;
public void setModel( final Button1Model button1Model )
{ this.button1Model = button1Model; }
public void actionPerformed /* MVC event (cpe) */
( final java.awt.event.ActionEvent actionEvent )
{ button1Model.toggle(); }}
class Button1Model
{ private boolean state = false;
javax.swing.event.SwingPropertyChangeSupport listeners;
public Button1Model()
{ listeners =
new javax.swing.event.SwingPropertyChangeSupport( this ); }
public boolean isOn(){ return state; }
public void toggle(){ state = !state; changedState(); }
private void changedState()
{ this.listeners.firePropertyChange
( new java.beans.PropertyChangeEvent
( this, "on", state, !state )); }
public void addButton1Listener
( final java.beans.PropertyChangeListener l )
{ this.listeners.addPropertyChangeListener( l ); }
public void removeButton1Listener
( final java.beans.PropertyChangeListener l )
{ this.listeners.removePropertyChangeListener( l ); }}
 
P

Philipp

Eric said:
I'd keep the "authoritative" boolean somewhere in the
main part of the app, possibly using a Preferences to preserve
the value from run to run. If the user opens the part of the
GUI that controls this boolean, I'd initialize a checkbox from
the current authoritative value and update that value if the
user changes the checkbox.

I'd try to avoid having the app query the checkbox state
directly, because this would tie the app to the specifics of
the GUI design. Rather, I'd use the GUI as a "knob" to show
and control the real value. This gives me the flexibility to
change the GUI, to implement a non-GUI command-line interface
for use in batch scripts, and so on.

Thanks for your answers. It makes sense to do it that way.

On the other hand, if for example the value is changed from somewhere
else in the code while the GUI is showing, the GUI will not be updated.
So you would have to implement some sort of ChangeListener equivalent,
is this correct?

Phil
 
J

jtv

I'd keep the "authoritative" boolean somewhere in the
main part of the app, possibly using a Preferences to preserve
the value from run to run. If the user opens the part of the
GUI that controls this boolean, I'd initialize a checkbox from
the current authoritative value and update that value if the
user changes the checkbox.

I'd try to avoid having the app query the checkbox state
directly, because this would tie the app to the specifics of
the GUI design. Rather, I'd use the GUI as a "knob" to show
and control the real value. This gives me the flexibility to
change the GUI, to implement a non-GUI command-line interface
for use in batch scripts, and so on.

YMMV.

I think Eric said it all, usually that kind of setting
lends itself to a Preferences persistence mechanism.
So you'd have a view object(GUI) to update those settings
and persist it through Preferences(whether it be db or fs),
provided you load it into memory whenever it is needed.

jtv
big hug
 
E

Eric Sosman

Philipp said:
Thanks for your answers. It makes sense to do it that way.

On the other hand, if for example the value is changed from somewhere
else in the code while the GUI is showing, the GUI will not be updated.
So you would have to implement some sort of ChangeListener equivalent,
is this correct?

You roll your own, or use ChangeListener and ChangeEvent.
The method that actually sets the boolean calls all the
registered listeners to notify them of the change.

There's also an Observable class and an Observer interface
(the terminology sounds odd) that appear to do much the same
sort of thing. You'd put the boolean in a class that extends
Observable, and your GUI component would implement Observer.
I've never used this pair myself, but they look quite similar
to the listener mechanism.
 
M

Mark Space

Philipp said:
On the other hand, if for example the value is changed from somewhere
else in the code while the GUI is showing, the GUI will not be updated.
So you would have to implement some sort of ChangeListener equivalent,
is this correct?


While Eric gave great answers, I wanted point out that a ChangeListener
would only be appropriate if the value of the checkBox (or any other
intern GUI state) if the state can change while the GUI is displaying on
screen.

I would construct the GUI element (JFrame, Dialog, etc.) from the
application state (the boolean or whatever) only when I was about to use
that particular GUI element. Then I'd display it, and retrieve state
from the GUI object when the GUI is done. I'd probably call dispose()
on the element too, just to release resources, and dispose the object.

If you GUI element is persistent (i.e., it's the main application
top-window) and always displayed then yes, you'll need a ChangeListener
(or whatever interface you decide is best) to update that state so the
user can see it. But normally I associate checkBoxes with Dialog Boxes
and simple user interactions that don't need to watch for an external state.
 
E

Eric Sosman

Mark said:
While Eric gave great answers, I wanted point out that a ChangeListener
would only be appropriate if the value of the checkBox (or any other
intern GUI state) if the state can change while the GUI is displaying on
screen.

I would construct the GUI element (JFrame, Dialog, etc.) from the
application state (the boolean or whatever) only when I was about to use
that particular GUI element. Then I'd display it, and retrieve state
from the GUI object when the GUI is done. I'd probably call dispose()
on the element too, just to release resources, and dispose the object.

If you GUI element is persistent (i.e., it's the main application
top-window) and always displayed then yes, you'll need a ChangeListener
(or whatever interface you decide is best) to update that state so the
user can see it. But normally I associate checkBoxes with Dialog Boxes
and simple user interactions that don't need to watch for an external
state.

Mark is right, of course: It's folly to commit canaricide
by cannon. And yet, the discipline of a stern separation between
model and view can yield unexpected advantages ...

When I was teaching myself Swing, I wrote an application GUI
that took a bunch of inputs, applied some application-specific
calculations, and displayed a bunch of outputs. Pretty simple
stuff: no need for all this arms'-length separation of model and
view. But as my knowledge grew I said "Oh, Eric! You have done
Wrong! Tut-tut!" And as a sort of exercise in Rightness I went
back and rewrote the thing so all the domain-specific logic lived
in a model and all the GUI stuff lived in a view. A perfectly
pointless exercise in political purity, no?

No! Because an interesting thing happened: I decided to add
the ability to save the application data to a file and restore
it later, in another invocation -- and The Save And Restore Code
Was Just Another View! The code that managed the I/O was able
to know Nothing about the GUI, and dealt only with the model.
The GUI's code knew a very little bit about the save/restore code,
only enough to invoke it when tickled from a JMenuBar. The whole
additional capability just "fell out of" the rigid separation
between model and view. I do not think I could have managed this
anywhere near so easily with the original all-in-one design.

The lesson (for me, anyhow), is that a careful discipline can
yield unlooked-for benefits. Do not short-circuit it casually.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,767
Messages
2,569,572
Members
45,046
Latest member
Gavizuho

Latest Threads

Top