Saturday, August 29, 2009

Simple Swing App - The View

In order to understand the view portion of the code, you must first understand the MVC framework class from which it extends. Let me list the whole class here,and then go over the important pieces and parts. Here is FormInputPanel.java:

public abstract class FormInputPanel
extends JPanel implements PropertyChangeListener {

private Model _model;
private boolean _processingData;
private Map<String, WeakReference<Method>> _methodMap;

/** Creates a new instance of FormInputPanel */
public FormInputPanel() {
super();
init();
}

public FormInputPanel(Model model) {
this();
setModel(model);
}

/**
* Return a title associated with this panel. This title can be used for
* text such as, a window title, tab text, or titled border text around
* this panel.
*/
public abstract String getTitle();

public void modelToView() {}

public void viewToModel() {}

public void updateModel(String methodName, Class[] params, Object[] values) {
if (isProcessingData()) {
return;
}

setProcessingData(true);
try {
Method method = getMethod(methodName, params);
method.invoke(_model, values);
} catch (IllegalArgumentException ex) {
ex.printStackTrace();
} catch (IllegalAccessException ex) {
ex.printStackTrace();
} catch (NoSuchMethodException ex) {
ex.printStackTrace();
} catch (InvocationTargetException ex) {
ex.printStackTrace();
} finally {
setProcessingData(false);
}
}

public void propertyChange(PropertyChangeEvent evt) {
modelToView();
}

public void setModel(Model model) {
if (_model != null) {
_model.removePropertyChangeListener(this);
}
_model = model;
registerModel(_model);
modelToView();
}

/**
* Create form components to be displayed in this panel. The order
* of Components returned will be the same order added to the panel.
*/
protected abstract Component[] createForms();

/**
* Allow subclasses to register with model as PropertyChangeListeners
* for specific properties.
*/
protected abstract void registerModel(Model model);

protected Model getModel() {
return _model;
}

protected boolean isProcessingData() {
return _processingData;
}

protected void setProcessingData(boolean b) {
_processingData = b;
}

protected void installBorder(JComponent comp, Border border) {
comp.setBorder(border);
}

private void init() {
_methodMap = Collections.synchronizedMap(new HashMap());

setLayout(new BorderLayout());
setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
Box inputs = Box.createVerticalBox();
Component[] comps = createForms();
for (int i=0; i<comps.length; i++) {
inputs.add(comps[i]);
}

add(inputs, BorderLayout.NORTH);
}

private Method getMethod(String methodName, Class[] params)
throws NoSuchMethodException {

WeakReference<Method> wr = _methodMap.get(methodName);
Object obj = null;
if (wr != null && (obj = wr.get()) != null) {
return (Method)obj;
}

Method method = _model.getClass().getMethod(methodName, params);
_methodMap.put(methodName, new WeakReference<Method>(method));

return method;
}
}
The important pieces are the following methods:
  • protected abstract Component[] createForms(); This method builds the actual UI components. This array of components returned are added to the panel in top-down fashion via a vertical box.
  • public void modelToView() {} This method takes the values from the associated Model and populates the view components.
  • public void viewToModel() {} This method takes the user-entered values in the view components and populates the Model.
  • protected abstract void registerModel(Model model); This method gives the view the opportunity to register itself (or anything else) as a PropertyChangeListener on the Model.

Just as important is knowing the order that these methods get called. When you call the constructor with the Model parameter, the methods get called in the following order:

  1. createForms() gets called and the components are added to the panel.
  2. registerModel(Model model) gets called.
  3. modelToView() is finally called so that the components can be back-filled with the Model values.
This order is important because the values from the Model will not be available when the UI components are first constructed. You will only be able to get the Model values upon the calling of modelToView. This will all probably make more sense when we see the application's code.

So, here is the code for MyContactsEditor.java - at least most of it; namely the part that shows those methods implemented from FormInputPanel.

public class MyContactsEditor extends FormInputPanel
implements ChangeListener {

private static final long serialVersionUID = -1459578361121044353L;
private MyUser currentUser;
private JButton saveBtn;
private JComboBox userList;
private JTextField firstFld;
private JTextField lastFld;
private JTextField ssnFld;
private JXDatePicker datePicker;
private JLabel contactImg;

/**
*
* @param model
*/
public MyContactsEditor(MyContactsModel model) {
super(model);
setBorder(BorderFactory.createEmptyBorder(3, 3, 0, 3));
List<MyUser> users = model.getContacts();
for (Iterator<MyUser> iter = users.iterator(); iter.hasNext();) {
userList.addItem(iter.next());
}
}

/**
*
* @return
*/
@Override
public String getTitle() {
return "Contact Editor";
}

/**
*
*/
@Override
public void modelToView() {
super.modelToView();
if (currentUser == null) {
firstFld.setText("");
lastFld.setText("");
ssnFld.setText("");
datePicker.setDate(null);
contactImg.setIcon(null);
} else {
firstFld.setText(currentUser.getFirstName());
lastFld.setText(currentUser.getLastName());
ssnFld.setText(currentUser.getSocialSecurityNumber());
datePicker.setDate(currentUser.getBirthday());
contactImg.setIcon(ImageTools.imageToIcon(
currentUser.getImage(),
contactImg.getPreferredSize(), true));
}
}

/**
*
* @return
*/
@Override
protected Component[] createForms() {
JStatusPanel sp = new JStatusPanel();
sp.addRightComponent(new TimeLabel());

Component[] forms = new Component[]{
buildUserImage(),
buildUserList(),
Box.createVerticalStrut(5),
new JSeparator(),
buildContactInputs(),
new JSeparator(),
buildButtons(),
sp
};

return forms;
}

/**
*
* @param model
*/
@Override
protected void registerModel(Model model) {
MyContactsModel cm = (MyContactsModel) model;
model.addPropertyChangeListener(this);

currentUser = cm.getContacts().get(0);
}
So follow the methods in the order that they are called. The building of the UI components is split into their own methods. The following image shows what areas each method builds:


First, let me apologize for the amount of code in this huge post. I think the code can sometimes illustrate more than what I discuss here. In any event, if anyone has any questions/comments/suggestions, feel free to add your input and I will respond to any questions.

Now, from the above code, you can see how the model values are used for populating the view. Let me now show some of the methods that build the UI. I did not choose to implement the viewToModel method. Instead, the event handlers for the buttons do all the work - I will cover that method last. For now, here is the UI construction:

private Component buildUserList() {
JPanel pnl = new JPanel(new FlowLayout(FlowLayout.CENTER));

userList = new JComboBox(new DefaultComboBoxModel());
userList.addActionListener(new ActionListener() {

public void actionPerformed(ActionEvent e) {
currentUser = (MyUser) userList.getSelectedItem();
modelToView();
}
});
pnl.add(userList);

return pnl;
}

private Component buildUserImage() {
JPanel pnl = new JPanel(new FlowLayout(FlowLayout.CENTER));

contactImg = new JLabel() {
@Override
public Dimension getPreferredSize() {
return new Dimension(100, 100);
}

@Override
public Dimension getMinimumSize() {
return getPreferredSize();
}

@Override
public Dimension getMaximumSize() {
return getPreferredSize();
}

@Override
public int getIconTextGap() {
return 0;
}

@Override
public int getHorizontalAlignment() {
return SwingConstants.CENTER;
}
};
pnl.add(contactImg);

return pnl;
}

/**
*
* @return
*/
private Component buildContactInputs() {
Box box = Box.createVerticalBox();
firstFld = new JTextField(25);
firstFld.setName("First Name");

lastFld = new JTextField(25);
lastFld.setName("Last Name");

try {
ssnFld = new JFormattedTextField(new MaskFormatter("###-##-####"));
ssnFld.setName("Soc. Sec. Num.");
ssnFld.setColumns(25);
} catch (ParseException ex) {
ssnFld = new JTextField(25);
Logger.getLogger(MyContactsEditor.class.getName()).log(Level.SEVERE, null, ex);
}

datePicker = new JXDatePicker();
datePicker.setFormats(new SimpleDateFormat("MMMMM dd, yyyy"));
datePicker.getEditor().setName("Birth Date");
datePicker.getEditor().setEditable(false);

JPanel p1 = new JPanel(new FlowLayout(FlowLayout.LEFT));
JLabel l1 = new JLabel("First Name:", SwingConstants.RIGHT);
p1.add(l1);
p1.add(firstFld);
box.add(p1);

JPanel p2 = new JPanel(new FlowLayout(FlowLayout.LEFT));
JLabel l2 = new JLabel("Last Name:", SwingConstants.RIGHT);
p2.add(l2);
p2.add(lastFld);
box.add(p2);

JPanel p3 = new JPanel(new FlowLayout(FlowLayout.LEFT));
JLabel l3 = new JLabel("Soc Sec Num:", SwingConstants.RIGHT);
p3.add(l3);
p3.add(ssnFld);
box.add(p3);

JPanel p4 = new JPanel(new FlowLayout(FlowLayout.LEFT));
JLabel l4 = new JLabel("Birth Date:", SwingConstants.RIGHT);
p4.add(l4);
p4.add(datePicker);
box.add(p4);

UITools.equalizeSizes(new JComponent[]{l1, l2, l3, l4});

ValidationPanel vp = new ValidationPanel();
vp.setInnerComponent(box);
vp.addChangeListener(this);
ValidationGroup grp = vp.getValidationGroup();
grp.add(firstFld, Validators.REQUIRE_NON_EMPTY_STRING);
grp.add(ssnFld, Validators.regexp("[0-9]{3}-[0-9]{2}-[0-9]{4}", "Invalid Social Security Number", false));

return vp;
}

So this basically shows the construction of the UI inputs. Again, the thing to remember here is that they do not get back-filled with data until after they are constructed and modelToView gets called.

Lastly, let's see the construction of the buttons. This also shows the event handlers that sync the UI data with the model:

private Component buildButtons() {
Box box = Box.createHorizontalBox();
box.setBorder(BorderFactory.createEmptyBorder(5, 3, 5, 3));

saveBtn = new JButton("Save");
final JButton addBtn = new JButton("Add");
final JButton deleteBtn = new JButton("Delete");
final JButton resetBtn = new JButton("Reset");

saveBtn.setMnemonic('S');
saveBtn.addActionListener(new ActionListener() {

public void actionPerformed(ActionEvent e) {
try {
MyUser modifiedUser = new MyUser(firstFld.getText(),
lastFld.getText(),
ssnFld.getText(),
datePicker.getDate(),
currentUser.getImage());
MyContactsModel cm = (MyContactsModel) getModel();
if (cm.modifyUser(currentUser, modifiedUser)) {
DefaultComboBoxModel cbm = (DefaultComboBoxModel) userList.getModel();
rebuildUserList();
currentUser = modifiedUser;
cbm.setSelectedItem(modifiedUser);
EventBus.publish(new StatusEvent("User Modified: " + currentUser));
} else {
EventBus.publish(new StatusEvent("Unable to modify user: " + currentUser));
}
} catch (Exception ex) {
Logger.getLogger(MyContactsEditor.class.getName()).log(Level.SEVERE, null, ex);
}
}
});

addBtn.setMnemonic('A');
addBtn.addActionListener(new ActionListener() {

public void actionPerformed(ActionEvent e) {
try {
MyContactsModel cm = (MyContactsModel) getModel();
MyUser newUser = new MyUser(
firstFld.getText(),
lastFld.getText(),
ssnFld.getText(),
datePicker.getDate());
if (cm.addUser(newUser)) {
DefaultComboBoxModel cbm = (DefaultComboBoxModel) userList.getModel();
rebuildUserList();
currentUser = newUser;
cbm.setSelectedItem(newUser);
saveBtn.setEnabled(cbm.getSize() > 0);
deleteBtn.setEnabled(cbm.getSize() > 0);
EventBus.publish(new StatusEvent("User Added: " + newUser));
} else {
EventBus.publish(new StatusEvent("User Already Exists: " + newUser));
}
} catch (Exception ex) {
Logger.getLogger(MyContactsEditor.class.getName()).log(Level.SEVERE, null, ex);
}
}
});

deleteBtn.setMnemonic('D');
deleteBtn.addActionListener(new ActionListener() {

public void actionPerformed(ActionEvent e) {
int answer = JOptionPane.showConfirmDialog(
MyContactsEditor.this,
"Are you sure you want to delete \"" + currentUser + "\"?",
"Delete Contact",
JOptionPane.YES_NO_OPTION);
if (JOptionPane.YES_OPTION != answer) {
return;
}

MyContactsModel cm = (MyContactsModel) getModel();
DefaultComboBoxModel cbm = (DefaultComboBoxModel) userList.getModel();
int idx = cbm.getIndexOf(currentUser);
if (cm.removeUser(currentUser)) {
String deletedUser = currentUser.toString();
cbm.removeElementAt(idx);
saveBtn.setEnabled(cbm.getSize() > 0);
deleteBtn.setEnabled(cbm.getSize() > 0);
EventBus.publish(new StatusEvent("User Deleted: " + deletedUser));
} else {
EventBus.publish(new StatusEvent("Delete Failed for: " + currentUser));
}
}
});

resetBtn.setMnemonic('R');
resetBtn.addActionListener(new ActionListener() {

public void actionPerformed(ActionEvent e) {
modelToView();
EventBus.publish(new StatusEvent("User data reset: " + currentUser));
}
});

UITools.equalizeSizes(new JComponent[]{saveBtn, addBtn, deleteBtn, resetBtn});

box.add(saveBtn);
box.add(Box.createHorizontalStrut(3));
box.add(resetBtn);
box.add(Box.createHorizontalGlue());
box.add(Box.createHorizontalStrut(3));
box.add(deleteBtn);

return box;
}

/**
*
*/
private void rebuildUserList() {
MyContactsModel cm = (MyContactsModel) getModel();
DefaultComboBoxModel cbm = (DefaultComboBoxModel) userList.getModel();

List<MyUser> users = cm.getContacts();
cbm.removeAllElements();
for (Iterator<MyUser> iter = users.iterator(); iter.hasNext();) {
userList.addItem(iter.next());
}
}
That's really it for the view portion. Next time we will just point out the other framework libraries that are being used. By looking at all the code above, you can already see some of the frameworks being used such as EventBus notifications to the JStatusPanel and Simple Validation added to the form inputs.

Just to wrap up the application as a whole, I will end this post showing the code used to create and launch the app:

public class ContactsApp {

public static void main(final String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
creatAndShowUI(args);
}
});
}

private static void creatAndShowUI(String[] args) {
ContactService<MyUser> svc = new MyPeanutsService();//MyBradyService();
MyContactsModel model = new MyContactsModel(svc.getContacts());
MyContactsEditor view = new MyContactsEditor(model);

JFrame frame = new JFrame(view.getTitle());
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setResizable(false);

frame.add(view, BorderLayout.NORTH);
UITools.centerAndShow(frame);
}
}
Until next time....

Tuesday, August 25, 2009

Simple Swing App - The Model

For this post, I just want to cover the model portion of the application. The next post will concentrate on the view, and then I will finally go over the libraries used within the application (SwingX, EventBus, etc.).
To help facilitate developing models, I have a class that all my models will extend. This class is Model.java, and it really does nothing except allow for any type of PropertyChangeListener registration as well as all methods to fire property change notifications.

Here is a snippet of the class:


public abstract class Model {

private PropertyChangeSupport _support;
private VetoableChangeSupport _vetoable;

/** Creates a new instance of Model */
public Model() {
_support = new PropertyChangeSupport(this);
_vetoable = new VetoableChangeSupport(this);
}

The rest of the methods are just pass through methods for all the methods found in the two XXXSupport classes. Pretty simple stuff.

That is it for the framework portion of the model. Now, the application's model is composed of two classes. The first class just models a contact/user, and it is called MyUser.java. It has the typical getters and setters for the user attributes I am modeling. The setter methods also fire PropertyChangeEvents whenever they are called. Here is a portion of this class:


public class MyUser extends Model implements Comparable<MyUser> {

public static final String FIRST_NAME_PROP = "MyModel.FirstName";
public static final String LAST_NAME_PROP = "MyModel.LastName";
public static final String SSN_PROP = "MyModel.SocialSecurityNumber";
public static final String BIRTHDAY_PROP = "MyModel.Birthday";
public static final String IMAGE_PROP = "MyModel.Image";

private String firstName;
private String lastName;
private String ssn;
private Date birthday;
private Image image;

/**
*
*/
public MyUser() {
this("John", "Doe", "000-00-0000", new Date(), null);
}

/**
*
* @param first
* @param last
* @param ssn
* @param birth
*/
public MyUser(String first, String last,
String ssn, Date birth) {
this(first, last, ssn, birth, null);
}

/**
*
* @param first
* @param last
* @param ssn
* @param birth
* @param image
*/
public MyUser(String first, String last,
String ssn, Date birth, Image image) {
super();
setFirstName(first);
setLastName(last);
setSocialSecurityNumber(ssn);
setBirthday(birth);
setImage(image);
}


This just shows the property names used when firing events and the constructors for creating a MyUser. Here is a typical getter and setter:


/**
* @return the firstName
*/
public String getFirstName() {
return firstName;
}

/**
* @param firstName the firstName to set
*/
public void setFirstName(String firstName) {
String oldValue = this.firstName;
this.firstName = firstName;
this.firePropertyChange(
FIRST_NAME_PROP, oldValue, this.firstName);
}


All of this is simple JavaBean specification coding.


I discovered a very cool feature in NetBeans when creating this class. After I completed all the getters/setters, I was poking around by right-clicking in the source editor and selecting "Insert Code...". One of the options was to override the equals and hashCode methods. I selected this because I thought it would be a good thing to do and not only did the methods show up, but they were completely implemented as well. Very nice, and saved me from looking up the proper ways to override these methods in the great Effective Java book.

Finally, the last class to be covered is MyContactsModel.java. Please don't comment on how incredibly descriptive my names are. This class is the actual model that gets wired to the view. It basically holds a List of MyUser objects, and has methods that operate on this list of users (as well as a settable Comparator for sorting).

Here is the constructors for the class:


public class MyContactsModel extends Model {

public static final String USER_MODIFIED_PROP = "MyContactsModel.UserModified";
public static final String USER_ADDED_PROP = "MyContactsModel.UserAdded";
public static final String USER_DELETED_PROP = "MyContactsModel.UserDeleted";
private static final Comparator COMPARATOR = new UserAgeComparator();

private final int[] LOCK = new int[0];
private Comparator _comparator;
private List<MyUser> _contacts;

/**
*
*/
public MyContactsModel() {
this(new ArrayList<MyUser>());
}

/**
*
* @param contacts
*/
public MyContactsModel(List<MyUser> contacts) {
super();
this._contacts = new ArrayList<MyUser>();
this._contacts.addAll(contacts);
Collections.sort(_contacts, (_comparator == null ? COMPARATOR : _comparator));
}

/**
*
* @param comparator
*/
public void setComparator(Comparator comparator) {
this._comparator = comparator;
Collections.sort(_contacts, (_comparator == null ? COMPARATOR : _comparator));
}


The methods used for operating on the list are:
  • public boolean addUser(MyUser user)
  • public boolean removeUser(MyUser user)
  • public boolean modifyUser(MyUser original, MyUser changed)


There is also the following for getting the data:

  • public List getContacts()


The operations on the model also fire PropertyChangeEvents. Here is the addUser method:


public boolean addUser(MyUser user) {
boolean added = true;

synchronized (LOCK) {
if (_contacts.contains(user) || user == null) {
return false;
}
_contacts.add(user);
Collections.sort(_contacts, (_comparator == null ? COMPARATOR : _comparator));
}
this.firePropertyChange(USER_ADDED_PROP, null, user);

return added;
}

That is it for the model portion. The code is really very simple (and partially written by NetBeans). Next we will look at the actual UI creation and how it interacts with the model. Again, the UI makes use of a framework class to help facilitate the MVC design. Just to keep from losing the focus: all these framework pieces are important because we want to see how it affects the porting of the application to the NetBeans Platform. Have patience, we'll get there soon enough.

Wednesday, August 19, 2009

Simple Swing App - REVISED

Sorry for the absence (did anyone notice?), but I had a struggle with proceeding with the Swing app I had initially proposed. It seemed a bit too simplistic in regards to learning some certain platform API's. Believe me, I did not want to backpedal on my earlier post - I even thought I could just edit that post since no one has probably even read any of these yet. But, in the interest of future posts, I realized I must change the app that will be ported. So, introducing the new Simplistic Swing App:


It is just a very simple contact editor. Once again, it makes use of the Model View Controller framework I had mentioned previously. It also contains the same status area (JStatusPanel) at the bottom of the app with the same clock component.

There are also a few Open Source libraries being used. The date selection component (for the Birth Date field) is the JXDatePicker from the SwingX project. The JStatusPanel was modified to make use of an EventBus for handling messages. Finally, validation is added to some of the input fields using a Simple Validation API. Along with these, I have my own library of classes that assist in writing apps: MVC Framework, image manipulation and UI tools. Again, we want to see how all these affect the porting to the NetBeans Platform.

With that said, here are some functional specs for the app displayed above:

  • Set of contacts to be viewed is read from a contacts service (basically just a class that adheres to an interface).
  • Contacts can be selected for individual viewing and editing.
  • Changes to the contact can be saved.
  • Uncommited contact values can be reset to their previously saved values.
  • A contact can be deleted - deletions only happen after a confirmation.
We will actually expand on these features as we port it to the NetBeans Platform. For example, we will add the capability to add a new contact.

The next series of posts will talk about the code (application code as well as framework classes being used). I think it is important to have at least a strong familiarity with the application before porting.

Before we get to the code, here are the files that make up this application:


Most of these files are very simple. The main classes with any substance are: MyContactsEditor.java, MyContactsModel.java and MyUser.java. The contacts data is provided by either the MyPeanutsService.java or the MyBradyService.java. Both adhere to the ContactService interface:



import java.util.List;

/**
*
* @author borak
*/
public interface ContactService<T> {

public List<T> getContacts();
}
Very simple. The contacts data is currently hard-coded in the service providers. For example, here is the code for MyBradyService:


public class MyBradyService implements ContactService<MyUser> {

public List<MyUser> getContacts() {
List<MyUser> contacts = new ArrayList<MyUser>();

SimpleDateFormat fmt = new SimpleDateFormat("MMMMM dd, yyyy");

try {
contacts.add(new MyUser("Greg", "Brady", "242-12-3987", fmt.parse("July 04, 1962")));
contacts.add(new MyUser("Peter", "Brady", "242-12-3976", fmt.parse("October 31, 1966")));
contacts.add(new MyUser("Bobby", "Brady", "242-23-9876", fmt.parse("March 12, 1970")));
contacts.add(new MyUser("Marsha", "Brady", "242-36-1012", fmt.parse("April 18, 1963")));
contacts.add(new MyUser("Jan", "Brady", "423-67-1012", fmt.parse("August 20, 1965")));
contacts.add(new MyUser("Cindy", "Brady", "223-67-1012", fmt.parse("February 29, 1972")));
contacts.add(new MyUser("Tiger", "Brady", "111-11-1111", fmt.parse("December 25, 1976")));
} catch (ParseException ex) {
Logger.getLogger(MyBradyService.class.getName()).log(Level.SEVERE, null, ex);
}

return contacts;
}
}
Next time, we will get into the MyUser.java and MyContactsModel.java. This will then lead into the Model View Controller framework and how it works. Until next time....

Ugh! Sorry for all that line wrapping! You really need to view the code with the "View Source" button.

Tuesday, August 11, 2009

Code Formatting Test

Just testing the syntax highlighting:

@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
setOpaque(true);
Color original = g.getColor();

switch (status) {
case INACTIVE:
g.setColor(Color.WHITE);
break;
case WARNING:
g.setColor(Color.YELLOW);
break;
case ERROR:
g.setColor(Color.RED);
break;
default:
g.setColor(Color.GREEN);
}
g.fillRect(0, 0, getWidth(), getHeight());
g.setColor(original);
}

Hopefully this will work....

Well, this looks good. I don't like the little toolbar covering the code, but if you click the "View Source" (first item in the toolbar), you get a nice pop-up with the source displayed - great for copying the code too. Overall, it looks really good (even though it mucked up my spaces a bit - we'll see how bad this is when the code is larger and spans more than one method).

To achieve this, I simply followed the instructions here and performed HTML encoding of my code here (which wasn't needed for the code I used, but an important step nonetheless). I wish Blogger handled code better by default, but this works. Next post, we will finally get to code for the simplistic Swing app.


NOTE: I will try and limit the characters-per-line in my code to avoid excessive line wrapping. This will not be totally avoidable since my blog template has such a limited width. I may have to modify my template at some point if this is an issue.

Saturday, August 8, 2009

The Simplistic Swing Application

This post shows and explains the application that we will be porting to the NetBeans Platform. We will also add enhancements based on anything that is of interest in the book The Definitive Guide to the NetBeans Platform.

Luckily, I created a quick app for a colleague to help demonstrate the Model View Controller (MVC) design as well as handling PropertyChangeEvents. Before I go into an explanation of how the app works, here is a screenshot:


It doesn't look like much, and it doesn't do much. It does have a nice separation between the model and the view, and it also makes use of some framework classes to help with the MVC design as well as add some nice UI features. We want to see how all of these affect the porting process (how easy is it to re-create your app), which makes it the perfect example for our learning the API's! First, let me go over the functional aspects of the application:

  1. When any data exists in the "Request ID" field, the "Send Request" button should be enabled; otherwise it will be disabled.
  2. When you send the request ID, the "Request ID" field will be cleared (thus disabling the button).
  3. The request ID can be sent by pressing the "Send Request" button, using the keyboard shortcut for the "Send Request" button (Alt-R), or by simply hitting the Enter key on the keyboard when the "Request ID" field has the focus.
  4. When a Request ID is sent, the status bar (bottom of the window) should display a message stating the ID that was sent.

So what's up with those colored squares? Well, those are status indicators that change color based on the current status that it is tracking. There should be some business rules that govern how they work, but for my app, I simply have a class that randomly generates status changes at a constant time interval. Like I said, this was just a simple example to illustrate handling events. Ideally, it would be nice to be able to plug in new implementations of this event generator that were implemented to some sort of interface. This way we could randomly generate status changes in one implementation (my example), and maybe later plug in a different implementation that reads status values from a database. This could be a later post about the NetBeans Lookup library.

So, from the functional specs, we know that a status bar exists at the bottom of the app as well as the visible Clock component. Here is another screenshot with the button enabled and text in the status bar:


Thanks to the Clock component, you can see how long it takes me to actually write this post! We now have the Swing application that we will be porting. Before we start that process, I will need to explain the code, which I will save for the next post (I am trying to keep these short). Let me just go through some high-level aspects right now.

The status bar on the bottom is a reusable component taken from Swing Hacks. The Clock is also a reusable component that was added to the status bar. The MVC implementation is created with the help of 2 other framework classes. The first is an abstract class called Model.java which really does nothing more than implement all the PropertyChange methods that exist (firing events and registering listeners). The second is an abstract class called FormInputPanel.java and it provides a mechanism for building form panels with a backing model for the data. We of course want to keep using all these in the NetBeans Platform version - either as-is, or via some new and easy Platform API. So we will see how possible that is (or is not).

Hopefully I am not going too slow for this project, but I am trying to avoid posts that are 50 pages in length. I am betting they will already be plenty long when I start into the code. Let me know any thoughts and comments - on the posts themselves, or the code I will be covering. That is, if anyone is listening. Anyone? Bueller? Bueller? Bueller? ....

Sunday, August 2, 2009

Outline of Goals

So here I want to explain what I am trying to accomplish with this blog - aside from the general statement of learning the NetBeans Platform API's. I want to get to a very cool (fancy pants?) desktop application that includes geospatial rendering (probably using the NASA World Wind Java SDK) and also tie this together via layers to media types such as images, sound and video (JMF). Sounds very fancy pants-ish, unfortunately I can be a slow learner. Therefore, before I get into any of those advanced topics, I intend to create a very simplistic Swing application and port that to the NetBeans platform. This way, I can do all my learning on a worthless app before moving onto something more useful.

Part of this learning journey will be my discovery of new features to add to my application while reading the excellent Definitive Guide to NetBeans Platform by Heiko Bock. No disrespect to Geertjan's book Rich Client Programming, I just find the other book more suited to my learning style. I own both books and highly recommend both of them. So, just to let you know, I am not completely clueless about the platform development concepts. I have researched the uses of Lookups outside NetBeans, and have implemented the pattern discussed in Fabrizio Giudici's excellent article on NetBeans design idioms (check out his Blue Marine product for an excellent platform application example). Still, this series should hopefully provide a detailed account of the learning curve I follow to get to the final destination.

I don't have a lot of time to learn these things outside of work, so why in the world am I bothering to write a blog about it? The answer is because I believe this will help me to actually get something done - I can imagine that a large group of people are waiting with excitement to hear my next topic. Also, since I tend to forget everything previous to the last 24 hours, I can document my learning as well as help my retention by writing it down.

In the next post, I will begin developing/showing the Swing app which will get ported to the platform. I am checking out the capability to do syntax highlighting for my code to make it more readable. Even blogging is new to me and requires a bit of learning - I am a social software misfit. Until next time....


A side note: I just have to say how happy I am that football is again starting. Go Bears!!! Even though I live in Colorado, I grew up in Chicago (Chicago Ridge), and will always be a Bear fan. Since Chicago Ridge is a SW suburb, I am also a White Sox fan. Any offended Cubs fans can follow this path or this path away from my Blog.

Saturday, August 1, 2009

Introduction

This is just a quick intro to discuss what this blog will generally be about. The main intent is to document my learning the NetBeans Platform API's. I am sure that there will also be tangents into some of my interests and gripes, but I will try to stay on topic.

First, a little history as to why I am doing this: I strongly believe in the whole modular architecture as described by Jaroslav Tulach (Modular Manifesto). I have been developing with Java for some time now, and have witnessed first hand where bad designs lead - likewise, I have witnessed the benefits of good design. I have always enjoyed developing Swing applications, but within the past 3 years, my job has unfortunately involved web development (which I really can't stand). This is my way to get back into desktop client apps and learn a platform API that helps build better apps.

This is a very short intro - especially for me, but I will expand on this later as well as outline what I want to accomplish.