Lets say I have a swing GUI which has textfeild and button. When I click button I want to save that value in text in db and return joptionpane "success" message.
The way I used to do this is
Model : JDBC class
View : GUI : In that button's 'action performed' action I call save method with parameter.
Controller con = new Controller();
con.save(text1.getText());
Controller : Write a save method.
JDBC db = new
public void save(jTextfeild text){
text= text1.getText();
boolean b= db.putData("insert into .. values(text)");
if(b){
JOptionPane("Success");
}
}
This is how I started. But later I understood this is not how this should be and this is utterly unsafe and stupid.
I really want to learn how to do this in MVC properly. Please be kind enough to explain this to with a small example. Thank you for your time.
This is a difficult subject to grasp in something like Swing, which already uses a form of MVC, albeit more like VC-M, where the model is separated from the view and controller, but where the view and controller are combined.
Think about a JButton, you don't supply a controller to manage how it's triggered when a user presses a key or clicks on it with the mouse, this is done internally and you are notified about the actions when the occur.
With this in mind, you need to allow the view to be semi self managed. For instance, based on your requirements, the view would have a button and text field.
The view itself would manage the interactions between the user and the button itself (maintain a internal ActionListener for example), but would then provide notifications to the controller about any state changes that the controller might be interested in.
In a more pure sense of a MVC, the view and model won't know anything about each other and the controller would manage them. This is a little contradictive to how Swing works, as Swing allows you to pass the model directly to the view, see just about any Swing component.
This doesn't mean that you can't make things work, but you need to know where the concept can falter or needs to be "massaged" to work better.
Normally, when I approach these type of things, I take step back and look at much wider picture, for example.
You have a view which can accept text and produce text or changes to it
You have a model which can load and modify text, but provides little other events
You have a controller which wants to get text from the model and supply it to the view and monitor for changes to the text by the view and update them within the model
Now, MVC works REALLY well with the concept of "code to interfaces (not implementation)", to that extent, I tend to start with the contracts...
View contract...
public interface TextView {
public void setText(String text);
public String getText();
public void addTextViewObserver(TextViewObserver observer);
public void removeTextViewObserver(TextViewObserver observer);
}
public interface TextViewObserver {
public void textWasChanged(TextView view);
}
Now, one of the requirements of the view is to generate events when the text has changed in some meaningful way, to this end, I've used a simple observer pattern to implement. Now you could argue that the controller is the observer, but to my mind, the controller may have functionality that I don't want to expose to the view (like the model for instance)
Model contract...
Next comes the model...
public interface TextModel {
public String getText();
public void setText(String text);
}
pretty simple really. Now, you might consider adding some kind of Exception to these methods to allow the model the ability to fail for some reason, but the Exception should be as generic as you can make it (or even a custom Exception), so that you can replace the implementation should you need to
Controller contract...
And finally, the controller...
public interface TextViewController {
public TextView getTextView();
public TextModel getTextModel();
}
again, pretty simple. You might have a more complex requirement for your controller, but for this example, this is about all we really need.
Implementations...
View...
public class TextViewPane extends JPanel implements TextView {
private JTextField textField;
private JButton updateButton;
private List<TextViewObserver> observers;
public TextViewPane() {
observers = new ArrayList<>(25);
textField = new JTextField(25);
updateButton = new JButton("Update");
updateButton.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
fireTextWasChanged();
}
});
setLayout(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
gbc.gridwidth = GridBagConstraints.REMAINDER;
add(textField, gbc);
add(updateButton, gbc);
}
#Override
public void setText(String text) {
textField.setText(text);
}
#Override
public String getText() {
return textField.getText();
}
#Override
public void addTextViewObserver(TextViewObserver observer) {
observers.add(observer);
}
#Override
public void removeTextViewObserver(TextViewObserver observer) {
observers.remove(observer);
}
protected void fireTextWasChanged() {
for (TextViewObserver observer : observers) {
observer.textWasChanged(this);
}
}
}
Model...
public class SimpleTextModel implements TextModel {
private String text = "This is some text";
#Override
public String getText() {
return text;
}
#Override
public void setText(String text) {
this.text = text;
}
}
Controller...
public class SimpleTextController implements TextViewController, TextViewObserver {
private TextView view;
private TextModel model;
public SimpleTextController(TextView view, TextModel model) {
this.view = Objects.requireNonNull(view, "TextView can not null");
this.model = Objects.requireNonNull(model, "TextModel can not be null");
view.addTextViewObserver(this);
}
#Override
public TextView getTextView() {
return view;
}
#Override
public TextModel getTextModel() {
return model;
}
#Override
public void textWasChanged(TextView view) {
getTextModel().setText(view.getText());
}
}
Putting it together...
TextViewPane view = new TextViewPane();
TextModel model = new SimpleTextModel();
TextViewController controller = new SimpleTextController(view, model);
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(view);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
Now, all this is just an example of one possible solution. You could have a controller implementation which has a particular implementation of the model or view or both, for example.
The point is, you just shouldn't care. The controller doesn't care how the view is implemented, it only cares that it will generate textWasChanged events. The model doesn't care about the view at all (and visa-versa) and the controller doesn't care about model, only that it will get and set some text.
For a more complex example, you can have a look at Java and GUI - Where do ActionListeners belong according to MVC pattern?
After thoughts
This is just ONE possible way to approach the problem. For example, you could limit the view to a single observer.
You should always be thinking "can I change any one part of the MVC and will it still work?" This makes you think about the possible issues that changing any one part of the implementation might have on the surrounding contracts. You should get to the point that it simply doesn't matter how each layer is implemented
A view may act as a controller for another sub-view (or act as a container for another controller of a sub-view). This can scare people sometimes, but it's possible for a view to act as parent container for one or more sub controllers/views, this allows you to develop complex UIs
Don't expose implementation details in your contracts, for example, the model shouldn't throw a SQLException, as another implementation might not be based on a SQL based solution. Don't expose UI elements, this means that ALL implementations would then need to implement those elements. What happens if I want a implementation of the view that presents a JComboBox to the user instead of JTextField? This is also the reason I don't use a ActionListener in the view contract, because I have no idea how a textWasChanged event might actually be generated by an implementation of the view
So i just stumbled upon this problem while coding my program using MVC.
I have a private JButton in the View class. I wrote the method to add the listener to all respective buttons. However, when i'm trying to code the ActionPerformed() part it throws an error about JButton not being visible.
Setting JButton to public solves the problem completly, but is it the right thing to do? Is there another way of setting the ActionListener without making the JButton public?
public class learningView extends JFrame {
private JButton viewButton = new JButton("View Resources");
public void addButtonListener(ActionListener listenerForButtons) {
viewButton.addActionListener(listenerForButtons);
saveButton.addActionListener(listenerForButtons);
addButton.addActionListener(listenerForButtons);
}
}
public class learningController {
private learningModel theModel;
private learningView theView;
public learningController(learningModel theModel, learningView theView) {
this.theModel = theModel;
this.theView = theView;
this.theView.addButtonListener(new buttonListener());
}
class buttonListener implements ActionListener {
public void actionPerformed(ActionEvent e) {
if (e.getSource() == theView.viewButton) {// This is where problem arises
}
}
}
}
Hastebin for view and controller classes (without model) for the convienience.
http://www.hastebin.com/ecawolusal.avrasm
Since viewButton has private access to LearningVew, it will simply be inaccessible out of that classes context.
Now, before you change the access level, you might consider changing your approach.
Rather then adding an ActionListener to each button which notifies an external source, it might be simpler to have the view to monitor the buttons itself.
Before you get up in arms over how this would break the MVC, the idea would be to then have the view raise a simpler, more dedicated event for the button in question, for example, viewButton could raise viewWasActivated or something, to which the controller would then respond.
This would require you to define a interface contract for both the view and the controller so they knew what information they were capable of passing to each other and what events might be triggered. This protects the view controls and means you don't need to expose the unnecessarily.
Which is demonstrated in more detail here.
The other choice would be to use the actionCommand property of the buttons instead of comparing references of the buttons to the event source, but you would first need to check that the source of the action event was a button ... and I personally don't like "bulk" ActionListeners, they get messy real quickly...
I've always understood MVC to mean the Model shouldn't know anything about the View and vice versa. However in my university course an example of a View using the MVC pattern is given like:
class View implements Updatable {
private final JButton button = new JButton("Press Me!");
private final JTextField textField = new JTextField(10);
//some setup of the button and textfield
public void update(Model model) {
if (model.morePressesAllowed()) {
textField.setText(String.valueOf(model.count()));
} else {
textField.setText("Too Many!");
button.setEnabled(false);
}
}
}
It seems odd to me that the view must know what methods the model has. It seems like it would be better in terms of the MVC pattern to expose the button and textfield to the controller, and have the update method on the controller?
The model just increments a number and if it gets to 5 then the morePressesAllowed returns false.
Also the model has a list of Updatable, and when the counter changes it loops through the updatables and calls update, while this is better than having a list Views, it still seems like the Controller should be responsible for telling the view when the model changes?
EDIT: The model is:
class Model {
private final List<Updatable> views = new ArrayList<Updatable>();
private int count;
private boolean morePressesAllowed = true;
public void addObserver(Updatable observer) {
views.add(observer);
}
public void increment() {
count++;
if (count >= 5) {
morePressesAllowed = false;
}
notifyObservers();
}
private void notifyObservers() {
for (Updatable view : views) {
view.update(this);
}
}
}
The Controller/Main class: (also shouldn't the controller create the model and view and be normal public class?)
public class GuiApp {
private View view = new View(new Controller());
private Model pressCounter = new Model();
class Controller implements ActionListener {
public void actionPerformed(ActionEvent actionEvent) {
pressCounter.increment();
}
}
GuiApp() {
pressCounter.addObserver(view);
}
}
Also I actually prefer this to something like this: http://www.tutorialspoint.com/design_pattern/mvc_pattern.htm Because in that the controller is just wrapping a bunch of methods of the view and model, which while it seems more MVC like in that the Model and View don't know anything about each other, it just seems less efficient and more complex?
It seems like it would be better in terms of the MVC pattern to expose the button and textfield to the controller, and have the update method on the controller?
This would create a coupling between the view and the controller. It is important that each layer knows as little about one another as possible. You don't want a controller that is dependant on the existence of some text boxes or buttons. You want the controller to pass the data back to the view, and not care what happens to it beyond then. That is the job of the view. That is the whole point of delegation.
Also the model has a list of Updatable, and when the counter changes it loops through the updatables and calls update, while this is better than having a list Views, it still seems like the Controller should be responsible for telling the view when the model changes?
You're correct. It is not the role of the view to call a method on the Model. Again, this creates an undesirable coupling.
also shouldn't the controller create the model and view and be normal public class?
Yes. More often than not, I see the controller communicate with the model layer, or some service, that then returns the models that are then delegated to the view.
Because in that the controller is just wrapping a bunch of methods of the view and model, which while it seems more MVC like in that the Model and View don't know anything about each other, it just seems less efficient and more complex?
These methods are known as Proxy Methods. Methods that simply delegate the call to something else. It's helpful when you're trying to maintain an appropriate separation of concerns in your architecture. It depends on your definition of complex. Yes, it adds more methods to your controller. Then again, when the next developer comes along, if he or she can make the assumption, safely, that your application strictly follows an MVC architecture, they will have a much easier time developing against your code, as opposed to having to work out your micro-optimisations and "work arounds".
One of the design patterns which I find most difficult to get a real grasp of in "real Swing life" is the MVC pattern. I've been through quite a few of the posts at this site which discuss the pattern, but I still do not feel that I have a clear understanding of how to take advantage of the pattern in my Java Swing application.
Let's say that I have a JFrame which contains a table, a couple of text fields and a few buttons. I would probably use a TableModel to "bridge" the JTable with an underlying data model. However, all functions responsible for clearing fields, validating fields, locking fields along with button actions would usually go directly in the JFrame. However, doesn't that mix the Controller and View of the pattern?
As far as I can see, I manage to get the MVC pattern "correctly" implemented when looking at the JTable (and the model), but things get muddy when I look at the entire JFrame as a whole.
I'd really like to hear how others go about with regard to this. How do you go about when you need to display a table, a couple of fields and some buttons to a user using the MVC pattern?
A book I'd highly recommend to you for MVC in swing would be "Head First Design Patterns" by Freeman and Freeman. They have a highly comprehensive explanation of MVC.
Brief Summary
You're the user--you interact with the view. The view is your window to the model. When you do something to the view (like click the
Play button) then the view tells the controller what you did. It's the
controller's job to handle that.
The controller asks the model to change its state. The controller takes your actions and interprets them. If you click on a
button, it's the controller's job to figure out what that means and
how the model should be manipulated based on that action.
The controller may also ask the view to change. When the controller receives an action from the view, it may need to tell the
view to change as a result. For example, the controller could enable
or disable certain buttons or menu items in the interface.
The model notifies the view when its state has changed. When something changes in the model, based either on some action you took
(like clicking a button) or some other internal change (like the next
song in the playlist has started), the model notifies the view that
its state has changed.
The view asks the model for state. The view gets the state it displays directly from the model. For instance, when the model
notifies the view that a new song has started playing, the view
requests the song name from the model and displays it. The view might
also ask the model for state as the result of the controller
requesting some change in the view.
Source (In case you're wondering what a "creamy controller" is, think of an Oreo cookie, with the controller being the creamy center, the view being the top biscuit and the model being the bottom biscuit.)
Um, in case you're interested, you could download a fairly entertaining song about the MVC pattern from here!
One issue you may face with Swing programming involves amalgamating the SwingWorker and EventDispatch thread with the MVC pattern. Depending on your program, your view or controller might have to extend the SwingWorker and override the doInBackground() method where resource intensive logic is placed. This can be easily fused with the typical MVC pattern, and is typical of Swing applications.
EDIT #1:
Additionally, it is important to consider MVC as a sort of composite of various patterns. For example, your model could be implemented using the Observer pattern (requiring the View to be registered as an observer to the model) while your controller might use the Strategy pattern.
EDIT #2:
I would additionally like to answer specifically your question. You should display your table buttons, etc in the View, which would obviously implement an ActionListener. In your actionPerformed() method, you detect the event and send it to a related method in the controller (remember- the view holds a reference to the controller). So when a button is clicked, the event is detected by the view, sent to the controller's method, the controller might directly ask the view to disable the button or something. Next, the controller will interact with and modify the model (which will mostly have getter and setter methods, and some other ones to register and notify observers and so on). As soon as the model is modified, it will call an update on registered observers (this will be the view in your case). Hence, the view will now update itself.
Not a fan of the idea that the view should be the one to be notified by the model when its data changes. I would delegate that functionality to the controller. In that case, if you change the application logic, you don't need to interfere to the view's code. The view's task is only for the applications components + layout nothing more nothing less. Layouting in swing is already a verbose task, why let it interfere with the applications logic?
My idea of MVC (which I'm currently working with, so far so good) is :
The view is the dumbest of the three. It doesn't know anything about the controller and the model. Its concern is only the swing components' prostethics and layout.
The model is also dumb, but not as dumb as the view. It performs the following functionalities.
a. when one of its setter is called by the controller, it will fire notification to its listeners/observers (like I said, I would deligate this role to the controller). I prefer SwingPropertyChangeSupport for achieving this since its already optimized for this purpose.
b. database interaction functionality.
A very smart controller. Knows the view and the model very well. The controller has two functionalities:
a. It defines the action that the view will execute when the user interacts to it.
b. It listens to the model. Like what I've said, when the setter of the model is called, the model will fire notification to the controller. It's the controller's job to interpret this notification. It might need to reflect the change to the view.
Code Sample
The View :
Like I said creating the view is already verbose so just create your own implementation :)
interface View{
JTextField getTxtFirstName();
JTextField getTxtLastName();
JTextField getTxtAddress();
}
It's ideal to interface the three for testability purposes. I only provided my implementation of Model and Controller.
The Model :
public class MyImplementationOfModel implements Model{
...
private SwingPropertyChangeSupport propChangeFirer;
private String address;
private String firstName;
private String lastName;
public MyImplementationOfModel() {
propChangeFirer = new SwingPropertyChangeSupport(this);
}
public void addListener(PropertyChangeListener prop) {
propChangeFirer.addPropertyChangeListener(prop);
}
public void setAddress(String address){
String oldVal = this.address;
this.address = address;
//after executing this, the controller will be notified that the new address has been set. Its then the controller's
//task to decide what to do when the address in the model has changed. Ideally, the controller will update the view about this
propChangeFirer.firePropertyChange("address", oldVal, address);
}
...
//some other setters for other properties & code for database interaction
...
}
The Controller :
public class MyImplementationOfController implements PropertyChangeListener, Controller{
private View view;
private Model model;
public MyImplementationOfController(View view, Model model){
this.view = view;
this.model = model;
//register the controller as the listener of the model
this.model.addListener(this);
setUpViewEvents();
}
//code for setting the actions to be performed when the user interacts to the view.
private void setUpViewEvents(){
view.getBtnClear().setAction(new AbstractAction("Clear") {
#Override
public void actionPerformed(ActionEvent arg0) {
model.setFirstName("");
model.setLastName("");
model.setAddress("");
}
});
view.getBtnSave().setAction(new AbstractAction("Save") {
#Override
public void actionPerformed(ActionEvent arg0) {
...
//validate etc.
...
model.setFirstName(view.getTxtFName().getText());
model.setLastName(view.getTxtLName().getText());
model.setAddress(view.getTxtAddress().getText());
model.save();
}
});
}
public void propertyChange(PropertyChangeEvent evt){
String propName = evt.getPropertyName();
Object newVal = evt.getNewValue();
if("address".equalsIgnoreCase(propName)){
view.getTxtAddress().setText((String)newVal);
}
//else if property (name) that fired the change event is first name property
//else if property (name) that fired the change event is last name property
}
}
The Main, where the MVC is setup :
public class Main{
public static void main(String[] args){
View view = new YourImplementationOfView();
Model model = new MyImplementationOfModel();
...
//create jframe
//frame.add(view.getUI());
...
//make sure the view and model is fully initialized before letting the controller control them.
Controller controller = new MyImplementationOfController(view, model);
...
//frame.setVisible(true);
...
}
}
The MVC pattern is a model of how a user interface can be structured.
Therefore it defines the 3 elements Model, View, Controller:
Model A model is an abstraction of something that is presented to the user. In swing you have a differentiation of gui models and data models. GUI models abstract the state of a ui component like ButtonModel. Data models abstract structured data that the ui presents to the user like TableModel.
View The view is a ui component that is responsible for presenting data to the user. Thus it is responsible for all ui dependent issues like layout, drawing, etc. E.g. JTable.
Controller A controller encapsulates the application code that is executed in order to an user interaction (mouse motion, mouse click, key press, etc.). Controllers might need input for their execution and they produce output. They read their input from models and update models as result of the execution. They might also restructure the ui (e.g. replace ui components or show a complete new view). However they must not know about the ui compoenents, because you can encapsulate the restructuring in a separate interface that the controller only invokes. In swing a controller is normally implemented by an ActionListener or Action.
Example
Red = model
Green = view
Blue = controller
When the Button is clicked it invokes the ActionListener. The ActionListener only depends on other models. It uses some models as it's input and others as it's result or output. It's like method arguments and return values. The models notify the ui when they get updated. So there is no need for the controller logic to know the ui component. The model objects don't know the ui. The notification is done by an observer pattern. Thus the model objects only know that there is someone who wants to get notified if the model changes.
In java swing there are some components that implement a model and controller as well. E.g. the javax.swing.Action. It implements a ui model (properties: enablement, small icon, name, etc.) and is a controller because it extends ActionListener.
A detailed explanation, example application and source code: https://www.link-intersystems.com/blog/2013/07/20/the-mvc-pattern-implemented-with-java-swing/.
MVC basics in less than 260 lines:
import java.awt.BorderLayout;
import java.awt.Container;
import java.awt.event.ActionEvent;
import java.util.ArrayList;
import java.util.List;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.DefaultListModel;
import javax.swing.DefaultListSelectionModel;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JList;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextField;
import javax.swing.ListSelectionModel;
import javax.swing.WindowConstants;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.text.BadLocationException;
import javax.swing.text.Document;
import javax.swing.text.PlainDocument;
public class Main {
public static void main(String[] args) {
JFrame mainFrame = new JFrame("MVC example");
mainFrame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
mainFrame.setSize(640, 300);
mainFrame.setLocationRelativeTo(null);
PersonService personService = new PersonServiceMock();
DefaultListModel searchResultListModel = new DefaultListModel();
DefaultListSelectionModel searchResultSelectionModel = new DefaultListSelectionModel();
searchResultSelectionModel
.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
Document searchInput = new PlainDocument();
PersonDetailsAction personDetailsAction = new PersonDetailsAction(
searchResultSelectionModel, searchResultListModel);
personDetailsAction.putValue(Action.NAME, "Person Details");
Action searchPersonAction = new SearchPersonAction(searchInput,
searchResultListModel, personService);
searchPersonAction.putValue(Action.NAME, "Search");
Container contentPane = mainFrame.getContentPane();
JPanel searchInputPanel = new JPanel();
searchInputPanel.setLayout(new BorderLayout());
JTextField searchField = new JTextField(searchInput, null, 0);
searchInputPanel.add(searchField, BorderLayout.CENTER);
searchField.addActionListener(searchPersonAction);
JButton searchButton = new JButton(searchPersonAction);
searchInputPanel.add(searchButton, BorderLayout.EAST);
JList searchResultList = new JList();
searchResultList.setModel(searchResultListModel);
searchResultList.setSelectionModel(searchResultSelectionModel);
JPanel searchResultPanel = new JPanel();
searchResultPanel.setLayout(new BorderLayout());
JScrollPane scrollableSearchResult = new JScrollPane(searchResultList);
searchResultPanel.add(scrollableSearchResult, BorderLayout.CENTER);
JPanel selectionOptionsPanel = new JPanel();
JButton showPersonDetailsButton = new JButton(personDetailsAction);
selectionOptionsPanel.add(showPersonDetailsButton);
contentPane.add(searchInputPanel, BorderLayout.NORTH);
contentPane.add(searchResultPanel, BorderLayout.CENTER);
contentPane.add(selectionOptionsPanel, BorderLayout.SOUTH);
mainFrame.setVisible(true);
}
}
class PersonDetailsAction extends AbstractAction {
private static final long serialVersionUID = -8816163868526676625L;
private ListSelectionModel personSelectionModel;
private DefaultListModel personListModel;
public PersonDetailsAction(ListSelectionModel personSelectionModel,
DefaultListModel personListModel) {
boolean unsupportedSelectionMode = personSelectionModel
.getSelectionMode() != ListSelectionModel.SINGLE_SELECTION;
if (unsupportedSelectionMode) {
throw new IllegalArgumentException(
"PersonDetailAction can only handle single list selections. "
+ "Please set the list selection mode to ListSelectionModel.SINGLE_SELECTION");
}
this.personSelectionModel = personSelectionModel;
this.personListModel = personListModel;
personSelectionModel
.addListSelectionListener(new ListSelectionListener() {
public void valueChanged(ListSelectionEvent e) {
ListSelectionModel listSelectionModel = (ListSelectionModel) e
.getSource();
updateEnablement(listSelectionModel);
}
});
updateEnablement(personSelectionModel);
}
public void actionPerformed(ActionEvent e) {
int selectionIndex = personSelectionModel.getMinSelectionIndex();
PersonElementModel personElementModel = (PersonElementModel) personListModel
.get(selectionIndex);
Person person = personElementModel.getPerson();
String personDetials = createPersonDetails(person);
JOptionPane.showMessageDialog(null, personDetials);
}
private String createPersonDetails(Person person) {
return person.getId() + ": " + person.getFirstName() + " "
+ person.getLastName();
}
private void updateEnablement(ListSelectionModel listSelectionModel) {
boolean emptySelection = listSelectionModel.isSelectionEmpty();
setEnabled(!emptySelection);
}
}
class SearchPersonAction extends AbstractAction {
private static final long serialVersionUID = 4083406832930707444L;
private Document searchInput;
private DefaultListModel searchResult;
private PersonService personService;
public SearchPersonAction(Document searchInput,
DefaultListModel searchResult, PersonService personService) {
this.searchInput = searchInput;
this.searchResult = searchResult;
this.personService = personService;
}
public void actionPerformed(ActionEvent e) {
String searchString = getSearchString();
List<Person> matchedPersons = personService.searchPersons(searchString);
searchResult.clear();
for (Person person : matchedPersons) {
Object elementModel = new PersonElementModel(person);
searchResult.addElement(elementModel);
}
}
private String getSearchString() {
try {
return searchInput.getText(0, searchInput.getLength());
} catch (BadLocationException e) {
return null;
}
}
}
class PersonElementModel {
private Person person;
public PersonElementModel(Person person) {
this.person = person;
}
public Person getPerson() {
return person;
}
#Override
public String toString() {
return person.getFirstName() + ", " + person.getLastName();
}
}
interface PersonService {
List<Person> searchPersons(String searchString);
}
class Person {
private int id;
private String firstName;
private String lastName;
public Person(int id, String firstName, String lastName) {
this.id = id;
this.firstName = firstName;
this.lastName = lastName;
}
public int getId() {
return id;
}
public String getFirstName() {
return firstName;
}
public String getLastName() {
return lastName;
}
}
class PersonServiceMock implements PersonService {
private List<Person> personDB;
public PersonServiceMock() {
personDB = new ArrayList<Person>();
personDB.add(new Person(1, "Graham", "Parrish"));
personDB.add(new Person(2, "Daniel", "Hendrix"));
personDB.add(new Person(3, "Rachel", "Holman"));
personDB.add(new Person(4, "Sarah", "Todd"));
personDB.add(new Person(5, "Talon", "Wolf"));
personDB.add(new Person(6, "Josephine", "Dunn"));
personDB.add(new Person(7, "Benjamin", "Hebert"));
personDB.add(new Person(8, "Lacota", "Browning "));
personDB.add(new Person(9, "Sydney", "Ayers"));
personDB.add(new Person(10, "Dustin", "Stephens"));
personDB.add(new Person(11, "Cara", "Moss"));
personDB.add(new Person(12, "Teegan", "Dillard"));
personDB.add(new Person(13, "Dai", "Yates"));
personDB.add(new Person(14, "Nora", "Garza"));
}
public List<Person> searchPersons(String searchString) {
List<Person> matches = new ArrayList<Person>();
if (searchString == null) {
return matches;
}
for (Person person : personDB) {
if (person.getFirstName().contains(searchString)
|| person.getLastName().contains(searchString)) {
matches.add(person);
}
}
return matches;
}
}
You can create model in a separate, plain Java class, and controller in another.
Then you can have Swing components on top of that. JTable would be one of the views (and table model would de facto be part of the view - it would only translate from the "shared model" to JTable).
Whenever the table is edited, its table model tells the "main controller" to update something. However, the controller should know nothing about the table. So the call should look more like: updateCustomer(customer, newValue), not updateCustomer(row, column, newValue).
Add a listener (observer) interface for the shared model. Some components (e.g. your table) could implement it directly. Another observer could be the controller that coordinates button availability etc.
That's one way to do it, but of course you can simplify or extend it if its an overkill for your use case.
You can merge the controller with model and have the same class process updates and maintain component availability. You even can make the "shared model" a TableModel (though if it's not only used by the table, I would recommend at least providing a friendlier API that doesn't leak table abstractions)
On the other hand, you can have complex interfaces for updates (CustomerUpdateListener, OrderItemListener, OrderCancellationListener) and dedicated controller (or mediator) only for coordination of different views.
It depends on how complicated your problem is.
For proper separation, you would typically have a controller class that the Frame class would delegate to. There are various ways to set up the relationships between the classes - you could implement a controller and extend it with your main view class, or use a standalone controller class that the Frame calls when events occur. The view would typically receive events from the controller by implementing a listener interface.
Sometimes one or more parts of the MVC pattern are trivial, or so 'thin' that it adds unnecessary complexity to separate them out. If your controller is full of one line calls, having it in a separate class can end up obfuscating the underlying behaviour. For instance, if the all of the events you are handling are related to a TableModel and are simple add and delete operations you might choose to implement all of the table manipulation functions within that model (as well as the callbacks necessary to display it in the JTable). It's not true MVC, but it avoids adding complexity where it isn't needed.
However you implement it, remember to JavaDoc your classes, methods and packages so that the components and their relationships are properly described!
I have found some interesting articles about implementing MVC Patterns, which might solve your problem.
http://www.oracle.com/technetwork/articles/javase/index-142890.html
http://accu.org/index.php/journals/1524
If you develop a program with a GUI, mvc pattern is almost there but blurred.
Disecting model, view and controller code is difficult, and normally is not only a refactor task.
You know you have it when your code is reusable. If you have correctly implemented MVC, should be easy to implement a TUI or a CLI or a RWD or a mobile first design with the same functionality. It's easy to see it done than do it actually, moreover on an existing code.
In fact, interactions between model, view and controller happens using other isolation patterns (as Observer or Listener)
I guess this post explains it in detail, from the direct non MVC pattern (as you will do on a Q&D) to the final reusable implementation:
http://www.austintek.com/mvc/
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());
}
...
}