COSC 2946 --- 05SF --- J. Rajnovich Lecture #9: Monday, May 30, 2005 Topic: Java GUI Programming (1 of 2) 0) Questions? 1) Course Evaluation 2) Hand Back and Review Test #3 3) Preparing for Test #4 4) Basic Video Graphics Concepts 5) Images, Frames, Panels and Panes 6) Asynchronous (Threaded) Execution 7) Graphics User Interface Programming (1) Course Evaluation Thank you for your participation in the course evaluation. (2) Review of Test #3 (3) Preparing for Test #4 30-minute test Wednesday, June 1, 9:15-9:45 p.m. Closed book. Based on all material presented in lectures one through eight, with an emphasis on lecture eight. In your test review, focus on: All definitions presented in lectures. Issues related to computer graphics in Java. Basic Java programming for elementary graphics application programs. Expect questions formats from the following types: Reciting definitions Comparing (show how same) and contrasting (show how different) pairs of concepts Drawing and labelling diagrams to help illustrate course concepts Writing short paragraphs to explain concepts. Writing very short Java programs or fragments. (4) Basic Video Graphics Concepts Pixels Pixel = picture element, the smallest visible unit which can be manipulated by a graphics processor. A pixel appears to the viewer as a single dot. Each pixel has an assigned location in a 2D grid, with (for Java graphics) the upper left corner of the screen (or of individual window panels) being coordinated (x=0,y=0). The x-coordinate increases to the left, the y-coordinate increases downward. Tristimulus Colour Theory In tristimulus colour theory, visible colours are compositions of three primary colours, in this case red, green and blue. (There is a comparable CMY (cyan, magenta, yellow) ink scheme used by printers.) Each pixel is assigned some colour and intensity value according to a 32-bit RGBA schema. Eight bits are associated with the „R‟ component to represent the intensity of the pure colour red, with 255 being full intensity and 0 being none at all. (The Java programmer may choose to specify the intensity with floating point values in the range 1.0 to 0.0, but the video processor hardware still represents this in eight bits.) Similarly eights bits each are associated with the „G‟ component for green and the „B‟ component for blue. Combinations of these three values are capable of producing any one of more than 16 million colours visible to the human eye. (As an aside, the human eye is capable of greater colour discrimination than this. Amazingly, the tristimulus system does not in fact cover the full gamut of what the human eye can perceive!) The remaining eight bits, associated with the „A‟ component, often called the “Alpha channel”, determine the extent to which a given pixel is transparent. Alpha channel values range from 0 (fully transparent) to 255 (fully opaque). Control over alpha channel values permits overlaid images to affect each other in various ways. As one example, this permits the graphics programmer to create a visible (glass) window through which the viewer can see objects on the other side. (5) Images, Frames, Panels and Panes In the last lecture we learned how to create an application frame and install a panel in it. We then learned the basics of 2D computer graphics, drawing primitive figures on the panel in various colours and coordinate locations. Today we start by learning how to fetch an already made image from a file and draw it on an application panel. Our first demonstration application, “ImageTest.java”, is a simple extension of the test driver for the graphics demos from last class. The driver is responsible for instantiating a frame object and a panel object for this application, adding the panel to the frame and ensuring that the resulting window and its contents will be visible. The important difference today is the call to a JPanel constructor in which we pass the name of the file which contains the image we wish to draw. Images Images can come from any source (eg. hand drawn, computer generated, scanned or digitally photographed) so long as it is saved in a “.gif”, “.jpg” or “.png” file format. The overloaded constructor can take either a String object representing the path/file name or a URL object representing its general internet location. As we learned from last class, the real work of the application is handled in an event- driven fashion by the paintComponent() method of the JPanel object. We now take a look at the file “ImagePanel.java” in which we implement a subclass of JPanel. Panels After making the obligatory call to the parent class constructor, our constructor calls the method java.awt.Graphics.createImage()(or the method java.awt.Graphics.getImage()) to fetch the image from the disk file and display it on the current panel. (See the notes in the file itself for comments on why Sun recommends we use the latter rather than the former.) In OOP fashion, all the file handling details are hidden from the calling program. Just pass the disk file name or url and “wait” for the results. (The “wait” part is the most important part of today‟s lecture! But we have to see the application in action before we can appreciate how important and how subtle all this is. So, we defer discussion of it until after discussing the actual drawing of the image.) The work horse of this class is the overridden method paintComponent(). There are six overloaded versions of this method in java.awt.Graphics. Here we examine the behaviour of only two of these. The call to g.drawImage(image, 0, 0, this)paints the image data in its original proportions on the available panel space. As we can see from the constructor, the panel has been instantiated with preferred dimensions of 20 pixels wide and 150 pixels height. It happens that the image I have chosen is a digital photograph 3008 pixels wide by 2000 pixels high, far too large to fit the computer screen let alone the panel. The alternative call to g.drawImage(image, 0, 0, getWidth(), getHeight(), this) scales the image as needed to fit the available panel space (200 x 150) and places it in the upper left corner of that space. This gives more satisfactory results, since we may now see the whole picture. In addition, event-driven calls to paintComponent() now ensure that if the application window is resized the picture will be rescaled to fit it. (Be sure you understand why resizing the window also means that the panel is resized.) Observe, however, that if the window/pane is resized in such a way that its aspect ratio (width/height) no longer matches the aspect ratio of the original image (3008/2000 or 3/2) then the automatic scaling of the image will distort the picture. (It is left as an exercise for you to find out how the Java graphics programmer might deal with this.) Finally, we need to understand why the reference to the calling JPanel object is passed as the „this‟ parameter to this version of the method. Asynchronous Execution It is crucial to understand that the call to java.awt.Graphics.createImage()initiates operating system calls involving file input and system buffering which may take quite long to complete. The method is designed to execute in an asynchronous fashion, meaning that it executes as a separate thread (= an independent process). This permits the rest of the program to continue execution. In time the program will be informed when the asynchronous thread has completed its execution. In most cases the constructor will have terminated and the program continued its normal execution long before the image is fully loaded. We can actually see this when we run the demonstration program. Watch carefully. You will see the frame first appear along with a blank panel. It will take a measurable number of seconds before the image appears. In those first few seconds paintComponent() is called numerous times with no image to display. Eventually the image is fully loaded and ready for display. How does paintComponent() get informed? The answer is that the JPanel object „this‟ has been registered as an ImageObserver object by having its reference passed in the fifth parameter position. (ImageObserver is in the inheritance hierarchy of all Container objects such as JFrame and JPanel.) This means that when createImage() has completed its execution thread, it signals the registered observer(s) that the image is now ready. At this point paintComponent() can sample the image to find its properties such as width and height. Be clear that the calls to getWidth() and getHeight() in the parameter list of paintComponent() are implicitly calls to this.getWidth() and this.getHeight(). These calls inform paintComponent() how big the available panel space is. The paint method uses these to scale the image to fit. But the information as to how big the image itself is is not known until a signal is sent asynchronously to the observer object, the panel itself. In this test driver we verify this by sampling the return values for image.getWidth() and image.getHeight() first at the time of the constructor call and later on each event-driven call to paintComponent(). These methods return -1 as a way of indicating that the image is not yet fully loaded, so its dimensions are not yet known. Eventually we learn that the image is 3008 x 2000 pixels. (6) Asynchronous (Threaded) Execution A thread is any program unit that is executed independently of other parts of the program. It is not possible to understand Java graphics, GUI programming or any other form of event-driven programming without understanding the concept of threaded execution. We‟ll look closely at this topic in future lectures. Each panel object, for instance, is an independent thread. As we shall see, an application may have multiple panels, each with its own paintComponent() method. Each of these threads executes concurrently (i.e. in parallel, or, at the same time), responding to whatever event-driven messages are received specific to it. Concurrent processes which execute strictly independently of each other are said to be asynchronous; i.e. their action steps do not need to be timed or synchronized. As we shall see in future lectures, it often necessary for threads to cooperate with each, timing their execution steps so as not to interfere with each other. Such threads are explicitly programmed to be synchronous. Viewing Multiple Images We now know the minimum Java programming to fetch an image from disk and display it for the user, scaled to fit a panel of any desired size. How might we display multiple images at the same time? There are several approaches. One is to create multiple frames, each with its own panel object to contain an image. Another is to have one frame containing multiple panels, each with its one image. Multiple Frames The test driver file „MultiFrameTest.java‟ shows us how an application can generate separate frames for each image. Here we create two frames, give each one its own panel and request a different image for each one. Observe that in this driver we provide a String argument to each JFrame constructor to give a title to each frame. In addition, we set the default close operation for the first frame to be HIDE_ON_CLOSE instead of EXIT_ON_CLOSE. This is just for demonstration purposes. You may experiment with all four of the options if you like. DO_NOTHING_ON_CLOSE DISPOSE_ON_CLOSE HIDE_ON_CLOSE EXIT_ON_CLOSE See javax.swing.JFrame.setDefaultCloseOperation() for the details. Be aware that the option EXIT_ON_CLOSE is to be used only for applications, not for applets. We won‟t spend further time on multiple frame applications, except to point out that numerous interesting possibilities exist. Keep in mind that each frame and its contents can execute as independent threads. This means that interesting things can be implemented involving concurrent activities, including both synchronous and asynchronous behaviour. Further, observe that this test driver generates the second frame on top of the first one. The user has to manually separate them before the first one is even visible. It is possible to have the application place the frames wherever you might like on the user‟s screen. See Darwin‟s Recipe 14.11 for details (and be aware that some GUI platforms prefer you do not take this kind of control of desktop layout). Single Frame with Multiple Panels Assignment #4 requires that you use a single frame containing not only multiple images, but additional GUI objects such a buttons. Test driver file “MultiImageTest.java” demonstrates a way to contain multiple GUI components in one frame. In this case we use only panel objects. But the stage is then set for full GUI (multi-component) applications. The basic idea is that every JFrame object comes with two default containers called panes, into which we may place any GUI components, includes panels. The most commonly used pane is called the “content” pane. (The other, called the “glass” pane, is used to do temporary painting of things over the main pane.) To create a pane we simply call the frame‟s setContentPane() method. The method returns a reference to a generic Container class object. Container c = frame.getContentPane() ; As soon as we plan to have more than one visible object in a GUI container object, we have to decide how they should be located in relation to each other. AWT provides five predefined layout manager classes to help us with this crucial GUI task. 1) BorderLayout 2) CondLayout 3) FlowLayout 4) GridBagLayout 5) GridLayout Here we‟ll use the simplest of these, FlowLayout. c.setLayout( new FlowLayout() ) ; We then create two panel objects add them to the pane. c.add( panel_1 ) ; c.add( panel_2 ) ; Observe that in all these test driver programs, we are using the same panel class definition. Notice that if we maximize the application window the two panel objects/images are not automatically resized to fill the new window space. Our frame object resizes its content pane object, but the content pane is not synonymous with its enclosed two panels. If we want one or both of the panels to resize we now have to include explicit instructions to support this. This will require an understanding of events, event sources, event objects, event handlers and event listeners. This topic will engage us for the remainder of this course. We now have the foundation concepts necessary to begin creating a GUI application with a single frame and as many GUI components as we like. We‟ll focus our investigation in the next lecture on the kinds of GUI objects which might be useful for Assignment #5. (6) GUI Programming Issues Among the first useful GUI components we should add to our application are labels and buttons. Adding labels and buttons to a container is very simple in Java. Not so simple is understanding how to get GUI objects such as buttons to do something useful in an event-driven environment. We‟ll need to understand various “listener” classes, a topic which we will pursue in the next lecture. Another challenging task is learning how to arrange the various components of our GUI application in an attractive and useful layout from the user‟s perspective. How do we want our application to look to the user? We need to think first about design issues, including ergonomics (the study of how to design work environments which increase human operator effectiveness). Then we have to find out how to exploit the layout manager classes provided by Java to get the components arranged in a pleasing and effective way.