|CPSC 329||Software Development||Fall 2017|
The goal of this lab is to make sure that everyone is comfortable with the basics of GUI programming in Java using Swing, and to provide some direction for extending that knowledge into more advanced aspects of working with Swing. The focus is on creating a GUI by hand rather than using a GUI builder tool in order to better understand what is going on in the program and how the different elements work.
Successful completion of this lab means that you:
Work in pairs (and one group of three) to complete this lab. Only one person needs to carry out the steps in the lab but everyone in the group should make sure they understand what is going on. There is no limit on collaboration with others, but you need to make sure that you understand for yourself how (and why) to do things.
One handin per group.
For full credit, you should complete the calculator application covered in the "Try It" section of "Creating a GUI with Swing" and should explore something from the "More Advanced Elements" section.
To hand in the lab, create a handin tag as directed. Make sure that you also have a readme.txt file (as described in "Setup" below) and that you've described what you did/investigated for this lab.
Create a project for this lab:
Create a new Eclipse project called lab6.
Import Calculator.java from /classes/cs329/lab6 into the project. Make sure it ends up under the src directory in the project.
Create a readme.txt file in the top level of your project (not in the src folder). Include the names of your group in this file. Also briefly describe what you did/investigated for this lab.
Share your project into the repository. (Name the folder lab6.) Remember to choose the correct repository layout, and to check the structure in the repository after sharing.
There are two main official sources of information: the Swing Tutorial and the Java API. The tutorial is a better place to start for an introduction to a particular type of component or layout manager; use the API once you have a basic familiarity with a thing and need more specifics about its methods.
You can Google "java 8 classname" to access the API for a given class (replace "classname" with the actual class name) and "using swing components" or "using layout managers" for the relevant section of the Swing tutorial. (If you are looking for a particular thing within the tutorial, you can add that to your search e.g. "using swing components jtextfield" or "using layout managers gridlayout". Note that the desired link may not be the first one in the search results, though it will usually be close to the top - look for a page title involving "The Java Tutorials" and/or a URL involving docs.oracle.com for the official Java reference.)
GUI programs (of any sort, not just Java programs) are typically event-driven. In a traditional console-based imperative programming paradigm, execution of the program starts in main and can be traced all the way to the end of the program just by reading through the statements in order. By contrast, event-driven programs contain a main loop that listens for events and a collection of event handlers that are triggered when an event occurs. Each event handler does some small task which is quickly done.
In Java, the main loop is handled by the system so you only need to worry about the initial setup of the GUI and the event handlers.
Note: the "small task which is quickly done" aspect of event handlers is very important - events are handled one at a time in order, so while one event handler is running, the program does not respond to any other events (or repaint requests). This has important consequences for the design of the methods in a GUI program - you do not want a playGame method that carries out the entire sequence of player turns or a runSimulation method which contains a loop to carry out many steps of the simulation, for example. Instead, the program's execution is divided into chunks based on user interactions - the event handler for the "start" button handles setting up the game but only goes as far as the next user interaction, not through the entire game. In the latter case, where there is a long computation that takes place without user interaction, the task needs to be backgrounded. Timers can be used in the case of a simulation where there is a short task (one step) that happens periodically. Threads are needed in other situations. (More on threads later in the course.)
With Swing, everything you see on the screen (windows, text, images, buttons, menus, etc) is represented by an object in the program. The Swing tutorial used to include a nice visual guide to all of the components; it has disappeared from the official tutorial but you can still find older versions of it if you Google "visual guide to swing components". (The tutorial now contains a list of the various kinds of components, which you can find by searching "swing tutorial how to use various components".)
Containers are special kinds of components that can contain other components. Top-level containers (such as JFrame) do not need to be contained - they can just be created and used.
The position and size of the components within a container can be specified manually, but is more commonly determined by a layout manager. Using layout managers simplifies the programmer's task and also allows the window to be resized gracefully. The Swing tutorial does still provide a visual guide to layout managers (Google "swing tutorial visual guide to layout managers") to aid in selecting a layout manager. Complex layouts can be created from just a few of the simpler layout managers (BorderLayout, BoxLayout, and GridLayout) by nesting containers (typically JPanels).
There are six basic steps for creating a GUI in Swing:
Swing components come from the javax.swing package. Some elements from the older AWT are also still used, so you will generally also need the java.awt package. You can let Eclipse prompt you for the imports as needed but you should be aware of the correct package in case you need to choose between several suggested choices.
SimpleGUIDemo provides an example. It also demonstrates nesting components to achieve a more complex layout - the buttons are contained in a panel and laid out in a row using BoxLayout, then the window itself is laid out with a BorderLayout (buttons at the top, CirclePanel in the center).
GUI programs actually do things in response to events - these are typically user-generated (mouse movement, mouse clicks, key presses, and other manipulations of GUI controls) but may also be generated by the system (such as from timers). Event handlers contain the code to handle the event i.e. to do whatever the program should do to respond to an event.
Event handling adds two more steps to the six steps already given above:
Event-related elements (event classes and event listener interfaces) come from the java.awt.event package.
SimpleGUIDemo2 adds event listener registration to the example in the previous section; ColorChanger contains the event listener for the buttons.
The examples above show one way to organize the setup and event handler code - the setup code is in main and event listeners are in separate classes - but that is not the only (or even necessarily the best) way.
An alternative to putting the setup code in main (like in the example) is to create a subclass of JFrame and to put the setup code in the constructor of the subclass. main then just creates an instance of that subclass.
While a little more complex (there's another class), this organization is preferred because it allows for greater flexibility and reuse - a program can easily have two or more circle windows by creating more instances of the frame subclass, and another program can easily use the frame subclass without having to cut and paste code.
In keeping with single purpose and the potential for reuse, there should be only one behavior per event handler method. This means that you shouldn't generally have an if statement that checks which component generated the event in order to determine what to do - create separate classes (inner or public) for each case, and register the appropriate listener object with each component. (If the difference is the value rather than the behavior itself - such as in the example above where ColorChanger's constructor takes the color to use - parameterize one class so the value can be configured instead of having separate classes with different hardcoded values.)
There are several choices for where to put event listener code:
In separate public (or package) classes. (as in the example)
In separate inner classes within a component class.
Directly in the component class itself. (the component class implements the appropriate listener interface)
The first two options are generally preferable - while they are a little more complex (more classes), they do a better job of separating components and layout from event handling. The first option (separate public/package classes) is the best solution if the event handler could be used in other situations, as it separates how events are handled from the particular components and how they are arranged. If, however, the event handlers are highly specific and won't be reused (either within the current program or elsewhere), the second option (inner classes) better encapsulates the GUI elements.
The third option is OK in simple cases but is not modular or single purpose, is not reusable, and isn't suitable if there are different behaviors for the same kind of event because there can only be one method with a given name in a class and thus there will need to be an if statement to determine which component generated the event so it can be handled correctly.
Your goal is to implement a basic calculator program. The calculator functionality is already provided for you in the Calculator class that you imported; you'll just be adding the GUI.
To see a demo of how the program should operate, run
java -jar /classes/cs329/lab6/guicalc.jar
To input a number, click on the appropriate digit buttons or type it directly into the input box (the second text field). When you click an operator button, the corresponding calculator method is invoked using the value in the input box and the calculator display (the first text field) is updated to show the result of the calculation.
The "Z" button invokes the calculator's zero method. On the "File" menu, "Clear", "Store", and "Load" each invoke the corresponding calculator method. "Store" prompts the user for the name to use; "Load" lets the user select the name from a list of those which have been defined. "Quit" quits the program.
Note that the operation of this calculator is a little different from most calculators. This is due in part to how Calculator's operator methods are defined (each only takes one parameter because the other operand is the calculator's current value) and in part to an attempt to keep the implementation of the calculator interface as simple as possible. To use the calculator successfully, it is necessary to keep two things in mind:
Thus, for example, computing 8*3+2 means that the calculation is really 0+8*3+2 and is entered as Z 8 + 3 * 2 +.
Create a class CalcFrame which extends JFrame and has the following elements:
super("calculator");as the first line and which initializes the instance variable with a new instance of the appropriate type. (The super line invokes the JFrame constructor and specifies that "calculator" should be the title of the window.)
Create a main program in a separate class named CalcMain. The main program should have just one line: create an instance of CalcFrame.
Add to CalcFrame's constructor: Create and arrange all of the components in the calculator window. Also do the final window setup steps (involving sizing, setting the default close operation, and making it visible). But don't worry about event handling yet! (Just get the components created and arranged.) Note that since CalcFrame extends JFrame, you do not need to create a JFrame object. Instead, this is already a JFrame object and you can invoke the methods you need that way. (Or omit this and call them directly e.g. setContentPane(...).)
Implementation notes: The calculator buttons are all JButtons. Use a GridLayout to arrange them. (Be sure to include small horizontal and vertical gaps so there is an attractive spacing between the buttons.) The two display boxes are JTextFields. Make the top one (the calculator display) non-editable, and set the horizontal alignment for both so that the components' text appears right-justified instead of left-justified. Use a BoxLayout for the top-level arrangement of components; use vertical struts to put a little space between components.
Create an inner class CalcDigitListener inside CalcFrame which implements ActionListener. This will be the event listener for clicks on digit buttons (including "."). Conveniently, the button label is exactly the text that should be added to the input box when a digit button is clicked, so you should retrieve the action command from the event and append that text to what is currently displayed in the input box.
Note that inner classes can directly access instance variables of the class they belong to, but they can't access local variables belonging to some other method. You may need to make instance variables in CalcFrame for some of the GUI components so you can access them here.
Add to CalcFrame's constructor: Register a CalcDigitListener for each of the digit buttons (and ".").
Create an inner class CalcOpListener inside CalcFrame which implements ActionListener. This will be the event listener for clicks on the operator buttons +, -, *, /. Retrieve the action command from the event to determine which operator was clicked, retrieve the text from the input box and parse it as a double to get a number, and then invoke the appropriate calculator method. Also clear the input box and update the calculator display to show the result of the computation.
Add to CalcFrame's constructor: Register a CalcOpListener for each of the operator buttons.
Create an inner class CalcCmdListener inside CalcFrame which implements ActionListener. This will be the event listener for the other calculator operations like zero, clear, store, load, and quit. Retrieve the action command from the event to determine which operation is desired, then carry out the appropriate action:
Z: zero the calculator, clear the input box, and update the calculator display to show the calculator's new current value.
Clear: clear the calculator, clear the input box, and update the calculator display to show the calculator's new current value.
Store: invoke the calculator's store method to store the current value with the name "x".
Load: invoke the calculator's load method to load the variable with the name "x" and update the calculator display to show the calculator's new current value.
Quit: exit the program.
Add to CalcFrame's constructor: Register a CalcCmdListener for the "Z", "Clear", "Store", "Load", and "Quit" buttons.
Make sure you commit when you are done.
Swing provides many types of components, including buttons, checkboxes, radio buttons, lists, combo boxes, text fields and editors, sliders, spinners, menus,tool bars, progress bars, scroll panes, color choosers, file choosers, and dialog boxes. Browse the list of available components (search for "using swing components") and explore how to use one or more of them.
Many components generate various kinds of events that can be handled. Google "listeners supported by swing components" for an overview. Writing listeners for other kinds of events is similar to writing ActionListeners - the difference that you are implementing a different interface with a different name for the handler method. Look up "writing event listeners" in the Swing tutorial for more information.
If the standard GUI components aren't sufficient for your application, you'll need to create a custom component. Typically this is done by subclassing JPanel (a basic lightweight component), but it is possible to subclass other components.
The most common reason to need to create your own component is for painting.
This is done by subclassing the desired component (e.g. JPanel) and then extending its paintComponent(g) method to do the desired painting. CirclePanel provides an example.
The upper left corner of the component is always (0,0) regardless of where the component is on the screen.
Note: Be sure to call super.paintComponent(g) (substitute the actual name of the Graphics parameter for g as needed) as the first thing in the body of your paintComponent. Your paintComponent should add on to the parent component's painting behavior, not completely replace it.
Note: paintComponent is called by the system any time a component needs to be drawn (e.g. when a window is revealed or when requested by a call to repaint()). Never call it directly (only call repaint) and also don't put non-painting code there (because you don't fully controlled when paintComponent may be called).
Note: Since the size of the component is controlled by a layout manager, it is important to take into account the size of the component when positioning and sizing the elements being painted. For example, if you want a component to display a 20x20 grid of fish and sharks, compute the size of the grid cell based on the size of the component rather than always making a cell a fixed number of pixels on a side. (See CirclePanel for an example.) Be careful to distinguish between things which should have fixed sizes/positions and those which should be sized/positioned relative to the component.
You may also be interested in handling events on your custom component. What events can be generated depends on the component type that you extended; JPanel supports the event types that all Swing components support but does not have any special ones of its own. Look up "listeners supported by Swing components" in the Swing tutorial for more information on what those listeners are.
Swing provides several layout managers. (Look up "visual guide to layout managers" in the Swing tutorial for an overview.) You can create quite complex layouts with just a few of the simpler layout managers by nesting JPanels so you will likely not ever need any more than that.
Layout managers both position and size the components within a container. Look up "solving common layout problems" in the Swing tutorial for information about how to influence the size of a component.
If you have special layout needs that cannot be solved with the standard layout managers, it is possible to write your own. Look up "creating a custom layout manager" in the Swing tutorial for more information.
Doing without a layout manager and manually specifying sizes and positions of components is generally not recommended because it does not allow for resizing. However, it is sometimes appropriate and you can look up "absolute positioning" in the Swing tutorial for more information.
The Java 2D API provides support for all sorts of 2D graphics, including drawing geometric primitives and arbitrary shapes, changing fill and stroke patterns, and working with text and images. Google "java tutorial 2D graphics".
Locate the lesson on "Drag and Drop and Data Transfer" in the Swing tutorial. Try out drag and drop from components where it is already fully supported (such as dragging from a JList to another JList or into a JTextField). If you want to go farther, investigate how to do drag and drop for other components. (For this, considering a particular application - perhaps something useful for the project - would give you something concrete to focus on.)
"Look and feel" refers to the appearance and behavior of the various components in an application. Locate and explore the lesson "Modifying the Look and Feel" in the Swing tutorial - determine what look and feel options are available and try selecting one. If you want to go farther, can you find third-party look and feel implementations that you can download and try? Creating your own look and feel is also a possibility if you are feeling ambitious.
Make sure you've committed everything to the repository.
Complete the readme.txt writeup. Be sure to include the names of your group!
Hand in your lab by creating a tag called handin.