![]() | Monkeys at Keyboards: The Javanomicon © Michael James Heron | ||||
| Topic: Java Programming Level: 2 Version: delta | |||||
5 - Event Driven Programming 2 | |||||
| Previous | Table of Contents | Next |
| Forum |
| Chapter Objectives |
By the end of this chapter, the reader will be able to:
|
In the last chapter we looked at Java's event driven programming structure and discussed how we can build simple user interfaces using two of Swing's provided GUI widgets: the button and the scrollbar. In this chapter we look at some more of the standard components in Swing's toolkit and discuss how we can present output to the user in a clear and effective way. There are many components provided by the Swing library of objects - of a necessity we will cover only a small subset of these. The interested student is directed towards the Java documentation for details of other useful components that have more specialised applications than the general tools we cover in this book.
In the last few sections we have looked only at how we can provide a user interface that provided an extremely limited set of interactions for the user. We haven't looked at any way of providing feedback to the user except through the console line System.out.println method. From now on, we are going to look at the tools Java provides for handling user feedback - one of the more versatile ways of doing this is through the showMessageDialog method. Unfortunately this method is not provided as standard in Java... it belongs to one of the many classes provided as part of the Java class library. In this particular case, it belongs to a class called JOptionPane. We'll look at classes and objects later in the book. The JOptionPane class provides a range of methods for flashing up message boxes to the user - these can be in many formats from simple informational dialogs to confirmation dialogs to input dialogs. We will look at examples of each of these in this chapter. To start with however we are going to look at the most simple syntax for showing a message box in Java. Consider our first event driven applet from the last few sections - in the actionPerformed method for that applet we used System.out.println to print a line of text to the console. This was necessary from a code simplicity standpoint but ultimately undesirable. For one thing, applets are designed to be viewed through a webpage and most browsers do not have console output enabled as default. If our sole form of user output is to the console, then most users will never see what we have written. This is obviously not an ideal state of affairs, even if it does allow us to make nasty, passive-aggressive comments about the people who use our programs. Instead of printing the text to a console, we can use a message box to flash up the text as part of the GUI interface of our applet. The method we use to accomplish this is called showMessageDialog, and in its simplest form it takes two parameters. The first of these we will ignore - it relates to where we want the message box to be displayed and we're not really interested in that at the moment. We simply pass in the special keyword null for the first parameter. The second parameter is the string that is to be displayed in the message box. For this example, we'll simply display the text that we previously printed to the console. The line of code that will do this for us is as follows:
We put this line of code in place of the System.out.println call of the last applet. This is what we get for our actionPerformed method:
Now when we press the button, instead of it sending a line of text to the console, it instead flashes up a message box:
Obviously this is a much better way of presenting some output to the user - it uses the standard message box paradigm which is very familiar to any user of modern operating systems, and is in keeping with standard GUI principles. This is the simplest format for displaying a message box in Java. There are other options that allow for further configuration of the appearance of the dialog... these are implemented as overloaded showMessageDialog methods. We can pass a further two parameters into the showMessageDialog method. The third parameter is the title text to display along the top of the dialog. The fourth parameter is the kind of icon to display: The third parameter is simple - it's just a string value. The fourth parameter is a little more complex and requires the use of what are known as constant values. We've already seen these in our use of colours, but here we'll actually explain what they are. Constant values are used when we want to future-proof some code. Java is an evolving language and the internal representation of classes changes all the time. Provided the method signatures within a class do not change, this doesn't make a difference. For example, consider that you had a method in your applet called addTwo, which simply took an integer variable and returned the number you passed plus two:
If you have code elsewhere in your applet that makes use of this method, it doesn't really matter if someone comes along and rewrites the code as follows:
Provided the method takes the same parameters as before, and returns the same value, it doesn't matter how the code works within the method. The parameters you pass to a method and the value you get returned are your interface to the method - as long as these remain constant, then it doesn't matter how the internals of a method are modified. We'll look at this idea in more detail in later chapters when we talk about encapsulation. It is the same principle for classes - sometimes the internals of a class will change. Provided you are making use of the provided interfaces, then this shouldn't affect your code. Constant values are a way of ensuring that this is the case. For the fourth parameter to showMessageDialog, all it is expecting is an integer value. Perhaps in one version of Java the integer value 0 corresponds to the error icon:
This may very well work for one version of Java - but if a later version of Java interprets the number 0 as the information icon, then your code will not work as intended. Constant values are provided to compensate for this... instead of giving a discreet number we make reference to a variable within the JOptionPane class. This variable usually has a meaningful name indicating what it represents. Later, if someone changes the internals of the class it doesn't matter because the constant value will change accordingly and your code will automatically be up to date. We access the constant values of JOptionPane in the same way we access the methods themselves - by giving the name of the class, a dot, and the variable we wish to access.
For example, to display a message dialog with the message 'You pressed the button, you dirty little button presser' and the title 'Oh no!', with a warning icon:
This displays the following message box:
We can force strings to be broken up onto multiple lines within a message box by using the special control code \n within our string. This is the new line control code, and it indicates to Java that we wish to move start any text that follows it on a new line.:
When this dialog is displayed, the main message text is broken onto two lines:
Message boxes are a neat and effective way of presenting output to the user, but this is not the only functionality provided to us by the JOptionPane class. There are a number of other methods that allow us to provide dialogs in a similar vein but for more specialised purposes. We shall have cause to revisit JOptionPane in a few moments.
Before we spend more time looking at JOptionPane, we're going to take detour through a couple of the other components provided by the Swing GUI architecture. The first of these is the text field component - this is a dual purpose component suitable for both displaying information and receiving input. Its name in the Swing architecture is JTextField, and is placed on an applet is exactly the same way as other components:
So for a JTextField called myText:
The number between the brackets in the line of code that creates the JTextField object (myText = new JTextField (20)) represents the number of columns that the text field has - if you are setting your own bounds on the control this may not actually correspond to anything when your applet is created, but it will become important when we look at layout managers in a later chapter. A JTextField component triggers action events, just like a button - these events are triggered whenever the component has focus (as in, the user has typed something and the caret is still in the box) and the user hits return. Usually however JTextField components are passive - they exist to have data placed in them and only when some other event occurs are we interested in what is contained within. The JTextField class has two methods that are particularly useful. The first is setText, which takes a single string parameter. Calling this method will place the contents of the parameter into the text field:
The second method is getText, and this will return a string containing the current contents of the JTextField:
There is also a JTextArea component... this works exactly the same was as a JTextField except that when you create the variable, you need to pass a number for rows and columns. It looks and behaves like a JTextField - it's just bigger and multi-line.
The JTextArea component also provides an append method that takes a single string parameter and adds it to the end of what is currently contained in the text field:
Sometimes, we want the flexibility of string output via a text box without the user being able to modify the contents. Text components are convenient ways of displaying information about a running program, particularly if using message boxes would be unwieldy. We can make use of the setEditable method to restrict users - this takes a boolean parameter which indicates whether the user is to be allowed to change the contents the text component:
Java also provides a JLabel component that is essentially a container for text - it does not allow the user to alter the contents. This is a very humble control, but also arguably one of the most important. One of the hallmarks of a good user interface is that it is intuitive - the user should be able to tell what values go into which components. With text components by themselves, all you get is a component that text can be entered into. You can have labels to accompany text components to indicate what the contents should be:
Later in the book we will discuss how labels can be used to display graphics and icons, but for now we'll simply use them as containers for text. A JLabel needs one parameter when created - this is the text that is to be displayed on the label:
Although JLabels can trigger events, usually they do not - they are simply for displaying information. You can change the contents of a label at runtime using the setText method, just as with a text component:
Labels are very simple and provide nothing in the way of complicated functionality. They are presentation artefacts only, and can be put to good use throughout your applets to ensure that all components have a textual description of the data they should contain.
As mentioned before, JOptionPane contains a wealth of methods for providing message boxes to the user. We've looked at a simple message box, but there are others provided for us that actually prompt the user for some information. The first of these methods is showInputDialog - this displays a message box with an attached JTextField, within which the user can enter information. The second is showConfirmDialog, and this displays a message box complete with a set of buttons. The buttons that are to be displayed can be specified by the developer. The simplest format for showInputDialog is exactly the same as for showMessageDialog. The only difference is in what kind of message box is displayed:
This line of code will display the following message box:
Notice the attached text component... whatever the user types into here is returned from the showInputDialog method when the user hits the OK button. If we want to do something with this information, we need to store it as it is returned:
With this code, if the user has typed something into the text component and hit the OK button, what they typed will then be stored in the variable name. If the user hits cancel, the method returns the value null, which is a special Java keyword which means nothing. We can then do a check to see if something was actually typed:
Input boxes give us a quick and easy way of prompting for information from the user. Over-reliance on these however is bad event driven practice - event driven applications are largely passive, they react in response to events triggered by the user. If your application relies heavily on input boxes, it is perhaps a sign that you are not thinking in an event driven way. Traditional structured programming involves prompting for information and then processing that information - a reliance on input boxes harkens back to this older way of writing programs where the flow of control was with the computer rather than the user. Properly designing a graphical interface to allow for all relevant information to be entered by the user is a much more event-driven way to deal with user input. This is not to say that input boxes should never be used - far from it. There are many occasions when an input box may be preferable to other controls. It is merely a suggestion that you should seek to avoid becoming too dependant on them as a means of prompting information from the user. The third kind of message box we can display in Java is a confirm message box - a message box that carries with it a selection of buttons prompting the user to select between them:
We invoke this kind of message box with the showConfirmDialog method - the parameters to this are almost identical to those of the more complex showMessageDialog. The first parameter we pass as null, the second is a string containing the text to display as the message text, the third is the title, and the fourth is a constant value that indicates which buttons are to be displayed. This works the same way as selecting an icon in showMessageDialog, except that here we are picking between which set of buttons to display:
So to show the confirm dialog box above, we would use the following line of code:
As with an input box, we often want to get some information back from this particular kind of dialog, so we need to assign its return value to a variable. In this case it does not return a string, it returns an integer value that corresponds to one of the constant values defined in JOptionPane: int response; response = JOptionPane.showConfirmDialog (null, "Show me the money!", "Money showing", JOptionPane.YES_NO_OPTION); You can then compare this integer value to the constant values in JOptionPane to determine which of the buttons were pressed:
So, to tie all this up into an applet, let's look at an example of how we can deal with the question of the user wishing to show us the money:
We have a problem with our JTextArea component as it stands. Consider if we create one of a certain set of dimensions, and then fill it full of text:
When we reach the bottom of the JTextArea component, we run out of space! We can use the JScrollPane class to solve this problem. We create our JTextArea in the same way as usual, then create a JScrollPane that takes a JTextArea as a parameter to its constructor. Then, we add this JScrollPane object to the form in place of the JTextArea:
The result of this is that we get a JTextArea that automatically provides scrollbars as required. It looks just the same as a normal text area, except that once the component has been filled with text, it will automatically sprout scroll bars just for us
Okay, that's enough about message boxes and text components for now - let's have a look at a couple of the other components we have available when we want to build a graphical user interface. The first of these is the combo box, which will be familiar to anyone who has used a computer for any period of time:
The combo box is a compact way of representing a list of choices to a user. The class we use to create this kind of component is called JComboBox. We place this on the applet in the standard way: JComboBox myCombo;public void init() {setLayout (null);myCombo = new JComboBox();add (myCombo);myCombo.setBounds (10, 10, 100, 20);} The combo box starts off empty - there are no options to select from. We can add options using the addItem method of the combo box. Again, by default we do this in the init method of our applet (although we can do it anywhere):
We can get information on the currently selected item in the combo box using the getSelectedText method - this returns the string representing the text currently selected in the combo box. The actionPerformed in the following applet gives a demonstration of this idea in practice:
If the user has selected Bing in the combo box, this will show the message 'You selected Bing.". If they selected Bong it will show 'You selected Bong'. There is a small problem here, in that what comes out of the getSelectedItem method isn't a string... it's actually an Object. We'll see this problem again in chapter seven when we talk about ArrayLists. We don't see the issue in this example, because Java automatically changes this object into a String when it tries to add it to another string. If we want to store the getSelectedItem return value for later use, we need to use a slightly unusual syntax:
We'll talk about this more in a later chapter. The next control we are going to look at is the check box control - this is a little box on the applet that can either be selected (ticked) or not. The component for this is called JCheckBox, and is placed on the form in the standard way. The JCheckBox constructor method takes a single string as a parameter - this is the caption that is to be displayed alongside the checkbox. There is a small problem here, in that what comes out of the getSelectedItem method isn't a string... it's actually an Object. We'll see this problem again in chapter seven when we talk about ArrayLists. We don't see the issue in this example, because Java automatically changes this object into a String when it tries to add it to another string. If we want to store the getSelectedItem return value for later use, we need to use a slightly unusual syntax:
We'll talk about this more in a later chapter. The next control we are going to look at is the check box control - this is a little box on the applet that can either be selected (ticked) or not. The component for this is called JCheckBox, and is placed on the form in the standard way. The JCheckBox constructor method takes a single string as a parameter - this is the caption that is to be displayed alongside the checkbox.
The checkbox component has two methods that are useful to us - isSelected, which returns true if the checkbox is currently ticked and false if it is not, and setSelected, which takes a single boolean parameter and sets the state of the checkbox accordingly. For example, to reset the state of our checkbox so that it is not ticked:
Note that you only need to make use of the setSelected method when you want to force the state of a checkbox to be something - you don't need to manually set the checkbox as ticked when a user clicks on it, Java handles that part for you.
Both the check box and combo box components trigger a new kind of event - an item event. This event is triggered when the state of the control is changed:
We register our interest in these events using the addItemListener method on the components - this works exactly the same way as an addActionListener or an addAdjustmentListener. The handler method called whenever an event is triggered is called itemStateChanged, and works exactly the same way as actionPerformed or adjustmentValueChanged:
In order for an applet to be used as an item listener, it must implement the ItemListener class in its definition - again this is directly comparable to implementing ActionListener or AdjustmentListener. There's a sticky little wicket when dealing with itemStateChanged events - it actually gets triggered twice! Once for when the current item is deselected, and once when the new item is selected. We usually don't want the method to be called twice. The ItemEvent object contains a useful method called getStateChange which we can compare against some constants in the ItemEvent class:
Or:
We can use these to ensure that our itemStateChanged method will only execute the appropriate code at the appropriate time:
By default, a radio button belongs to a group of one - itself. Java treats these as three different groups of radio buttons, and each group allows only one to be selected. This is no good to us - what we want is to be able to setup all three of these as belonging to a single group. We do this through the use of the ButtonGroup class...we create a new instance of this class, and then add each of the buttons we want to be part of the group. We do this through the use of the add method of ButtonGroup:
Note that this is a logical grouping, not a physical grouping - we still need to call setBounds on each of the buttons, and we still need to add each of them to the container. The only thing that the ButtonGroup does is setup a relationship between them. Once we have the ButtonGroup configured properly, then we will only be able to select one of the radio buttons within the group. If we need a second group of buttons, then we create each of those, create a separate ButtonGroup and add to it each of the second group of radio buttons. It's as easy as pie:
Radio buttons are very similar to check boxes in their functionality - you put them on a screen, and you call getSelected to query their state. However, they differ somewhat in the way they are setup. Check boxes are complementary - the user should be able to tick as many of them as they like. Radio buttons on the other hand are exclusive - only one in any given group should be selected at any one time. The Swing class used to setup a radio button is called JRadioButton, and we go through the standard process to set one up. Consider this applet which sets up three of them:
When we run this applet, we should only be able to select one of these. Alas, it is not to be so:
As with check boxes, radio buttons will trigger item events... we can register an itemListener on them in the usual way. Once again, this is done on the individual radio buttons, not on the button group.
So, what happens if we have an applet that has a button that we want to trigger events, and a combo box that we want to trigger events - these require entirely different implements statements in the class definition. Does Java force us to listen for only one kind of event within a particular applet? No, Java lets us listen for as many different kind of events as we'd like to - the only restriction is that we must implement the correct listener structure for each family of events in which we are interested. We can separate these out with a comma in the class definition:
There is no restriction on how many kinds of listener that can be implemented by a particular applet. The only restriction is that if you place a particular kind of listener in the class definition then you must have the right method(s) in the body of your applet:
We will be looking at other listener events throughout the book - some of these are more complex than the ones we have looked at so far and require more than one method to be present in the applet before they can be used.
One other thing we haven't looked at is how to tell the difference between events of the same type that come from different components. Consider a simple applet where there are two buttons, each with a registered action listener. You press the first button, it calls the method actionPerformed, and executes all the code within. You press the second button, it calls the method actionPerformed and executes all the code within. Hrm - I see a problem! Very rarely do we want to perform exactly the same code when different buttons are pressed - usually we have some separate functionality that should be executed depending on which button was clicked. We can do this in Java - the answer to this problem lies in the parameter that gets passed to all event handler methods. All of these parameters share some common functionality which is modelled through Java's object oriented hierarchy (more on this later), but one critical piece of functionality they all share is that each of them has a method called getSource. This method returns the object reference of the component that triggered the event. We can check the returned value of getSource against each of the relevant components to determine which of them triggered the event. Consider the following applet:
As you have seen, there is a very similar routine we go through to set up a component on our applet. There is also a similarity that extends through all swing components in terms of the methods they share - the methods we discuss in this section of the chapter are common to all swing components and thus can be used on all. The first of these is the setToolTipText method - we use this to add some context sensitive help to a control. Whenever the user hovers their pointer over a component for a few seconds, a little banner will appear containing the text you passed as a parameter to this method:
You can use this to ensure that the user can receive help if they have trouble determining what a particular component is supposed to do. Tooltips can be setup to use HTML in the way we discussed during the last chapter. In this way we can provide sophisticated formatting for large, detailed descriptions. As usual, we should use this sparingly - overuse will only make your interfaces garish. It is a good design principle to make use of tooltips where appropriate - of course, we shouldn't go overboard. If we have a TextField with a corresponding label that says 'enter name', we gain nothing by adding a tooltip that says 'The user should enter their name here'. Another commanality is that all text-based swing components allow us to set the font we wish to be used for display using the setFont method. In order to do this, we must first actually create a Font variable:
Then put something into it:
The first parameter here is the font to use (if it doesn't exist on the deployment system then Java will substitute a suitable font from the existing selection). The second parameter is the style to give the font - this is a constant value contained within Font. The third parameter is the size at which the text should be set. Once we have this font object, we pass it to the setFont method to change the way our component displays text:
The final method we will look at in this section is the setVisible method - this takes a boolean parameter and makes a component invisible if it is set to false and visible if it is set to true. If it is set to invisible, the component will still exist but the user will have no way of interacting with it:
The following example applet shows these methods in use:
Java provides us with a wide range of components that are suitable for utilisation in constructing effective user applications. We haven't looked at many of them, but this chapter has given a taster of just what Java is capable of in terms of graphical user interfaces. The basic tools covered in this chapter should provide you with enough components to complete almost all of the interfaces required throughout the module, although we will be returning to the subject of user interface tools in a later chapter.
Now that we've covered the basics of Java, and looked at some neat event-driven stuff, we can start to develop much more complex projects. Consider these as larger exercises that you can attempt to explore the concepts discussed. More of these will be introduced at the end of each integrative chapter. Shonky Computers Inc'Shonky Computers Inc' are developing an application that allows interested users to design their ideal computer system. Users should be able to choose from a range of processors, peripherals and bundled software. Upon designing their dream computer, the user should be able to press a button that summarises their choice and asks them to confirm. If they confirm that the details are correct, the application should display another message box thanking them for their custom. The following options should be supported: CPU (One of) - 1800 Athlon XP, 2000 Athlon XP, 2400 Athlon XP, 3000 Athlon XP, Celeron 1800, Celeron 2400, Pentium IV 1.4 GHz, Pentium 1.66 Ghz, Pentium IV 2.20 GHz. Memory (One of) - 256 MB, 512 MB, 1024 MB, 2048 MB Hard Drive (One of) - 40 GB, 60 GB, 80 GB, 120 GB, 160 GB Software (One of) - None, Microsoft Windows 2000, Microsoft Windows XP home, Microsoft Windows XP professional, Red-hat Linux 9 Graphics (One of) - 32 MB AGP, 64 MB AGP, 128 MB AGP Monitor (One of) - None, 15" CRT, 15" LCD, 17" CRT, 17" LCD, 19" CRT, 21" CRT Peripherals (Any number of) - CD drive, DVD-ROM, CD-rewriter, DVD-RAM, Inkjet printer, scanner, smartcard reader, network card, TV-Out card, Fire-wire card. Exactly which controls you use to design your user interface is up to you. Once again, it is a good idea to storyboard your design before you start. There is a lot of choice for the user in how their system will be configured - there is a danger that the user may be overwhelmed if the GUI isn't clearly laid out. The options vary from those that are mutually exclusive (you can't have both an Athlon XP and a P4, for example) and those that are complementary (you can have a CD-writer and a DVD-Rom). There is no one control that will be suitable for all options, but there may be a selection of different controls that will fit the requirements for any one section. Remember too that when the user clicks the button, the message box should summarise their choices... all of the user's choices should be built into the text of the message box, and then they should be asked if this is the system they wish to order. Creepy Thought-StealersThe 'Creepy Thought-stealers Questionnaire Corporation' have decided to go All Digital and make their next questionnaire available as an internet applet. The plan is to deploy this on a publicly available web-server accessible by the general public... this single applet will need to allow multiple users to enter their information, and then keep a track of information of interest. This particular set of questions relates to the user's interest in subscriptions to particular magazines, as well as general comments involving what most interests them about the magazines on offer. The questionnaire is intended to be anonymous, so it will not ask for the user's name... instead it will prompt the user for the following information:
It is your task to provide the online questionnaire. Once someone has submitted all of this information, your applet should record the relevant details and then clear all the GUI widgets ready for someone else to enter their information. Likewise, you should provide a button that lets someone reset the applet back to its empty state without recording their information. It should record all of this information in a suitable data structure and output the following statistical information:
Further ReadingThe following table details further reading on the topic in this chapter, and also any external resources that you may find useful.
|
| Previous | Table of Contents | Next |
© 2004-2006 Michael James Heron