1 2 Previous Next

pkeegan

18 posts

Apologies in advance to those of you expecting a weightier post. Unfortunately, I've been busy lately, I haven't had time to come up with working examples of applications that handle things like many-to-many relationships or which display information from multiple tables in one cell. But I do have time to squeeze out entries on other simple-but-cool things that have been on my mind a long time. Today I tackle embedding a database within a Java desktop application.

Most of the tutorials I've run across talk about creating applications that connect with a database that is managed from a server. This is appropriate for most business applications. However, sometimes you might want to create a more portable application that carries its own data with it, such as an application in which a user manages personal data.

In this application, we will use NetBeans to create a simple desktop application to store info on your personal music collection. We will use Java DB, which has an embedded mode so that it can be packaged within the application. The application will also make use of the Beans Binding library and the Java Persistence API.

Setting Up the Database

First we will create a "connection". This isn't a connection to a real database but it gives us a place to create a database structure, which we can then use to generate application code.

  1. In NetBeans, open the Services window and expand the Drivers node.
  2. Right-click Java DB (Embedded) and choose Connect Using.
  3. embedded-connectusing.png
  4. For Database URL, enterjdbc:derby:Recordings;create=true.
  5. For User Name, enter APP.
  6. Enter whatever you wish the password and click OK. embedded-dbconnwiz.png

Now we need to generate the database structure. We will do so by executing an SQL script that defines a single table and its columns.

To generate the database structure:

  1. In the Services window, scroll down to thejdbc:derby:Recordings;create=true node, right-click, and choose Execute Command. embedded-executecommand.png
  2. Paste the following code into the editor. 
    create table "APP".RECORD
    (
        ARTIST VARCHAR(30) NOT NULL,
        TITLE VARCHAR(30) NOT NULL PRIMARY KEY,
        FORMAT VARCHAR(30) NOT NULL,
        RATING INTEGER,
        CONDITION VARCHAR(10),
        COMMENTS VARCHAR(30)
    )
    
    
    
  3. Click the Run SQL button to execute the command.
  4. embedded-executescript.png
  5. Right-click the jdbc:derby:Recordings;create=truenode and choose Refresh.
  6. Expand the node and then expand the Tables node.

Creating the Application

With the database structure set up, we can now use the Java Desktop Application project template to create a basic CRUD application based on that structure.

  1. Choose File | New Project.
  2. In the wizard select the Java | Java Desktop Application template. embedded-javadesktop.png
  3. In the Name and Location page of the wizard, select the Database Application skeleton. embedded-wiznamelocation.png
  4. In the Master Table page of the wizard, select the connection for the Recordings database. embedded-wizmastertable.png
  5. In the Detail Options page, click Finish.

Once you complete the wizard, you have a basic CRUD application that should be ready to build and run. Here is how the application looks in the Design view of the GUI Builder:

embedded-designview.png 

Building, Testing, and Distributing the Application

Before building and running, make sure that you have all of the necessary libraries by expanding the project's Libraries node.

embedded-libraries.png 

You should see libraries for the Swing Application Framework, Beans Binding, TopLink (which contains classes from the Java Persistence API), and Derby (Java DB). Depending on your setup, it might happen that TopLink and Derby are not added. If those libraries are not listed, you need to add the libraries manually. The TopLink library is available within the IDE's list of libraries. You can get the Derby JAR file from an installation of JDK 6, Glassfish, or from a Java DB or Derby standalone installation.

To add the TopLink library:

  1. Right-click the Libraries node and choose Add Library.
  2. From the Available Libraries list, add TopLink Essentials.
embedded-addlibrary.png 

To add derby.jar:

  1. Right-click the Libraries node and choose Add JAR/Folder.
  2. Navigate to your Derby/Java DB installation and select derby.jar. (I used the copy I found in C:\Program Files\glassfish-v2ur2\javadb\lib, but you might have it as part of your JDK.)

To build and test run the project:

  1. Press F11 to build the project. (If this project is not your main project, right-click the project's node in the Projects window and choose Build.)
  2. Press F6 to run the project in the IDE. (If this project is not your main project, right-click the project's node in the Projects window and choose Run.)
  3. In the running application, add a few records and save them.
embedded-runningapp.png 

The database is created and saved in your project directory. You can glimpse the database files that were created in the test run by opening the Files window and expanding the node for your project.

embedded-fileswindow.png 

You'll notice that there is a sub-folder called Recordings (based on the database name) which contains the database files.

Note: If you run the application directly from dist/Recordings.jar, the database once again will be empty. When you add records, the database files will be created in a location that depends conventions of your operating system. I run on Vista, and so my database files are created in the VirtualStore folder of my Windows user directory.

You can distribute the application by zipping up the project's dist folder and giving it to the user. The dist folder contains the application's main JAR file, Recordings.jar, and thelib folder. The lib folder contains various libraries essential for the project, includingderby.jar, which contains pretty much all of Java DB (which is just 2.2 MB).

embedded-dist.png 

Once they unzip the file, they can run the Recordings.jar file, either by double-clicking it (if they have the .jar file extension associated with Java on their system) or by running it from the command line with the command java -jar Recordings.jar.

So there you have it - a portable database application with no hand coding.

Bonus Note on the Database Structure

For purposes of quickly showing how to use Java DB as an embedded db, I used and over-simplified database structure, especially regarding the primary key. So that you can you can have multiple entries for the same artist, you might want to create an auto-generated identity field and make that the primary key instead. For example:

create table "APP".RECORD
(
    ID INTEGER NOT NULL GENERATED ALWAYS AS IDENTITY,
    ARTIST VARCHAR(30) NOT NULL,
    TITLE VARCHAR(30) NOT NULL PRIMARY KEY,
    FORMAT VARCHAR(30) NOT NULL,
    RATING INTEGER,
    CONDITION VARCHAR(10),
    COMMENTS VARCHAR(30)
);

ALTER TABLE "APP".RECORD
    ADD CONSTRAINT CONTACTS2008_PK Primary Key (ID);

Then after creating the project, you would need to modify the Recordings entity class by inserting the line@GeneratedValue(strategy=GenerationType.IDENTITY)@Id.

I'm back from vacation and (almost) back up to speed, so it's time to resume with my semi-regular posting. Soon, I'd like to show an example of a many-to-many relationship and other examples with joins. But in this post, I'll address a question from Istr that he submitted as a comment to my last post - how to add Next and Previous buttons to the application created in this Java desktop application. That's a pretty reasonable request, given that someone who is doing heavy editing of records might not want to have to go through the trouble of closing the dialog, selecting the next record, etc.

However, for several reasons, it is hard to add the Next/Previous functionality to the dialog itself. Currently, the dialog is passed the current record when it is instantiated, but this is a very one-way transaction. There is no simple way for the dialog to get information on other records. The list object that contains the database records is static and can not be referenced from a non-static method in the dialog. In addition, the transaction logic is in the main view.

The easiest way to add Previous/Next functionality (and probably the best approach from a design point of view) is to redesign the application so that the editable fields are in the main view and directly to the corresponding properties in the master table. You can then add Previous and Next buttons that change the selected record in the master table (and thus also in the text fields because they are bound to the table properties).

One other consideration is how data is saved and how changes are canceled. In the original application, data can only be added or changed for one record at a time, and you have save or cancel those changes to exit the dialog. This design decision is partially based on the assumption that the database will eventually be huge and a person wouldn't go through the records one by one to update them. But for a design where you anticipate wanting to go through many different records, it might be more desirable to save more at once, so that's what we will do here.

You can manage these changes to the application relatively quickly through the following steps:

  1. Download the CustomerRecords application zip file (the application that results from the Creating a Custom Java Desktop Database Application tutorial).
  2. Unzip the file.
  3. In NetBeans 6.0 or 6.1, Choose File | Open Project and navigate to the project folder to open the project.
  4. Open the CustomerEditor class in Design view.
  5. In the Inspector, select all of the components (with the exception of the Save and Cancel buttons).
  6. Right-click and choose Enclose In | Panel.
  7. Right-click the new panel and choose Copy.
  8. Open the CustomerRecordsView class in Design view.
  9. Right-click in the design area and choose Paste.
  10. Drag the panel to the right of the Customers table so that the form widens.
  11. Bind the text fields to the corresponding properties of masterTable.selectedElement.
  12. Drag the CountryCellListRenderer to the form and assign the renderer to the Country combo box (using the Advanced tab of the Bind dialog box).
  13. Add Previous and Next buttons.
  14. Right-click the Previous button and choose Events | Action | actionPerformed.
  15. In the body of the event handler, type or paste the following code: 
    if(masterTable.getSelectedRow() > 0)
        masterTable.getSelectionModel().setSelectionInterval(masterTable.getSelectionModel().getMinSelectionIndex() - 1, masterTable.getSelectionModel().getMinSelectionIndex() - 1);       
    
    
  16. Right-click the Next button and choose Events | Action | actionPerformed.
  17. In the body of the event handler, type or paste the following code: 
    if(masterTable.getSelectedRow() + 1 <  masterTable.getRowCount())
        masterTable.getSelectionModel().setSelectionInterval(masterTable.getSelectionModel().getMinSelectionIndex() + 1, masterTable.getSelectionModel().getMinSelectionIndex() + 1);
    

Optionally, you can also do the following things to polish the application:

  • Remove the Edit Customer button.
  • Change the New Customer button so that it does not open up a new dialog box (delete the methods in the below thesetSaveNeeded(true); statement in thenewRecord method).
  • Set minimum widths for the columns of interest (e.g. First Name and Last Name) in the customer table so that you can better browse them. To do so, right-click the customer table, choose Table Contents, and select the Columns tab. You can also remove columns from the table, though doing so would mean that the search wouldn't work for values in those columns.
  • In the refresh() action, callsetText("") each of the various text fields so the text fields do not remain populated. (This does not affect any records, because no records are selected after therefresh() action runs.

Once you done with the design changes, you main form might look something like the following screenshot.

next-preview.png 

When you run the application, you should be able to press the Preview and Next buttons and see the selection change in the table and the corresponding text fields and combo box.

I have published a draft of an extended tutorial on creating desktop Java applications on netbeans.org. The tutorial is based on my recent series of blog posts. Thanks to everybody who provided questions and suggestions! A lot of them have been incorporated into the tutorial. Others are on my to-do list and are not forgotten.

The main things that appear in the tutorial that were missing from the blog are currency and date rendering and more customizations of table columns.

The tutorial needs some more polishing, but I think it's in a reasonably useful state. Let me know what you think.

By the way, I'm going on vacation tomorrow, so excuse the upcoming silence. Talk to you in three weeks.

Another question that has come up in the course of my recent postings is how to bind a combo box selection to a JTable's elements (so that the rows of the table are determined by the selected item of the combo box).

Here are some rough steps to mocking up this behavior. These steps assume that you want db connectivity for the app and that you have a db based on the sql script here: http://weblogs.java.net/blog/pkeegan/archive/2008/05/input_on_a_new.html . Be sure to populate the db tables with actual data.

First we'll create the project, entity classes, and the form:

  1. Create a new Java Application project.
  2. Use the Entity Class from Database template in the New File wizard to create entity classes for the Clients and Orders db tables. Leave the Include Related Tables checkbox selected. (A class for the Countries table will also be generated, though we will not use it.)
  3. In the last page of the wizard, be sure to create a persistence unit.
  4. Create a new JFrame.
  5. Add a JComboBox and JTable.

Now we'll create a list of Clients and bind the combo box to that list.

  1. Right-click the combo box and choose Bind -> elements.
  2. Click Import Data to Form and choose the the appropriate db connection and the clients table. 

    This step generates the list object.

  3. Make sure clientsList is shown as the binding source and click OK.

At this point, you would might expect to be able to right-click the JTable, select Bind | elements, and bind to the selectedItem property of the combo box. (In fact, you can do this in NetBeans 6.5, and you might even want to try it in the upcoming M1 build). However, in NetBeans 6.1, the code generation for the binding between the JTable elements and the combo box's selectedItem doesn't work as you might expect. The generated code assumes that the selected item is a plain Object, not a Clients object with sub-properties that can be represented by columns. So ColumnBindings code is not generated. You can work around this problem with some manual coding:

  1. Right-click the JTable and choose Customize Code.
  2. *Above* the jScrollPane1.setViewportView(jTable1); line, insert the following code: 
    org.jdesktop.beansbinding.ELProperty eLProperty = org.jdesktop.beansbinding.ELProperty.create("${selectedItem.ordersCollection}");
    org.jdesktop.swingbinding.JTableBinding jTableBinding = org.jdesktop.swingbinding.SwingBindings.createJTableBinding(org.jdesktop.beansbinding.AutoBinding.UpdateStrategy.READ_WRITE, jComboBox1, eLProperty, jTable1);
    org.jdesktop.swingbinding.JTableBinding.ColumnBinding columnBinding = jTableBinding.addColumnBinding(org.jdesktop.beansbinding.ELProperty.create("${product}"));
           columnBinding.setColumnName("Product");
           columnBinding.setColumnClass(String.class);
           columnBinding = jTableBinding.addColumnBinding(org.jdesktop.beansbinding.ELProperty.create("${amount}"));
           columnBinding.setColumnName("Amount");
           columnBinding.setColumnClass(Integer.class);
           jTableBinding.setSourceUnreadableValue(null);
           bindingGroup.addBinding(jTableBinding);
           jTableBinding.bind();
    
  3. Click OK to close the Customize Code dialog box.
  4. Select the table and click the Properties button in the Properties window.
  5. For the model property, click the ellipsis (...) button to open the property editor.
  6. Click Reset to Default. This removes the superfluous table model that is generated when you add the table to the form but which is rendered obsolete by the binding code you have just added.

When you run the application, the JTable should respond to the selection in the combo box, as shown below.

comboToTable.png 

You still need to write a custom renderer for the JTable so that it shows human-relevant text (such as the name of one of the table columns). That task is covered here: http://weblogs.java.net/blog/pkeegan/archive/2007/10/binding_jcombob.html

Now and again someone will ask me how you can search records in a desktop database app. Here's a reasonably simple way to do so, using mechanisms that exist in Swing and the Beans Binding library. We will create a binding between the rowSorter property of the master table in the example in my previous entries and a text field that I've just added for the search string. For this binding we will need a binding converter so that the table knows how to respond to the search string.

To follow along, you can either continue with the project created in previous entries or begin with a new NetBeans project (Java Desktop Application project template) that connects to a database.

Let's get started. First of all, we'll add a label and a text field for the search field as shown below.

masterdetail5-searchfield.png 

Now we will add a converter class to the project.

  1. Create a new Java class in your project. Call itRowSorterToStringConverter.
  2. Replace the generated code in the new class with the following code: 
    package clientpurchaseapp;
    
    import javax.swing.JTable;
    import javax.swing.RowFilter;
    import javax.swing.table.TableRowSorter;
    import org.jdesktop.beansbinding.Converter;
    
    /**
     * Binding converter between String and regex RowFilter (encapsulated by RowSorterToStringConverter).
     *  */
    public class RowSorterToStringConverter extends Converter {
    
        private JTable table;
    
        public JTable getTable() {
            return table;
        }
    
        public void setTable(JTable table) {
            this.table = table;
        }
    
        @Override
        public Object convertForward(Object value) {
            return value.toString();
        }
    
        @Override
        public Object convertReverse(Object mask) {
            TableRowSorter sorter = new TableRowSorter(table.getModel());
    
            // The following statement makes the filter case-sensitive. If you want 
            //filter to work in a case-insensitive way, uncomment the line below, comment 
            //the 7 code lines below
            //sorter.setRowFilter(RowFilter.regexFilter(".*" + mask + ".*"));
    
            //The following 7 lines create a case-insensitive filter. If you want 
            //the filter to be case-sensitive, comment them out and uncomment the 
            //line above
            String m = mask.toString();
            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < m.length(); i++) {
                char c = m.charAt(i);
                sb.append('[').append(Character.toLowerCase(c)).append(Character.toUpperCase(c)).append(']');
            }
            sorter.setRowFilter(RowFilter.regexFilter(".*" + sb + ".*"));
            return sorter;
        }
    }
    
  3. Adjust the package statement, if necessary.
  4. Save the file and compile it. Compiling the file enables you to treat it as a bean that you can add to the form by dragging and dropping from within the IDE's GUI builder.
  5. Drag the class from the Projects window and drop it in white area surrounding the form, as shown in the screenshot below. masterdetail5-dragconverter.png 

    A node called rowSorterToStringConverter1 should appear in the Inspector window.

    masterdetail5-inspector.png
  6. Select the rowSorterToStringConverter1 node and set its table property tomasterTable.

We'll use this converter when we create the binding.

To create the binding:

  1. In the main form, right-click the text field and choose Bind | text.
  2. In the Bind dialog, select masterTable as the binding source and rowSorter as the expression. masterdetail5-binding-basic.png
  3. Click the Advanced tab of the dialog box.
  4. From the Converter combo box, selectrowSorterToStringConverter1. masterdetail5-binding-advanced.png
  5. Click OK to close the dialog and generate the binding code.

Now when you run the application, you should be able to type in the Search Filter field and see that the list of rows is reduced to only rows that contain text matching what you have typed.

masterdetail5-filter-working.png 

Note: If you have been following this whole series of posts, you will need to make a few changes to get the New Record and Edit Record buttons to work correctly. The Edit Record button doesn't work correctly because the number of the record to display is determined according to the records displayed but applied to the whole list of records in the database. In other words, if you select the first record in a filtered list, the first record of the whole database appears for editing in the dialog.

masterdetail5-synchproblem.png 

The New Record button fails with an exception because the code to select the new row is determined according to number of records in the database table, not according to the number of records currently displayed in the table.

To fix the first problem, replace the line:

ec.setCurrentRecord(list.get(masterTable.getSelectedRow()));

with:

ec.setCurrentRecord(list.get(masterTable.convertRowIndexToModel(masterTable.getSelectedRow())));

To fix the second problem, replace the line:

int row = list.size() - 1;

with:

int row = masterTable.getRowCount() - 1;

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 therefresh() 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 clientConfirmedas 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 handlercancelNewClient.
  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 tryblock).

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, thesetVisible() 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 recordSelectedfor 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 lineec.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.

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 ]]>

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.

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 propertycurrentRecord, 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 generatedsetCurrentRecord 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 textproperty of each text field to the corresponding property of theClients object represented bycurrentRecord.

To bind a dialog text field to the appropriate property ofcurrentRecord:

  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 thecurrentRecord 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.

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.IDENTITYannotation for that column in the table's entity class. See http://weblogs.java.net/blog/pkeegan/archive/2007/12/index.htmlfor 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.IDENTITYannotations (as noted above) to the identity columns in theClient.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!

In the first comment on my last post, I was asked why there isn't a separate JPanel generated for both the master and detail views in NetBeans' Java Desktop Application template. In the process of trying to answer that question, I learned a thing or two about the Swing Application Framework and was reminded of a very useful trick in the GUI Builder (the ability to generate a container to surround components that have already been added to the form).

I found that the main reason why there is just one panel to serve both the master and detail views is the structure of the org.jdesktop.application.FrameView component.

When you generate a master/detail database app using the Java Desktop Application template in NetBeans IDE 6.0, you get a component structure similar to what we ended up with in the ContactMasterDetail app that I wrote about a few days ago. The representation of those components is shown below in a screenshot of the Inspector window.

enclose-in-InspectorPrevious.png 

You'll notice the FrameView component appears as the first node under the root node of the form. If you switch from Design view to Source view and poke around the code, you'll find that the ContactsMasterDetailView class extends FrameView and that the other components get added to this component.

I decided to learn more about this class by looking at it's Javadoc, which is bundled with the IDE. So I right-clicked FrameView in the Source Editor and chose Show Javadoc as shown below.

enclose-in-showjavadoc.png 

Looking at the Javadoc, I didn't find much about FrameView, but I did noticed that it extends View, which has much more interesting documentation. It turns out that it is a wrapper for some standard elements - a menu bar, tool bar, component, and a status bar, all of which are optional. It seems that the IDE's Java Desktop Application uses three of those (menu bar, component, and status bar) and that the generated mainPanel serves as the "component".

So, in the current app, we can't remove mainPanel and replace it with two new panels (at least not without a lot of fuss). But we can put two panels within the main panel and divide the parts of the UI that way. And there is a way we *can* do that without too much fuss - use the Enclose In feature:

  1. In the Inspector window (remember to put the form back into Design view so that the Inspector window is displayed), Ctrl-click to select masterScrollPane, newButton, and deleteButton.
  2. Right-click the selection and choose Enclose In > Panel.
  3. enclose-in.png
  4. Rename jPanel1 to something that you'll remember, e.g. masterPanel.
  5. Ctrl-click to select detailScrollPane, saveButton, refreshButton, deleteDetailButton, and newDetailButton.
  6. Right-click the selection and choose Enclose In > Panel.
  7. Rename jPanel2 to detailPanel.

Voila! Now the master and detail sections are separately encapsulated on the form, which will make it easier to add other components to the form without disturbing the layout of the existing components. You can also easily provide some visual separation between the two sections. I did the latter by adding some titled borders to both masterPanel and detailPanel (which you can do by selecting the panel in the Inspector window and customizing its border property in the Properties window.

My results are below. Not a work of art, but hopefully demonstrative of some of the possibilities at your fingertips.

enclose-in-runningapp.png

I haven't had a chance to write a full tutorial on creating a true master/detail view using the NetBeans GUI Builder. Naturally, lots of questions have arisen about it in the meantime, particularly since the IDE's Java Desktop Application template shows things that pretty much beg those questions.

For example, if you follow the Building a Java Desktop Database Application, you will encounter something curious in page 4 of the wizard for the Java Desktop Application template.

master-detail-notableoption.png 

You have the option to display your "detail" as text fields or as a table. But the table option is greyed out. Why is that?

The answer is that table vs. text field is not the only difference here. If you select "Textfields", the generated text fields represent columns from the master table. If you select "Table", the a JTable is generated to represent columns from a different database table, which is related to the master table through a foreign key. Since the car database example only has a single table, the "Table" option here is irrelevant.

To show how the "Table" option works, let's create a project based on a database that contains two tables. One table will hold people's names. The other table will hold email addresses. "Normalizing" the database in this manner enables the database to scale more efficiently. We can add multiple email addresses for each person without setting a high character limit for the email address column. And we don't need to have any application logic to parse out multiple email addresses that are stored in the same record.

So let's get started. First we'll set up the database:

  1. Choose Tools > Java DB Database > Start Server. (If this option is not available to you, see Setting Up the Database section of the Building a Java Desktop Database Application tutorial.) 

    Within a few seconds, you should see a message in the Output window indicating that the database server has started.

  2. Choose Tools > Java DB Database > Create Database. In the Create Java DB Database dialog box: 
    1. Fill in CONTACTS2008 as the database name. For both User Name and Password, enter nbuser.
    2. For both User Name and Password, enternbuser.
    3. Click OK.
    master-detail-createdb.png
  3. In the Services window, once right-click the node for your database connection (CONTACTS2008) and choose Connect as shown in the image below.
  4. master-detail-connect.png
  5. In the Services window, once again right-click the CONTACTS2008 connection, and choose Execute Command. 

    master-detail-execute.png

    A new tab called SQL Command should appear in the Source Editor.

  6. In the SQL Command tab, paste in the following SQL script: 
    
       CREATE TABLE CONTACTS2008
        (
        ID INTEGER NOT NULL GENERATED ALWAYS AS IDENTITY,
        FIRST_NAME VARCHAR(50),
        LAST_NAME VARCHAR(50),
        TITLE VARCHAR(50),
        NICKNAME VARCHAR(50)
        );
    
        ALTER TABLE CONTACTS2008
           ADD CONSTRAINT CONTACTS2008_PK Primary Key (
          ID);
    
        CREATE TABLE EMAIL_ADDRESSES2008
        (
        ID INTEGER NOT NULL ,
        ADDRESS VARCHAR(50) NOT NULL
        );
        
        ALTER TABLE EMAIL_ADDRESSES2008
           ADD CONSTRAINT EMAIL_ADDRESSES2008_PK Primary Key (
          ADDRESS);
    
    ALTER TABLE EMAIL_ADDRESSES2008
       ADD CONSTRAINT CONTACTS2008_FK Foreign Key (ID)
       REFERENCES CONTACTS2008 (ID);
       
    INSERT INTO CONTACTS2008 (FIRST_NAME, LAST_NAME, TITLE, NICKNAME)
           VALUES   ('Jake', 'Roberts', 'Mr.', '"The Snake"'), 
                    ('Andre', 'Giant', 'Mr.', '"The Giant"');   
       
    
    INSERT INTO EMAIL_ADDRESSES2008 (ID, ADDRESS)
           VALUES   (1, 'snake@ifpwafcad.com'),
                    (2, 'bobo@ifpwafcad.com'),
                    (1, 'jake@ifpwafcad.com'),
                    (2, 'haha@ifpwafcad.com');
                    
    
  7. Click the master-detail-sqlrun.png (Run SQL) button in the toolbar of the editor. 

    Several lines should appear in the Output window that indicate that the script was run successfully.

    This script creates two database tables and populates each with a few rows of data. The CONTACTS2008 table contains names of people (with columns for first name, last name, title, and nickname). The ID column is "GENERATED ALWAYS AS IDENTITY", meaning that every time a new row is created in that table, a new record number is automatically generated for that row. We do this to ensure that each record is unique, which enables us to reference unique records from other tables.

    The EMAIL_ADDRESSES2008 table is used to store email addresses for the names in the CONTACTS2008 table. A foreign key is set on the ID column of the EMAIL_ADDRESSES2008 table to reference the ID column in the CONTACTS2008 table so that each email address is associated with a specific person.

Now that the database is set up, we can use the New Project wizard to help us generate the Master/Detail form.

  1. Choose File > New Project.
  2. In the New Project wizard's Choose Project page, select the Java category and the Java Desktop Application template.
  3. master-detail-javadesktop.png
  4. In the Name and Location page: 
    1. Type ContactMasterDetail as the project name.
    2. Select Database Application as the application shell.
    master-detail-dbshell.png
  5. In the Master Table page, select the CONTACTS2008 connection. 

    The CONTACTS2008 table should be selected by default, and 5 database column names should be listed in the Columns To Include list.

  6. Select the Id column and click the < button to move out of the list of columns to be included. (Since the ID is generated, we want to make sure that users don't try to replace the ID with their own.)
  7. master-detail-mastertable.png
  8. In the Detail Options page, select the Table radio button. 

    By selecting the Table button, the Detail panel in the generated application will use the ADDRESS2008 table.

    The Available Columns list should now display just the ID column, and the Columns To Include list should just show the ADDRESS column. Leave those values as they are and click Finish to exit the wizard.

    master-detail-detailoptions.png

After you click Finish, code for the application is generated. In the Projects window, you can view the various classes that are generated by expanding the ContactMasterDetail > Source Packages > contactmasterdetail node. In the Source Editor, you can see the visual design for the master/Detail view. The top table is the "master" table, which shows columns from the CONTACTS2008 table. The bottom table is for the detail view, which shows the ADDRESS column from the EMAIL_ADDRESSES2008 table. When a row is selected in the master table, the detail table displays all address records that reference the contact that is selected in the master table.

You can run the application (choose Run > Run > Run Main Project) to see the relations between the tables. When you select a contact in the master table, the email addresses for that contact are displayed in the detail table.

master-detail-runbeforefixing.png 

However, the application is not quite ready to work yet. You still need to make a few changes to make the creation of new records work correctly.

The first problem is that our use of the "identity" construct for the ID column in the CONTACTS2008 table is not reflected in the entity class that was generated for the table. So if we try to use the application to create a new row in the database, an exception occurs because the application tries to assign a value to the ID column when it should just let it be generated automatically.

Luckily this is easy to fix. You just need to add one line of code to the Contacts2008 entity class:

  1. In the Projects window, double-click Contacts2008.java to open the entity class in the Source Editor.
  2. On the line after @Id, insert@GeneratedValue(strategy=GenerationType.IDENTITY).

The relevant section of code should look like the following screenshot:

master-detail-annotation.png 

With that problem fixed, we should now be able to use the New button in the running application to add a new record to the master table.

However, there is still a second problem we have to fix. When creating a new address record, the record does not appear immediately. In order to see the new empty record so that you can edit it, you have to select a different contact and then reselect the contact for which you want to add the address. This is a bug that is a result of the IDE's support for beans binding falling out of synch with the beans binding library, which underwent some late bug fixes. This will be fixed in a post-6.0 IDE release, but for now you have to use this workaround:

  1. In the Design view of the ContactMasterDetailView class, right-click the bottom table and choose Bind > elements.
  2. In the Bind dialog box, select the Advanced tab.
  3. Select the Unreadable Source Value checkbox.
  4. Click OK.
master-detail-unreadable.png 

Now when we run the application again, we should be able to use the New button below the detail view to add new email addresses for our contacts.

That's it for now. I plan to elaborate on this topic more over the next few months (though I'll probably be pretty quiet over the next few weeks as I'm taking some time away from the computer). But in the meantime, please feel free to write in about specific aspects of DB applications you would like me to help you explore.

Writing converters and validators for beans binding is not hard. But of course it is much easier to learn how to do so when you have concrete examples in front of you.

The most recent NetBeans daily build has a new sample project that includes custom converters and validators. The sample project is called Client Editor and can be found by choosing File > New Project > Samples > Java. This sample is not in Beta 2, but it will be in the release candidate which is due soon.

Since you might not want wait for the release candidate or final release (or download a nightly build) just to see the sample code, I'll provide an example of a converter and a validator here.

For converters, all you need to do is:

  • Extend org.jdesktop.beansbinding.Converter and pass the source type and target type as parameters.
  • Implement the convertForward and convertReverse methods.

Here is a sample converter that converts an Integer into a String. There is already a default Integer/String converter, but this converter adds the custom behavior of returning 0 if the user does not enter an integer.

import org.jdesktop.beansbinding.Converter; public class AgeConverter extends Converter { public String convertForward(Integer arg) { return String.valueOf(arg); } public Integer convertReverse(String arg) { int value; try { value = (arg == null) ? 0 : Integer.parseInt(arg); } catch (NumberFormatException ex) { value = 0; } return value; } }

For validators, it is even simpler:

  • Extend org.jdesktop.beansbinding.Validator and pass the source type to be validated as the parameter.
  • Implement the validate() method.

This example makes sure that an entered age remains with the range of 1 to 199:

import org.jdesktop.beansbinding.Validator; public class AgeValidator extends Validator { public Validator.Result validate(Integer arg) { if ((arg < 1) || (arg > 199)) { return new Result(null, "Age range is 1-199"); } return null; } } 

Notice how in this case the converter and validator work together. The converter handles a non-integer entry by returning the integer 0, which prevents the user from receiving a generic message that the synchronization failed. Then 0 is passed to the validator, which outputs a more meaningful message ("Age range is 1-199").

Just a few days after I published my first major foray into explaining Beans Binding in NetBeans, I received some feedback asking how to populate a JComboBox with reasonable display values from a data source. I had been wondering the same thing. With the help of Honza Stola (explanation plus code snippet) and the trusty Beans Binding Javadoc (available from NetBeans by choosing Help > Javadoc References > Beans Binding), here's my stab at it.

First some background. The Beans Binding library supplies special classes for binding JTables, JLists, and JComboBox components with data from another source (such as a database).

For JTable objects, you use the JTableBinding class to bind the table to a List object. The objects contained in the List object correspond with rows in the JTable. You can then use JTableBinding.ColumnBinding to map specific columns from the data source to the JTable columns. (The IDE helps you generate all of this code, whether you are using the Bind dialog box or you are using the New Java Desktop Application template in the New Project Wizard.)

For JList objects, you use the JListBinding class to bind a List of object to the JList component. Since each item in the JList corresponds with an object rather than a simple display value, you might need to use JListBinding.DetailBinding to map a property from the objects in the List to the display value of the JList. In the IDE, you have JListBinding.DetailBinding code generated by opening the Bind dialog box and filling in the Display Expression field with an EL expression to refer to a property of the bound List object. In the image below, the Expression Source is a List object that contains objects that represent all of the rows in the Customer database table. The Display Expression field is filled with an EL expression that causes the displayed values to be derived from the name property of the Customer entity class (which represents the NAME column in the CUSTOMER database table).

jlist-bind-dialog.png

There is also a JComboBoxBinding class in the Beans Binding library, which enables you to bind a List object to a combo box. Unfortunately, as of Beans Binding 1.1.1, there is not yet a DetailBinding class that enables you to specify how to derive the values that are displayed in the JComboBox. If you were to bind a combo box to a database table and then run the application, you would get a combo box that looks something like the following image.

bind-without-setting-detail.png

Probably not what you want! To fix this, you could override thetoString() method of the bean you are binding. The disadvantage there is that the toString() method might need to be used by other things than just your JComboBox. A better way to handle this is to customize how the cell is rendered. Here's some boilerplate code for that approach:

jComboBox1.setRenderer(new 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 MyEntityClass) {
                   MyEntityClass mec = (MyEntityClass)value;
                   setText(mec.getPropertyFromMyEntityClass());
               }
               return this;
           }
       })

You can add that code in the IDE's GUI Builder by right-clicking the combo box, choosing Customize Code, and typing the code just below where the combo box is instantiated. The image below shows the Customize Code dialog with the code I have added and customized to work my Customer entity class.

customcode.png

When you run the application again, you should then get something that looks much more reasonable.

bind-with-detail.png

Recently I've been working on help for NetBeans support for the new beans binding spec (JSR-295). Much of the excitement around beans binding is that it greatly simplifies data binding in Java desktop apps. No more writing of adapter classes by hand to enable the display of values from a database in JTable.

But there is also the question of how beans binding simplifies general UI programming. Much of Java GUI programming involves specifying event listeners for UI components and then writing code to specify what happens in response to the UI event. This makes sense for cases when some complex processing needs to occur. But at other times, the only thing that really needs to happen is the passing of values from one component to another. In such cases, specifying the event listeners and event handlers comprises more lines of code than actual does the application logic. Yes, the IDE's Connection Wizard is very helpful in such situations, but there is still a lot of code that needs to be created.

Take the ColorPicker example (shown below) that some of you NetBeans stalwarts may remember as a sample app from early NetBeans releases.

colorpicker-old.png

In that program, we had three sliders that represented the red, green, and blue color spectra. We also had a custom bean that showed a resulting color depending on the red, green, and blue values that were passed to it from the sliders.

In the past, to use this bean you had to generate event listeners and create handling code to keep the color preview panel in sync with the adjustments made to the sliders. Now it is much simpler. At design time, you just need to declare bindings between the slider value properties and the corresponding properties in the ColorPreview bean. You can establish these bindings at design time through the GUI Builder.

To see how this works, do the following:

  1. Create a new Java Application project in NetBeans by following these steps: 
    1. Choose File > New Project.
    2. Select the Java category and choose Java Application. Click Next.
    3. Name the project ColorPicker.
    4. Clear the Create Main Class checkbox.
    5. Click Finish
  2. Create a new JFrame by right-clicking the ColorPicker project node and choose New > JFrame Form. Call the formColorPickerFrame, specifyexamples.colorpicker as the package, and Click Finish. 

    If the JFrame template does not appear in the contextual menu, select Other. Then select the Swing GUI Forms category and select the JFrame Form node.

  3. Once the blank form opens in the editor area, drag three JSlider components to the form from the Palette window.
  4. To make it easier to keep track of which slider belongs to which color, rename the variables for the sliders toredSlider, greenSlider, andblueSlider

    You can rename a component's variable by right-clicking the component in the Design area and choosing Change Variable Name.

  5. In the Properties window, change the maximumproperty for each the sliders to 255 so that the value range of each slider corresponds the value range for RGB colors. You can change this value simultaneously for all of the sliders by selecting all three sliders in the Inspector window before changing the value in the Properties window.
  6. Using the Properties window, use the Other Properties |property to designate a titled border for each slider and give each slider a title corresponding to the color it represents.
  7. Download the ColorPreview.java and ColorPreviewBeanInfo.java classes to the folder on your system that contains the ColorPickerFrame class. 

    ColorPreview is a visual bean based on a JPanel that has customred, green, and blueproperties.

  8. Right-click the examples.colorpicker node and choose Compile Package. 

    Doing this makes the ColorPreview bean a component that you can drag on to your form.

  9. Drag the ColorPreview class from the Projects window to the Design area and drop it below the sliders. Resize it to taste.
  10. Bind the bean's blue property to the value property of the blue slider. To do this: 
    1. Select the ColorPreview panel in the Design area.
    2. In the Properties window, select the Binding tab.
    3. Click the ellipsis (...) button that is next to theblue property to open the Bind dialog box.
    4. In the Binding Source combo box, selectblueSlider.
    5. In the Binding Expression combo box, select value int as shown below.
    6. Click OK to establish the binding. 

      colorpreview-blue-bind.png

  11. Repeat step 10 to bind the green andred properties to their corresponding sliders.

When you run the application, you should see something that works very much like the application in the first image in this blog entry, and all created without having to specify any event listening or handling.

I have just finished a short guide to beans binding in NetBeans that is being published to coincide with the fresh Beta 2 release of NetBeans IDE 6.0. In this guide I tackle the basics and introduce the advanced features. This document will evolve with more details as I find time to fill them in. Please send me feedback on what you would like to see added and/or clarified.

In related news, Beta 2 also contains an update to the beans binding library (version 1.1) that greatly improves performance. Shannon Hickey writes about this in greater detail in his blog. Since code freeze for Beta 2, version 1.1.1 of Beans Binding has been released. This micro version contains one additional bug fix over the 1.1 version that is in NetBeans beta 2. This difference won't affect the code that you write based on the library. And naturally subsequent builds of NetBeans 6 will have the latest version.

Filter Blog

By date: