My project follows the MVC pattern. To make it quick, I will only post the code relevant to my problem (which doesn't involve the Model). Explanations below.
Controller.java :
public class Controller {
public View myView;
public Controller() {
myView = new myJFrame(this);
}
public void displayViews() {
myView.display();
}
public void closeViews() {
myView.close();
}
}
View.java :
public abstract class View {
private Controller controller;
public View(Controller controller) {
super();
this.controller = controller;
}
public final Controller getController() {
return controller;
}
public abstract void display();
public abstract void close();
}
myJFrame.java :
public class myJFrame extends View {
private JFrame frame;
private JMenuBar myMenuBar;
public myJFrame(Controller controller) {
super(controller);
buildFrame();
}
private void buildFrame() {
frame = new JFrame();
menuBar = new JMenuBar();
...
}
#Override
public void close() {
frame.dispose();
}
#Override
public void display() {
frame.setVisible(true);
}
}
This code works perfectly. However, the buildFrame() method will soon become huge if I keep adding components, so I would like to split it and create a new class in a new file for every Swing component.
The problem is that I want my components to retain their access to the Controller. myFrame extends View, therefore the getController() method can be called anytime. But it is no longer the case when I create a separate file. Extending View doesn't seem to be an option (besides, myMenuBar already extends JMenuBar, for example).
What would you suggest?
The problem is that I want my components to retain their access to the Controller.
Fine. Create a class for the JPanels that make up your GUI, and pass an instance of View to each of them through their respective constructors.
You shouldn't extend Swing components unless you're overriding a component method. You should use Swing components.
Composition over inheritance
You shouldn't be extending View either. Passing an instance of the view is sufficient.
Read this excellent article, Sudoku Solver Swing GUI, for a better idea of how to use the MVC pattern when constructing a Swing GUI.
Related
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
I'm new to Java and I've hit a brick wall. I want to access GUI components (that have been created in one class) from another class. I am creating a new GUI class from one class, like so;
GUI gui = new GUI();
and I can access the components in that class, but when I go to a different class I cant. I really just need to access the JTextAreas to update their content. Could someone point me in the right direction please, any help is greatly appreciated.
GUI Class:
public class GUI {
JFrame frame = new JFrame("Server");
...
JTextArea textAreaClients = new JTextArea(20, 1);
JTextArea textAreaEvents = new JTextArea(8, 1);
public GUI()
{
frame.setLayout(new FlowLayout(FlowLayout.LEADING, 5, 3));
...
frame.setVisible(true);
}
}
First respect encapsulation rules. Make your fields private. Next you want to have getters for the fields you need to access.
public class GUI {
private JTextField field = new JTextField();
public GUI() {
// pass this instance of GUI to other class
SomeListener listener = new SomeListener(GUI.this);
}
public JTextField getTextField() {
return field;
}
}
Then you'll want to pass your GUI to whatever class needs to access the text field. Say an ActionListener class. Use constructor injection (or "pass reference") for the passing of the GUI class. When you do this, the GUI being referenced in the SomeListener is the same one, and you don't ever create a new one (which will not reference the same instance you need).
public class SomeListener implements ActionListener {
private GUI gui;
private JTextField field;
public SomeListener(GUI gui) {
this.gui = gui;
this.field = gui.getTextField();
}
}
Though the above may work, it may be unnecesary. First think about what exactly it is you want to do with the text field. If some some action that can be performed in the GUI class, but you just need to access something in the class to perform it, you could just implement an interface with a method that needs to perform something. Something like this
public interface Performable {
public void someMethod();
}
public class GUI implements Performable {
private JTextField field = ..
public GUI() {
SomeListener listener = new SomeListener(GUI.this);
}
#Override
public void someMethod() {
field.setText("Hello");
}
}
public class SomeListener implements ActionListener {
private Performable perf;
public SomeListener(Performable perf) {
this.perf = perf;
}
#Override
public void actionPerformed(ActionEvent e) {
perf.someMethod();
}
}
Several approaches are possible:
The identifier gui is a reference to your GUI instance. You can pass gui to whatever class needs it, as long as you respect the event dispatch thread. Add public accessor methods to GUI as required.
Declarations such as JTextArea textAreaClients have package-private accessibility. They can be referenced form other classes in the same package.
Arrange for your text areas to receive events from another class using a PropertyChangeListener, as shown here.
The best option to access that text areas is creating a get method for them. Something like this:
public JTextArea getTextAreaClients(){
return this.textAreaClients;
}
And the same for the other one.So to access it from another class:
GUI gui = new GUI();
gui.getTextAreaClients();
Anyway you will need a reference for the gui object at any class in which you want to use it, or a reference of an object from the class in which you create it.
EDIT ---------------------------------------
To get the text area from GUI to Server you could do something like this inside of Create-Server.
GUI gui = new GUI();
Server server = new Server();
server.setTextAreaClients(gui.getTextAreaClients());
For this you should include a JTextArea field inside of Server and the setTextAreaClients method that will look like this:
JTextArea clients;
public void setTextAreaClients(JTextArea clients){
this.clients = clients;
}
So in this way you will have a reference to the JTextArea from gui.
here i add a simple solution hope it works good,
Form A
controls
Textfield : txtusername
FormB fb = new FormB();
fb.loginreset(txtusername); //only textfield name no other attributes
Form B
to access FormA's control
public void ResetTextbox(JTextField jf)
{
jf.setText(null); // or you can set or get any text
}
There is actually no need to use a class that implements ActionListener.
It works without, what might be easier to implement:
public class SomeActionListener {
private Gui gui;
private JButton button1;
public SomeActionListener(Gui gui){
this.gui = gui;
this.button1 = gui.getButton();
this.button1.addActionListener(l -> System.out.println("one"));
}
}
and then, like others have elaborated before me in this topic:
public class GUI {
private JButton button = new JButton();
public GUI() {
// pass this instance of GUI to other class
SomeActionListener listener = new SomeActionListener(GUI.this);
}
public JButton getButton() {
return button;
}
}
I've been trying to implement a simple JList using MVC. Basicaly, show the JList and under it add a button to delete an item. I want to use AbstractListModel for the model because later on I want more than just a simple ArrayList as data.
I'm having trouble using the JList in a proper MVC way. For example in the View I create the list. But this list need the model (addModel(method), and is added in the View.
It seems weird because I thought in MVC the View had no knowledge of the model.
I also don't really know what I should put in the controller.
Anyway if someone could give me guidelines to implement this it would be nice.
Here's the code I started:
public class SimpleJlist extends JFrame
{
public static void main(String[] args)
{
Controller controller = new Controller();
View view = new View(controller);
Model model = new Model();
SimpleJlist jl = new SimpleJlist();
jl.setLayout(new FlowLayout());
jl.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jl.add(view);
jl.setVisible(true);
jl.pack();
}
}
public class View extends JPanel implements ListDataListener, ActionListener
{
Controller controller;
JButton button;
JList list;
public View(Controller controller)
{
this.controller = controller;
button = new JButton("Delete");
/* Creation of the Jlist, but need the model. */
}
/* For the button */
public void actionPerformed(ActionEvent event) { }
/* For the list */
public void contentsChanged(ListDataEvent event) { }
public void intervalAdded(ListDataEvent event) { }
public void intervalRemoved(ListDataEvent event) { }
}
public class Model extends AbstractListModel
{
private ArrayList<String> names;
public Model()
{
names = new ArrayList<String>();
/* add names... */
}
public void deleteElement(int index) { names.remove(index); }
public String getElementAt(int index) { return names.get(index); }
public int getSize() { return names.size(); }
}
The code is far from complete obviously, but this is about where I got to before wondering what to do next...
The controller is not there, because I'm simply not sure what to put in it.
I've been trying to implement a simple JList using MVC.
Swing components are already designed in an MVC like style. You just need to use the components. The LIstModel is the model and the JList is a combined view-controller. You don't create additional classes called Model-View-Controller.
Basicaly, show the JList and under it add a button to delete an item.
Read the section from the Swing tutorial on How to Use Lists for an example of how to add/remove items from the DefaultListModel
I want to use AbstractListModel for the model because later on I want more than just a simple ArrayList as data.
That's fine, all you are doing is replacing the model. You don't need to make any changes to the JList when you do this, assuming that your model invokes the proper fireXXX() methods when the data is changed.
You should also check out the section from the Swing tutorial on How to Use Models which shows how you might use the MVC approach for your own custom component.
this is a homework btw,
I am asked to make a jframe containing multiple jpanels which have buttons and action listeners attached to them. I have to use the MVC model to do it but, since my buttons/actions are in jpanels instead of the jframe, i do not know how to recover them. I wont put down all of my code but, just what is needed to see what I try to do. I want to get the button "ajouter" from panel 3 first to do whatever action:
So this is pannel 3
import java.awt.FlowLayout;
import javax.swing.JButton;
import javax.swing.JPanel;
import ca.uqam.inf2120.tp2.modele.GestionAbsenceEmployes;
public class RechercherAbsenceP3 extends JPanel {
private GestionAbsenceEmployes aControleur;
private JButton ajouter, modifier, supprimer, afficher, fermer;
FlowLayout gestionnaireComposant;
RechercherAbsenceP3() {
try {
jbInitP3();
} catch (Exception e) {
e.printStackTrace();
}
ajouter.addActionListener(aControleur);
modifier.addActionListener(aControleur);
supprimer.addActionListener(aControleur);
afficher.addActionListener(aControleur);
fermer.addActionListener(aControleur);
}
private void jbInitP3() throws Exception {
gestionnaireComposant = new FlowLayout(FlowLayout.RIGHT);
this.setLayout(gestionnaireComposant);
ajouter = new JButton("Ajouter");
modifier = new JButton("Modifier");
modifier.setEnabled(false);
supprimer = new JButton("Supprimer");
supprimer.setEnabled(false);
afficher = new JButton("Afficher");
afficher.setEnabled(false);
fermer = new JButton("Fermer");
this.add(ajouter);
this.add(modifier);
this.add(supprimer);
this.add(afficher);
this.add(fermer);
}
public JButton getAjouter() {
return ajouter;
}
}
This is the window
package ca.uqam.inf2120.tp2.interfacegraphique;
import java.awt.BorderLayout;
import ca.uqam.inf2120.tp2.interfacegraphique.RechercherAbsenceP3;
import javax.swing.JFrame;
import javax.swing.JPanel;
import ca.uqam.inf2120.tp2.modele.GestionAbsenceEmployes;
public class CreerRechercherAbsence extends JFrame {
private GestionAbsenceEmployes aControleur;
private JPanel absenceP1, absenceP2, absenceP3;
private BorderLayout gestionnaireComposant;
public CreerRechercherAbsence() {
super("Gestionnaire des employés absents");
try {
jbInit();
} catch (Exception ex) {
ex.printStackTrace();
}
aControleur = new GestionAbsenceEmployes(this);
}
void jbInit() throws Exception {
gestionnaireComposant = new BorderLayout(5, 5);
this.getContentPane().setLayout(gestionnaireComposant);
absenceP1 = new RechercherAbsenceP1();
absenceP2 = new RechercherAbsenceP2();
absenceP3 = new RechercherAbsenceP3();
this.getContentPane().add(absenceP1, BorderLayout.NORTH);
this.getContentPane().add(absenceP2, BorderLayout.CENTER);
this.getContentPane().add(absenceP3, BorderLayout.SOUTH);
}
}
now the not finished controler:
package ca.uqam.inf2120.tp2.modele;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import ca.uqam.inf2120.tp1.partie1.adt.impl.ListeAdtArrayListImpl;
import ca.uqam.inf2120.tp2.interfacegraphique.CreerRechercherAbsence;
public class GestionAbsenceEmployes implements ActionListener{
private AbsenceEmploye modele;
private CreerRechercherAbsence vue;
public GestionAbsenceEmployes(CreerRechercherAbsence uneVue) {
this.modele = new AbsenceEmploye();
vue = uneVue;
}
public AbsenceEmploye getModele() {
return modele;
}
#Override
public void actionPerformed(ActionEvent event) {
Object source = event.getSource();
if(source == vue.getAjouter()) {
}
}
}
When I add the vue.getAjouter() it does not know what it is !!!
What do I do/get wrong ?
The problem is you are calling getAjouter() on CreerRechercherAbsence JFrame instance in your ActionListener where as you'd want to be calling getAjouter() on RechercherAbsenceP3 JPanel instance.
My solution:
Convert your ActionListener class GestionAbsenceEmployes to accept RechercherAbsenceP3 as the parameter so we can call getAjouter() on its instance like so:
class GestionAbsenceEmployes implements ActionListener {
private AbsenceEmploye modele;
private RechercherAbsenceP3 vue;
public GestionAbsenceEmployes(RechercherAbsenceP3 uneVue) {
this.modele = new AbsenceEmploye();
vue = uneVue;
}
public AbsenceEmploye getModele() {
return modele;
}
#Override
public void actionPerformed(ActionEvent event) {
Object source = event.getSource();
if (source == vue.getAjouter()) {
}
}
}
you would than do:
aControleur = new GestionAbsenceEmployes(absenceP3);
but in order for the above statement to function you must change this:
private JPanel absenceP1, absenceP2,absenceP3;
in CreerRechercherAbsence class to this:
private JPanel absenceP1, absenceP2;
private RechercherAbsenceP3 absenceP3;
because you extend JPanel to add functionality hence the RechercherAbsenceP3 but by declaring it as JPanel you down cast it, thus it does not have access to the methods of extended JPanel RechercherAbsenceP3 and only those of default JPanel.
Some other suggestions:
Do not extend JFrame class unnecessarily
No need for getContentPane.add(..) as add(..) has been diverted to contentPane
Be sure to create and manipulate Swing components on Event Dispatch Thread
Not sure whether the following approach will be considered MVC, or whether it will result in good marks on your assignment.
My "problem" with your current approach is that the reusability is limited to the model, and that it looks difficult to write a decent test case for this code, unless you are prepared to write tests which include the whole view.
When I need to write a Swing application, it seems that I only end up with 2 classes: a model class defining the data and the operations available on that data, and the view class. The view class functions both as view as well as controller. When I have a button as in your example, I would attach an ActionListener to it (or use an Action) which just retrieves the necessary information from the view without any logic. It passes all that information directly to the model side where all the logic is located.
The two main benefits I see in this approach:
I can re-design my view without any problems. If I decide to remove a JButton and provide the user with another mechanism for that same operation, all my changes are limited to the view. I have no dependency on UI elements except in my view class. I see all the "information gathering and passing it to the model" directly in my view class, and due to the implementation of that view this will not affect other classes. Compare that with your code where you have a source == vue.getAjouter() check in a class outside your view.
I can test the model and all its logic without needing my actual view. So I can skip the whole "firing up a Swing UI" in a unit test and still test all my logic. If I want to test the UI (for example to test whether a certain button is disabled when a field is left blank) I can test this separately in an integration test (as having a UI tends to slow down your tests).
What I found a very interesting article in this regard is The humble dialog box
Here is how I would do it. Make GestionAbsenceEmployes a non-static inner class of CreerRechercherAbsence
public class CreerRechercherAbsence extends JFrame {
private GestionAbsenceEmployes aControleur;
private JPanel absenceP1, absenceP2;
private RechercherAbsenceP3 absenceP3;
// code omitted
public CreerRechercherAbsence() {
super("Gestionnaire des employés absents");
try {
jbInit();
} catch (Exception ex) {
ex.printStackTrace();
}
aControleur = new GestionAbsenceEmployes();
}
// code omitted
class GestionAbsenceEmployes implements ActionListener{
private AbsenceEmploye modele;
public GestionAbsenceEmployes() {
this.modele = new AbsenceEmploye();
}
public AbsenceEmploye getModele() {
return modele;
}
#Override
public void actionPerformed(ActionEvent event) {
Object source = event.getSource();
if(source == absenceP3.getAjouter()) {
}
}
}
No need to pass this to the constructor and the controller does not need a reference to vue. You get all that for free by making this an inner class. Your controller can access all the member variables of the view. So you can now access the absenseP3 panel with the getAjouter() method.
See http://docs.oracle.com/javase/tutorial/java/javaOO/nested.html for more information on when it makes sense to use inner classes.
I how it's possible to setup non resizable window with JFace API. Consider code below that creates application window. I can't find any methods to setup window as not resizable on shell object or application window parent. Is there something I'm missing?
public class Application extends ApplicationWindow
{
public Application()
{
super(null);
}
protected Control createContents(Composite parent)
{
prepareShell();
return parent;
}
protected void prepareShell() {
Shell shell = getShell();
shell.setSize(450, 300);
}
public static void main(String[] args)
{
Application app = new Application();
app.setBlockOnOpen(true);
app.open();
Display.getCurrent().dispose();
}
}
Thanks for your help.
As far as I understand you, you want to set shell style bits prior to the shell creation.
Simply add
#Override
public void create() {
setShellStyle(SWT.DIALOG_TRIM);
super.create();
}
to your class, to do so. This omits the SWT.RESIZE style bit, therefore prevents resizing..