The Source for Java Technology Collaboration
User: Password:



Patrick Keegan's Blog

May 2008 Archives


Passing Dialog Input to the Main View and Database

Posted by pkeegan on May 27, 2008 at 08:33 PM | Permalink | Comments (3)

This is the fourth in a series of posts on creating a Java database application. In my last few posts, I started with skeleton code generated by the IDE and provided my own customizations, including adding a dialog to use for data entry and binding those fields with a table on the main form. In this post, I finish coding the connection between the dialog and the main form. I'll also add an Edit Client button and its corresponding Action code to the main form.

First let's hook up the buttons in the EditClient dialog with appropriate event-handling code. We already have save() and refresh() actions that are provided with the skeleton application. We will code the dialog so that the buttons reuse these actions. We can accomplish this by setting up a boolean property in the dialog that returns true when the Save Record button is pushed and returns false when Cancel is selected. Based on the value that is returned when the dialog is closed, the the save() or the refresh() action will be run from the main view class.

To set up the property, do the following:

  1. Open up the EditClient file and select the Source view.
  2. Place the cursor somewhere below the block of generated code that contains the initComponents() method.
  3. Press Alt-Insert and choose Add Property.
  4. In the Add Property dialog, type clientConfirmed as the property name.
  5. Set the type to boolean.
  6. Make sure the Generate Getters and Setters checkbox is selected.
  7. Click OK to close the dialog box and generate the code.

We'll set this property's value in event handling code for the buttons. Let's create the event listeners and handlers now:

  1. Switch to the Design view for the EditClient class.
  2. Select the Save button in the EditClient form.
  3. In the Properties window, click the Events button.
  4. Click the ellipsis (...) button next to the actionPerformed property.
  5. In the Handlers for actionPerformed dialog box, add a handler called saveNewClient.
  6. Within the saveNewClient method in the Source Editor (where the cursor jumps after you create the new handler), type the following code:
    setClientConfirmed(true);
    setVisible(false);
    
  7. Repeat steps 2-5 for the Cancel button and call its handler cancelNewClient.
  8. In the cancelNewRecord method, type the following:
    setClientConfirmed(false);
    setVisible(false);
    

Navigate to the newRecord() method and add the following code to the bottom of the method:

        if (ec.isClientConfirmed()) {
            save().run();
        } else {
            refresh().run();
        }

In the RefreshTask inner class, Thread.sleep is called four times to slow down the rollback code to better demonstrate how Swing Application Framework tasks work. We don't need this code for this application, so delete those four statements. Similarly, we don't need a try/catch block here, so delete the try and catch statements as well (but leave the rest of the body of the try block).

Since the save() and refresh() actions act on any changes made during the application's session, we will want to make the dialog modal and make the tables in the main form uneditable. We also need to make the dialog modal so that when the user presses either the Save or Cancel button, the setVisible() method doesn't return until the event handler (which includes the setClientConfirmed method) has run.

To make the dialog modal:

  1. Open the Design view of the EditClient class.
  2. Select the dialog.
  3. In the Properties window, click Properties and select the checkbox for the modal property.

To make the main form's Clients table uneditable:

  1. Open the main view class in the Source Editor and select the Design view.
  2. Right-click the top table and choose Table Contents.
  3. In the Customizer dialog, select the Columns tab.
  4. For each column, clear the Editable checkbox.
  5. Click Close.

You can now run the application and click New Client to add a new record. When you press Save in the New Client dialog, the record is saved. When you press Cancel, the new record you have changed is rolled back.

This is all well and good, but by disabling the editability of the table on the main form, we can no longer edit existing records. To solve this, we'll add an Edit button to the main client form so that we can edit existing records. For event-handling, we'll take advantage of the Swing Application Framework's Action facility.

To add the button and its corresponding event-handling code, do the following:

  1. Drag the New Client Button a bit to the left.
  2. Drag a button from the palette into the opening just created.
  3. Right-click the button and choose Set Action.
  4. In the Action field, select Create New Action.
  5. For Action Method, type editClient.
  6. For Text, type Edit Client.
  7. Click the Advanced Tab and select recordSelected for the Enabled Property.

    This generates an annotation attribute to ensure that the button and any other trigger for the action (e.g. a menu item) are only enabled when a record is selected.

  8. Click OK to close the dialog box.

    The Source view of the file should appear with the cursor in the following new method:

        @Action(enabledProperty = "recordSelected")
        public void editClient() {
        }
    
  9. Within the method, paste the following code:
            setSaveNeeded(true);
            JFrame mainFrame = ClientAndPurchaseApp.getApplication().getMainFrame();
            EditClient ec = new EditClient(mainFrame, false);
            ec.setCurrentRecord(list.get(masterTable.getSelectedRow()));
            ec.setVisible(true);
            if (ec.isClientConfirmed()) {
                save().run();
            } else {
                refresh().run();
            }
    

Most of that code is copied straight from the newRecord action. The key difference is the line ec.setCurrentRecord(list.get(masterTable.getSelectedRow()));, which populates the current record in the dialog with the currently selected record.

The Client part of the application is almost completely set. You should be able to freely add, edit, and delete records from your CLIENTS table using the specialized GUI we have created.

One last detail: the main form still has the title of Database Application Example, and it's not obvious where to change. Hint: it's not within the GUI Builder.

To change the title of the main frame of the application:

  1. In the Projects window, select the project's node and choose Properties.
  2. In the Project Properties dialog box, select the Application node.
  3. Edit the Title property and any other properties that are important to you.
masterdetail4-projpropertiesapp.png

Now when you run the application, most of the key elements are in place. In the screenshot below, you can see the Edit Client dialog as it appears after having selected a record and pressed the Edit button.

masterdetail4-nearfinishedapp.png

I could continue with customization of the bottom part of the main form and other fine tuning of the application, but I'll save most of those details for the tutorial and individual blog posts with more atomic examples. As always, keep your questions coming and I'll try to deal with as many of them as I can.



Binding JComboBox's Elements and Selected Item

Posted by pkeegan on May 22, 2008 at 07:08 PM | Permalink | Comments (7)

This is part 3 in a series of posts that I'm doing to show how to use beans binding and JPA to create a Java desktop database application. In this installment, I concentrate on combo boxes, namely how to populate combo boxes from a table and then how to bind the user selection to a record. If you want to code along with me, be sure to read my previous two posts.

When we created the skeleton for this application, we (or, rather, the New Java Desktop Application wizard) did not take into account the foreign key from the Clients table to the Countries table. The only relationship between tables that was acknowledged was the one pertinent to the master/detail relationship between the Clients and the Orders tables. So now we will need to take some extra steps to establish the Clients/Countries relationship in the entity classes:

  1. Create an entity class for the Countries table by right-clicking the package that contains your classes and choosing New | Entity Classes from Database.
  2. In the generated Countries class add the following line below the @ID annotation, just as we did for the Clients and Orders classes.
        @GeneratedValue(strategy=GenerationType.IDENTITY)
    
  3. Press Ctrl-Shift-I to add the necessary import statements.
  4. Modify the Clients entity class so that countryId property is of type Countries instead of Integer and that it is joined with the Countries db table. The following changes are necessary:
    • Replace this field declaration and annotations
          @Column(name = "COUNTRY_ID")
          private Integer countryId;
      

      with this code:

          @JoinColumn(name = "COUNTRY_ID", referencedColumnName = "COUNTRY_ID")
          @ManyToOne
          private Countries countryId;
      
    • Change the type of the getCountryId() method from Integer to Countries.
    • In the setCountryId() method, change the types of countryId and oldCountryId from Integer to Countries.
    • Press Ctrl-Shift-I to add the imports for the pasted code.

We also need to update the column binding for the country field so that it refers to the country property of the Countries object instead of an Integer. (Code to use a country ID Integer was generated by the project since the skeleton was generated without having an entity class for the COUNTRIES table. If we don't make a change here, a ClassCastException will be thrown when you run the application.) Here are the steps:

  1. In the main form, right-click the top table and choose Table of Contents and then click the Columns tab.
  2. In the customizer, select the Country Id row.
  3. Change the Expression to ${countryId.country}. After you do so, the type should also change to String. masterdetail3-changeTableContents.png
  4. Change the Title from Country Id to Country (this affects the column heading in the running application).

Now it's time to do the binding for the Country combo box in the dialog.

  1. Switch back to the EditClient.java file and click Design at the top of the editor to work with the file in Design view.
  2. Right-click the combo box and choose Bind | elements.
  3. Click Import Data to Form, select the database connection, and select the Countries table. countriesList should appear as the binding source. Click OK.
  4. Right-click the combo box again and choose Bind | selectedItem.
  5. Select Form as the binding source and currentRecord | countryId as the expression. Click OK. (As you may recall from the last post, we are using a custom bean called CurrentRecord as a liaison between this dialog and the main form.)

The combo box is almost ready to work properly in the dialog. It is set up to draw its values from the Countries db table, and the item that the user selects is then applied to the country field in the current record. However, we still need to customize the rendering of the combo box, since the values bound to the combo box are Countries objects, not simple names. We will do that by specifying a custom cell renderer. (For JTables and JLists, the beans binding library enables you to specify display expressions, thus avoiding the need to create a custom renderer, but that feature does not exist yet for combo boxes.)

To get the combo boxes to render country names, do the following:

  1. Create a new class called CountryListCellRenderer in your project.
  2. Delete the generated class declaration and paste the following code below the package statement:
    import java.awt.Component;
    import javax.swing.DefaultListCellRenderer;
    import javax.swing.JList;
    
    public class CountryListCellRenderer extends DefaultListCellRenderer {
    
        @Override
        public Component getListCellRendererComponent(
                JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
            super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
            if (value instanceof Countries) {
                Countries c = (Countries) value;
                setText(c.getCountry());
            }
            return this;
        }
    }
    
  3. Compile the class.
  4. Select the EditClient form in the Source Editor (make sure that the Design view is selected).
  5. Drag the class from the Projects window to the white space surrounding the form, as shown in the screenshot below.

    Doing so adds the renderer to your form as a bean, much like dragging a component from the Palette adds that component to your form.

    masterdetail3-dragrenderer.png
  6. In the form, select the combo box.
  7. In the Properties window, scroll to the renderer property and choose countryListCellRenderer1 from the drop-down list for that property. masterdetail3-selectrenderer.png

The combo box should be ready to go - except for one thing. It doesn't have any values to display yet. You can go ahead and populate the table with a few SQL commands and then run the project. Or you can indulge me in this digression that demonstrates how you can quickly do this with a few hacks within the IDE (and shows you some handy features along the way).

First, create a separate form for adding countries to the db by doing the following:

  1. Right-click the package containing your classes and choose New | Other.
  2. Select the Swing GUI Forms | Master/Detail Sample Form and click Next.
  3. Give the class the name CountriesForm and click Next.
  4. Select the database connection, select the countries table.
  5. Since we won't be editing the Country_ID fields by hand (the values will be automatically generated), move the Country_ID column to the list of Available Columns. Then click Next.
  6. Click Finish to exit the wizard.

We have just essentially created another application with its own main class. In order to properly run this class, we need to temporarily make it the main class of the project. (Simply using the Run File command won't work since this command doesn't pick up classpath dependencies.) We can do so by creating a new project configuration.

  1. Choose Build | Set Main Project Configuration | Customize.
  2. Click New and then enter CountryEditing as the configuration name.
  3. Click the Browse button next to the Main Class field and select the CountriesForm class.
  4. Click OK.

    The configuration is automatically switched to the new configuration.

You can now start editing the list of countries.

  1. Choose Run | Run Main Project.
  2. In the simple application that runs, click New to create a new row and fill in a country. masterdetail3-CountriesForm.png
  3. Repeat step 2 a few times so that you have multiple countries to choose from.
  4. Choose Build | Set Main Project Configuration | so that the main application runs the next time we use the Run Project command.

Once you have some countries in the the Countries table, you can run the main application and see the combo box in action:

  1. Choose Run | Run Main Project.
  2. In the running application, click the first New button.
  3. Enter values into the various text fields and choose a country from the combo box.

    Notice that the values that you enter in the dialog box also appear in the top table in the main form, including the country you selected from the combo box.

    masterdetail3-combo-working.png
  4. Since we have not coded the buttons in the dialog box yet, move the dialog out of the way and click Save in the main form to save the changes to the database.

The application works, but it's still very rough around the edges. Here is some quick tidying up we can do now:

  1. Make the the columns in the table uneditable. You can do so by right-clicking the table, choosing Table of Contents, clicking the Columns tab, and then clearing the Editable checkbox for each of the items. This is particularly desirable for the Country column so that you can manage the what people enter for countries (e.g. to avoid misspellings) and better handle changes in country names (the change only needs to be made in one place), etc.)
  2. Change the text of the New buttons to distinguish them. I'm going to use New Client and New Order. You can change the text inline (by clicking the button once, pausing, and then clicking again). Or, if you want to change the text in every place that the action is used (such as from a menu), you can right-click the button, choose Set Action and change the Text attribute.
  3. Delete the superfluous main() method in the EditClient.java class.

We still have some work to do, such as:

  • Adding functionality to the Save and Cancel buttons in the dialog
  • Making it possible to edit existing records
  • Doing some currency formatting

I'll cover those topics and others in ensuing posts. Where time and personal knowledge allows, I'll try to field requests as well.



Beans Binding Between Separate Forms

Posted by pkeegan on May 21, 2008 at 07:45 PM | Permalink | Comments (15)

Continuing from my last post, I'll show the next steps in the creation of this simple (but not too simple) client purchase application. This time, our main focus is in creating a separate dialog which we will use for data entry. We'll need to do a few tricks so that input from the dialog is propagated to the main form and then the database.

But first we'll need to clear up a few loose ends. As I alluded to last time, I use AUTO_INCREMENT attributes for the ID columns of the CLIENTS, COUNTRIES, and ORDERS tables. This means that whenever a new row is added to those tables, that row's AUTO_INCREMENT field is given a unique value (the value of the last new record + 1). For me using AUTO_INCREMENT is a handy way to ensure having unique records.

When you generate the skeleton of the application in the New Java Desktop Application wizard, the IDE generates two entity classes (Clients.java and Orders.java) that represent database tables with the same names. However, these entity classes are missing code to deal with the AUTO_INCREMENT fields. Without that code, you will get errors when trying to enter new records. To fix that:

  1. Open the Clients.java class.
  2. Navigate to the line after the one containing @Id.
  3. Enter the line @GeneratedValue(strategy=GenerationType.IDENTITY).
  4. Press Ctrl-Shift-I to generate the necessary import statements for this annotation.
  5. Repeat the process for Orders.java.
masterdetail2-identityannotation.png

Note: You can also use code completion here. It takes three selections to get the entire line, but the import statements are generated for free.

Once you have added these annotations, you can run the application and start adding data. Click the top New button to add a client. With a client selected, click the bottom New button to add an order for that client. Click Save to push your changes to the database. Click Refresh to back out any unsaved changes.

masterdetail2-skeletonapp.png

I like tables for browsing data, but I think they leave something to be desired for data entry. So for this application, we'll add dialogs for data entry.

To create and populate the JDialog, follow these steps:

  1. Right-click the package that contains your classes and choose New | Other. Select Swing GUI Forms | JDialog Form template and name it EditClient.
  2. From the Palette window drag, drop, and arrange components for the customer's personal details.

    You should have JLabels for each of the following fields: first name, last name, address, city, state, zip code, country, and phone number. You should have JTextFields for each of those fields, except for country, for which we will use a JComboBox.

  3. Edit the display text for JLabels.
  4. Add two buttons and name them Save and Cancel.
  5. (Optional) Rename all of the components you have added to more memorable names, such as firstNameLabel. You can do this inline in the Inspector window.

The resulting layout should look something like what you see below.

masterdetail2-clientdialoglayout.png

Now we need to bind the various fields to the corresponding columns in the table. We can't bind directly to components from other forms in the Bind dialog box, so we'll have to create an intermediary property of type Clients to hold the record. When the user presses New, the property will be given the value of the currently selected record.

We can quickly generate the bean property with the IDE's bean support:

  1. At the top of the design area of the EditClient form, click the Source tab. Click somewhere within the class, such as above the variable declaration block.
  2. Press Alt-Insert (or right-click and choose Insert Code) and choose Add Property.
  3. In the Add Property dialog, name the property currentRecord, give it the type Clients, select Generate Getter and Setter, and select Generate Property Change Support.
  4. Click OK to generate the property.
masterdetail2-beanpropeditor.png

We now need to customize the generated setCurrentRecord method. Replace the body of the method with these three lines:

        Clients oldRecord = this.currentRecord;
        this.currentRecord = currentRecord;
        propertyChangeSupport.firePropertyChange("currentRecord", oldRecord, currentRecord);

Now we need to add code to the New action to open the dialog and clear the currentRecord property when a user wants to add a new record:

        JFrame mainFrame = ClientAndPurchaseApp.getApplication().getMainFrame();
        EditClient ec = new EditClient(mainFrame, false);
        ec.setCurrentRecord(c);
        ec.setVisible(true);

For now, we won't code the Save and Cancel buttons on the dialog (we've had enough digressions!). I'll cover that in an upcoming post.

With those preliminaries out of the way, we can proceed with the binding of the text fields. We'll be binding the text property of each text field to the corresponding property of the Clients object represented by currentRecord.

To bind a dialog text field to the appropriate property of currentRecord:

  1. Right-click a text field and Choose Bind | text.
  2. In the Bind dialog box, select Form as the Binding Source (note that Form is at the very bottom of the drop-down list).
  3. In the Binding Expression drop-down list, expand the currentRecord node and select the property corresponding to the text field that you are binding.
  4. Click OK to close the Bind dialog box.
masterdetail2-addressbinding.png

Do this procedure for each of the text fields in the dialog. For now, don't bind the JComboBox to anything. We'll need to do some other preparation to get that to work properly. I'll cover that in my next post.

Now you should be able to run the application, press the first New button, and enter data in the dialog. The Save and Cancel buttons on the dialog don't do anything yet, but we can save the records from the main frame. In the next post, we'll clear up some of these loose ends so that the application behaves more like applications that we are used to.



Input on a New Desktop Java Database Tutorial

Posted by pkeegan on May 13, 2008 at 09:20 AM | Permalink | Comments (14)

Recently I've found time again to work on actual tutorials. I don't have anything written yet, but I have something resembling a plan, which you can find here: http://wiki.netbeans.org/PlanGuiBuilderDocImprovements.

Over the next few weeks, I'll be blogging about creating a Swing desktop application with database connectivity. These postings will essentially serve as a rough sneak preview of a full-fledged tutorial on the subject that I'll later post to netbeans.org. The tutorial will go beyond simple database connectivity and show things such as one-to-many and many-to-one relationships as well as how to bind database tables to a variety of GUI components. We'll use a MySQL database that has tables for client info, order info, and countries. There will be a one-to-many relationship between the client and order tables. There will be a many-to-one relationship between client and countries tables. Along the way, I'll be looking at any feedback that comes through and do my best to respond to it, whether in quick responses, in separate articles, or by modifying the final tutorial. Chances are that I'll also tweak the structure along the way as I find better ways of doing things.

To start off, I'll provide an SQL script that provides a beginning database structure:

CREATE TABLE CLIENTS (
	ID INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY,
	FIRST_NAME VARCHAR(20),
	SURNAME VARCHAR(30),
        ADDRESS VARCHAR(30),
	CITY VARCHAR(30),
	STATE_ VARCHAR(30),
	ZIP INTEGER,
	COUNTRY_ID INTEGER,
	PHONE INTEGER
);

    CREATE TABLE EMAIL_ADDRESSES (
    CLIENT_ID INTEGER NOT NULL,
    ADDRESS VARCHAR(50) NOT NULL PRIMARY KEY,
    FORMAT INTEGER,
    FOREIGN KEY (CLIENT_ID) REFERENCES CLIENTS(ID)
    );
    
CREATE TABLE ORDERS (
    ID INTEGER NOT NULL AUTO_INCREMENT,
    CLIENT_ID INTEGER NOT NULL,
    PRODUCT VARCHAR(50) NOT NULL,
    AMOUNT INTEGER NOT NULL,
    PRIMARY KEY(ID),
    FOREIGN KEY (CLIENT_ID) REFERENCES CLIENTS(ID)
);

CREATE TABLE PRODUCTS (
    MODEL VARCHAR(50) NOT NULL PRIMARY KEY,
    PRICE DECIMAL NOT NULL
);

CREATE TABLE COUNTRIES (
    COUNTRY_ID INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY,
    COUNTRY VARCHAR(30) 
);

ALTER TABLE CLIENTS    
ADD CONSTRAINT COUNTRIES_FK Foreign Key (COUNTRY_ID)
   REFERENCES COUNTRIES (COUNTRY_ID);

A few notes on the structure:

  • I use AUTO_INCREMENT in some of the tables so that there is a unique identifier for each row in those tables. For this feature to work properly within the application, you need to add the @GeneratedValue(strategy=GenerationType.IDENTITY annotation for that column in the table's entity class. See http://weblogs.java.net/blog/pkeegan/archive/2007/12/index.html for some more context.
  • The foreign key in the ORDERS table is there to link each order record with a client. In the application's UI, all ORDER records are displayed for the selected CLIENT.
  • The foreign key in the CLIENTS table points to a COUNTRIES table. We will use this relationship in the application to enable the user to select a client's country from a combo box.
  • EMAIL_ADDRESSES is a separate table with a foreign key linking it to the CLIENTS table. This is in attempt to keep one of the entry dialogs looking as much as possible like http://www.netbeans.org/kb/60/java/quickstart-gui.html, where it is possible to enter multiple email addresses person. The motivation is so that this new tutorial can build on that previous one (or a similar version of it), but very likely I will delete that table in the end and merely have an EMAIL column in the CLIENTS table.

After you have created the above database and have connected to it from IDE (see Connecting to a MySQL Database), you can go ahead and create the initial application skeleton by following these steps:

  1. Choose File | New Project.
  2. Select the Java category and the Java Desktop Application template.
  3. In the Name and Location page of the wizard, select the Database Application skeleton.
  4. In the Master Table page of the wizard, select the connection to the just-created database. Then select the clients table, and then move ID from Columns to Include to Available Columns. clientrecordproject1.png
  5. In the Detail Options page, click the Table radio button and select the orders table from the combo box.
  6. clientrecordproject2.png
  7. Click Finish to exit the wizard.
  8. Choose Run | Run Main Project to see the main application window. clientrecord-runwithoutdata.png

So that's the start of the application. The next steps include:

  • Adding @GeneratedValue(strategy=GenerationType.IDENTITY annotations (as noted above) to the identity columns in the Client.java and Countries.java entity classes.
  • Creating an entity class for the COUNTRIES table and modifying the CLIENTS entity class to handle the relation.
  • Customizing the generated JTables (e.g. changing column headings, making the columns read-only). You can get a head start on this by right-clicking the JTable and choosing Table Contents.
  • Creating separate dialogs for the entry of client and order records.

I'll cover those topics and more starting early next week. I welcome all feedback!





Powered by
Movable Type 3.01D
 Feed java.net RSS Feeds