开发者

MVC-Pattern enabling buttons on event in Java

I'm getting started with the MVC-Pattern right now and 开发者_开发知识库have something I'm pondering about. Imagine following case:

I've got a view (obviously). In this view the User can select a file and then click a "Load"-Button. As soon as this load-button is clicked, all other components in my gui should be enabled as they are set to "disabled" as long as no file has been loaded.

What I've done till now:

I have created a view according to the following scheme:

public class GUI implements Observer {
  private JButton loadButton, showButton;
  private JComboBox nameBox;
  private Controller controller = new Controller();

  // Initialising Component and so on...
  loadButton.addActionListener(... // calls the loadFile()-Method

  public void update(Observable arg0, Object arg1) {
    // What to do here?

  }
}

And a controller like this:

public class Controller extends Observable {
  public void loadFile() {
    // Load selected File
    notifyObservers(); // <--- What should they be notified about in order to enable their component?
  }
}

So my problem is that I am not sure whether it is sensible to enable the components over the controller. Or is it better to check in the view, if the file has been loaded and then set the components to enabled?


You don't show your model, but presumably it "knows" whether the file has been loaded successfully.

So I would simply tell the view "the model has changed".

Then the view has the responsibility for deciding what to show, what buttons to enable or whatever. So it might have logic such as

if ( model.isFileLoaded() ){
     enable buttons
} else {
      disable all buttons except the load
      if ( model.hadError() ) {
          display helpful message (and let user specify a different file)
      }
}

This way the controller does not need to know what specific pieces in the model are interesting to the View, and doesn't need to know anything about buttons and the View state in general.


The way I might do it is to key everything off of changes to the model. Some observable bean would hold the "file" and your 'controller layer' would consist of the listeners to the bean that update button states based on the existence or absence of the "file" property value.

This decouples everything as you can easily add additional buttons, remove buttons, etc. without changing your code. If you have some other way of setting or clearing the "file" property then you don't need to copy+paste all of the disabling/enabling code.

So in your above example, loadFile() would load the file and set it on the model bean. That value change will cause PropertyChangeEvents to fire at your listener(s) that update button state.

From my perspective: extra credit for manipulating Actions over directly setEnable()ing the buttons. The actions could even be the bean change listener and just call setEnabled() on themselves. But I like to couple controller code to actual swing components as little as I can get away with.

If that is confusing, I can provide sample code. -- ok, code provided...

So, my "pure" way would be to have actions backing all of the buttons... maybe even with a common base class:

public abstract class FileRequiredAction extends AbstractAction 
                                implements PropertyChangeListener {
    public FileRequiredAction( String name ) {
        super( name );
    }

    public void propertyChanged( PropertyChangeEvent event ) {
        if ("file".equals( event.getName())
            setEnabled( event.getNewValue() != null );
    }
}

Then presuming you have some proper model bean with a setFile() method that fires off the right events, your UI setup code would look something like:

Action a;
a = new FileRequiredAction( "Show" ) {
        public void actionPerformed( ActionEvent event ) {
            // Do the showing
        }
    };
myBean.addPropertyChangeListener( "file", a );
JButton showButton = new JButton( a );

...repeat for other actions that require "file".

All your Load button needs to do is call myBean.setFile( someNonNullValue ) and the buttons will enable themselves. The bonus is that is some other action clears the value that they get disabled again.

For complete code, I've left out some things like making sure the action initializes its enabled state in case myBean.getFile() already has a value, etc.

Hopefully that makes it clearer.


The controller is the layer which controls the view. So if you have some rules, like disable buttons, it's the controller which have to knows it.

You GUI have to respect a contract which expose a disabling/enabling method for the buttons.

And your controller have to call this method according to your rules. Your GUI have to send events and controller manipulate them.

If you do so, your GUI and controller will be modular. When you will have to change your GUI framework (switch to light client, use another library...) your controller will not change. And, of course, you will have not to implement any rules, because only the controller knows them.


I won't go too complicated with all this architecture stuff... I this case from my point of view your model is simply the file (and it's contents). Your view is your view ;-) and the controller is the button/actionListener. Just do:

class MyViewClass extends SomeSwingComponent

{

  JButton loadButton;
  JButton disabledEnabledButton;

  //....

  void init()
  { 
    //...
    loadButton.addActionListener(new ActionListener()
    {
       public void actionPerformed(ActionEvent e)
       {
          loadFile();
       }
     });
     disabledEnabledButton.setEnabled(false);
     //...
   }

  //....


 private void loadFile()
 {
   //doFileLoadingStuff synchoniously
   disabledEnabledButton.setEnabled(true);
 }
//...

}

Only if there is anything the user can do while the file is loading, you should think about loading the file in an observed thread.


One approach I can think of without using the Observer classes is as below:

public class GUI {
private JButton loadButton, showButton;
private JComboBox nameBox;
...

loadButton.addActionListener(new Controller(GUI));  

public void enableShow(boolean b) {  
    // enable Show button if b is true  
}  

public void enableName(boolean b) {  
    // display Name combo box if b is true  
}  

}

public class Controller implements ActionListener {
private GUI view;

public Controller(GUI v) {  
    this.view = v;  
}  

public void actionPerformed(ActionEvent pActionEvt) {  
    boolean status = loadFile();  
    if (status) {  
        view.enableShow(true);  
        view.enableName(true);  
    }
}  

private boolean loadFile(String file) {
    // loads the specified file and return true if load is successful  
}  

}

Swing framework takes care of calling the actionPerformed method in controller
when user clicks the load button.

You can also put the loadFile method in a seperate class which
would act as a Model.

One advantage with seggeragation of the Controller is that you can
unit test the Controller indpendently using some mock framework.
Or you can make a GUI class to implement an interface and then use
dependency injection to unit test the logic in Controller class.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜