I l @ ve RuBoard |
If you have an existing AWT Java application you want to deploy under .NET, porting it is a sensible approach. But if you have an application built using Swing, you'll most likely want to redevelop the GUI using .NET classes, for three main reasons:
In this section, we'll continue with the theme of the text editor. Specifically , rather than using JDK constructs, we'll build an equivalent application using the .NET Framework. As you walk through the code, pay particular attention to areas where .NET development differs from traditional Java development. In particular, notice the object model implemented by Windows Forms and the way in which events are raised and handled. Creating the Windows FormTo create the Windows Form, you must first create a new solution in Visual Studio .NET: open the IDE, and click New Project. The New Project dialog box will appear. In the Project Types pane, select Visual J# Projects, and in the Templates pane, select Windows Application. Give the project a name (use Editor for this example), and then click OK. When the application is created, Visual Studio .NET will also create a default form called Form.jsl and a file containing assembly and attribute information called AssemblyInfo.jsl (which you can ignore for now). The Design View window, will display a GUI representation of Form1. To the left of the Design View window is the Toolbox (Figure 4-6), which contains controls that you can drag-and-drop onto the form. Figure 4-6. The Toolbox and Design View in Visual Studio .NET
Before you start adding controls to the form, look at the code behind Form1. To view the code, choose Code from the View menu. The code should look like that shown on the facing page. (You might need to expand some of the contracted code to see everything.) Form1.jslpackageEditor; importSystem.Drawing.*; importSystem.Collections.*; importSystem.ComponentModel.*; importSystem.Windows.Forms.*; importSystem.Data.*; /** *SummarydescriptionforForm1. */ publicclassForm1extendsSystem.Windows.Forms.Form { /** *Requireddesignervariable. */ privateSystem.ComponentModel.Containercomponents=null; publicForm1() { // //RequiredforWindowsFormDesignersupport // InitializeComponent(); // //TODO:AddanyconstructorcodeafterInitializeComponentcall // } /** *Cleanupanyresourcesbeingused. */ protectedvoidDispose(booleandisposing) { if(disposing) { if(components!=null) { components.Dispose(); } } super.Dispose(disposing); } #regionWindowsFormDesignergeneratedcode /** *RequiredmethodforDesignersupport-donotmodify *thecontentsofthismethodwiththecodeeditor. */ privatevoidInitializeComponent() { this.components=newSystem.ComponentModel.Container(); this.set_Size(newSystem.Drawing.Size(300,300)); this.set_Text("Form1"); } #endregion /** *Themainentrypointfortheapplication. */ /**@attributeSystem.STAThread()*/ publicstaticvoidmain(String[]args) { Application.Run(newForm1()); } } This is actually a complete working application. You can build and run it, and it will display an empty form! Your task as a developer is to add functionality to make it useful. Before we examine this code in detail, we need to change the filename and form name to something more meaningful. Note In a difference from the JDK, the name of a class or form in J# does not have to be the same as the filename in which the form or class is defined. Furthermore, a single J# (JSL) file can contain the definitions of multiple public classes. However, this means that if you change the name of a file to something meaningful, you should also change the names of all forms or classes in that file to something equally meaningful because it will not happen automatically! To change the filename, right-click on Form1.jsl in Solution Explorer (normally found on the right side of the Design View window). Choose Solution Explorer from the View menu if it is not displayed. Select Rename, and change the filename to Editor.jsl. To change the form name, switch back to the Design View window (choose Designer from the View menu), click anywhere on the form, and then change the value of the (Name) property to Editor in the Properties window. (The Properties window is normally displayed below Solution Explorer; you can display it if it is not visible by choosing Properties from the View menu.) You can change the caption of the form by setting the Text property to Editor as well. Returning to Code View, the program commences by declaring the package name to which this form belongs, and then it imports a number of namespaces that the application uses. Although Visual Studio imports all these namespaces, you might not necessarily use classes from all of the namespaces. For example, in this application you won't use any of the classes or interfaces that the System.Data namespace contains. Table 4-1 lists the imported namespaces and describes their contents. Table 4-1. Visual Studio .NET Default Imports for a Windows Form
After the import statements, the code defines the Editor class. This class represents a form ”that is, a window displayed in an application. A standalone form, the parent form of an application, and a dialog box (such as a file dialog box) are all examples of forms as defined by the .NET Framework. This class, like all other Windows Forms, extends the System.Windows.Forms.Form class in much the same way that an AWT frame extends java.awt.Frame or a Swing frame extends javax.swing.JFrame . The default constructor for this class simply calls the InitializeComponent method, which on the surface performs a similar role to the initComponents method in the AWT application shown earlier. One thing that might strike you about this method is that there is no reference to a layout manager. This is because both forms and individual controls are positioned and sized using coordinates, or absolute sizes. This means you can catch up on all that sleep you lost when you were working out how to fit all those Java components into a GridBag layout! We'll take another look at this method when you start adding controls to the form. The Dispose method frees any resources associated with the particular form. You do not need to call this method explicitly because it is called when you invoke the form's Close method. Finally, the main method executes the static Run method of the Application class to start the application. The Application class exposes methods and properties that you can use to control the current application. For a complete list of the Application class's members , see the .NET Framework Class Library documentation. The Run method has three overloads, all of which run an application in the current thread:
Adding Controls to the FormOnce you've defined a form, you can add controls to it. In the Design View window, you can drag controls from the Toolbox onto the form. You'll notice that most of the controls in the Toolbox offer similar functionality to many of the Java Swing components and have similar names. To start building the Editor application, drag a MainMenu component and drop it anywhere on the form. A MainMenu control called mainMenu1 will appear in the area below the form, and a menu bar will be added to the form. You can rename this component, and any other controls, by selecting it and changing the (Name) property in the Properties window. In this case, change the name of the mainMenu1 control to editorMenu . Creating menus , even complex ones, is a relatively simple procedure in Visual Studio .NET. Click where it says "Type Here" in the menu bar near the top of the form displayed in the Design View window, and type the text (the menu label , in AWT and Swing parlance) for the first menu item (File, in this case). The display will change to allow you to add this menu and additional menus, as shown in Figure 4-7. Figure 4-7. Creating a menu in Visual Studio .NET
As you type the name of the menu item, the item will be created and added to the main menu automatically (as you'll see shortly, when we examine the code that is generated). You can use the same technique to add the remaining menu items to create the menus for the application. Table 4-2 lists the menu items you should create for this application. You should notice that unlike the AWT or Swing, the Windows.Forms library does not differentiate between menus and menu items; a menu (such as File ”or Edit, in this example) is a menu item that happens to contain child menu items (such as Open, Save, Exit, and so on). Note that to insert a separator, you can either right-click at the place where you want to place the separator and then choose Insert Separator from the shortcut menu, or you can type a "-" sign. As you add the menus items, also change the Name property of each item as shown in Table 4-2. (You must click away from the new menu item and select it again for the properties to be displayed in the Properties window.) Table 4-2. Menus and Menu Items for the Editor Application
Note The menu built for the Editor application is very basic. If you look at the properties available for menu items, you'll see that you can add shortcuts for each menu item, and items can be rendered with check boxes to indicate on/off values. Menu items can also be disabled and hidden. You can add code to the application that selectively enables or disables, or shows or hides, menu items by dynamically modifying these properties. If you look at the variables added to the Editor class and InializeComponent method for the form in the Code View window, you'll see the code that initializes the menu items you've just added to the form: publicclassEditorextendsSystem.Windows.Forms.Form { privateSystem.Windows.Forms.MainMenueditorMenu; privateSystem.Windows.Forms.MenuItemfileMenu; privateSystem.Windows.Forms.MenuItemopenMenuItem; privateSystem.Windows.Forms.MenuItemsaveMenuItem; privateSystem.Windows.Forms.MenuItemseperator1; privateSystem.Windows.Forms.MenuItemexitMenuItem; privateSystem.Windows.Forms.MenuItemeditMenu; privateSystem.Windows.Forms.MenuItemcutMenuItem; privateSystem.Windows.Forms.MenuItemcopyMenuItem; privateSystem.Windows.Forms.MenuItempasteMenuItem; ... privatevoidInitializeComponent() { this.editorMenu=newSystem.Windows.Forms.MainMenu(); this.fileMenu=newSystem.Windows.Forms.MenuItem(); this.openMenuItem=newSystem.Windows.Forms.MenuItem(); this.saveMenuItem=newSystem.Windows.Forms.MenuItem(); this.seperator1=newSystem.Windows.Forms.MenuItem(); this.exitMenuItem=newSystem.Windows.Forms.MenuItem(); this.editMenu=newSystem.Windows.Forms.MenuItem(); this.cutMenuItem=newSystem.Windows.Forms.MenuItem(); this.copyMenuItem=newSystem.Windows.Forms.MenuItem(); this.pasteMenuItem=newSystem.Windows.Forms.MenuItem(); ... } } The InitializeComponent method also sets the properties of each menu item using the values that you specified in the Properties window. You'll notice that the way in which menus are constructed at run time is not dissimilar to the procedure followed by the AWT or Swing. The main difference between a .NET menu and its Java equivalents is that in .NET menu items are added to menus, and menus are added to the menu bar, in the same way ”by calling the AddRange method. This method accepts an array of MenuItem objects, which are then associated with that particular menu or menu bar. For example, the following code fragment generated by Visual Studio .NET adds the File and Edit menus to the main menu bar: this.editMenu.get_MenuItems().AddRange (newSystem.Windows.Forms.MenuItem[]{this.fileMenu,this.editMenu}); The next task for you to perform is to add a control that the user can type text into. The AWT application used a TextArea . The Toolbox has two controls that at first glance look like they might provide the functionality the application requires, namely TextBox and RichTextBox . TextBox is typically used for single-line text entry, much like a Swing JTextField or an AWT TextField . However, setting its Multiline property to true and its Scrollbars property to Vertical allows it to display multiple lines of text in a way that is almost the same as a Swing JTextArea . The RichTextBox control provides richer functionality than the TextBox . For example, you can assign character and paragraph formatting. The RichTextBox control is probably overkill for this application. In the Design View window, drag a TextBox control anywhere onto the form and then display its properties by choosing Properties from the View menu. Many of the TextBox properties are common to all controls because its parent class ( TextBoxBase ) inherits from System.Windows.Forms.Control , which is the base class for all controls that display information to a user, as Figure 4-8 shows. Figure 4-8. The TextBoxBase Control class hierarchy
As you scroll through the list of properties, you'll see some that are familiar from an AWT and Swing perspective, but many others might be less so. You should consult the Visual Studio .NET documentation for a definitive list of all the TextBox properties. Table 4-3 shows a selection of TextBox properties that might be unfamiliar to Java developers and indicates the class from which each property originates in the Control hierarchy: Table 4-3. TextBox Control Properties
For the Editor application, you should change the values of the TextBox properties shown in Table 4-4. Leave the remaining properties at their default values. Tip You might find it easier to locate each property if you select the Alphabetic toggle button in the toolbar directly above the Properties window. (It has an image showing an A above a Z alongside an arrow.) The properties will then be displayed in alphabetical order. Table 4-4. TextBox Properties in the Editor Application
If you return to the Code window and examine the InitializeComponent method again, you can observe the changes that resulted from your adding the TextBox and setting its properties. As the following code shows, the two points where the .NET-generated code radically departs from the Java GUI development model are the set_Size and set_Dock accessor methods. These two methods, which were described in Table 4-3, further illustrate that the Windows GUI development model revolves around coordinate sizes (and positioning) and relative positioning. this.textEditor.set_AcceptsTab(true); this.textEditor.set_Dock(System.Windows.Forms.DockStyle.Fill); this.textEditor.set_Multiline(true); this.textEditor.set_Name("textEditor"); this.textEditor.set_ScrollBars(System.Windows.Forms.ScrollBars.Both); this.textEditor.set_Size(newSystem.Drawing.Size(292,266)); this.textEditor.set_TabIndex(0); this.textEditor.set_Text(""); Handling EventsThe next step is to write code to handle the events that are triggered when the user chooses the various menu items. To understand the event model used by Windows Forms, we'll examine the simplest of the events in the application ”the event associated with the Click event of the Exit menu item. To create the event handler method, use the Design View windows and choose the File menu item belonging to the Editor form. Click the Exit menu item when it appears. With the Exit menu item highlighted, switch to the Properties window (which should now be displaying the properties for the exitMenuItem control) and click the "lightning bolt" button (the Events button) on the toolbar. This action will display the events available for the menu item. Type the method name exitMenuItemClick in the slot adjacent to the Click event and press the Enter key. The display will automatically change to the Code View window, and a new method called exitMenuItemClick will be created to handle the Click event. Following the conventions discussed in Chapter 3, the exitMenuItemClick method accepts two parameters rather than the single parameter you normally expect with Swing and AWT events: privatevoidexitMenuItemClick(System.Objectsender,System.EventArgse) The first parameter, sender , is a reference to the object that raised the event. The second parameter contains additional information specific to the event. For example, for some events it might contain information about which key the user pressed or the position of the cursor when the event fired . All events for Windows Forms components are passed this parameter, even if there's no useful additional information. The click event is just one of the events supported by the menu item. All controls, and the form itself, expose other events. For a complete guide to the use of each of these events, see the Visual Studio .NET documentation. When you work with an IDE, the actual mechanics of how events are connected to event handlers is often hidden from you. But sometimes you might need to explicitly wire events to their corresponding handlers. In a .NET Windows Forms application, you can think of an event as an action that you can code against. A user might trigger this action by moving a mouse, or code within your application or the system itself might invoke the action. The code that handles the event is the event handler . As discussed in Chapter 3, a delegate performs the binding between an event and an event handler. Event handler methods are added to the delegate. When an event fires, the runtime will execute the delegate and cause all the associated event handlers to run. If you're using an IDE such as Visual Studio .NET, the binding of an event method to a delegate will occur automatically, but you can also write code to do it manually. This technique is useful if you want to exploit the event model in your own applications, but without using GUI components. The following code fragment, which is part of the InitializeComponent method in the Editor application, shows how Visual Studio .NET uses a delegate to bind the Click event of the exitMenuItem control to the exitMenuItem_Click method. The type of delegate used is System.EventHandler : this.exitMenuItem.add_Click (newSystem.EventHandler(this.exitMenuItemClick)); The menu item control inherits the method add_Click from its ancestor class, Control . For each type of event that a control or .NET class exposes, there is a corresponding add_XXX subscriber method and a corresponding remove_XXX unsubscriber method. The add_Click method accepts a single parameter, which is an instance of System.EventHandler that refers to the method to be executed when the delegate is invoked. (See Chapter 3 for more details about how events are raised.) Note In .NET, delegates support multicasting , in which multiple event handlers can be associated with a single delegate. For example, the following code fragment shows how to bind three event handlers to a button that fires when the button is resized: this.button1.add_Resize(new System.EventHandler(this.firstB1_Resize()); this.button1.add_Resize(new System.EventHandler(this.secondB1_Resize()); this.button1.add_Resize(new System.EventHandler(this.thirdB1_Resize()); In the Editor application, the purpose of the exitMenuItemClick event handler method is to quit the application. You can implement this in three main ways. The first approach is to use the Application.Exit method, but this is rather draconian ”it terminates the form without giving it a chance to perform any tidying up or without saving any unsaved data. ( System.exit is the equivalent method in the JDK, and it has much the same effect.) A better solution is to use the Close method of the Form class. This method raises the Form.Closing and Form.Closed events, which can be intercepted by event handlers attached to the form and can be used to save any data or even give the user the option of vetoing the close operation. (The Form.Closing method can set a flag that prevents the form from terminating.) The Exit event handler method should appear as follows : privatevoidexitMenuItem_Click(System.Objectsender,System.EventArgse) { this.Close(); } Tip If you want to simply hide a form rather than dispose of it, you can call the form's Hide method (which is inherited from Control ). This method makes the form invisible but allows you to redisplay it later by calling its Show method. Note also that the Hide method, unlike the Close method, does not release the resources associated with the form. Using File Dialog BoxesThe next task in creating the Editor application is to provide the code that allows a user to open and save files. Unlike Swing and the AWT, .NET provides dedicated file dialog boxes for both Save and Open operations. Figure 4-9 shows an example of the Open File dialog box. The user can navigate to a folder, select a file (or type in a name), and click OK. Clicking Cancel aborts the operation: Figure 4-9. The Open File dialog box
To display a file dialog box, you call its ShowDialog method. There are two overloaded versions of this method. The first accepts no parameters, and the second accepts a single parameter that indicates the dialog box's parent form. If you pass the ShowDialog method a reference to a form, the dialog box will be modal; if you do not, it will be modeless. Note A modal dialog box is one that blocks the application until the user closes it. A modeless dialog box allows other forms in the same application to receive the focus. You'll also notice that the ShowDialog method returns a member of the DialogResult enumeration, which indicates which button the user clicked to close the dialog box. In our sample application, the code tests to determine whether the value returned is equal to OK . The ShowDialog method returns the OK value when the user makes a positive choice ”for example, by clicking the OK button or double-clicking on a file. The values from this enumeration are used as the return values not just from the file dialog boxes but by other dialog boxes throughout the .NET Framework Class Library. Table 4-5 describes the members of the enumeration. Table 4-5. The DialogResult Enumeration Members
The following code shows the Open menu item event handler. You should add this method to the Editor form and connect it to the Click event of the Open menu item; in the Design View window, select the File menu item, click the Open menu item, and then use the Properties window to set the Click event of the Open menu item to the openMenuItemClick method. The code for the openMenuItemClick is shown here ”type it in: privatevoidopenMenuItemClick(System.Objectsender,System.EventArgse) { StringtheText= ""; //Opensafilethatauserselects OpenFileDialogofd=newOpenFileDialog(); ofd.set_Title("Pickafile"); //Passownertoensurethatitismodal DialogResultdr=ofd.ShowDialog(this); if(dr==dr.OK) { StringtheFile=ofd.get_FileName(); try { StreamReadersr=newStreamReader(theFile); theText=sr.ReadToEnd(); textEditor.set_Text(theText); } catch(System.Exceptionioe) { MessageBox.Show(this,ioe.get_Message()); } } } In the .NET Framework Class Library, as in Swing, the file dialog classes provide the functionality that allows the user to select a file, but they do not actually read or write to the file that has been selected. You must write the code to do this. You could use the objects in the java.io package of the JDK to read or write a file, but because the purpose of this chapter is to show you how to develop Windows Forms applications, the preceding openMenuItemClick method uses classes from .NET Framework Class Library to perform the I/O. As you can see, the I/O elements of the method use a StreamReader in much the same way that you'd use a Java FileReader . To use the StreamReader class (and all of the other .NET- related I/O classes), you must import the System. IO package at the start of the file: importSystem.IO.*; You can read data from a text input stream a line at a time using the Read method of the StreamReader class, but an alternative is to use the ReadToEnd method, which reads the entire contents of the file, including any carriage return characters. This is the approach taken by the openMenuItemClick method. Like the event handler for the Open menu item, the event handler for the Save menu item uses .NET classes for both the dialog box and I/O. The following code shows the completed method; it also illustrates that the SaveFileDialog object is instantiated and used in the same way as the OpenFileDialog object. You should add this method to the Editor form and attach it to the Click event of the Save menu item. (You can select an existing method when you define an event handler as well as create a new one ”in this way, it is possible for two handlers to refer to the same method and execute the same code.) privatevoidsaveMenuItemClick(System.Objectsender,System.EventArgse) { //thesavefilemethod StringtheFile=null; StreamWritersw=null; //getthefilenametosaveas SaveFileDialogsfd=newSaveFileDialog(); DialogResultdr=sfd.ShowDialog(this); if(dr==dr.OK) { theFile=sfd.get_FileName(); try { sw=newStreamWriter(theFile); sw.Write(textEditor.get_Text()); } catch(System.Exceptionioe) { MessageBox.Show(this,ioe.get_Message()); } finally { try { sw.Flush(); sw.Close(); } catch(System.Exceptionex) { MessageBox.Show(ex.get_Message()); } } } } You might notice in both of the event handlers described in this section that a MessageBox is displayed when an exception is caught. We've included this because it is a really useful class that has similar functionality to the Swing JOptionPane class ”that is, it displays simple dialog-type boxes. The code in these examples calls the static Show method to display a message box. In both fragments , the code passes two parameters: a form to ensure that the box is modal, and a message. However, the Show method has 12 overloads, allowing you to specify parameters that indicate objects such as captions, buttons, icons, and default buttons . For a full guide to these overloads, see the Visual Studio .NET documentation. Working with the System ClipboardWorking with the system clipboard is straightforward, and the code is almost identical to using the system clipboard with the JTextArea class in Swing. The .NET TextBox class inherits three system clipboard manipulation methods from its parent class, TextBoxBase : these are cut , copy , and paste . Implementing the event handler methods for the Cut, Copy, and Paste menu items is straightforward, as shown in the following code. These methods should be added to the Editor form and the methods attached to the Click event handlers of the appropriate menu items: privatevoidcopyMenuItemClick(System.Objectsender,System.EventArgse) { textEditor.Copy(); } privatevoidpasteMenuItemClick(System.Objectsender,System.EventArgse) { textEditor.Paste(); } privatevoidcutMenuItemClick(System.Objectsender,System.EventArgse) { textEditor.Cut(); } This is all the code that most applications will need to interact with the clipboard. However, you can also access the clipboard through the System.Windows.Forms.Clipboard class, which gives you much greater control. The Clipboard class has only two members, both of which are static methods: GetDataObject to retrieve from the clipboard, and SetDataObject to place items on the clipboard. The SetDataObject method has two overloads: You can either pass it an object or you can pass it an object and a Boolean flag to indicate whether the data should remain on the clipboard after the application exits. (Pass the value true to indicate this.) As the generic names of the methods suggest, you can copy and retrieve objects of different types to the clipboard. In much the same that as you work with data flavors in the AWT, you can test the data types held on the clipboard within .NET. The following code fragment shows a modified Paste menu item event handler that tests whether the data on the clipboard is of the type Text : privatevoidpasteMenuItemClick(System.Objectsender,System.EventArgse) { StringtheText=""; DataObjectdobj=(DataObject)Clipboard.GetDataObject(); if(dobj.GetDataPresent(DataFormats.Text)) { theText=(System.String)dobj.GetData(DataFormats.Text); textEditor.set_SelectedText(theText); } } This event handler obtains the data from the clipboard by calling the GetDataObject method. The returned type is an object that implements the IDataObject interface. The object is cast to the DataObject type, which is the generic class used for storing data on the clipboard (all objects stored on the clipboard must descend from the DataObject class), and implements the IDataObject interface. The interface defines four methods, as listed in Table 4-6. Table 4-6. Methods of the IDataObject Interface
In the preceding code fragment, if the data is of the type specified ( Text ), it is assigned to the local variable theText , and the data is then inserted into the text of the TextBox using the Textbox class's set_SelectedText method. Note that the code casts Text format data to a System.String because an unqualified String would be a java.lang.String , which would trip an invalid cast exception. In our example, the code specifically tests for a data type of Text ; however, the DataFormats class defines a large number of other data types, including Bitmap , HTML , RTF , Serializable , and WaveAudio . For a complete list of supported formats, see the Visual Studio .NET documentation. Building and Running the ApplicationTo compile the application, choose Build Solution from the Build menu. Any syntax errors will be displayed in the Output window. Once you have successfully compiled the application, you can run it from within Visual Studio .NET by choosing Start or Start Without Debugging from the Debug menu. Alternatively, you can run the program from the command line by navigating to the bin\Debug folder under the project folder and executing Editor.exe. Figure 4-11 shows the application running. (The form has been expanded slightly.) Figure 4-11. The .NET Editor application running
A completed version of the Editor application is available in the Editor folder among the book's sample files. |
I l @ ve RuBoard |