Customizing cell rendering

how-to
Nov 2, 201419 mins

Swing’s javax.swing.JList component and JavaFX’s javafx.scene.control.ListView control let you customize how their various cells are rendered. In this post, I show you how to accomplish these tasks.

Customizing cell rendering in Swing JLists

Q: How do I customize how a JList instance’s cells are rendered?

A: Swing provides the javax.swing.ListCellRenderer<E> interface to identify components that can be used as “rubber stamps” to render a JList instance’s cells. This interface declares the following method:

Component getListCellRendererComponent(JList<? extends E> list,
                                       E value,
                                       int index,
                                       boolean isSelected,
                                       boolean cellHasFocus)

According to ListCellRenderer‘s Javadoc, getListCellRendererComponent() returns a java.awt.Component that’s configured to display the specified value. The Component‘s paint() method is then called to render the cell.

getListCellRendererComponent() declares the following parameters:

  • list: references the JList instance whose cell is being rendered
  • value: the value returned from list.getModel().getElementAt(index)
  • index: the cell’s index
  • isSelected: true when the specified cell is selected; otherwise, false
  • cellHasFocus: true when the specified cell has the focus; otherwise, false

You install a ListCellRenderer instance into a JList instance by calling JList‘s public void setCellRenderer(ListCellRenderer<? super E> cellRenderer) method.

Q: Can you provide an example showing how to customize the rendering of a JList instance’s cells?

A: I’ve created an example that renders a country flag image and the name of a country in each JList cell. Listing 1 presents the example’s Country class.

Listing 1. Country.java

import javax.swing.ImageIcon;

public class Country implements Comparable<Country>
{
   private ImageIcon flagIcon;

   private String name;

   private String path;

   public Country(String name, String path)
   {
      this.name = name;
      this.path = path;
   }

   public String getName()
   {
      return name;
   }

   public ImageIcon getFlagIcon()
   {
      // Lazily load flag icon. Make sure that each country's flag icon is 
      // loaded only once.

      if (flagIcon == null)
         flagIcon = new ImageIcon(path);

      return flagIcon;
   }

   @Override
   public int compareTo(Country o)
   {
      return name.compareTo(o.name);
   }

   @Override
   public String toString()
   {
      return name;
   }
}

Listing 1’s Country class stores a country’s name and the path to a file that stores an image of the country’s flag. This information is stored by the constructor and returned by accessor methods getName() and getFlagIcon().

Country is used by the example’s CountryCellRenderer and Countries classes. Listing 2 presents CountryCellRenderer, which is responsible for rendering each JList cell.

Listing 2. CountryCellRenderer.java

import java.awt.Color;
import java.awt.Component;

import javax.swing.BorderFactory;
import javax.swing.ImageIcon;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.ListCellRenderer;

import javax.swing.border.Border;

public class CountryCellRenderer extends JLabel 
                                 implements ListCellRenderer<Country>
{
   private Border border;

   CountryCellRenderer()
   {
      // Leave a 10-pixel separator between the flag icon and country name.

      setIconTextGap(10);

      // Swing labels default to being transparent; the container's color
      // shows through. To change a Swing label's background color, you must
      // first make the label opaque (by passing true to setOpaque()). Later,
      // you invoke setBackground(), passing the new color as the argument.

      setOpaque(true);

      // This border is placed around a cell that is selected and has focus.

      border = BorderFactory.createLineBorder(Color.RED, 1);
   }

   @Override
   public Component getListCellRendererComponent(JList<? extends Country> list,
                                                 Country value,
                                                 int index,
                                                 boolean isSelected,
                                                 boolean cellHasFocus)
   {
      setText(value.getName());
      setIcon(value.getFlagIcon());

      if (isSelected)

      {
         setBackground(list.getSelectionBackground());
         setForeground(list.getSelectionForeground());
      }
      else
      {
         setBackground(list.getBackground());
         setForeground(list.getForeground());
      }

      setFont(list.getFont());

      setEnabled(list.isEnabled());

      if (isSelected && cellHasFocus)
         setBorder(border);
      else
         setBorder(null);

      return this;
   }
}

Listing 2’s CountryCellRenderer class extends javax.swing.JLabel, which simplifies the rendering of an image and text. getListCellRendererComponent() accomplishes these tasks by invoking JLabel‘s setText() and setIcon() methods on the country’s name and icon, which are returned by invoking the value argument’s getName() and getFlagIcon() methods.

After specifying the text and icon, cell selection is handled by specifying either the list’s selection background and foreground colors or its normal background and foreground colors as the list’s background and foreground colors.

Next, the list’s font is assigned (we might assign a different font to the list), the rendering component is enabled or disabled to track the list’s enabled state, and a special border is assigned to a selected and focused cell, to help it stand out.

Finally, the label component is returned and its paint() method is subsequently called (behind the scenes) to render the specified cell.

To complete this example, Listing 3 presents its Countries application class, which creates a suitable list-based user interface that responds to selection events by outputting country names to the standard output stream.

Listing 3. Countries.java

import java.awt.EventQueue;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import javax.swing.JFrame;
import javax.swing.JList;
import javax.swing.JScrollPane;

import javax.swing.border.Border;

import javax.swing.event.ListSelectionListener;

public class Countries extends JFrame
{
   public Countries(String title)
   {
      super(title);
      setDefaultCloseOperation(EXIT_ON_CLOSE);

      JList<Country> list = new JList<>(createCountriesArray());
      //list.setEnabled(false);
      list.setCellRenderer(new CountryCellRenderer());
      list.setVisibleRowCount(8);
      ListSelectionListener lsl;
      lsl = lse -> {
                      if (!lse.getValueIsAdjusting())
                         System.out.println(list.getSelectedValue());
                   };
      list.addListSelectionListener(lsl);
      list.setSelectedIndex(0);

      setContentPane(new JScrollPane(list));

      pack();
      setVisible(true);
   }

   Country[] createCountriesArray()
   {
      String[] citems =
      {
         "AD,Andorra",
         "AE,United Arab Emirates",
         "AF,Afghanistan",
         "AG,Antigua and Barbuda",
         "AI,Anguilla",
         "AL,Albania",
         "AM,Armenia",
         "AN,Netherlands Antilles",
         "AO,Angola",
         "AR,Argentina",
         "AS,American Samoa",
         "AT,Austria",
         "AU,Australia",
         "AW,Aruba",
         "AX,Åland Islands",
         "AZ,Azerbaijan",
         "BA,Bosnia and Herzegovina",
         "BB,Barbados",
         "BD,Bangladesh",
         "BE,Belgium",
         "BF,Burkina Faso",
         "BG,Bulgaria",
         "BH,Bahrain",
         "BI,Burundi",
         "BJ,Benin",
         "BM,Bermuda",
         "BN,Brunei Darussalam",
         "BO,Bolivia",
         "BR,Brazil",
         "BS,Bahamas",
         "BT,Bhutan",
         "BV,Bouvet Island",
         "BW,Botswana",
         "BY,Belarus",
         "BZ,Belize",
         "CA,Canada",
         "CC,Cocos (Keeling) Islands",
         "CD,Congo, the Democratic Republic of the",
         "CF,Central African Republic",
         "CG,Congo",
         "CH,Switzerland",
         "CI,Cote d'Ivoire Côte d'Ivoire",
         "CK,Cook Islands",
         "CL,Chile",
         "CM,Cameroon",
         "CN,China",
         "CO,Colombia",
         "CR,Costa Rica",
         "CU,Cuba",
         "CV,Cape Verde",
         "CX,Christmas Island",
         "CY,Cyprus",
         "CZ,Czech Republic",
         "DE,Germany",
         "DJ,Djibouti",
         "DK,Denmark",
         "DM,Dominica",
         "DO,Dominican Republic",
         "DZ,Algeria",
         "EC,Ecuador",
         "EE,Estonia",
         "EG,Egypt",
         "EH,Western Sahara",
         "ER,Eritrea",
         "ES,Spain",
         "ET,Ethiopia",
         "FI,Finland",
         "FJ,Fiji",
         "FK,Falkland Islands (Malvinas)",
         "FM,Micronesia, Federated States of",
         "FO,Faroe Islands",
         "FR,France",
         "GA,Gabon",
         "GB,United Kingdom",
         "GD,Grenada",
         "GE,Georgia",
         "GF,French Guiana",
         "GH,Ghana",
         "GI,Gibraltar",
         "GL,Greenland",
         "GM,Gambia",
         "GN,Guinea",
         "GP,Guadeloupe",
         "GQ,Equatorial Guinea",
         "GR,Greece",
         "GS,South Georgia and the South Sandwich Islands",
         "GT,Guatemala",
         "GU,Guam",
         "GW,Guinea-Bissau",
         "GY,Guyana",
         "HK,Hong Kong",
         "HM,Heard Island and McDonald Islands",
         "HN,Honduras",
         "HR,Croatia",
         "HT,Haiti",
         "HU,Hungary",
         "ID,Indonesia",
         "IE,Ireland",
         "IL,Israel",
         "IN,India",
         "IO,British Indian Ocean Territory",
         "IQ,Iraq",
         "IR,Iran, Islamic Republic of",
         "IS,Iceland",
         "IT,Italy",
         "JM,Jamaica",
         "JO,Jordan",
         "JP,Japan",
         "KE,Kenya",
         "KG,Kyrgyzstan",
         "KH,Cambodia",
         "KI,Kiribati",
         "KM,Comoros",
         "KN,Saint Kitts and Nevis",
         "KP,Korea, Democratic People's Republic of",
         "KR,Korea, Republic of",
         "KW,Kuwait",
         "KY,Cayman Islands",
         "KZ,Kazakhstan",
         "LA,Lao People's Democratic Republic",
         "LB,Lebanon",
         "LC,Saint Lucia",
         "LI,Liechtenstein",
         "LK,Sri Lanka",
         "LR,Liberia",
         "LS,Lesotho",
         "LT,Lithuania",
         "LU,Luxembourg",
         "LV,Latvia",
         "LY,Libyan Arab Jamahiriya",
         "MA,Morocco",
         "MC,Monaco",
         "MD,Moldova, Republic of",
         "ME,Montenegro",
         "MG,Madagascar",
         "MH,Marshall Islands",
         "MK,Macedonia, the former Yugoslav Republic of",
         "ML,Mali",
         "MM,Myanmar",
         "MN,Mongolia",
         "MO,Macao",
         "MP,Northern Mariana Islands",
         "MQ,Martinique",
         "MR,Mauritania",
         "MS,Montserrat",
         "MT,Malta",
         "MU,Mauritius",
         "MV,Maldives",
         "MW,Malawi",
         "MX,Mexico",
         "MY,Malaysia",
         "MZ,Mozambique",
         "NA,Namibia",
         "NC,New Caledonia",
         "NE,Niger",
         "NF,Norfolk Island",
         "NG,Nigeria",
         "NI,Nicaragua",
         "NL,Netherlands",
         "NO,Norway",
         "NP,Nepal",
         "NR,Nauru",
         "NU,Niue",
         "NZ,New Zealand",
         "OM,Oman",
         "PA,Panama",
         "PE,Peru",
         "PF,French Polynesia",
         "PG,Papua New Guinea",
         "PH,Philippines",
         "PK,Pakistan",
         "PL,Poland",
         "PM,Saint Pierre and Miquelon",
         "PN,Pitcairn",
         "PR,Puerto Rico",
         "PS,Palestinian Territory, Occupied",
         "PT,Portugal",
         "PW,Palau",
         "PY,Paraguay",
         "QA,Qatar",
         "RE,Reunion Réunion",
         "RO,Romania",
         "RS,Serbia",
         "RU,Russian Federation",
         "RW,Rwanda",
         "SA,Saudi Arabia",
         "SB,Solomon Islands",
         "SC,Seychelles",
         "SD,Sudan",
         "SE,Sweden",
         "SG,Singapore",
         "SH,Saint Helena",
         "SI,Slovenia",
         "SJ,Svalbard and Jan Mayen",
         "SK,Slovakia",
         "SL,Sierra Leone",
         "SM,San Marino",
         "SN,Senegal",
         "SO,Somalia",
         "SR,Suriname",
         "ST,Sao Tome and Principe",
         "SV,El Salvador",
         "SY,Syrian Arab Republic",
         "SZ,Swaziland",
         "TC,Turks and Caicos Islands",
         "TD,Chad",
         "TF,French Southern Territories",
         "TG,Togo",
         "TH,Thailand",
         "TJ,Tajikistan",
         "TK,Tokelau",
         "TL,Timor-Leste",
         "TM,Turkmenistan",
         "TN,Tunisia",
         "TO,Tonga",
         "TR,Turkey",
         "TT,Trinidad and Tobago",
         "TV,Tuvalu",
         "TW,Taiwan, Province of China",
         "TZ,Tanzania, United Republic of",
         "UA,Ukraine",
         "UG,Uganda",
         "UM,United States Minor Outlying Islands",
         "US,United States",
         "UY,Uruguay",
         "UZ,Uzbekistan",
         "VA,Holy See (Vatican City State)",
         "VC,Saint Vincent and the Grenadines",
         "VE,Venezuela",
         "VG,Virgin Islands, British",
         "VI,Virgin Islands, U.S.",
         "VN,Viet Nam",
         "VU,Vanuatu",
         "WF,Wallis and Futuna",
         "WS,Samoa",
         "YE,Yemen",
         "YT,Mayotte",
         "ZA,South Africa",
         "ZM,Zambia",
         "ZW,Zimbabwe"
      };

      List<Country> clist = new ArrayList<Country>();
      for (String citem: citems)
      {
         String[] cdata = citem.split(",");
         clist.add(new Country(cdata[1], 
                   "icons/" + cdata[0].toLowerCase() + ".png"));
      }
      Country[] carray = clist.toArray(new Country[0]);
      Arrays.sort(carray);
      return carray;
   }

   public static void main(String[] args)
   {
      EventQueue.invokeLater(() -> new Countries("Countries"));
   }
}

Listing 3’s Countries class creates the user interface in its constructor, creates an array of Country objects that’s stored in the JList instance in its createCountriesArray() method, and launches the application in its main() method.

The following excerpt shows how the list of countries is created:

JList<Country> list = new JList<>(createCountriesArray());

The following excerpt shows how the previously described country cell renderer is created and installed into the JList instance:

list.setCellRenderer(new CountryCellRenderer());

Building and running the example

The current directory must contain Country.java, CountryCellRenderer.java, and Countries.java. Assuming that this is the case, execute the following command to compile this source code:

javac *.java

Assuming success and that the current directory also contains an icons subdirectory with PNG image files for all of the country flags (e.g., ca.png for Canada’s flag), execute the following command to run this application:

java Countries

Figure 1 shows the resulting user interface with customized cells.

Figure 1. The user interface renders the selected and focused cell with a thin red border

The user interface renders the selected and focused cell with a thin red border.

Let’s experiment with this code to learn more about the renderer. Start by uncommenting the following line in Countries.java and recompile the source code:

//list.setEnabled(false);

This time, you should observed the disabled user interface that appears in Figure 2.

Figure 2. The cell renderer paints a disabled user interface

The cell renderer paints a disabled user interface.

getListCellRendererComponent()‘s setEnabled(list.isEnabled()); call ensures that a disabled user interface is rendered when the list is disabled. Comment out the setEnabled() call and see what happens.

Here are a couple more experiments to try:

  • Assign a font to the list, as in list.setFont(new Font(Font.MONOSPACED, Font.PLAIN, 22));. You should observe the cell text rendered according to the font’s name, style, and size.
  • Introduce a javax.swing.JButton instance to the user interface, perhaps via code such as the following:
    getContentPane().add(new JScrollPane(list));
    getContentPane().add(new JButton("OK"), java.awt.BorderLayout.SOUTH);

    Tab the focus to the button and the red outline border around the selected (but no longer focused) list item should disappear.

Customizing cell rendering in JavaFX ListViews

Q: How do I customize how a ListView instance’s cells are rendered?

A: JavaFX provides the javafx.scene.control.ListCell<T> class for rendering a single row inside a ListView instance. This class declares the following method:

protected void updateItem(T item, boolean empty)

updateItem() declares the following parameters:

  • item: the cell’s new item
  • empty: true when the cell is empty and doesn’t represent list data (it’s being used to render an empty row); otherwise, false when the cell isn’t empty and represents list data

The Javadoc for the ListCell class recommends that this method be overridden to allow a cell’s rendering to be customized. This method should be overridden as follows:

protected void updateItem(T item, boolean empty) 
{
   super.updateItem(item, empty);
   if (empty || item == null) 
   {
      setText(null);
      setGraphic(null);
   } 
   else
      setText(item.toString());
}

ListCell inherits the void setText(String value) and void setGraphic(Node value) methods from its javafx.scene.control.Labeled ancestor. Call these methods to set the cell’s text and graphic.

There are two important points to note:

  • Invoke super.updateItem(item, empty) first. If you don’t do this, the item and empty properties won’t be set correctly and you might end up with graphical issues.
  • Test for the empty condition. If true, set the text and graphic properties to null. If you don’t do this, you’ll most likely see graphical artifacts in cells.

You override ListCell and then install a new subclass instance into a ListView instance by calling ListView‘s void setCellFactory(Callback<ListView<T>,ListCell<T>> value) method.

Q: Can you provide an example showing how to customize the rendering of a ListView instance’s cells?

A: I’ve created an example that renders a country flag image and the name of a country in each ListView cell. Listing 4 presents the example’s Country class.

Listing 4. Country.java

import javafx.scene.image.Image;

public class Country implements Comparable<Country>
{
   private Image flagIcon;

   private String name;

   private String path;

   public Country(String name, String path)
   {
      this.name = name;
      this.path = path;
   }

   public String getName()
   {
      return name;
   }

   public Image getFlagIcon()
   {
      // Lazily load flag icon. Make sure that each country's flag icon is 
      // loaded only once.

      if (flagIcon == null)
         flagIcon = new Image(path, false);

      return flagIcon;
   }

   @Override
   public int compareTo(Country o)
   {
      return name.compareTo(o.name);
   }

   @Override
   public String toString()
   {
      return name;
   }
}

Listing 4 is nearly identical to Listing 1. The only difference is that I’ve replaced Swing’s ImageIcon with JavaFX’s javafx.scene.image.Image.

To complete this example, Listing 5 presents its Countries application class, which creates a suitable list-based user interface that responds to selection events by outputting country names to the standard output stream.

Listing 5. Countries.java

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import javafx.application.Application;

import javafx.scene.Scene;

import javafx.scene.control.ListCell;
import javafx.scene.control.ListView;

import javafx.scene.image.ImageView;

import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;

import javafx.scene.paint.Color;

import javafx.stage.Stage;

import javafx.util.Callback;

public class Countries extends Application 
{
   @Override
   public void start(Stage stage) 
   {
      ListView<Country> list = new ListView<>();
      //list.setDisable(true);
      list.getItems().setAll(createCountriesArray());
      list.setFixedCellSize(24);
      VBox box = new VBox();
      Scene scene = new Scene(box, 200, 194);
      stage.setScene(scene);
      stage.setTitle("Countries");
      box.getChildren().addAll(list);
      VBox.setVgrow(list, Priority.ALWAYS);

      list.setCellFactory(cb -> new FlagCell());

      list.getSelectionModel().selectedItemProperty()
          .addListener((observable, oldValue, newValue) -> 
                       {
                          System.out.println(newValue);
                       });
      list.getSelectionModel().select(0);

      stage.show();
   }

   static class FlagCell extends ListCell<Country> 
   {
      @Override
      public void updateItem(Country item, boolean empty) 
      {
         super.updateItem(item, empty);
         if (empty || item == null)
         {
            setText(null);
            setGraphic(null);
         }
         else
         {
            setText(item.getName());
            ImageView iv = new ImageView();
            iv.setImage(item.getFlagIcon());
            setGraphic(iv);
         }
      }
   }

   Country[] createCountriesArray()
   {
      String[] citems =
      {
         "AD,Andorra",
         "AE,United Arab Emirates",
         "AF,Afghanistan",
         "AG,Antigua and Barbuda",
         "AI,Anguilla",
         "AL,Albania",
         "AM,Armenia",
         "AN,Netherlands Antilles",
         "AO,Angola",
         "AR,Argentina",
         "AS,American Samoa",
         "AT,Austria",
         "AU,Australia",
         "AW,Aruba",
         "AX,Åland Islands",
         "AZ,Azerbaijan",
         "BA,Bosnia and Herzegovina",
         "BB,Barbados",
         "BD,Bangladesh",
         "BE,Belgium",
         "BF,Burkina Faso",
         "BG,Bulgaria",
         "BH,Bahrain",
         "BI,Burundi",
         "BJ,Benin",
         "BM,Bermuda",
         "BN,Brunei Darussalam",
         "BO,Bolivia",
         "BR,Brazil",
         "BS,Bahamas",
         "BT,Bhutan",
         "BV,Bouvet Island",
         "BW,Botswana",
         "BY,Belarus",
         "BZ,Belize",
         "CA,Canada",
         "CC,Cocos (Keeling) Islands",
         "CD,Congo, the Democratic Republic of the",
         "CF,Central African Republic",
         "CG,Congo",
         "CH,Switzerland",
         "CI,Cote d'Ivoire Côte d'Ivoire",
         "CK,Cook Islands",
         "CL,Chile",
         "CM,Cameroon",
         "CN,China",
         "CO,Colombia",
         "CR,Costa Rica",
         "CU,Cuba",
         "CV,Cape Verde",
         "CX,Christmas Island",
         "CY,Cyprus",
         "CZ,Czech Republic",
         "DE,Germany",
         "DJ,Djibouti",
         "DK,Denmark",
         "DM,Dominica",
         "DO,Dominican Republic",
         "DZ,Algeria",
         "EC,Ecuador",
         "EE,Estonia",
         "EG,Egypt",
         "EH,Western Sahara",
         "ER,Eritrea",
         "ES,Spain",
         "ET,Ethiopia",
         "FI,Finland",
         "FJ,Fiji",
         "FK,Falkland Islands (Malvinas)",
         "FM,Micronesia, Federated States of",
         "FO,Faroe Islands",
         "FR,France",
         "GA,Gabon",
         "GB,United Kingdom",
         "GD,Grenada",
         "GE,Georgia",
         "GF,French Guiana",
         "GH,Ghana",
         "GI,Gibraltar",
         "GL,Greenland",
         "GM,Gambia",
         "GN,Guinea",
         "GP,Guadeloupe",
         "GQ,Equatorial Guinea",
         "GR,Greece",
         "GS,South Georgia and the South Sandwich Islands",
         "GT,Guatemala",
         "GU,Guam",
         "GW,Guinea-Bissau",
         "GY,Guyana",
         "HK,Hong Kong",
         "HM,Heard Island and McDonald Islands",
         "HN,Honduras",
         "HR,Croatia",
         "HT,Haiti",
         "HU,Hungary",
         "ID,Indonesia",
         "IE,Ireland",
         "IL,Israel",
         "IN,India",
         "IO,British Indian Ocean Territory",
         "IQ,Iraq",
         "IR,Iran, Islamic Republic of",
         "IS,Iceland",
         "IT,Italy",
         "JM,Jamaica",
         "JO,Jordan",
         "JP,Japan",
         "KE,Kenya",
         "KG,Kyrgyzstan",
         "KH,Cambodia",
         "KI,Kiribati",
         "KM,Comoros",
         "KN,Saint Kitts and Nevis",
         "KP,Korea, Democratic People's Republic of",
         "KR,Korea, Republic of",
         "KW,Kuwait",
         "KY,Cayman Islands",
         "KZ,Kazakhstan",
         "LA,Lao People's Democratic Republic",
         "LB,Lebanon",
         "LC,Saint Lucia",
         "LI,Liechtenstein",
         "LK,Sri Lanka",
         "LR,Liberia",
         "LS,Lesotho",
         "LT,Lithuania",
         "LU,Luxembourg",
         "LV,Latvia",
         "LY,Libyan Arab Jamahiriya",
         "MA,Morocco",
         "MC,Monaco",
         "MD,Moldova, Republic of",
         "ME,Montenegro",
         "MG,Madagascar",
         "MH,Marshall Islands",
         "MK,Macedonia, the former Yugoslav Republic of",
         "ML,Mali",
         "MM,Myanmar",
         "MN,Mongolia",
         "MO,Macao",
         "MP,Northern Mariana Islands",
         "MQ,Martinique",
         "MR,Mauritania",
         "MS,Montserrat",
         "MT,Malta",
         "MU,Mauritius",
         "MV,Maldives",
         "MW,Malawi",
         "MX,Mexico",
         "MY,Malaysia",
         "MZ,Mozambique",
         "NA,Namibia",
         "NC,New Caledonia",
         "NE,Niger",
         "NF,Norfolk Island",
         "NG,Nigeria",
         "NI,Nicaragua",
         "NL,Netherlands",
         "NO,Norway",
         "NP,Nepal",
         "NR,Nauru",
         "NU,Niue",
         "NZ,New Zealand",
         "OM,Oman",
         "PA,Panama",
         "PE,Peru",
         "PF,French Polynesia",
         "PG,Papua New Guinea",
         "PH,Philippines",
         "PK,Pakistan",
         "PL,Poland",
         "PM,Saint Pierre and Miquelon",
         "PN,Pitcairn",
         "PR,Puerto Rico",
         "PS,Palestinian Territory, Occupied",
         "PT,Portugal",
         "PW,Palau",
         "PY,Paraguay",
         "QA,Qatar",
         "RE,Reunion Réunion",
         "RO,Romania",
         "RS,Serbia",
         "RU,Russian Federation",
         "RW,Rwanda",
         "SA,Saudi Arabia",
         "SB,Solomon Islands",
         "SC,Seychelles",
         "SD,Sudan",
         "SE,Sweden",
         "SG,Singapore",
         "SH,Saint Helena",
         "SI,Slovenia",
         "SJ,Svalbard and Jan Mayen",
         "SK,Slovakia",
         "SL,Sierra Leone",
         "SM,San Marino",
         "SN,Senegal",
         "SO,Somalia",
         "SR,Suriname",
         "ST,Sao Tome and Principe",
         "SV,El Salvador",
         "SY,Syrian Arab Republic",
         "SZ,Swaziland",
         "TC,Turks and Caicos Islands",
         "TD,Chad",
         "TF,French Southern Territories",
         "TG,Togo",
         "TH,Thailand",
         "TJ,Tajikistan",
         "TK,Tokelau",
         "TL,Timor-Leste",
         "TM,Turkmenistan",
         "TN,Tunisia",
         "TO,Tonga",
         "TR,Turkey",
         "TT,Trinidad and Tobago",
         "TV,Tuvalu",
         "TW,Taiwan, Province of China",
         "TZ,Tanzania, United Republic of",
         "UA,Ukraine",
         "UG,Uganda",
         "UM,United States Minor Outlying Islands",
         "US,United States",
         "UY,Uruguay",
         "UZ,Uzbekistan",
         "VA,Holy See (Vatican City State)",
         "VC,Saint Vincent and the Grenadines",
         "VE,Venezuela",
         "VG,Virgin Islands, British",
         "VI,Virgin Islands, U.S.",
         "VN,Viet Nam",
         "VU,Vanuatu",
         "WF,Wallis and Futuna",
         "WS,Samoa",
         "YE,Yemen",
         "YT,Mayotte",
         "ZA,South Africa",
         "ZM,Zambia",
         "ZW,Zimbabwe"
      };

      List<Country> clist = new ArrayList<>();
      for (String citem: citems)
      {

         String[] cdata = citem.split(",");
         clist.add(new Country(cdata[1],
                   getClass().getResource("icons/" + cdata[0].toLowerCase() +
                                          ".png").toString()));
      }
      Country[] carray = clist.toArray(new Country[0]);
      Arrays.sort(carray);
      return carray;
   }
}

Listing 5’s Countries class creates the user interface in its start() method and an array of Country objects that’s stored in the ListView instance in its createCountriesArray() method. The main() method is automatically created.

The following excerpt shows how the list of countries is created:

ListView<Country> list = new ListView<>();
list.getItems().setAll(createCountriesArray());

The following excerpt shows how the previously described country cell renderer is created (from the nested FlagCell renderer class) and installed into the ListView instance:

list.setCellFactory(cb -> new FlagCell());

Building and running the example

The current directory must contain Country.java and Countries.java. Assuming that this is the case, execute the following command to compile this source code:

javac *.java

Assuming success and that the current directory also contains an icons subdirectory with PNG image files for all of the country flags (e.g., ca.png for Canada’s flag), execute the following command to run this application:

java Countries

Figure 3 shows the resulting user interface with customized cells.

Figure 3. The user interface renders each cell with a flag image and a country name

The user interface renders each cell with a flag image and a country name.

To observe the user interface when the list is disabled, uncomment the following line and recompile Countries.java:

//list.setDisable(true);

This time, you should observed the disabled user interface that appears in Figure 4.

Figure 4. The cell renderer paints a disabled user interface

The cell renderer paints a disabled user interface.

What’s next?

Next time, I explore the invokedynamic instruction that was added to the virtual machine in Java 7. I also explore the associated java.lang.invoke package.

download
Get the source code for this post’s applications. Created by Jeff Friesen for JavaWorld

The following software was used to develop the post’s code:

  • 64-bit JDK 7u6
  • 64-bit JDK 8 (build 132)

The post’s code was tested on the following platform(s):

  • JVM on 64-bit Windows 7 SP1