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.
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:
Open the Clients.java class.
Navigate to the line after the one containing @Id.
Enter the line @GeneratedValue(strategy=GenerationType.IDENTITY).
Press Ctrl-Shift-I to generate the necessary import statements for this annotation.
Repeat the process for Orders.java.
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.
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:
Right-click the package that contains your classes and choose New | Other. Select Swing GUI Forms | JDialog Form template and name it EditClient.
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.
Edit the display text for JLabels.
Add two buttons and name them Save and Cancel.
(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.
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:
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.
Press Alt-Insert (or right-click and choose Insert Code) and choose Add Property.
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.
Click OK to generate the property.
We now need to customize the generated setCurrentRecord method. Replace the body of the method with these three lines:
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:
Right-click a text field and Choose Bind | text.
In the Bind dialog box, select Form as the Binding Source (note that Form is at the very bottom of the drop-down list).
In the Binding Expression drop-down list, expand the currentRecord node and select the property corresponding to the text field that you are binding.
Click OK to close the Bind dialog box.
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.
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,
CREATE TABLE EMAIL_ADDRESSES (
CLIENT_ID INTEGER NOT NULL,
ADDRESS VARCHAR(50) NOT NULL PRIMARY KEY,
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,
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,
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:
Choose File | New Project.
Select the Java category and the Java Desktop Application template.
In the Name and Location page of the wizard, select the Database Application skeleton.
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.
In the Detail Options page, click the Table radio button and select the orders table from the combo box.
Click Finish to exit the wizard.
Choose Run | Run Main Project to see the main application window.
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!
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.
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.
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:
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.
Right-click the selection and choose Enclose In > Panel.
Rename jPanel1 to something that you'll remember, e.g. masterPanel.
Ctrl-click to select detailScrollPane, saveButton, refreshButton, deleteDetailButton, and newDetailButton.
Right-click the selection and choose Enclose In > Panel.
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.
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.