ListView Control

The ListView control is for viewing lists of objects, much like viewing objects in a simple ListBox control. Unlike the ListBox control, however, a list view is capable of displaying the items it contains as icons or in a multicolumn list that contains the item's associated data.

If you want to know what a list view looks like, open Microsoft Windows Explorer. The control that Windows Explorer uses to display the file names and optional detailed information is a ListView control.

The following ListViewDemo app mimics much of the functionality of Windows Explorer. ListViewDemo populates a ListView control with the names of the files in a directory; the directory is specified in the argument list to the program when the program is started. If no arguments are provided, the program lists names of files in the current directory. Subdirectories are listed before files and both are listed in independent alphabetic order. ListViewDemo supports different viewing modes, including several icon-based modes and a fully detailed report mode that includes the size and modification date of the files. Before starting to work on ListViewDemo, you might want to look ahead to Figures 8-4 and 8-5 to get an idea of what the app looks like in action. All the sample programs in this chapter are in the Windows apps subfolder on the companion CD.

NOTE
The ListView class can be used to display other objects besides files; however, it seems to be almost uniquely suitable for that type of display.

Forms Designer Work

Screenshot-1 shows the Forms Designer with the app that we are trying to build. ListViewDemo consists of a large ListView control in the middle of the form. Along the bottom are four buttons that are used to switch the list view display from one mode to another. The ImageList controls are used to assign images to the different file types.

Screenshot

Screenshot-1. The ListViewDemo app contains only a few controls.

Begin by creating the Windows app ListViewDemo using the Windows app builder. Open the form in the Forms Designer. Now position a ListView control in the middle of the form, leaving enough room along the bottom of the form for four buttons.

ImageList control

Before adjusting the properties of the ListView control, add two ImageList controls to the form. Use the Properties window to name the first ImageList smallIconImageList and the second largeIconImageList. Add four images to the smallIconImageList object as shown in Figure 8-2. (See for details on how to use the Image Editor to add images to the image lists.) Create whatever images you like; however, the first image needs to be reminiscent of a disk, since this image will be used to flag directories. Choose the other three images to represent HTML files, executable files, and miscellaneous ("don't know") files. Each of these images should be the size of the icon, 16-by-16 pixels.

The largeIconImageList object contains the same type of images as smallIconImageList except that you will need to increase the x and y imageSize properties to 32. You must do this before adding the images in the Image Editor.

Screenshot

Screenshot-2. The icon image lists for ListViewDemo should have an image for directories, HTML files, executables, and miscellaneous files.

Editing the ListView properties

You are now ready to edit the properties of the ListView control using the Properties window. First change the name of the control to listView. Next anchor the ListView control to all four walls (this will ensure that the list view automatically resizes itself if the form in which it resides is resized). Set the smallImageList and stateImageList properties to point to the smallIconImageList object. Set the largeImageList property to reference the largeIconImageList object.

In addition, you will need to create three columns. Click the ellipses button on the value box for the columns property to access the ColumnHeader Editor. Add three objects, and then return to the Properties window to edit the text property of the column headers to be File Name, Size, and Last Modified. These headers will appear when the list view is switched to Detailed report mode.

Now make sure that the sorting property is set to None. This is critical since we will be sorting the displayed list manually. (We have to do this manually because the directories are sorted before the files.) Set the gridLines property to true if you would like spreadsheet-style gridlines when the list view is in detailed report mode. Set fullRowSelect to true. In addition, leave the multiSelect property set to true.

Set the activation property to Twoclick—Oneclick is also acceptable, if you prefer—and set the headerStyle property to Nonclickable for reasons I will explain shortly. Set the View property to List to make list view the default view. Set the autoArrange property to true. Once you have finished, the Properties window should look like that shown in Figure 8-3.

Screenshot

Screenshot-3. The data properties of the edited ListView demonstrating some of the proper settings for the ListViewDemo app.

Completing the forms designer work

Add four buttons equally spaced along the bottom of the app. Label them Big Icon, Small Icon, List, and Details, corresponding to the four display modes of the list view. Anchor the left two buttons to the left and bottom of the form and the right two buttons to the right and bottom of the form. Double-click each button to create the clickEvent() method for each.

Code

The source code for ListViewDemo consists of two .java files: the automatically created Form1.java file and a manually created ImageEnum.java class file (select Add Class from the Project menu). The second file is smaller, so let's start there.

ImageEnum class

It is the job of the ImageEnum class to define a final static int index for each image in the two icon image lists. The source code for this class is as follows:

import com.ms.wfc.core.Enum;
import com.ms.wfc.io.*;
/**
 * Controls the indices of the smallIconImageList and
 * largeIconImageList objects.
 */
public class ImageEnum extends Enum
{
 public final static int DATA = 0;
 public final static int HTML = 1;
 public final static int EXECUTABLE = 2;
 public final static int DONTKNOW = 3;
 /**
 * Return true if value is a valid index.
 */
 public static boolean valid(int value)
 {
 return (value >= DATA) && (value <= DONTKNOW);
 }
 /**
 * Given a file name, return the index of the
 * associated image base.
 */
 public static int getImageIndex(String fileName)
 {
 // if any problems arise, just return DONTKNOW
 try
 {
 // if this is a directory…
 if (File.isDirectory(fileName))
 {
 // then always return DATA
 return DATA;
 }
 // otherwise, base the image on the file extension
 String ext = File.getExtension(fileName);
 if (ext.equalsIgnoreCase(".EXE") ||
 ext.equalsIgnoreCase(".BAT"))
 {
 return EXECUTABLE;
 }
 if (ext.equalsIgnoreCase(".HTM") ||
 ext.equalsIgnoreCase(".HTML")||
 ext.equalsIgnoreCase(".ASP"))
 {
 return HTML;
 }
 }
 catch(Exception e)
 {
 }
 // either we didn't find a recognizable extension,
 // or a problem cropped up; either way, DONTKNOW
 return DONTKNOW;
 }
}


First you can see the four final static int members as mentioned. Each must match the index of the corresponding image. If you added more image types than I did, you will need to add the corresponding data members here. Extending the class Enum and overriding the valid() method complete the role of this class as an enumeration class.

One further method, getImageIndex(), has been added. This method takes as its argument the name of a file and returns one of the class's four constant data members. If the file name refers to a directory, the method returns the constant DATA. If the file name extension is .EXE or .BAT, the class returns EXECUTABLE. If the file name extension is .HTM, .HTML, or .ASP, the function returns HTML. If the file name extension is none of these or if any type of exception is thrown, the function returns DONTKNOW, indicating that it was not able to type the file.

main app code

The source code for Form1.java represents the vast majority of the source code for ListViewDemo.

import com.ms.wfc.app.*;
import com.ms.wfc.core.*;
import com.ms.wfc.ui.*;
import com.ms.wfc.html.*;
import com.ms.wfc.io.*;
import com.ms.wfc.util.*;
/**
 * Demonstrate the ListView class by adding a listing of the current
 * directory, subdirectories first, into the ListView object.
 */
public class Form1 extends Form
{
 // the currently listed directory
 String directory = File.getCurrentDirectory();
 public Form1(String[] args)
 {
 // Required for Visual J Plus Plus Form Designer support
 initForm();
 // store the contents of the directory passed
 // to the list view; if no directory passed, use
 // the current directory as the default
 if (args.length == 1)
 {
 directory = args[0];
 }
 createListView();
 }
 .
 .
 .
 /**
 * Fill the list view with the name, size, and date
 * of the files in the specified or current directory.
 */
 void createListView()
 {
 // first, clear out the list view
 listView.setItems(new ListItem[0]);
 // let's add the directories…
 addFilesToListView(listView, true);
 // then add the files
 addFilesToListView(listView, false);
 }
 /**
 * Add files to a list view.
 * * @param listView - the list view target
 * @param isDirectory - true->load directories, false->load files
 */
 void addFilesToListView(ListView listView,
 boolean isDirectory)
 {
 // create a list of file names using the FileEnumerator
 // class; this class let's you enumerate through all
 // the files that pass a given filter
 List list = new List();
 int index = 0;
 String filter = File.combine(directory, "*.*");
 FileEnumerator fe = new FileEnumerator(filter);
 while(fe.hasMoreFiles())
 {
 // check if this is a directory…
 String fileName = fe.getName();
 String fullPath = File.combine(directory, fileName);
 if (File.isDirectory(fullPath) == isDirectory)
 {
 // whatever it is, it matches what we're looking
 // for; now add the "extra" information
 String[] subItems = new String[2];
 subItems[0] = Long.toString(fe.getSize());
 Time dateTime = fe.getLastWriteTime();
 subItems[1] = dateTime.toString();
 // create an item for the ListView out of this data
 // (convert the file extension into an image index)
 ListItem item =
 new ListItem(fileName,
 ImageEnum.getImageIndex(fullPath),
 subItems);
 // add the item to the list
 list.addItem(item);
 }
 // move over to the next file
 fe.getNextFile();
 }
 // now sort the list
 list.sort(new FileNameComparer());
 // and add the sorted list to the list view
 IEnumerator itemEnumerator = list.getItemEnumerator();
 while (itemEnumerator.hasMoreItems())
 {
 ListItem li = (ListItem)itemEnumerator.nextItem();
 listView.addItem(li);
 }
 }
 /**
 * Implement the IComparer interface to support List.sort().
 */
 class FileNameComparer implements IComparer
 {
 /**
 * Compare o1 to o2; return -1, 0, or 1.
 */
 public int compare(Object o1, Object o2)
 {
 ListItem l1 = (ListItem)o1;
 ListItem l2 = (ListItem)o2;
 String f1 = l1.getText();
 String f2 = l2.getText();
 return StringSorter.compare(f1, f2, StringSorter.STRINGSORT | StringSorter.IGNORECASE);
 }
 }
 private void button1_click(Object source, Event e)
 {
 listView.setView(ViewEnum.LARGEICON);
 }
 private void button2_click(Object source, Event e)
 {
 listView.setView(ViewEnum.SMALLICON);
 }
 private void button3_click(Object source, Event e)
 {
 listView.setView(ViewEnum.LIST);
 }
 private void button4_click(Object source, Event e)
 {
 listView.setView(ViewEnum.REPORT);
 }
 /**
 * Handle item activation events.
 */
 private void dbl_click(Object source, Event e)
 {
 // get the first ListItem selected (in single select mode,
 // there will only ever be one)
 ListItem li = listView.getSelectedItems()[0];
 String dirName = li.getText();
 // if this item is the "."…
 if (dirName.equals("."))
 {
 // then ignore it (there's no change)
 return;
 }
 // if this is the ".."…
 if (dirName.equals(".."))
 {
 // then remove the last directory in the path
 // including the "\\"
 int offset = directory.lastIndexOf("\\");
 directory = directory.substring(0, offset);
 // if there's nothing left…
 if (directory.lastIndexOf("\\") == -1)
 {
 // change the directory to the root
 directory += "\\";
 }
 }
 // otherwise (this isn't "..")
 else
 {
 // make sure this is a directory
 dirName = File.combine(directory, dirName);
 if (!File.isDirectory(dirName))
 {
 return;
 }
 // it is - save it as the new directory
 directory = dirName;
 }
 // recreate the list view using this new directory
 createListView();
 }
 /**
 * NOTE: The following code is required by the Visual J Plus Plus form
 * designer. It can be modified using the form editor. Do not
 * modify it using the code editor.
 */
 Container components = new Container();
 ListView listView = new ListView();
 ColumnHeader columnHeader1 = new ColumnHeader();
 ColumnHeader columnHeader2 = new ColumnHeader();
 ColumnHeader columnHeader3 = new ColumnHeader();
 ImageList smallIconImageList = new ImageList();
 Button button1 = new Button();
 Button button2 = new Button();
 Button button3 = new Button();
 ImageList largeIconImageList = new ImageList();
 Button button4 = new Button();
 private void initForm()
 {
 // …created by Forms Designer…
 }
 /**
 * The main entry point for the app. *
 * @param args Array of parameters passed to the app
 * via the command line.
 */
 public static void main(String args[])
 {
 app.run(new Form1(args));
 }
}


FileViewDemo defines one data member, directory, which refers to the full path of the directory being listed. The constructor for Form1 initializes directory to the current directory, but if the user provides an argument when FileViewDemo is started, this argument is copied into directory instead. Once directory has been initialized, the Form1() constructor calls createListView() to populate the list view with the contents of that directory.

createListView() method

Two of the methods provided by the class ListView are addItem() and setItems(). The addItem() method adds a new item to those already present and setItems() replaces the current list of items with a new list of items. Both of these methods accept an object of class ListItem. Thus, a ListItem object describes a row in a list view.

The createListView() method begins with clearing out the list view by calling setItems() and passing it a zero-length array of ListItem objects. Then createListView() begins adding files to the now empty list view by calling the addFilesToListView() method twice, once for the subdirectory values contained in the directory variable and once for the file name values in directory.

NOTE
Iterating through the directory once for subdirectories and a second time for files might not be the most efficient algorithm, but it decreases the code complexity.

The addFilesToListView() method contains three distinct sections. In the first section, addFilesToListView() creates a List object and adds ListItem objects to it using the FileEnumerator class.

The FileEnumerator class provides a convenient means of iterating through a list of files. The constructor for FileEnumerator takes as its argument a file filter. For example, the file filter "C:\temp\*.txt" would find all of the .txt files in the \temp directory. The program navigates FileEnumerator using three methods: getName() returns the name of the file to which the FileEnumerator is currently pointing, getNextFile() moves the pointer to the next file, and hasMoreFiles() returns true as long as there are more files in the enumerator.

Before addFilesToListView() processes a file name from the enumerator, it must first check to see if the file name refers to a directory or a file. If the return from File.isDirectory() is the value the calling method specified in the isDirectory parameter (true for directories and false for files), then addFilesToListView() retrieves some extra information about the file that, together with the file name, is converted into a ListItem object.

The constructor for a ListItem object takes three arguments. The first argument is the name of the file. The second argument is the index of the image to display for this file. This index refers to the icon image list associated with the list view, and it is returned from ImageEnum.getImageIndex() as described earlier. The final argument to the constructor is an array of String objects. In our case, this array refers to the second and third columns in report view mode—the file size and the time of last modification (the "extra" information). Both of these pieces of information come from the FileEnumerator class. The constructed ListItem object is then added to the list object of class List.

In the second portion of addFilesToListView(), the method sorts the list object containing the individual list items. It does this by calling the List.sort() method. The only argument to List.sort() is an object of a class that implements the IComparer interface. The class FileNameComparer implements this interface by implementing the method compare(), which compares two items of a list and returns a -1, 0, or 1 depending on the relationship between the two list items.

Since we're using the file names to determine the sort order, my implementation of compare() first calls ListItem.getText() to pull the file names from the two list items that are being compared. Then compare() calls StringSorter.compare() to perform the actual comparison. This public static method is similar to the method String.compareTo() except that it allows for the IGNORECASE option. In addition, the STRINGSORT option causes the compare() method to ignore nonalphanumeric characters when performing its comparison.

NOTE
If you're already familiar with Java, you probably already know about the String.compareTo() method. If you don't know how this method works, refer to the MSDN Library Visual Studio.

Once the list object has been sorted, addFilesToListView() iterates through the list using an item enumerator. The item enumerator works with the List class in the same way that the FileEnumerator class worked on the files in a directory. The call to list.getItemEnumerator() returns the list enumerator. The call to nextItem() returns the current item and moves the pointer to the next item. The method hasMoreItems() returns false when there are no more objects left in the list. As each object is removed from the list, it is added to the listView object by means of a call to addItem().

event handlers

The first four event handlers in ListViewDemo are the button_click() methods for the four buttons on the form. Each of these simply sets the viewing mode of the listView object by calling the setView() method and passing it one of the four ViewEnum constants.

I created the method dbl_click() in the Forms Designer by typing this name into the value box for the itemActivate property, which is listed among the active properties of the ListView control. This event is triggered when the user chooses one of the objects in the list. (Whether this requires a single click or a double click depends on the activation property of the ListView control that we set in the Properties window earlier.)

The dbl_click() method begins by loading the first item in the list the user selected. (More than one item can be chosen if the list view is in multiselect mode, but only the first item in the list is ever loaded.) The method ListItem.getText() returns the file name for the selected list item. There are two special cases to be considered. If the user chooses the current directory ".", the app does nothing. If the user selects "..", the app strips off the last directory name in the path and assigns the result to the data member directory. With any other choice, the app tacks the selected file name onto the current directory. If the result is a directory, this result is assigned to the directory variable.

Finally, the method calls createListView() to repopulate the list view with the contents of the files contained in directory.

Result

The result of executing the ListViewDemo app on the current directory is shown in Figures 8-4 and 8-5. Figure 8-4 shows ListViewDemo in list mode and Figure 8-5 shows the same directory in Details report mode.

Screenshot

Screenshot-4. The current directory in list mode showing the images subdirectory, the ListViewDemo executable file, and a series of ListViewDemo support files.

Java Click to view at full size.

Screenshot-5. Details report mode shows a lot more information about each file, but doesn't display as many files in the same amount of space.

Although impossible to demonstrate in a figure, ListViewDemo allows the user to navigate up in the directory structure by double-clicking the ".." entry and down in the directory structure by double-clicking a subdirectory. Comments