Developer

Getting back on the GridBagLayout with RSS

In our last look at GridBagLayout we were left with a frame that resembled our goal but was not functional, this time we shall complete the frame

In our last look at GridBagLayout we were left with a frame that resembled our goal but was not functional, this time we shall complete the frame.

Fill and Weight

Enlarging the frame produced by the completed code from last time results in the components remaining in the centre of the frame and their size is unaffected. To change this we need to give the components fill and weight values.

The fill value describes how the component will behave if the display area size is larger than the component and the weightx and weighty values say how to fill any extra horizontal or vertical space.

Looking back at our desired result we see that the labels, combo boxes, buttons and textfields at the top of the frame remain unchanged and the extra space is taken up by the table and image viewing label.

Prior to giving these variables values, you may notice that the table and image viewing label have scrollbars on them. Therefore we need to contain them within a JScrollPane. Below is the code for the table and it is almost identical for the label.

JScrollPane scrollPane = new JScrollPane(resTable);
scrollPane.setMinimumSize(new Dimension(450, 110));
....
gridbag.setConstraints(scrollPane, c);
contentPane.add(scrollPane);

Next we need to fill the available space and have the frame resize sensibly. The following code is added to the table and image viewing label, although if there was a component between them in the code the fill value would need resetting:

c.fill = GridBagConstraints.BOTH;

Because the current dimensions of the table are suitable, we really only want the image label to expand and contract when resizing. Therefore we add the following code to the image viewing label:

c.weightx = 1;
c.weighty = 1;

However, this does not explain much about weight. When weight is assigned throughout a GridBagLayout, it must add up to 1 for each row and column. If we wanted the table to expand vertically at half of the image viewing label, we would use the following code:

//For the JTable
c.weighty = 0.33;

//For the image labebl
c.weighty = 0.67;

This is where a lot of the "voodoo" with GridBagLayout comes in, and why it is a good idea to sketch down your original design. I find it especially useful to be able to visualise the grid when assigning values to weights. Simply cranking out GUI code is never a good idea, and even more so when dealing with GridBagLayout.

The last changes we need to make until we have the GUI we are aiming for is to align the tag and tagmode labels to the right and create some padding around those components.

The alignment is achieved easily:

tagLbl.setHorizontalAlignment(SwingConstants.RIGHT);
tagModeLbl.setHorizontalAlignment(SwingConstants.RIGHT);

To create the padding around components, we need to use Insets. An Inset specifies how much space there should be at the edge of a component, the parameters are top, left, bottom, right.

We put the following Inset on tag label:

c.insets = new Insets(5,5,5,15);

and reset it when we instantiate the textfield, thereby having the Inset on the tab label and the tag mode label:

c.insets = new Insets(0,0,0,0);

And that's it. Our GUI should now be what we were after to begin with.

Bringing the functionality

Now that we have the GUI behaving how we want for the moment, we shall turn our attention to the purpose of this frame — to show images from a flickr RSS search.

However we cannot simply fetch the results of this feed and assume that we will be able to handle the XML result with the Java base classes — we could try, but the amount of effort required is hardly worth the result.

Thankfully a project exists to handle such a problem called Rome. When you download Rome, be sure to grab its one dependency, JDOM, as well.

As this example shows, the purpose of Rome is to allow us to quickly and easily fetch and retrieve results from an RSS feed. Because we will only be interested doing this when the user presses the search button, the code that uses Rome shall be contained within an action listener.

We add an action listener to our button:

searchBtn.addActionListener(new GridBagAL()); 

Our listening class looks like this:

	class GridBagAL implements ActionListener{
        public void actionPerformed(ActionEvent e) { 
            try{
                URL feedUrl = new URL("http://api.flickr.com/services/feeds/photos_public.gne?tags="+URLEncoder.encode(tagTxt.getText(), "UTF-8") +"&tagmode="+modeCombo.getSelectedItem().toString()+"&format=rss_200_enc");

                SyndFeedInput input = new SyndFeedInput();
                SyndFeed feed = input.build(new XmlReader(feedUrl));
                                
                Vector tableData = new Vector();
                
                for(Object ent: feed.getEntries()){
                    SyndEntryImpl sei = (SyndEntryImpl) ent;
                    for(Object enc_ent: sei.getEnclosures()){
                        SyndEnclosureImpl senci = (SyndEnclosureImpl) enc_ent;
                        Vector vectmp = new Vector();
                        vectmp.add(new FlickrTableEntry(sei.getTitle(), senci.getUrl()));
                        tableData.add(vectmp);
                    }
                }
                
                Vector colNames = new Vector();
                colNames.add("Pictures"); 
                
                resTable.setModel(new DefaultTableModel(tableData, colNames));
                }
                catch (Exception ex){
                    ex.printStackTrace();
                    System.out.println("ERROR: "+ex.getMessage());
                }
          }
    }

What this listener does when invoked is create the URL we wish to fetch by retrieving and URL encoding the text from the textfield and selected mode from the combobox. Then using Rome, it retrieves the feed into a SyndFeed object. A Vector is used to store the date we are interested in from the feed.

To see why we are interested in the enclosures of the feed, have a look at the source of this result feed: http://api.flickr.com/services/feeds/photos_public.gne?tags=dog,cat&tagmode=any&format=rss_200_enc. If all we need is a title for each entry to display in the JTable and its image url to display in the image label, then we can easily gain that information from the enclosure.

To help with the display of this information, we create a class FlickrTableEntry which stores the image title and URL. It has the following code:

    class FlickrTableEntry{
        private String title, picture;
        
        public FlickrTableEntry(String title, String picture){
            this.title = title;
            this.picture = picture;
        }
        
        public String getTitle(){return title;}
        
        public String getPicture(){return picture;}
        
        public String toString(){return title;}
    }

Once we have stored our table data, we create another Vector to set the column names on the table and update the table by creating a new table model containing our new table data and column names.

To allow our code to compile, we need to have the following includes:

import java.util.Vector;

//Rome imports
import java.net.*;
import java.io.InputStreamReader;
import com.sun.syndication.feed.module.*;
import com.sun.syndication.feed.synd.*;
import com.sun.syndication.io.*;
import org.jdom.Document;

When we try our code to this point, we see that the table is filled with the titles of images from flickr. All that is left is to make the images display in the image label. For this we need to use a ListSelectionListener — we add the following code where we initialised the table.

        resTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
        ListSelectionModel rowSM = resTable.getSelectionModel();
        rowSM.addListSelectionListener(new ListSelectionListener() {
                  public void valueChanged(ListSelectionEvent e) {
                    //Ignore extra messages.
                    if (e.getValueIsAdjusting()) return;

                    ListSelectionModel lsm = (ListSelectionModel)e.getSource();
                    if (lsm.isSelectionEmpty()) {
                        //No rows are selected
                    } else {
                        int selectedRow = lsm.getMinSelectionIndex();
                        FlickrTableEntry fte = ( FlickrTableEntry) resTable.getValueAt(selectedRow, 0);                        
                        previewLbl.setText(fte.getTitle());
                        try{
                            previewLbl.setIcon(new ImageIcon(new URL(fte.getPicture())));
                        } catch (Exception ex){
                            ex.printStackTrace();
                            System.out.println("ERROR: "+ex.getMessage());
                        }
                    }
                }
            });

We also need the following include:

import javax.swing.event.*;

We now have the full functionality we desire. When we select a title from the results table, the image associated to it displays in the image label.

Wrapping Up

We now have a frame that looks how we wanted it to look and behaves how we desire. Of course this is just an example of how to use GridBagLayout and incorporate RSS feeds — therefore it could use an amount of polish, such as threading the interface.

If you wish to further improve the interface, here is the full code listing with some quick cleaning up of the interface to help get you started.

import java.awt.*;
import java.awt.event.*;

import javax.swing.*;
import javax.swing.table.*;
import javax.swing.event.*;

import java.util.Vector;

//Rome imports
import java.net.*;
import java.io.InputStreamReader;
import com.sun.syndication.feed.module.*;
import com.sun.syndication.feed.synd.*;
import com.sun.syndication.io.*;
import org.jdom.Document;

public class OldGridBagWindow extends JFrame {

    private JButton searchBtn;
    
    private JComboBox modeCombo;

    private JLabel tagLbl;
    private JLabel tagModeLbl;
    private JLabel previewLbl;
    
    private JTable resTable;
    
    private JTextField tagTxt;
     
    public OldGridBagWindow() {
        Container contentPane = getContentPane();
        GridBagLayout gridbag = new GridBagLayout();
        contentPane.setLayout(gridbag);
        
        GridBagConstraints c = new GridBagConstraints();

        //setting a default constraint value
        c.fill = GridBagConstraints.HORIZONTAL;
   
        tagLbl = new JLabel("Tags");
        tagLbl.setHorizontalAlignment(SwingConstants.RIGHT);
        c.gridx = 0; //x grid position
        c.gridy = 0; //y grid position
        c.insets = new Insets(5,5,5,15);
        gridbag.setConstraints(tagLbl, c); //associate the label with a constraint object 
        contentPane.add(tagLbl); //add it to content pane
        
        tagModeLbl = new JLabel("Tag Mode");
        tagModeLbl.setHorizontalAlignment(SwingConstants.RIGHT);
        c.gridx = 0;
        c.gridy = 1;
        gridbag.setConstraints(tagModeLbl, c);
        contentPane.add(tagModeLbl);
        
        tagTxt = new JTextField("plinth");
        c.gridx = 1;
        c.gridy = 0;
        c.gridwidth = 2;
        c.insets = new Insets(0,0,0,0);
        gridbag.setConstraints(tagTxt, c);
        contentPane.add(tagTxt);
        
        String[] options = {"all", "any"};
        modeCombo = new JComboBox(options);
        c.gridx = 1;
        c.gridy = 1;
        c.gridwidth = 1;
        gridbag.setConstraints(modeCombo, c);
        contentPane.add(modeCombo);
        
        searchBtn = new JButton("Search");
        searchBtn.addActionListener(new GridBagAL());
        c.gridx = 1;
        c.gridy = 2;
        gridbag.setConstraints(searchBtn, c);
        contentPane.add(searchBtn);

        resTable = new JTable();
        resTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
        
        ListSelectionModel rowSM = resTable.getSelectionModel();
        rowSM.addListSelectionListener(new ListSelectionListener() {
                  public void valueChanged(ListSelectionEvent e) {
                    //Ignore extra messages.
                    if (e.getValueIsAdjusting()) return;

                    ListSelectionModel lsm = (ListSelectionModel)e.getSource();
                    if (lsm.isSelectionEmpty()) {
                        //No rows are selected
                    } else {
                        int selectedRow = lsm.getMinSelectionIndex();
                        FlickrTableEntry fte = ( FlickrTableEntry) resTable.getValueAt(selectedRow, 0);                        
                        previewLbl.setText(fte.getTitle());
                        try{
                            previewLbl.setIcon(new ImageIcon(new URL(fte.getPicture())));
                        } catch (Exception ex){
                            ex.printStackTrace();
                            System.out.println("ERROR: "+ex.getMessage());
                        }
                    }
                }
            });
            
        JScrollPane scrollPane = new JScrollPane(resTable);
        scrollPane.setMinimumSize(new Dimension(450, 110));
        c.gridx = 0;
        c.gridy = 3;
        c.gridwidth = 3;
        c.fill = GridBagConstraints.BOTH;
        gridbag.setConstraints(scrollPane, c);
        contentPane.add(scrollPane);
        
        previewLbl = new JLabel("Preview goes here");
        previewLbl.setHorizontalAlignment(SwingConstants.CENTER);
        previewLbl.setVerticalAlignment(SwingConstants.TOP);
        previewLbl.setVerticalTextPosition(SwingConstants.TOP);
        previewLbl.setHorizontalTextPosition(SwingConstants.CENTER);
        previewLbl.setOpaque(true);
        
        JScrollPane tmpPane = new JScrollPane(previewLbl);
        tmpPane.setPreferredSize(new Dimension(450, 600));
        c.gridx = 0;
        c.gridy = 4;
        c.weightx = 1;
        c.weighty = 1;
        c.fill = GridBagConstraints.BOTH;
        gridbag.setConstraints(tmpPane, c);
        
        contentPane.add(tmpPane);

        addWindowListener(new WindowAdapter() {
            public void windowClosing(WindowEvent e) {
                    System.exit(0);
            }
        });
    }

    public static void main(String args[]) {
        OldGridBagWindow window = new OldGridBagWindow();

        window.setTitle("GridBagWindow");
        window.pack();
        window.setVisible(true);
    }
    
    class GridBagAL implements ActionListener{
        public void actionPerformed(ActionEvent e) { 
            try{
                URL feedUrl = new URL("http://api.flickr.com/services/feeds/photos_public.gne?tags="+URLEncoder.encode(tagTxt.getText(), "UTF-8") +"&tagmode="+modeCombo.getSelectedItem().toString()+"&format=rss_200_enc");

                SyndFeedInput input = new SyndFeedInput();
                SyndFeed feed = input.build(new XmlReader(feedUrl));
                                
                Vector tableData = new Vector();
                
                for(Object ent: feed.getEntries()){
                    SyndEntryImpl sei = (SyndEntryImpl) ent;
                    for(Object enc_ent: sei.getEnclosures()){
                        SyndEnclosureImpl senci = (SyndEnclosureImpl) enc_ent;
                        Vector vectmp = new Vector();
                        vectmp.add(new FlickrTableEntry(sei.getTitle(), senci.getUrl()));
                        tableData.add(vectmp);
                    }
                }
                
                Vector colNames = new Vector();
                colNames.add("Pictures"); 
                
                resTable.setModel(new DefaultTableModel(tableData, colNames));
                }
                catch (Exception ex){
                    ex.printStackTrace();
                    System.out.println("ERROR: "+ex.getMessage());
                }
          }
    }
    
    class FlickrTableEntry{
        private String title, picture;
        
        public FlickrTableEntry(String title, String picture){
            this.title = title;
            this.picture = picture;
        }
        
        public String getTitle(){return title;}
        
        public String getPicture(){return picture;}
        
        public String toString(){return title;}
    }

}

About Chris Duckett

Some would say that it is a long way from software engineering to journalism, others would correctly argue that it is a mere 10 metres according to the floor plan.During his first five years with CBS Interactive, Chris started his journalistic advent...

Editor's Picks

Free Newsletters, In your Inbox