The new forums will be named Coin Return (based on the most recent vote)! You can check on the status and timeline of the transition to the new forums here.
The Guiding Principles and New Rules document is now in effect.

Java: MVC and Listeners

SeguerSeguer of the VoidSydney, AustraliaRegistered User regular
edited April 2007 in Help / Advice Forum
Hey again :D

As you can tell, I'm working on an assignment. After talking with my tutor yesterday, we need to implement MVC by having a single Controller class.

For example, I have a MenuPanel (with menus, menuitems) and an EntryPanel (text fields, buttons), that need to be controlled by Controller (original, I know). The underlying models to these only exist for EntryPanel (MenuPanel has no model, it's only a means to do stuff, not store/change data). From my current knowledge, I know I need to implement listeners, and have the controller handle them in some way.

What I can't see is the connection between all three parts - what code goes into the View, Model, and Controller. From an earlier assignment that we did in a different subject (that uses some MVC) I've determined that a View is constructed with a controller [class View (Controller controller)] and the Controller will implement something like View.Listener. Am I correct in these surmises, and are they any good articles on the net that talk about this?

Thanks. (P.S: The image thing works beautifully now :D)

Seguer on

Posts

  • PhilodoxPhilodox Registered User regular
    edited April 2007
    I'll try and do this subject justice. Most of my work I spend concerned with the C so my M and V aren't quite as strong.

    The view code should only be concerned with presenting data. There should be absolutely no business logic in it. In practice a little bit usually seeps in but ideally it should be only concerned with presenting the model that it is given.

    The model is an abstraction of the domain of data you are dealing with. The model also typically doesn't contain any business logic. Any logic in the model is used for translating from one form to another (I have a string of a date, I need a double representing time since x, etc).

    The controller translates and passes elements of the model to the view. Almost all of your business logic should be in the controller.

    I'll see if I can cook up a short Java program to illustrate this but basically you want it set up something like this for a simple input->popup type program.

    View elements:
    input - text field
    popup - joptionpane (or whatever) that shows a string to the user

    Model:
    String message

    Controller:
    router - takes input string and passes it to popup

    You would make the controller implement ActionListener and register it with the input view. When the user enters text and hits enter that event will be sent to the controller. The controller would update the model with the value of the text field and alert the view that it had completed operation (there are quite a few ways to do this). The view would then take the contents of the model and display that using a popup.

    I hope this makes some sense.

    Philodox on
    That's a Freudian mansex if I ever cocked one.
    twinsbanneroq0.jpg
  • SeguerSeguer of the Void Sydney, AustraliaRegistered User regular
    edited April 2007
    Thanks for the reply Philodox, but I already understand all of that :D. I just don't know how to actually implement the code side of it in java.

    Seguer on
  • PhilodoxPhilodox Registered User regular
    edited April 2007
    b'oh, alright :P

    Assuming somebody doesn't beat me to it, I'll see if I can throw together a sample program illustrating it.

    Philodox on
    That's a Freudian mansex if I ever cocked one.
    twinsbanneroq0.jpg
  • SeguerSeguer of the Void Sydney, AustraliaRegistered User regular
    edited April 2007
    Sweet, thanks. Actually that is my preferred method of learning - I learn better off examples :D Thanks!

    Seguer on
  • PhilodoxPhilodox Registered User regular
    edited April 2007
    Alright hopefully this explains everything. One note, I combined the controller and the listener for action events. Depending on how many components are using the listener you may want to separate them to help clean up the code.

    IView:
    public interface IView {
    	public void present(String model);
    }
    

    View:
    import java.awt.Container;
    import java.awt.FlowLayout;
    
    import javax.swing.JFrame;
    import javax.swing.JLabel;
    import javax.swing.JOptionPane;
    import javax.swing.JTextField;
    
    
    public class View implements IView {
    
    	private JFrame jfWindow;
    	
    	public View() {
    		jfWindow = new JFrame();
    		
    		Container c = jfWindow.getContentPane();
    		
    		setupMainContainer(c);
    		
    		jfWindow.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    		jfWindow.setSize(320, 100);
    		jfWindow.setTitle("MVC Test");
    		jfWindow.setVisible(true);
    	}
    	
    	protected void setupMainContainer(Container c) {
    		
    		IController controller = new Controller();
    		controller.setView(this);
    		
    		JLabel jlText = new JLabel("Enter Text: ");
    		JTextField jtfText = new JTextField(20);
    		jtfText.addActionListener(controller);
    		
    		c.setLayout(new FlowLayout());
    		c.add(jlText);
    		c.add(jtfText);
    	}
    	
    	public void present(String model) {
    		JOptionPane.showMessageDialog(null, model);
    	}
    }
    

    IController:
    import java.awt.event.ActionListener;
    
    
    public interface IController extends ActionListener {
    	public void setView(IView view);
    	public void process(String model);
    }
    

    Controller:
    import java.awt.event.ActionEvent;
    
    
    public class Controller implements IController {
    
    	private IView view;
    	
    	public void process(String model) {
    		view.present(model + " controlled!");
    	}
    
    	public void setView(IView view) {
    		this.view = view;
    	}
    
    	public void actionPerformed(ActionEvent e) {
    		process(e.getActionCommand());
    	}
    }
    

    MVCTest:
    
    public class MVCTest {
    	public static void main(String[] args) {
    		View view = new View();
    	}
    }
    

    Philodox on
    That's a Freudian mansex if I ever cocked one.
    twinsbanneroq0.jpg
  • SeguerSeguer of the Void Sydney, AustraliaRegistered User regular
    edited April 2007
    Woo great thanks. I'm going to take a good look at that tomorrow, but what is the significance of the I(something)?

    Seguer on
  • PhilodoxPhilodox Registered User regular
    edited April 2007
    It's a hungarian notation (wart) indicating that it's an interface.

    Philodox on
    That's a Freudian mansex if I ever cocked one.
    twinsbanneroq0.jpg
  • SeguerSeguer of the Void Sydney, AustraliaRegistered User regular
    edited April 2007
    Ok I've taken a good look at this, played with it a bit and I think I understand how to implement this for my own project. I'll have a go at that and let you know how it goes :D. However, while I understand how to implement it, what is an interface? We haven't worked with them before.

    EDIT: How do I process different methods? (instead of just having process() ), as Controller will be controller about 3 classes eventually, all with different methods.

    EDIT2: Also, I receive input/events/actions in one class, and have to perform stuff in another class, how do I go about this?

    Seguer on
  • PhilodoxPhilodox Registered User regular
    edited April 2007
    An interface simply defines a set of methods, a way to interact with the classes that implement it.

    Question 1: There's no simple answer to this. How you do it is a matter of requirements and coding style.


    I'll see if I can modify my example to include both of your edits and post it up when I'm done.

    Philodox on
    That's a Freudian mansex if I ever cocked one.
    twinsbanneroq0.jpg
  • SeguerSeguer of the Void Sydney, AustraliaRegistered User regular
    edited April 2007
    Cool, thanks. :D

    Seguer on
  • PhilodoxPhilodox Registered User regular
    edited April 2007
    Alright I think this does what you want. You can extend this pretty far by adding more controller classes.

    IView:
    public interface IView {
    	public void present(String model);
    }
    

    View:
    import java.awt.Container;
    import java.awt.FlowLayout;
    
    import javax.swing.JButton;
    import javax.swing.JFrame;
    import javax.swing.JLabel;
    import javax.swing.JOptionPane;
    
    
    public class View implements IView {
    
    	private JFrame jfWindow;
    	
    	public View() {
    		jfWindow = new JFrame();
    		
    		Container c = jfWindow.getContentPane();
    		
    		setupMainContainer(c);
    		
    		jfWindow.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    		jfWindow.setSize(320, 75);
    		jfWindow.setTitle("MVC Test");
    		jfWindow.setVisible(true);
    	}
    	
    	protected void setupMainContainer(Container c) {
    		
    		IController suffixController = new SuffixController();
    		IController prefixController = new PrefixController();
    		
    		suffixController.setView(this);
    		prefixController.setView(this);
    		
    		JLabel jlText = new JLabel("Actions: ");
    		JButton jbAction1 = new JButton("Action 1");
    		JButton jbAction2 = new JButton("Action 2");
    		
    		ButtonListener buttonListener = new ButtonListener();
    		buttonListener.associate(jbAction1, prefixController);
    		buttonListener.associate(jbAction2, suffixController);
    		
    		c.setLayout(new FlowLayout());
    		c.add(jlText);
    		c.add(jbAction1);
    		c.add(jbAction2);
    	}
    	
    	public void present(String model) {
    		JOptionPane.showMessageDialog(null, model);
    	}
    }
    

    IController:
    public interface IController {
    	public void setView(IView view);
    	public void process(String model);
    }
    

    PrefixController:
    public class PrefixController implements IController {
    
    	private IView view;
    	
    	public void process(String model) {
    		if(null != view) {
    			view.present("Controlling "+model);
    		}
    	}
    
    	public void setView(IView view) {
    		this.view = view;
    	}
    }
    
    

    SuffixController:
    import java.awt.event.ActionEvent;
    
    public class SuffixController implements IController {
    
    	private IView view;
    	
    	public void process(String model) {
    		if(null != view) {
    			view.present(model + " controlled!");
    		}
    	}
    
    	public void setView(IView view) {
    		this.view = view;
    	}
    }
    

    ButtonListener:
    import java.awt.event.ActionEvent;
    import java.awt.event.ActionListener;
    import java.util.Hashtable;
    
    import javax.swing.AbstractButton;
    
    
    public class ButtonListener implements ActionListener {
    	
    	private Hashtable<AbstractButton, IController> componentControllerMap;
    	
    	public ButtonListener() {
    		componentControllerMap = new Hashtable<AbstractButton, IController>();
    	}
    
    	public void actionPerformed(ActionEvent ae) {
    		IController controller = componentControllerMap.get(ae.getSource());
    		
    		if(null != controller) {
    			controller.process(ae.getActionCommand());
    		}
    	}
    	
    	public void associate(AbstractButton button, IController controller) {
    		button.addActionListener(this);
    		componentControllerMap.put(button, controller);
    	}
    }
    

    MVCTest:
    public class MVCTest {
    	public static void main(String[] args) {
    		View view = new View();
    	}
    }
    

    One thing to note when designing listeners and controllers, you want to make them based on their intended function not necessarily on the type of component that invokes them. If you were writing the logic for a login page you'd want something like a LoginFormListener rather than trying to use one Listener for every single TextField. This isn't an absolute rule, it just makes code maintenance easier.

    Philodox on
    That's a Freudian mansex if I ever cocked one.
    twinsbanneroq0.jpg
  • SeguerSeguer of the Void Sydney, AustraliaRegistered User regular
    edited April 2007
    <3 Will take a good long look at this too, thanks :D

    EDIT: I'm getting an error that the new controller's aren't implementing actionPerformed - are they meant to?

    Also, to give you the best example possible, I have a text field in an EntryPanel class and a button that will "submit" an item (whatever is in the text field), and as part of the process behind that, must update an image in a different panel, ImagePanel. (I also need to update about 2-3 other panels, but that's just an example).

    Seguer on
  • PhilodoxPhilodox Registered User regular
    edited April 2007
    In the second example I posted I took extending ActionListener out of the IController class since you mentioned that the controller and the listener should be separate.

    This is the order of things:
    1. Place button on gui
    2. Create your controller object
    3. Create your listener object
    4. Call associate on your listener (this adds itself as an ActionListener)
    5. When the button is pressed the button listener looks up which controller is associated with the button and calls the process method.

    It sounds like you are trying to set the controller as the ActionListener. My example will need to be modified if you want to use it with a text field and a button (since it's just taking the label from the button as its model). I don't think it should be too hard to do though.

    Philodox on
    That's a Freudian mansex if I ever cocked one.
    twinsbanneroq0.jpg
  • SeguerSeguer of the Void Sydney, AustraliaRegistered User regular
    edited April 2007
    Hmm ok, when you put it like that it's a lot easier to understand. I have uni today so I can't work on my project until much later today, but I then have three days straight in which to work on it. Your examples and help have been greatly appreciated, Philodox. :D

    RE: Listeners and Controllers - I don't think I mentioned the need to separate them, unless you read that in one my examples? *shrug*

    Seguer on
  • PhilodoxPhilodox Registered User regular
    edited April 2007
    EDIT2: Also, I receive input/events/actions in one class, and have to perform stuff in another class, how do I go about this?

    I guess I misinterpreted this then, oh well. You don't need to separate your listeners and your controllers.

    Philodox on
    That's a Freudian mansex if I ever cocked one.
    twinsbanneroq0.jpg
  • SeguerSeguer of the Void Sydney, AustraliaRegistered User regular
    edited April 2007
    Damn. I've spent the last hour and a half trying to get this to work in my project and I'm in way over my head.

    Your example is perfect Philodox, but I'm afraid it's just too complicated for me. I had to look up what a Hashtable was :P. This is the way I see it (code-wise) needing to be:

    1. In a class with a button (or menu item), add an action listener that tells the controller class to listen to it
    2. In the controller class, have an actionPerformed or similar that responds to all the events from buttons/menu items and does a specified method (pure example: so if I push a Print menu item, the controller would run a controller.print() method or similar)

    In this, the controller needs to know about all of the potential objects that it needs to listen to and (from my research) I assume thats what the Hashtable is for. To me it would seem that the Hashtable needs to consist of keys and values such as this:

    EnterItemButton, entryPanel.EnterItemButton (as a reference to the object button)
    etc for each button

    Or am I completely off track?


    MAJOR EDIT: I think I got something working, by using my own code. I've made it so that my GUI instantiates the Controller, then my Entry Panel is instantiated with a parameter of controller, which it uses to set as it's controller and to tell controller to listen to it. I then have controller implement ActionListener, and I have it printing out the source of the ae and the getActionCommand.

    Holy crap that took a lot of thinking. Is this a decent way of doing it, Philodox? At this point I don't much care for "proper" MVC - this is MY way. :P


    EDIT: So I don't make a new post and bump, can java return arrays of objects? It won't seem to let me return a Product object (that I've made).

    Seguer on
  • PhilodoxPhilodox Registered User regular
    edited April 2007
    Writing a proper MVC application is hard unfortunately. It's also much better to go with what works for you and using your own style figure out what works and what doesn't. There's no "this is the way you have to do MVC" document, it just has some general principles.

    You can return any valid data type in java (which includes arrays of objects). Maybe post the offending code? It should look something like
    MyObject[] objectArrayMethod() { ....
    

    Philodox on
    That's a Freudian mansex if I ever cocked one.
    twinsbanneroq0.jpg
  • SeguerSeguer of the Void Sydney, AustraliaRegistered User regular
    edited April 2007
    I got around it by having that class send it as a parameter to my controller, which then sends it to the other class. A lot of work for the CPU, but meh.

    I'm having trouble now with a textfield and .getText(). It constantly returns "" (empty) even though I've just typed stuff into it.


    I access it from a method in that class that returns it to my controller class, however even if I try to system.out.print it from there, it is empty. Any idea?

    Seguer on
  • PhilodoxPhilodox Registered User regular
    edited April 2007
    Anyway you can post a code snippet? .getText() should return whatever the current contents of the text box are.

    Philodox on
    That's a Freudian mansex if I ever cocked one.
    twinsbanneroq0.jpg
  • SeguerSeguer of the Void Sydney, AustraliaRegistered User regular
    edited April 2007
    EntryPanel:
       /**
         * Instance variables
         */
        private     JLabel      entryLabel, tenderedLabel;
        private     JTextField  [b]entryTextField[/b], tenderedTextField;
        private     JButton     enterItemButton;
        private     JButton     paymentButton;
        private     JButton     cancelButton;
        private     Controller  controller;
    
    public String getItemID()
        {
            String item;
            item = entryTextField.getText();
            return item; [b]//I changed this around to put it into a temp var to test - still doesn't work[/b]
        }
    

    Controller:
    public void actionPerformed(ActionEvent ae)
        {
            if (ae.getSource() == entryPanel.getEntryButton()) {
                System.out.println("Enter Item Button pressed");
                System.out.println(entryPanel.getItemID());
                //Product tmpProduct = matchProduct(Double.parseDouble(entryPanel.getItemID()), new Float(1));
                //Product tmpProduct = matchProduct(Double.parseDouble("23456"), new Float(1));
                //System.out.println(tmpProduct.getID() + " : " + tmpProduct.getName());
            } else {
                System.out.println(ae.getSource());
                System.out.println(ae.getActionCommand());
            }
        }
    

    I have a method in controller that sets entryPanel to the parameter. My GUI creates a controller and passes it as a parameter to EntryPanel, which calls setController on itself - setController does this:

    EntryPanel.setController
    private void setController(Controller ctrller)
        {
            this.controller = ctrller;
            controller.setEntryPanel(this);
        }
    


    All of this is academic however, as in the EntryPanel.getItemID() method, if I System.out.println() it, it still shows up as "" (empty string).

    Completely stumped on this :S

    Seguer on
  • PhilodoxPhilodox Registered User regular
    edited April 2007
    Is the event generated by the text field, or something else?

    Philodox on
    That's a Freudian mansex if I ever cocked one.
    twinsbanneroq0.jpg
  • SeguerSeguer of the Void Sydney, AustraliaRegistered User regular
    edited April 2007
    Whoops, sorry. The event is generated by a JButton.
    JButton    enterItemButton;
    

    EDIT:

    Then Controller handles it with:
    if (ae.getSource() == entryPanel.getEntryButton()) {
    

    Seguer on
  • PhilodoxPhilodox Registered User regular
    edited April 2007
    Actually that really shouldn't make a difference. Is there any way you can step through this with a debugger to see what's going on?

    Philodox on
    That's a Freudian mansex if I ever cocked one.
    twinsbanneroq0.jpg
  • SeguerSeguer of the Void Sydney, AustraliaRegistered User regular
    edited April 2007
    I've tried to - no matter where I try and getText(), it's always empty.

    The only result I've been able to get where it ISN'T empty is when i precede the line with a setText()

    Seguer on
  • PhilodoxPhilodox Registered User regular
    edited April 2007
    The only thing I can think of is that entryTextField isn't set to what you think it should be.

    Philodox on
    That's a Freudian mansex if I ever cocked one.
    twinsbanneroq0.jpg
  • SeguerSeguer of the Void Sydney, AustraliaRegistered User regular
    edited April 2007
    Well, considering that from the getItemID() method, I can put in a line that does entryTextField.setText("Test"); and then it will return "Test" instead of the empty string, there shouldn't be any problem with the actual object?

    Seguer on
  • PhilodoxPhilodox Registered User regular
    edited April 2007
    No, there shouldn't be. There goes that theory...

    Philodox on
    That's a Freudian mansex if I ever cocked one.
    twinsbanneroq0.jpg
  • SeguerSeguer of the Void Sydney, AustraliaRegistered User regular
    edited April 2007
    Now you see what has me so baffled.

    Seguer on
  • PhilodoxPhilodox Registered User regular
    edited April 2007
    Here's something you can do:

    In your getItemId() put a setText(...) and see if that actually changes the value of the text field on the gui (it should).

    Philodox on
    That's a Freudian mansex if I ever cocked one.
    twinsbanneroq0.jpg
  • SeguerSeguer of the Void Sydney, AustraliaRegistered User regular
    edited April 2007
    Hahahahaha omg. I found the problem. Thanks Philodox. I was using the "entryTextField" variable twice, instead of "tenderedTextField". Imagine my surprise when the other text field changed!

    Thanks.

    Seguer on
  • PhilodoxPhilodox Registered User regular
    edited April 2007
    no problem :)

    Philodox on
    That's a Freudian mansex if I ever cocked one.
    twinsbanneroq0.jpg
  • SeguerSeguer of the Void Sydney, AustraliaRegistered User regular
    edited April 2007
    Hey Philodox, this is off-topic to MVC and Listeners, but you've helped me heaps so far :D

    In a previous thread, I wanted to draw an Image, and ended up using paint(Graphics g). It works fine. For the first image. Subsequent images leave ghostings of the previous image, as well as one of my textboxes. I call repaint() as part of my setImage() method as well, and draw the image with g.drawImage(). Any ideas?

    EDIT: Another question: I have a class called Product, and two classes that extend Product, UPC_Product, and Non_UPC_Product. The Non UPC product has a method called getUnit().

    I'm using instanceof to determine whether a variable (Product product = new...) is of either instance. If it is Non_UPC, I need to use getUnit(), but it is complaining of not being able to find the symbol. Is this because it thinks product is a "Product" and not a "Non_UPC_Product" during the compile? And how can I fix this?

    Seguer on
  • Mr.FragBaitMr.FragBait Registered User regular
    edited April 2007
    Seguer wrote: »
    Another question: I have a class called Product, and two classes that extend Product, UPC_Product, and Non_UPC_Product. The Non UPC product has a method called getUnit().

    I'm using instanceof to determine whether a variable (Product product = new...) is of either instance. If it is Non_UPC, I need to use getUnit(), but it is complaining of not being able to find the symbol. Is this because it thinks product is a "Product" and not a "Non_UPC_Product" during the compile? And how can I fix this?

    Are you casting right?
    if(  a_product instanceof Non_UPC_Product ){
        Non_UPC_Product  non_upc = (Non_UPC_Product ) a_product;
        non_upc.getUnit();
    }
    

    Mr.FragBait on
  • SeguerSeguer of the Void Sydney, AustraliaRegistered User regular
    edited April 2007
    Ah, that works now. Thanks FragBait. That type casting stuff didn't show up in any examples I saw of instanceof :(

    EDIT: I got the image stuff to work. Weeee.

    Seguer on
  • Mr.FragBaitMr.FragBait Registered User regular
    edited April 2007
    You'll be doing it alot more type casting once you get further into java and data structures. Though you'll then start learning generics, which is a bitch itself.

    Don't forget you can simplify that if you only want to call the method
    if(  a_product instanceof Non_UPC_Product ){
        ( (Non_UPC_Product ) a_product ).getUnit();
    }
    

    Though it makes it harder to read, it's a little more succinct.

    Also, don't forget if you're only want to call the getUnit() method on your implementation of Non_UPC_Product you can use this instead
    if(  a_product.getClass().equals( Non_UPC_Product.class ) ){
        ( (Non_UPC_Product ) a_product ).getUnit();
    }
    

    This should only be done if you don't want using anyone else's implementation of getUnit(). Otherwise instanceof is the better choice, since it doesn't stop others from extending your classes and reusing your code.

    Mr.FragBait on
  • SeguerSeguer of the Void Sydney, AustraliaRegistered User regular
    edited April 2007
    Ah ok, yeah, see, we haven't really been taught about this stuff. They just sort of go "hey guys lolz you can extend classes, and use MVC, here's your assignment!"

    I'd generally go with instanceof I think, cos the whole point is to be able to reuse code :)

    Thanks!

    Seguer on
Sign In or Register to comment.