I am developing my GUI according to the MVC pattern:
-GUIview: Swing components (JFrame and several JTables).
-GUIcontroller: listeners (added here, and defined here in Inner classes)
-GUImodel: modify and store data, firing change-events.
Changes in the model are passed to the view through the controller (and not directly), like in this example.
I have also written different customized JTableModels (extending AbstractTableModel) for the different JTables contained in the View class. All the JTableModels are defined in different classes within the package "GUImodel". Each JTableModel defines an ArrayList and a few methods to manipulate the ArrayList.
According to the MVC guidelines, the Model should know nothing about the view. In fact, the main() method is defined as follows:
GUImodel model = new GUImodel();
GUIcontroller controller = new GUIcontroller();
GUIview view = new GUIview(controller, model);
controller.addView(view);
controller.addModel(model);
view.setVisible(true);
controller.addControllerListerners();
My problem is:
When I am executing a method within the GUImodel (for example because a JButton has been pressed and I need to load data from an external file), I need to modify some JTableModels (to add data/rows to its ArrayList) and get the changes reflected in the JTable. My first idea would be:
ArrayList newArrayList = fileLoader(filePath); //create ArrayList and load info
guiView.getTable1Model().updateArrayList(newArrayList); //update JTableModel ArrayList
However, this approach is not valid, since GUImodel should be totally independent of GUIview.
Any idea?
It may be good to realize that MVC is primarily a pattern concerned with data encapsulation, which uses another pattern, Observer, to communicate changes. As data encapsulator, the Model knows nothing of Views and Controllers, but as an Observable it does know that it has Observers, which need to be notified when a change occurs.
A Description of the Model-View-Controller User Interface Paradigm in the Smalltalk-80 System, page 4 explains it well:
To manage change notification, the notion of objects as dependents was developed. Views and
controllers of a model are registered in a list as dependents of the model, to be informed whenever
some aspect of the model is changed. When a model has changed, a message is broadcast to notify
all of its dependents about the change. This message can be parameterized (with arguments), so
that there can be many types of model change messages. Each view or controller responds to the
appropriate model changes in the appropriate manner.
To illustrate the concept, you can start out with your own Observer/Observable classes:
public interface Observer {
public void update(int message);
}
public interface Observable {
public void registerObserver(Observer observer);
}
public class Model implements Observable {
List<Observer> observers;
public void registerObserver(Observer observer) {
observers.add(observer);
}
public void loadFile(String path) {
// load file and change data
foreach (Observer observer: observers)
observer.update(READ_NEW_DATA);
}
public ArrayList getData() { return data; }
}
public class View implements Observer {
public void update(int message) {
doWhateverWith(model.getData());
}
}
public class Controller implements Observer {
public void update(int message) {
doWhateverWith(model.getData());
}
public void onClick() {
model.loadFile("someFile");
}
}
As you can see, the Model knows nothing of the internal workings of the Views and Controllers. It doesn't even know if returning an ArrayList will be particularly useful to them (although in practice you'd like for that to be the case). So in this regard, independence is achieved.
There is no independence in the communication between the Obervable and Observers, but that isn't part of the requirements of the MVC pattern.
If you want your GUI to hitchhike on top of the existing Swing Observer pattern (Listeners), then your classes should inherit from the appropriate classes:
public class Model extends AbstractTableModel...
public class View implements TableModelListener...
public class Controller implements CellEditorListener...
Etcetera. Since JTable implements both TableModelListener and CellEditorListener, it is actually a composite of View and Controller. So you have the choice to either have a combined ViewController class extend JTable, or to have them separately. In the latter case, the View could extend JTable, overriding the control Listeners, so that they pass their events to the Controller class. But that sounds like more work than it's worth.
As discussed here, you are correct to loosely couple the model and view. JTable implements TableModelListener to listen to its own model, and your AbstractTableModel no doubt fires events that cause the listening table to update itself.
In this case, let the dependent TableModel add itself as a TableModelListener to the master TableModel. The dependent model can then fire the events needed to notify it own listeners of changes propagated from the master.
However, this approach is not valid, since GUImodel should be totally independent of GUIview.
The Swing Components themselves use the MVC model. Changes in the model have to trigger changes in the view. The question is how do you do this?
One way is for the model to have access to the view instance(s), as you've illustrated in your question.
ArrayList newArrayList = fileLoader(filePath); //create ArrayList and load info
guiView.getTable1Model().updateArrayList(newArrayList); //update JTableModel ArrayList
Another way is for the controller to update the model and update the view. This is what I usually do in a Swing application.
model.loadArrayList(filePath);
frame.getFrame().getMainPanel().repaint();
Another way is to fire actions. This is how the Swing components update the GUI.
ArrayList newArrayList = fileLoader(filePath); //create ArrayList and load info
fireAction(newArrayLiat);
The fireAction method would work with listeners. Here's a fire method I copied from AbstractListModel.
protected void fireContentsChanged(Object source, int index0, int index1) {
Object[] listeners = listenerList.getListenerList();
ListDataEvent e = null;
for (int i = listeners.length - 2; i >= 0; i -= 2) {
if (listeners[i] == ListDataListener.class) {
if (e == null) {
e = new ListDataEvent(source,
ListDataEvent.CONTENTS_CHANGED, index0, index1);
}
((ListDataListener) listeners[i + 1]).contentsChanged(e);
}
}
}
You would have to write listeners in your model classes that the view classes can write code to change the view.
The Javadoc for the EventListenerList has more information about listeners. Thanks
Catalina Island.
My style of MVC in swing is, the model and the view is oblivious of each other as well as of the controller, but the controller knows the view and the model soo well. This way, I do all of the logic in the controller. I just left the long codes of UI + complex layouts in the view and think of all the data that the application will need for the model & decide whether a certain data should appear in my view. I put the functionality of adding listeners to the buttons, etc. to the controller via view.getBtn().setAction(new ActionForThisOrThatInnerClass()) kind of stuff
In your case, I agree that the data that the table will use should be stored in your main model in the form of, ideally, a List, but I would not bother myself to subclass a new TableModel to handle those data, I think the DefaultTableModel is powerful enough to do a lot.
Here is the runnable example if I am to code your requirements
public class Sample {
public static void main(String[] args){
View view = new View();
Model model = new Model();
Controller controller = new Controller(view, model);
JFrame frame = new JFrame("MVC Demo");
frame.getContentPane().setLayout(new BorderLayout());
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(view.getUI());
frame.pack();
frame.setVisible(true);
view.getBtnFileLoader().doClick();
}
}
class View{
private JButton btnFileChooser;
private JButton btnFileLoader;
private JTable tblData;
private JPanel pnlMain;
public View(){
pnlMain = new JPanel(new BorderLayout()){
#Override public Dimension getPreferredSize(){
return new Dimension(300, 400);
}
};
JPanel pnlFileLoader = new JPanel();
pnlFileLoader.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
pnlFileLoader.setLayout(new BoxLayout(pnlFileLoader, BoxLayout.LINE_AXIS));
JTextField txtFileDir = new JTextField();
pnlFileLoader.add(txtFileDir);
btnFileLoader = new JButton();
pnlFileLoader.add(btnFileLoader);
btnFileChooser = new JButton();
pnlFileLoader.add(btnFileChooser);
tblData = new JTable();
JScrollPane pane = new JScrollPane(tblData);
pnlMain.add(pane);
pnlMain.add(pnlFileLoader, BorderLayout.PAGE_START);
}
public JPanel getUI(){
return pnlMain;
}
public JButton getBtnFileLoader(){
return btnFileLoader;
}
public JButton getBtnFileChooser(){
return btnFileChooser;
}
public JTable getTblData(){
return tblData;
}
}
class Controller implements PropertyChangeListener{
private View view;
private Model model;
private DefaultTableModel tmodel;
public Controller(View view, Model model){
this.view = view;
this.model = model;
model.addModelListener(this);
setupViewEvents();
setupTable();
}
private void setupTable(){
tmodel = new DefaultTableModel();
tmodel.addColumn("First Name");
tmodel.addColumn("Last Name");
tmodel.addColumn("Occupation");
view.getTblData().setModel(tmodel);
}
private void setupViewEvents(){
view.getBtnFileChooser().setAction(new AbstractAction("Choose"){
#Override
public void actionPerformed(ActionEvent arg0) {
//choose the file then put the dir
//in the txtfield
}
});
view.getBtnFileLoader().setAction(new AbstractAction("Load"){
#Override
public void actionPerformed(ActionEvent arg0) {
//validate if the dir in the textfield exists and the file is loadable
//load the file specified in the textfield
//assumming the list is already retrieved from the file
//and the list contains the following person
List<Person> list = new ArrayList<Person>();
Person p1 = new Person("Bernardo", "Santos", "Developer");
Person p2 = new Person("Robert", "Erasquin", "Architect");
Person p3 = new Person("Klarrise", "Caparas", "Food Scientist");
list.add(p1);
list.add(p2);
list.add(p3);
//now update the model of the new value for the list
model.setTheList(list);
}
});
}
#Override
#SuppressWarnings("unchecked")
public void propertyChange(PropertyChangeEvent evt) {
if(evt.getPropertyName().equals("theList")){
List<Person> newVal = (List<Person>) evt.getNewValue();
DefaultTableModel tmodel = (DefaultTableModel)view.getTblData().getModel();
for(Person p : newVal){
tmodel.addRow(new Object[]{p.getFirstName(), p.getLastName(), p.getOccupation()});
}
}
}
}
class Model{
private List<Person> theList;
private SwingPropertyChangeSupport propChangeFirer;
public Model(){
propChangeFirer = new SwingPropertyChangeSupport(this);
}
public void setTheList(List<Person> theList){
List<Person> oldVal = this.theList;
this.theList = theList;
//after the model has been updated, notify its listener about
//the update, in our case the controller itself listens to the model
propChangeFirer.firePropertyChange("theList", oldVal, theList);
}
public void addModelListener(PropertyChangeListener prop) {
propChangeFirer.addPropertyChangeListener(prop);
}
}
class Person{
private String firstName;
private String lastName;
private String occupation;
public Person(String firstName, String lastName, String occupation){
this.firstName = firstName;
this.lastName = lastName;
this.occupation = occupation;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getOccupation() {
return occupation;
}
public void setOccupation(String occupation) {
this.occupation = occupation;
}
}
Related
I am often struggling with the same problem of custom Objects that creates a e.g. gui Component. And I never know what is the best way to get from the gui Component back to the object.
So multiple hacks and tricks are welcome.
Let me explain it to you:
This is my custom Object I need to find afterwards
public class MyObject {
int yearOfBirth;
String name;
public MyObject(int yearOfBirth, String name) {
this.yearOfBirth = yearOfBirth;
this.name = name;
}
public int getYearOfBirth() {
return yearOfBirth;
}
public Component getPanel() {
Component panel1 = makeTextPanel("This is the personal tab of "+name);
return panell;
}
}
This is where I need to find it through the Tab I am focusing
public class MyTabControl implements ChangeListener {
JTabbedPane myTabPane = new JTabbedPane();
public MyTabControl(){
//This will add a Listener for clicking on one Tab
myTabPane.addChangeListener(this);
}
public void oneMoreTab(MyObject myObject) {
myTabPane.addTab(myObject.name, myObject.getPanel())
}
public void stateChanged(ChangeEvent arg0) {
System.out.println("Focus of Tab changed");
int actualFocusedTabIndex = myTabPane.getSelectedIndex();
Component acutalFocusedComponent = myTabPane.getComponentAt(actualFocusedTabIndex);
//This works fine, I can get the Tab. Or at least the Component.
//But how do I get the yearOfBirth or better the Object itself?
int yearOfBirthOfTheSelectedTab = ???
}
}
This is just the main function
public static void main(String[] args) {
//Commands to start and create the GUI
MyTabControl myTabControl = new MyTabControl();
MyObject mother = new MyObject(1960, "Helen");
myTabControl.oneMoreTab(mother);
MyObject father = new MyObject(1955, "James");
myTabControl.oneMoreTab(father);
}
EDIT:
1 not working solution: Extend Component class
I have tried to extend the class Component. But this will create a failure (see comment in code):
public class ComponentWithExtras extends Component {
MyObject myObject;
public void addMyObject(MyObject myObject) {
this.myObject = myObject;
}
}
// The following line will create failure: Can't cast Component to ComponentWithExtras
ComponentWithExtras componentWithExtras = (ComponentWithExtras) myObject.getPanel();
componentWithExtras.addMyObject(myObject);
myTabPane.addTab(myObject.name, componentWithExtras);
I need to call an API, get a response, add items to ComboBox and update the view in the plugin.
Attached is the image of the combobox
I need to update the thread Ids as they load from an API. My custom Combobox for this is as shown below. I am not sure how to update the custom component from outside the class. Any help?
public class MyComboBox extends AnAction implements CustomComponentAction {
#Override
public void actionPerformed(#NotNull AnActionEvent e) {
}
#Override
public #NotNull JComponent createCustomComponent(#NotNull Presentation presentation, #NotNull String place) {
ComboBox<String> jComboBox = new ComboBox<>();
jComboBox.setMinLength(100);
jComboBox.addItem("Thread Id: " + UUID.randomUUID().toString());
jComboBox.addItem("Thread Id: " + UUID.randomUUID().toString());
jComboBox.setEnabled(true);
return jComboBox;
}
}
I needed a reference to the already instantiated combobox.
I got it with
MyComboBox myComboBox = (MyComboBox) ActionManager.getInstance().getAction("searchThread")
I added another method in MyComboBox:
public void updateUI(List<String> ids) {
this.ids = ids;
String[] array = this.ids.toArray(new String[0]);
jComboBox.setModel(new DefaultComboBoxModel<>(array));
jComboBox.setSelectedIndex(0);
jComboBox.updateUI();
}
and used:
myComboBox.updateUI(newList);
That solved my problem.
I know this question has been covered in many posts here.
However, something is still not clear to me, so i wanted to ask you my problem in detail.
I have to develop a java application using Swing and using the MVC model.
The application is mainly divided into two parts:
login part
questionnaire part (after login, a questionnaire is displayed)
So following MVC model i divided my code into 3 packages containing the following classes:
Model
LoginModel
QuestionModel
View
LoginView
QuestionView
Controller
LoginController
QuestionController
After developing these classes, i didn't know how to set the window that the program was current working on (login, questionnaire or other future implementations).
So i thought about implementing 3 other classes that use the Observer pattern:
MainModel - Observable
MainView
MainController - Observer
But now i'm not sure how to change the current window.
For example when login is successful, the window must change from LOGIN to QUESTION, so "MainModel.window = Window.QUESTION" and send it to the View.
Should it be added in LoginModel.login() by extending LoginModel with MainModel?
Or how can I do this?
My code:
public class main {
public static void main(String[] args) {
MainView view = new MainView();
MainModel model = new MainModel();
MainController controller = new MainController(view, model);
}
}
public class MainView {
private JFrame window;
public MainView() {
window = new JFrame();
window.setLayout(new CardLayout(0, 0));
LoginView login = new LoginView(); // init window at opening
QuestionView question = new QuestionView();
window.add(login);
window.add(question);
window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
window.setVisible(true);
}
public void update(Window window) {
// ??
}
}
public class MainModel {
private List<Observer> observers = new ArrayList<>();
private Window window;
public MainModel() {
window = Window.LOGIN; // init window at opening
}
public void addObserver(Observer observer) {
observers.add(observer);
}
public void removeObserver(Observer observer) {
observers.remove(observer);
}
public void setWindow(Window newWindow) {
newWindow = window;
for (Observer o : observers)
o.update(newWindow);
}
}
public class MainController implements Observer {
private MainView view;
private MainModel model;
public MainController(MainView view, MainModel model) {
this.view = view;
this.model = model;
this.model.addObserver(this);
}
#Override
public void update(Window window) {
this.view.update(window);
}
}
public class LoginView extends JPanel {
private JButton btnLogin;
// ... other attributes
public LoginView() {
btnLogin = new JButton("Login");
new LoginController(this);
}
public JButton getBtnLogin() {
return btnLogin;
}
public void ShowResult(boolean bResult) {
// print result with JOptionPane.showMessageDialog
}
}
public class LoginController {
private LoginView view;
public LoginController(LoginView view) {
this.view = view;
setActionListener();
}
public void setActionListener() {
ActionListener loginButton;
loginButton = new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
LoginModel model = new LoginModel();
boolean bResult = model.login(view.getUserNameField(), view.getPasswordField());
view.ShowResult(bResult);
}
};
view.getBtnLogin().addActionListener(loginButton);
}
}
public class LoginModel {
// ... attributes etc
public boolean login(String username, String password) {
boolean bResult;
// ... Some operation etc (useless for this example)
bResult = true; // Simulation login successful
if (bResult)
// ? Change window model to Window.QUESTION.
// But how?
// LoginModel extends MainModel? To call "super.setWindow(Window.QUESTION)?
return bResult;
}
}
// My Observer class
public interface Observer {
public void update(Window window);
}
// My Window class
public enum Window {
LOGIN,
QUESTION,
}
// Questionnaire classes code is very similar to the Login code
public class QuestionView extends JPanel {
private JButton btn;
// ...
new QuestionController(this);
// ...
}
public class QuestionController {
private QuestionView view;
// ...
setActionListener();
// ...
}
So in conclusion is it correct to use this approach? Or how else could i view/update the current window better?
In Swing, the MVC pattern looks like this:
The view reads from the model
The view may not update the model
The controller updates the model and the view
The MVC name implies that you create the model first, then the view, then the controllers.
There's usually not one controller to "rule them all". Each listener is responsible for its own part of the model and the view.
You usually have one application model. An application model is made up of one or more plain Java getter/setter classes. In your case, it looks like a Person class and a Questionaire class. You would probably also have a Question class, to hold one question, several possible answers, and the chosen answer. You may have additional plain Java getter/setter classes I'm not thinking about now.
You would have one JFrame, one JPanel to hold a question and possible answers, and a JDialog for the login and password. You may need multiple JPanels for different types of answers (not different questions), so you might need a main JPanel with a CardLayout.
Your controllers will be the ActionListener for the login JButton, and the "I'm finished answering this question" JButton. You may have other listeners that I'm not thinking about now.
I'm working on developing an Vaadin application. When developing a Java desktop application I use AbstractAction to create the buttons of my GUI. How to do that using Vaadin
Here is how I do in Java Desktop application:
// In my view
JButton button = new JButton(new Action(BUTTON_NAME, presenter, "methodToInvoke", Object... arguments));
class Action extends AbstractAction {
public Action(ButtonName name, Presenter presenter, String method, Object... arguments) {
this.name = name;
this.presenter = presenter;
this.method = method;
this.arguments = arguments;
readButtonProperties();
}
public void actionPerformed(ActionEvent e) {
//Call method from presenter with arguments using reflection
}
}
Edit:
I already read this. It's not the way I'm asking for.
From your example it isn't really clear what your requirements are.
Sounds like you insist on using reflection, but I'm not sure why. You could just do this, which is basically the same thing the Button documentation already says.
Presenter presenter = getPresenter();
String with = "with";
int my = 42;
List<Data> = new List<>();
Button vaadinButton = new Button("I'm a button.", clickEvent -> presenter.doStuff(with, my, arguments));
If you need more than a simple method call, you could just as well create a more elaborate ClickListener implementation that has more than the buttonClick() method.
Button vaadinButton = new Button("Button Caption", new Action(...))
class Action extends AbstractAction implements Button.ClickListener {
public Action(ButtonName name, Presenter presenter, String method, Object... arguments) {
this.name = name;
this.presenter = presenter;
this.method = method;
this.arguments = arguments;
readButtonProperties();
}
public void actionPerformed(ActionEvent e) {
//Call method from presenter with arguments using reflection
}
#Override
public void buttonClick(ClickEvent event) {
actionPerformed(null);
}
}
I follow the Presentation Model pattern for coding some screens.
I store some Beans in an ArrayList
I will display the content of this List in a JTable, thanks to an AbstractTableModel
I also want to display some records from this list in a Combo Box (in a form) and some others in a JList, at the same time
These three screens (and their model) are independent from each other
How to manage add {one or more}/remove {one or more} on my List and view changes in "real-time" everywhere?
I'm about to write my own ObservableList or implement that around an EventDispatcher... What do you think?
PS:
I know that in C# the BindingList helps for that purpose, what about Java?
I'm already able to display updates of each bean, thanks to PropertyChangeSupport.
Let your AbstractTableModel implement ListModel, which is usable with both JComboBox andJList. You can forward methods to the default model implementations as required.
Addendum: SharedModelDemo, mentioned in How to Use Tables, is an example that may get you started. It extends DefaultListModel implements TableModel, while you should do extends AbstractTableModel implements ListModel
Addendum: For reference, here's an outline of the minimal implementation and three test instantiations. I've used the default combo and list implementations, but you can use the corresponding abstract implementations if necessary.
public class SharedModel extends AbstractTableModel
implements ComboBoxModel, ListModel {
private ComboBoxModel comboModel = new DefaultComboBoxModel();
private ListModel listModel = new DefaultListModel();
//ComboBoxModel
#Override
public void setSelectedItem(Object anItem) {
comboModel.setSelectedItem(anItem);
}
#Override
public Object getSelectedItem() {
return comboModel.getSelectedItem();
}
// ListModel
#Override
public int getSize() {
return listModel.getSize();
}
#Override
public Object getElementAt(int index) {
return listModel.getElementAt(index);
}
#Override
public void addListDataListener(ListDataListener l) {
listModel.addListDataListener(l);
}
#Override
public void removeListDataListener(ListDataListener l) {
listModel.removeListDataListener(l);
}
// TableModel
#Override
public int getRowCount() {
return 0;
}
#Override
public int getColumnCount() {
return 0;
}
#Override
public Object getValueAt(int rowIndex, int columnIndex) {
return null;
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
SharedModel sm = new SharedModel();
JTable table = new JTable(sm);
JList list = new JList(sm);
JComboBox check = new JComboBox(sm);
}
});
}
}
For the JComboBox and the JList you could simply reference sections of the ArrayList using the subList() method. This will work if you can easily identify the starting and ending locations within the ArrayList and the elements you need are sequential.
If the situation is more dynamic than that you could implement custom List classes that took the ArrayList in the constructor and then apply whatever logic you need to return the appropriate records.