I'm trying to understand how to structure a simple application. It seems it must be a really common scenario but so far I can't work it out.
Lets say I have a class A with the main method. My expectation is that it will co-ordinate activities. The application has a JFrame with several panels in it, each with some GUI control. Each of these panels will be a class, say named B1, B2 B3... etc. Each class B has a listener for the control that is shown on its panel.
Question: How do I make the main class A aware when events have occurred in the panel classes B1, B2 etc.? I can think of three approaches, none of which seems that attractive:
a) I have a get method in each B class relating to some class field affected by the GUI. This method would then be called periodically from A to determine if an event has been handled. This seems to be a really poor solution, one comparable to not using event listeners at all in the B classes.
b) making each B class inherit a property that i) is altered by the GUI component's action listener in that B class and ii) can itself be listened to by the A class. This seems to be quite an elegant solution, except I have found no reference to it (in this terminology) in any of the documentation that I have read.
c) Start again with a different architecture completely and put all listeners in class A, together with their panels and GUI controls. This seems to be a poor solution not taking advantage of any properties of object orientation.
So, I'm sure I have missed something simple, I just can't see where to go from here.
Haven't you noticed that all of those controls have methods with names like "addWhatsitListener"? Just use those.
In other words, if object X needs to be a WhatsitListener for object Y, then first of all X needs to implement WhatsitListener, and second X needs to declare itself to be a WhatsitListener for Y like this:
Or alternatively X could have an inner class which performed the listener function. Surely you must have seen code like this while going through the Swing tutorials?
Joined: Jun 21, 2011
Hi Paul, thanks, this is encouraging.
Let me make sure I understand you, in your example X could be A and Y could be B1 etc.? I think you are suggesting that approach (b) is workable.
The problem I have had in understanding the documentation is that every example of class Y I have seen is itself a GUI control, a button or a slider. So far I'd go as far as saying that the documentation has been so convincing on this front there seemed little scope for option (b). In my case class B is just something that extends a panel upon which buttons etc are defined. Just to be clear, I think we are saying that I will have two action listeners, one within class B for the button, and one within class A to register that something has happened in class B. If I have several classes B1, B2, B3, each with a GUI control then I would have several action listeners in class A.
I'll trying and flesh this out and come back if I run into difficulty.
I'm sorry, I can't answer that. At my age I find I have difficulty dealing with designs which call their classes things like "A" and "B". The meaninglessness of the names tends to interfere with whatever else I'm reading. Maybe somebody else who is better at abstraction will add their comments.
If you look at for example class JButton, it has a method addActionListener with which you can add a listener to the button that gets called when the button is clicked. The action listener can be any class you like, as long as it implements the ActionListener interface.
When you have an application with multiple windows or dialogs, then I don't think it is a good idea to make one big main class that has all the action listeners for all GUI controls on all screens. That big main class will quickly become a mess, mixing the functionality of all the screens together.
Normally you would make a class for each window or dialog, which holds the GUI controls that are on that window. Anonymous inner classes are often used for the event handlers. If you use a GUI builder, for example the GUI builder in the NetBeans IDE, than that's the kind of code that it will generate when you create your windows and place controls on it.
Here's a complete example that I just created with the NetBeans GUI builder. All of this code, except for one line, was auto-generated by the NetBeans GUI builder. As you can see, it creates a class that extends JFrame, which holds the GUI components as member variables. In lines 29-33 it adds an action listener to the submit button using an anonymous inner class, but it delegates to the private method submitButtonActionPerformed in the HelloFrame class.
Hi Paul and Jesper
Thanks for your responses. I've had to go away and think for a bit. I find that whilst I think I can see the applicability of the MVC idea at a superficial level I'm having trouble bridging from there to a simple concrete example.
In the mean time, and maybe I have misunderstood your example (Jesper) but I think it does not address the problem I had in mind. Lines 29-33 can not delegate anywhere outside of the Hello Frame class. Noting Paul's comments I have re-written the problem description with the following two classes. The example problem is just to show a counter increment each time the button is pressed. The comment text in each class says what I think is missing.
This is the second class, ButtonPanel, referred to in the one above:
Anyway, writing the problem this way helped me to understand that I need to make ButtonPanel source a custom event. I then found a solution which is in the next post.
Joined: Jun 21, 2011
This is the continuation of my progress. Basically I have found a solution and it works, though it seems a little clumsy. I am pleased at least that I can parcel some of the distractions out of the way where I guess they can be used by other classes similar to ButtonPanel. Nonetheless, I'd be interested if anyone can suggest alternatives.
The following two classes are developed from the two in my last e-mail and show the BasicFrame class responding in step to button presses in the ButtonPanel class. There is also a new third class and interface which I show at the end.
So there is now an event listener in the first class, but its one that expects ButtonPanel to be an event source. That second class now becomes:
...The basic change being the addition of the two new methods addMyEventListener() and fireMyEvent() though I confess I haven't yet quite got to a fluent understanding of the detail yet. Then to keep the other essential bits around but tidily separate I need to provide the event object and interface:
As I say, it works but seems a bit clumsy so if you can see another way of doing this I'd be interested to hear.
There are 3 or 4 methods to accomplish this - some elegant, some not so. I will post one by one.
First one is:
and the key lines to be added to ButtonPanel class would be:
as you can see, I just pass the BasicFrame instance to the ButtonPanel constructor and then register it with the button's actionListener. Now, there are 2 listeners for the button. One the ButtonPanel itself and the next ButtonFrame. So, when the button is clicked, both classes are notified.
However, this solution has some drawbacks:
1) You need to maintain a reference to the ButtonPanel class with BasicFrame.
2) You have to pass the BasicFrame instance to the ButtonPanel constructor (as an alternative, you can just take an ActionListener as parameter)
3) Most important drawback is that: Note the order of adding the listeners. If you change them, you won't get the correct value in the BasicFrame listener. This is beacuse, the actionListeners are notified in the reverse order by the API. So, this ties us to add and maintain the particular order.
The most elegant way would be follow the 'Observer' pattern and implement it for your problem (this is one of the GoF patterns). To make our life easier, Java API provides 'Observer' interface and 'Observable' class implementations to make use of.
Check this link to understand Observer and Observable: Javaworld article
API Javadoc says:
A class can implement the Observer interface when it wants to be informed of changes in observable objects.
And the signature of the update method is: - you will be passing the observed object itself along with an argument - in your case, this would be the count value. In this implementation, you choose when to notify the observer i.e BasicFrame in this case.
However, the biggest drawback for us is that, Observable is a class. So, the ButtonPanel has to extend the Observable - but we need it to extend JPanel. So, you have to do it in a roundabout way.
Or, you can write your own Observer/Observable interfaces and implement them. But, you will have to manage handling the listeners - or for now, you can just handle one listener.
There is a 3rd way, which also uses the observer pattern: PropertyChangeSupport.
With this, we can overcome the class extension limitation using a HAS-A approach against a IS-A approach. This class is very helpful and has all the needed methods to add,remove and also to notify listeners via one method call.
Note that, the BasicFrame doesn't have to hold on to the reference of ButtonPanel here. And you can add more listeners to be notified when the button is clicked. And the order doesn't matter! Also, it is the ButtonPanel which controls when the listeners are notified. I think this is an elegant solution.
Joined: Jun 21, 2011
Thanks for these ideas, I do appreciate it. I can see how the first example works quite readily and by chance I had actually drifted across both the Observer / Observable article and the topic of PropertyChangeSupport already. However at that time I found it difficult to be confident that they were actually addressing the problem that I was facing.
Now you have clarified things it will make it easier to go back over them. Up to now I have found most talk about design patterns too abstract to absorb so I hope this one will be easier now that I have a problem in mind.