ListView

The ListView control displays a list of items, with optional icons and subitems associated with each. A ListView is used as the right pane in Windows Explorer, displaying the files and directories contained within the directory currently selected in the tree view in the left pane. The ListView class contains a number of useful properties, methods, and events, listed in Tables Table 14-17, Table 14-22, and Table 14-23, respectively.

The ListView control contains a collection of objects of type ListViewItem: the Items property (listed in Table 14-17, along with other commonly used ListView properties). The Items property itself is of type ListViewItemCollection, which has methods (listed in Table 14-30) for adding, inserting, removing, and otherwise manipulating items in the collection.

The ListViewItemCollection class implements the ICollection interface, the IList interface that derives from it, and the IEnumerable interface. The first two interfaces provide access to individual items via a zero-based index, and the IEnumerable interface provides the capability to iterate over the collection. Both features are central to the capabilities of the ListView, as seen below.

The ListView control can be viewed in one of four ways, specified by the ListView.View property: large icon, small icon, list, and detail. Valid values of the View property, corresponding to each of these view modes, are members of the View enumeration, listed and described in Table 14-21.

When the View property is set to either View.SmallIcon or View.List, the image displayed with each item is obtained from the image list specified by the SmallImageList property. When the View property is set to View.LargeIcon, the images are obtained from the image list specified by the LargeImageList property. Typically, small images are displayed at 16 x 16 pixels (which is the default image size for an ImageList), while large images are typically displayed at anywhere from 32 to 64 pixels square. In all three view modes, the only information displayed with each item, in addition to an image, is the item's Text property.

ImageLists are covered in Chapter 7. Remember that all the images in a particular ImageList display at the same size, specified in pixels by the ImageList.ImageSize property, and not dictated by the original size of the image.

When the View property is set to View.Details, the ListView displays columns containing subitems (pieces of information related to the item). In Windows Explorer, these subitems typically include the size and type of file and the last modified date. Although the Explorer UI allows the user to specify the subitems and order of columns, this capability is not native to the ListView control, but must be added programmatically. What is native to the control, and comes for free if the AllowColumnReorder property is set to true, is the ability for the user to reorder the columns at runtime simply by dragging a column to a new location relative to the other columns in the ListView.

One or more items in a ListView can be selected. If the MultiSelect property is set to true (the default value), then multiple items can be selected using the standard Windows techniques of selecting contiguous items by holding down the Shift key or noncontiguous items by holding down the Ctrl key. (If the HoverSelection property is set to true, then simply hovering the mouse over an item selects it.) Selected items typically display as highlighted.

In any case, the selected item (or items) are accessible through the SelectedItems and SelectedIndices properties. The SelectedItems property contains a read-only collection of the selected items and the SelectedIndices property contains a read-only collection of indexes of the selected items, either of which can be iterated to process the selection. When the selected item in a single-selection ListView changes, or when an item is selected or deselected in a multiple-selection ListView, then the SelectedIndexChanged event (described in Table 14-23) is raised. You can then process either the SelectedItems or the SelectedIndices collections in the event handler.

Be aware that the SelectedIndexChanged event may be raised twice when selecting an itemonce when the first item is deselected and again when the new selection is selected. To avoid duplicate event handling, test the SelectedIndices.Count property of the ListView (see Table 14-17 for a list of ListView properties) inside the event handler and do only the desired processing when the count is nonzero. That way, it will not process when the first item is deselected. For example:

private void listView1_SelectedIndexChanged (object sender, EventArgs e) { if (listView1.SelectedIndices.Count != 0) { // Do some processing here } }

Item activation is closely related to item selection. When an item is activated, the ItemActivate event (described in Table 14-23) is raised, and again, the SelectedItems or the SelectedIndices properties can be processed in the event handler. Activation lets your program respond to user action, such as by opening a file.

The user action required to initiate activation is controlled by the Activation property, valid values of which are members of the ItemActivation enumeration, listed in Table 14-18. The default value, ItemActivation.Standard, specifies that double-clicking on the item activates it, but there is no visual cue to that effect. If the Activation property is set to either OneClick or TwoClick, then the mouse cursor changes to a hand pointer and the item text changes color when the mouse cursor passes over. OneClick gives browser-like functionality (similar to clicking on a hyperlink), and TwoClick is similar to the standard behavior, but with the visual cue and, more significantly, with two single clicks (the first click selects the item and the second click activates it) rather than one double-click causing activation. Irrespective of the value of the Activation property, pressing Enter effectively activates all the currently selected items.

It is also possible to display checkboxes with each list item by setting the CheckBoxes property to true. If this is the case, then the user can select items by clicking on the checkbox next to that item, allowing the user to select multiple items without using either the Ctrl or Shift keys.

If the CheckBoxes property is set to true, then multiple items can be selected even if the MultiSelect property is set to false.

Processing items selected with checkboxes is analogous to the behavior when checkboxes are disabled. The ItemCheck event (listed in Table 14-23) is raised whenever the check state of an item changes. The checked items can be accessed via the CheckedItems or CheckedIndices properties, both of which are read-only collections of items or indexes, respectively.

Table 14-17. ListView properties

Property

Value type

Description

Activation

ItemActivation

Read/write. Specifies how the user activates an item. Valid values are members of the ItemActivation enumeration, listed in Table 14-18. Default value is ItemActivation.Standard. If value is non-default, label editing will be disallowed, regardless of LabelEdit property value.

Alignment

ListViewAlignment

Read/write. Specifies the alignment of items in the control. Valid values are members of the ListViewAlignment enumeration, listed in Table 14-19. Default is ListViewAlignment.Top.

AllowColumnReorder

Boolean

Read/write. If false (the default), drag-and-drop column reordering at runtime is not allowed. Only applicable if View property is set to View.Details.

AutoArrange

Boolean

Read/write. If true (the default), icons are automatically arranged and snapped to grid to not overlap at runtime. Only applicable if View property is set to View.LargeIcon or View.SmallIcon.

BorderStyle

BorderStyle

Read/write. The border style of the control. Valid values are members of the BorderStyle enumeration, listed in Table 14-2. Default is BorderStyle.Fixed3D.

CheckBoxes

Boolean

Read/write. If true, a checkbox appears next to each item. User can then select items by clicking on the checkbox. Allows multiselection without use of the Ctrl key and even if MultiSelect property is false. ItemCheck event is raised when an item is checked. Default is false.

CheckedIndices

ListView.CheckedIndexCollection

Read-only. Collection of indexes of the currently checked items. Returns an empty collection if no items are checked. Only applicable if CheckBoxes property is set to true.

CheckedItems

ListView.CheckedListViewItemCollection

Read-only. Collection of the currently checked items. Returns an empty collection if no items are checked. Only applicable if CheckBoxes property is set to true.

Columns

ListView.ColumnHeaderCollection

Read-only. Collection of ColumnHeader objects, each of which represents the a column header when the View property is set to View.Details. If no column headers are specified and View property is set to View.Details, no items will be displayed.

FocusedItem

ListViewItem

Read-only. The item that currently has focus. If no item has focus, returns null (Nothing).

ForeColor

Color

Read/write. Foreground color of the control. This is the color used to display the text of the item.

FullRowSelect

Boolean

Read/write. If true, clicking an item selects all of its subitems. Default is false. Only applicable if View property is set to View.Details.

GridLines

Boolean

Read/write. If true, gridlines are displayed around items and subitems. Default is false. Only applicable if View property is set to View.Details. These gridlines do not give users resizing functionality, as is the case in Microsoft Excel.

HeaderStyle

ColumnHeaderStyle

Read/write. Valid values are the members of the ColumnHeaderStyle enumeration, listed in Table 14-20. If set to ColumnHeaderStyle.Clickable (the default), header can respond to clicks. If set to ColumnHeaderStyle.None, the header will not be displayed. Only applicable if View property set to View.Details and the ColumnHeaderCollection is not empty.

HideSelection

Boolean

Read/write. If true (the default), the highlighting of the selected item disappears when the control does not have focus.

HoverSelection

Boolean

Read/write. If true, hovering mouse cursor over an item automatically selects it. Default is false.

Items

ListView.ListViewItemCollection

Read-only. The collection of items in the control. Items can be added, removed, and otherwise manipulated from the collection using methods of the ListViewItemCollection class, listed in Table 14-30.

LabelEdit

Boolean

Read/write. If true, users can edit the item labels at runtime. Default is false.

LabelWrap

Boolean

Read/write. If true (the default), item labels will wrap when necessary if icons are displayed. Only applicable if View property is set to View.LargeIcon or ViewSmallIcon.

LargeImageList

ImageList

Read/write. The ImageList to use when the View property is set to View.LargeIcon.

ListViewItemSorter

IComparer

Read/write. The IComparer object used to perform sorting on the items in the ListView. Setting the value of this property allows the Sort method (described in Table 14-22) to be called.

MultiSelect

Boolean

Read/write. If true (the default), multiple items in the ListView can be selected.

Scrollable

Boolean

Read/write. If true (the default), scrollbars are added to the control as necessary.

SelectedIndices

ListView.SelectedIndexCollection

Read-only. A collection of indexes of selected items. If no items are selected, an empty collection is returned. If the MultiSelect property is false, the collection contains at most a single item.

SelectedItems

ListView.SelectedListViewItemCollection

Read-only. A collection of selected items. If no items are selected, an empty collection is returned. If the MultiSelect property is false, the collection contains at most a single item.

SmallImageList

ImageList

Read/write. The ImageList to use when the View property is set to View.SmallIcon.

Sorting

SortOrder

Read/write. The sort order for the items in the control. Valid values are members of the SortOrder enumeration: Ascending, Descending, or None (the default).

StateImageList

ImageList

Read/write. The ImageList used to display an application-defined state of an item. The state image is displayed for all values of the View property, to the left of the icon for the item.

If the CheckBoxes property is true, then the image at index 0 is displayed instead of an unchecked box and the image at index 1 is used instead of a checked box.

Text

String

Read/write. The text string displayed with an item in the control.

TopItem

ListViewItem

Read-only. The first item visible in the control.

View

View

Read/write. Specifies how items are displayed in the control. Valid values are members of the View enumeration, listed in Table 14-21. Default is View.LargeIcon.

Table 14-18. ItemActivation enumeration values

Value

Description

OneClick

User single-clicks to activate the item. Mouse cursor changes to hand pointer and item text changes color as mouse cursor passes over the item.

Standard

User double-clicks to activate the item. No visual changes to item as mouse cursor passes over.

TwoClick

User double-clicks to activate the item, but the two clicks can have any duration between them. Item text changes color as mouse cursor passes over.

Table 14-19. ListViewAlignment enumeration values

Value

Description

Default

Item remains where dropped when moved by user.

Left

Items in ListView aligned to the left.

SnapToGrid

Items aligned to invisible grid when moved by user.

Top

Items in ListView aligned to the top.

Table 14-20. ColumnHeaderStyle enumeration values

Value

Description

Clickable

Column headers behave like buttons and can perform an action when clicked.

Nonclickable

Column headers do not behave like buttons, ignoring clicks.

None

Column headers not displayed and do not respond to clicks.

Table 14-21. View enumeration values

Value

Description

Details

Each item appears on a separate line with subitems arranged in columns.

LargeIcon

Each item appears with the specified icon from the image list specified in the LargeImageList property. The item label appears below the icon.

List

Items are arranged in columns with no column headers. Each item appears with the specified icon from the image list specified in the SmallImageList property. The item label appears to the right of the icon.

SmallIcon

Each item appears with the specified icon from the image list specified in the SmallImageList property. The item label appears to the right of the icon.

Table 14-22. ListView methods

Method

Description

ArrangeIcons

Overloaded. Arranges icons using either the default or specified alignment setting (possible values are members of the ListViewAlignment enumeration listed in Table 14-19) when the View property is set to View.LargeIcon or View.SmallIcon.

BeginUpdate

Prevents the control from repainting until the EndUpdate method is called. This improves performance and prevents flickering when adding multiple items by using the Add method.

Clear

Removes all items and columns from the control.

EnsureVisible

Ensures that the item with the specified index is visible in the control, scrolling as necessary.

EndUpdate

Causes the control to repaint after the BeginUpdate method has been called.

EnsureVisible

Ensures that the item with the specified zero-based index is visible, scrolling the control as necessary.

GetItemAt

Gets the item at the point specified by the X and Y client coordinates. Typically these coordinates are determined by a mouseclick.

GetItemRect

Overloaded. Gets either the bounding rectangle or the specified portion of the bounding rectangle for the item specified by the zero-based index.

Sort

Sorts the items in the ListView using the IComparer specified in the ListViewItemSorter property.

Table 14-23. ListView events

Event

Event argument

Description

ColumnClick

ColumnClickEventArgs

Raised when the user clicks a column header and the View property is set to View.Details. This is typically used to sort the items in the ListView by the clicked column.

The ColumnClickEventArgs has a single property, Column, which returns the zero-based index of the clicked column.

AfterLabelEdit

LabelEditEventArgs

Raised after the label of a ListViewItem has been edited. The LabelEditEventArgs returns the properties listed in Table 14-24.

BeforeLabelEdit

LabelEditEventArgs

Raised before the label of a ListViewItem is about to be edited. The LabelEditEventArgs event argument returns the properties listed in Table 14-24. The ListView.Activation property must be set to Standard in order for this event to be raised and label editing to be allowed.

ItemActivate

EventArgs

Raised when an item is activated.

ItemCheck

ItemCheckEventArgs

Raised when the check state of an item changes. The ItemCheckEventArgs event argument returns the properties listed in Table 14-25. The ListView.CheckBoxes property must be set to true for this event to be raised

ItemDrag

ItemDragEventArgs

Raised when the user begins dragging an item.

SelectedIndexChanged

EventArgs

If MultiSelect property is false, this event is raised when there is a change in the index of the selected item. If MultiSelect property is true, this event is raised when an item is either selected or deselected.

Table 14-24. LabelEditEventArgs properties

Property

Description

CancelEdit

Read-write Boolean value indicating if the edit has been canceled.

Item

Returns the zero-based index of the ListViewItem whose label is to be edited.

Label

Returns the new text associated with the ListViewItem.

Table 14-25. ItemCheckEventArgs properties

Property

Description

CurrentValue

Returns the current state of the item's checkbox. Valid values are members of the CheckState enumeration, listed in Table 14-26.

Index

Zero-based index of the current item.

NewValue

Read/write. The new value of the item after the change takes effect. Valid values are members of the CheckState enumeration, listed in Table 14-26. This value can be changed in the event handler to override the user action.

Table 14-26. CheckState enumeration values

Value

Description

Checked

Item is checked.

Unchecked

Item is not checked.

Indeterminate

Item is neither checked nor unchecked. Appears shaded.

As mentioned earlier, each item in a ListView, i.e., each element of the Items collection, is an object of type ListViewItem. The ListViewItem class has seven different constructors. The argument list for each constructor is listed in Table 14-27. As you can see, there are many different ways to instantiate a new list item, depending upon what objects are currently available and whether subitems go with the item.

Table 14-27. ListViewItem constructors

C# argument list

VB.NET argument list

Description

No parameters

No parameters

Initializes a new instance with default values.

string

String

Initializes a new instance with the text specified in the string.

string[ ]

String( )

Initializes a new instance with subitems represented by the array of strings.

ListViewItem.ListViewSubItem[ ], int

ListViewItem.ListViewSubItem( ), Integer

Initializes a new instance with the array of ListViewSubItem objects representing the subitems and an icon represented by the specified index into the appropriate image list.

string, int

String, Integer

Initializes a new instance with text specified in the string and an icon represented by the specified index into the appropriate image list.

string[ ], int

String( ), Integer

Initializes a new instance with subitems represented by the array of strings and an icon represented by the specified index into the appropriate image list.

string[ ], int, Color, Color, Font

String( ), Integer, Color, Color, Font

Initializes a new instance with subitems represented by the array of strings, an icon represented by the specified index into the appropriate image list, the foreground color, the background color, and the item's font.

In addition to the ListView properties listed in Table 14-17 that apply to the ListView as a whole, each item in the ListView has its own set of properties, listed in Table 14-28, and methods, listed in Table 14-29. So, for example, you can set the foreground color of each item programmatically, depending on some feature of that item, such as whether it exceeds a certain numeric value.

Table 14-28. ListViewItem properties

Property

Value type

Description

BackColor

Color

Read/write. Specifies the item's background color.

Bounds

Rectangle

Read-only. The item's bounding rectangle, including any subitems.

Checked

Boolean

Read/write. If the CheckBoxes property is set to true, then this property is true if the checkbox is checked. Default is false.

Focused

Boolean

Read/write. If true, the item has focus.

Font

Font

Read/write. Specifies the font of the text associated with the item.

ForeColor

Color

Read/write. Specifies the color of the item's text.

ImageIndex

Integer

Read/write. The zero-based index of the image, from the appropriate image list, displayed with the item.

ImageList

ImageList

Read-only. The image list that contains the image displayed with the item.

Index

Integer

Read-only. The zero-based index of the item within the control's ListView.ListViewItemCollection. Returns -1 if the item is not associated with a ListView control.

ListView

ListView

Read-only. The ListView control that contains the item.

Selected

Boolean

Read/write. If true, the item is selected.

StateImageIndex

Integer

Read/write. The zero-based index of the state image in the image list specified by the ListView.StateImageList property. Typically used to display images representing checked and unchecked states. Maximum value of StateImageIndex property is 14, but only the images at index and 1 can be displayed as state images.

SubItems

ListViewItem.ListViewSubItemCollection

Read-only. The collection containing the subitems for the item. The ListViewSubItemCollection class contains methods (listed in Table 14-32) for manipulating the collection.

Tag

Object

Read/write. An object containing data associated with the item.

Text

String

Read/write. Specifies the text associated with the item.

UseItemStyleForSubItems

Boolean

Read/write. If true (the default), the Font, ForeColor, and BackColor properties of the item will also apply to the item's subitems and changes to the appropriate ListViewSubItem properties are ignored.

Table 14-29. ListViewItem methods

Method

Description

BeginEdit

Places the item in edit mode so that the text associated with the item can be modified by the user. Only relevant if the LabelEdit property is set to true.

Clone

Creates a new instance of a ListViewItem, which is an identical copy of the original, including subitems. Implements IClonable.Clone.

EnsureVisible

Similar to the ListView.EnsureVisible method. Scrolls the list as necessary to ensure that the item is visible. Often used to make visible any item that fails validation.

Remove

Removes the item from the ListView.

All ListViewItems in the control are contained within a collection of type ListViewItemCollection. This collection has several methods that allow you to programmatically manipulate the collection, such as by adding one or more ListViewItems, removing one or more items, or finding the index of a specific item in the collection. The commonly used ListViewItemCollection methods are listed in Table 14-30.

Table 14-30. ListViewItemCollection methods

Method

Description

Add

Overloaded. Adds either a string or an existing ListViewItem to the collection. Returns the ListViewItem object that was added to the collection.

AddRange

Removes all existing items from the ListView control and adds an array of ListViewItem objects to the collection.

Clear

Removes all the items from the collection. Implements IList.Clear.

Contains

Returns true if the specified ListViewItem is contained within the collection.

CopyTo

Copies the entire collection to the specified array at the index specified by the index. Implements ICollection.CopyTo.

IndexOf

Returns the zero-based index of the specified ListViewItem. If not found, returns -1.

Insert

Overloaded. Inserts and returns a ListViewItem with the specified text into the collection at the specified index, optionally using the image at the specified index of the appropriate image list.

Remove

Removes the specified ListViewItem from the collection.

RemoveAt

Removes the ListViewItem with the specified zero-based index from the collection.

If the ListView.View property is set to View.Details, then each item can have subitems associated with it. For example, the ListView in Windows Explorer has an Items collection consisting of files and directories. The subitems typically displayed with each item are size, file type, and the last modified date. Each subitem has a set of properties, listed in Table 14-31.

Table 14-31. ListViewSubItem properties

Property

Value type

Description

BackColor

Color

Read/write. Specifies the background color of the subitem.

Font

Font

Read/write. Specifies the font of the text associated with the subitem.

ForeColor

Color

Read/write. Specifies the color of the subitem's text.

Text

string

Read/write. Specifies the text associated with the subitem.

All of the subitems associated with a ListViewItem are contained in a collection of type ListViewSubItemCollection. Analogously to the ListViewItemCollection class, this class also has methods for manipulating the subitems in the collection. The commonly used ListViewSubItemCollection methods are listed in Table 14-32.

Table 14-32. ListViewItem.ListViewSubItemCollection methods

Method

Description

Add

Overloaded. Adds either an existing ListViewSubItem, a string, or a string with specified colors and font to the collection. Returns the ListViewSubItem that was added to the collection.

AddRange

Overloaded. Removes all existing subitems from the parent ListViewItem and adds either an array of ListViewSubItem objects, an array of strings, or an array of strings with specified colors and font to the collection.

Clear

Removes all the subitems from the collection. Implements IList.Clear.

Contains

Returns true if the specified ListViewSubItem is contained within the collection.

IndexOf

Returns the zero-based index of the specified ListViewSubItem. If not found, returns -1.

Insert

Inserts an existing ListViewSubItem into the collection at the specified zero-based index.

Remove

Removes the specified ListViewSubItem from the collection.

RemoveAt

Removes the ListViewSubItem with the specified zero-based index from the collection.

14.4.1 Windows Explorer Clone

The best way to put all this together is to look at an example that uses the ListView in a real-world way. The code built up over the next several pages and shown in its entirety in Example 14-5 (in C#) and in Example 14-6 (in VB.NET) creates a clone of Windows Explorer, with a TreeView on the left side of the window and ListView on the right, separated by a splitter. The code creating and handling the TreeView is borrowed essentially intact from Example 14-3 (in C#) and Example 14-4 (in VB.NET).

14.4.1.1 Creating the form

Unlike the previous examples in this chapter, these examples are created by using Visual Studio .NET. When you create an Explorer clone in Visual Studio .NET, you add the controls to the form in the opposite order from when hand coding in a text editor: add the TreeView first, set its Dock property to Left, add the splitter, and then add ListView with its Dock property set to Fill.

To create this example, open Visual Studio .NET and create a new project by using the Windows Application template in the language of your choice. Resize the form so it is approximately 600 square pixels.

Drag a TreeView control from the toolbox onto the form. Rename the TreeView to tvw. Set its Dock property to Left.

Next, drag a splitter control onto the form. It will glom onto the right edge of the TreeView and its Dock property will default to Left. Since you will not refer to the splitter anywhere in your code, there is no need to rename the splitter control.

Finally, drag a ListView control onto the form. Rename the control to lv. Set its Dock property to Fill.

If you now build and run the project, you will get a form similar to Windows Explorer, except with no content. However, the splitter is functional and will resize the two panes of the window. Next, add code to add the Explorer functionality.

Right-click on Form1.cs or Form1.vb in the Solution Explorer and click on View Code. The code will be displayed for editing. If you are working in C#, add the following two statements at the top of the code:

using System.IO; using System.Diagnostics;

If you are working in VB.NET, add the following import statement at the top of the code, above the Form1 class declaration:

imports System.IO;

VB.NET automatically imports the System.Diagnostics namespace, so it is not necessary to do so explicitly. The namespaces that are imported by default can be seen in the Project Property Pages by rightclicking on the project in Solution Explorer, selecting Properties, and clicking on Imports under Common Properties.

The three controls on the form are initialized in the InitializeComponent method created for you by Visual Studio .NET and called from within the constructor Form1( ) in C# and New( ) in VB.NET. However, there is more work required on form initialization, so you will add code to the constructor to do that work after the call to InitializeComponent.

First create two image listsone for small images and the other for large images. The ListView will use both image lists, and the TreeView will also use the small image list. The code to create these image lists resembles that used previously in Example 14-3 (in C#) and Example 14-4 (in VB.NET). Add the following code to the appropriate constructor:

String[ ] arFiles = { @"C:Program FilesMicrosoft Visual Studio .NET 2003Common7" + @"Graphicsiconscomputerform.ico", @"C:Program FilesMicrosoft Visual Studio .NET 2003Common7" + @"Graphicsiconswin95clsdfold.ico", @"C:Program FilesMicrosoft Visual Studio .NET 2003Common7" + @"Graphicsiconswin95openfold.ico", @"C:Program FilesMicrosoft Visual Studio .NET 2003Common7" + @"Graphicsitmapsassortedhappy.bmp", @"C:Program FilesMicrosoft Visual Studio .NET 2003Common7" + @"GraphicsitmapsoutlineNoMaskdoc.bmp", @"C:Program FilesMicrosoft Visual Studio .NET 2003Common7" + @"GraphicsitmapsoutlineNoMaskexe.bmp", @"C:Program FilesMicrosoft Visual Studio .NET 2003Common7" + @"GraphicsitmapsoutlineNoMask xt.bmp", @"C:Program FilesMicrosoft Visual Studio .NET 2003Common7" + @"GraphicsitmapsoutlineNoMaskwindoc.bmp" }; ImageList imgListSmall = new ImageList( ); // default size 16x16 ImageList imgListLarge = new ImageList( ); imgListLarge.ImageSize = new Size(32,32); for (int i = 0; i < arFiles.Length; i++) { imgListSmall.Images.Add(Image.FromFile(arFiles[i])); imgListLarge.Images.Add(Image.FromFile(arFiles[i])); }

dim arFiles( ) as string = { _ "C:Program FilesMicrosoft Visual Studio .NET 2003Common7" + _ "Graphicsiconscomputerform.ico", _ "C:Program FilesMicrosoft Visual Studio .NET 2003Common7" + _ "Graphicsiconswin95clsdfold.ico", _ "C:Program FilesMicrosoft Visual Studio .NET 2003Common7" + _ "Graphicsiconswin95openfold.ico", _ "C:Program FilesMicrosoft Visual Studio .NET 2003Common7" + _ "Graphicsitmapsassortedhappy.bmp", _ "C:Program FilesMicrosoft Visual Studio .NET 2003Common7" + _ "GraphicsitmapsoutlineNoMaskdoc.bmp", _ "C:Program FilesMicrosoft Visual Studio .NET 2003Common7" + _ "GraphicsitmapsoutlineNoMaskexe.bmp", _ "C:Program FilesMicrosoft Visual Studio .NET 2003Common7" + _ "GraphicsitmapsoutlineNoMask xt.bmp", _ "C:Program FilesMicrosoft Visual Studio .NET 2003Common7" + _ "GraphicsitmapsoutlineNoMaskwindoc.bmp"} dim imgListSmall as new ImageList( ) ' default size 16x16 dim imgListLarge as new ImageList( ) imgListLarge.ImageSize = new Size(32,32) dim i as Integer for i = 0 to arFiles.Length - 1 imgListSmall.Images.Add(Image.FromFile(arFiles(i))) imgListLarge.Images.Add(Image.FromFile(arFiles(i))) next

This code first creates an array of strings, with each string containing the full path and filename of an image file found as part of the standard Visual Studio .NET installation. It then instantiates and populates two image lists. imgListSmall retains the default size of 16 x 16 pixels, while the size of the images in imgListLarge is set to 32 square pixels.

Next, add several lines to set additional properties of the TreeView. Add these lines of code:

tvw.Size = new Size(ClientSize.Width / 3, ClientSize.Height); tvw.BackColor = Color.Moccasin; tvw.HideSelection = false; tvw.ImageList = imgListSmall; tvw.ImageIndex = 1; tvw.SelectedImageIndex = 2;

tvw.Size = new Size(ClientSize.Width / 3, ClientSize.Height) tvw.BackColor = Color.Moccasin tvw.HideSelection = false tvw.ImageList = imgListSmall tvw.ImageIndex = 1 tvw.SelectedImageIndex = 2

You have previously dragged a ListView control onto the form in the design view and renamed it lv. Now you must add the following lines of code to set additional properties for that ListView. All of these properties are described in Table 14-17. Several of the properties are set to default values, primarily for self-documentation. This code is identical in both languages, except for the trailing semicolon in C# and different comment characters.

lv.BackColor = Color.PaleTurquoise; lv.ForeColor = Color.DarkBlue; lv.HideSelection = false; lv.SmallImageList = imgListSmall; lv.LargeImageList = imgListLarge; lv.View = View.SmallIcon; lv.Activation = ItemActivation.TwoClick; lv.MultiSelect = true; // default lv.HoverSelection = false; // default lv.Sorting = SortOrder.None; // default lv.AllowColumnReorder = true; lv.FullRowSelect = true; lv.GridLines = true; lv.HeaderStyle = ColumnHeaderStyle.Clickable; // default

The next line of code calls to the FillDirectoryTree method, which fills the TreeView control with the directory structure from the filesystem. This method is identical to that used in Example 14-3 and Example 14-4, and described with those examples.

FillDirectoryTree( );

The FillDirectoryTree method calls the GetSubDirectoryNodes method, which is also identical to that used in Example 14-3 and Example 14-4. A third method borrowed from Example 14-3 and Example 14-4 is tvw_BeforeExpand. The only difference between these methods in this example and in Example 14-3 and Example 14-4 is that the TreeView example had a checkbox to indicate whether the GetSubDirectoryNodes would get filenames as well as directory names. The value of this checkbox is passed as a parameter to GetSubDirectoryNodes. In the current examples, that value is hardcoded to false, since the TreeView in the left pane never displays files and the checkbox is not present in this program.

The previously mentioned method tvw_BeforeExpand handles the TreeView BeforeExpand event. Visual Studio .NET eases the chore of creating the event handler and adding it to the event delegate.

14.4.1.2 Setting events

In C#, go to the design view of the form, click on the tree view (which has already been named tvw) on the form, and click on the yellow lightning bolt in the Properties window. All possible events for tvw will be listed in the Properties window. Find the BeforeExpand event and double-click on the empty field next to it. This will automatically fill in the event handler name as tvw_BeforeExpand, insert a line in the InitializeComponent method to add the event handler to the event delegate, and create an empty code skeleton for the event handler method. Then you can just copy the code from Example 14-3, with the minor change of hardcoding the second parameter to the GetSubDirectoryNodes method to false.

In VB.NET, the procedure is different. Go to the code page, click on the drop-down menu at the top left of the code window, and select tvw from the list of objects in the class. Then click on the drop-down menu at the top right of the code window, which displays a list of all the events for that control. Select BeforeExpand. This will automatically create the requisite code skeleton with the necessary Handles keyword to implement the event handling. Again, copy the code from Example 14-4 with the minor change of hard-coding the second parameter to the GetSubDirectoryNodes method to false.

14.4.1.3 Creating the menus

The next task is to create a simple menuing system for the application, consisting of a View menu item with four subitems: Small Icons, Large Icons, List, and Details, corresponding to the four values of the View property listed in Table 14-21. (Menus will be covered in detail in Chapter 18.)

To create the menu, go to design view and drag a MainMenu control onto the form. It will display at the bottom of the design window as MainMenu1, and a field will be visible in the form's menu area with the legend Type Here. Type in &View. The leading ampersand will cause the V to display underlined when the program runs and act as a shortcut key. When you press Enter, there will be two Type Here's displayed; click on the one below and type in &Small Icons. After pressing Enter, continue typing in the following submenu items: &Large Icons, Lis&t, and &Details. Click back on the each menu subitem, in turn, and change the name of each in the Properties window, respectively to mnuSmallIcons, mnuLargeIcons, mnuList, and mnuDetails.

The final step is clicking on the form itself, and then going to the Properties window and setting the Menu property of the form to MainMenu1.

You will now create a single event handler method to handle the Click event for all the subitems, called mnuView_Click.

Events are discussed in detail in Chapter 4. In that chapter, the differences in implementing events in C# versus VB.NET is explained.

In C#, add the following code to the Form1 class:

private void mnuView_Click(object sender, EventArgs e) { MenuItem mnu = (MenuItem)sender; switch (mnu.Mnemonic.ToString( )) { case "L" : // Large Icons lv.View = View.LargeIcon; break; case "S" : // Small Icons lv.View = View.SmallIcon; break; case "T" : // List view lv.View = View.List; break; case "D" : // Detail view lv.View = View.Details; break; } }

In VB.NET, the event handler looks like the following:

private sub mnuView_Click(ByVal sender As Object, ByVal e As EventArgs) _ Handles mnuDetails.Click, mnuLargeIcons.Click, mnuList.Click, _ mnuSmallIcons.Click dim mnu as New MenuItem mnu = CType(sender, MenuItem) select case (mnu.Mnemonic.ToString( )) case "L" : ' Large Icons lv.View = View.LargeIcon case "S" : ' Small Icons lv.View = View.SmallIcon case "T" : ' List view lv.View = View.List case "D" : ' Detail view lv.View = View.Details end select end sub

Once the event handler is in place, you can now assign that event handler to each menu subitem. In C#, this is done with the following sequence of steps:

  1. Click on the yellow lightning bolt in the Properties window.
  2. Click on each menu subitem in turn.
  3. For each menu subitem, click on the drop-down arrow next to the Click event.
  4. Select mnuView_Click.

In VB.NET, these steps not necessary. Adding the Handles keyword to the event handler declaration and listing the events to be handled by the method is all that is necessary to hook the event handler to those events. Alternatively, you could omit the Handles keyword and instead add the following AddHandler statements back in the constructor:

AddHandler mnuSmallIcons.Click, AddressOf mnuView_Click AddHandler mnuLargeIcons.Click, AddressOf mnuView_Click AddHandler mnuList.Click, AddressOf mnuView_Click AddHandler mnuDetails.Click, AddressOf mnuView_Click

14.4.1.4 Populating the ListView

If you run the program at this point, it will work well, except that the ListView on the right side will never be populated. To implement that functionality, the program must respond to a selection in the tree view on the left. This is done by handling the TreeView AfterSelect event.

In C#, go to the Design view, click on the TreeView control, and then the yellow lightning bolt in the Properties window. Click on the space next to the AfterSelect event. The default method name tvw_AfterSelect will be filled in, and you will be taken to a code skeleton for that event handler. Enter the following code in the method:

private void tvw_AfterSelect(object sender, TreeViewEventArgs e) { lv.Clear( ); lv.BeginUpdate( ); DirectoryInfo di = new DirectoryInfo(e.Node.FullPath); FileSystemInfo[ ] afsi = di.GetFileSystemInfos( ); foreach (FileSystemInfo fsi in afsi) { ListViewItem lvi = new ListViewItem(fsi.Name); if ((fsi.Attributes & FileAttributes.Directory) != 0) { lvi.ImageIndex = 1; lvi.SubItems.Add(""); // Bytes subitem } else { switch(fsi.Extension.ToUpper( )) { case ".DOC" : lvi.ImageIndex = 4; break; case ".EXE" : lvi.ImageIndex = 5; break; case ".TXT" : lvi.ImageIndex = 6; break; default : lvi.ImageIndex = 7; break; } // Bytes subitem, w/ commas // Cast FileSystemInfo object to FileInfo object so the size // can be obtained. lvi.SubItems.Add(((FileInfo)fsi).Length.ToString("N0")); } // Add the remaining subitems to the ListViewItem lvi.SubItems.Add(fsi.Extension); // type lvi.SubItems.Add(fsi.LastWriteTime.ToString( )); // modified // Build up the Attributes string string strAtt = ""; if ((fsi.Attributes & FileAttributes.ReadOnly) != 0) strAtt += "R"; if ((fsi.Attributes & FileAttributes.Hidden) != 0) strAtt += "H"; if ((fsi.Attributes & FileAttributes.System) != 0) strAtt += "S"; if ((fsi.Attributes & FileAttributes.Archive) != 0) strAtt += "A"; lvi.SubItems.Add(strAtt); // attributes lv.Items.Add(lvi); } // end foreach lv.Columns.Add("Name", 150, HorizontalAlignment.Left); lv.Columns.Add("Bytes", 75, HorizontalAlignment.Right); lv.Columns.Add("Ext.", 50, HorizontalAlignment.Left); lv.Columns.Add("Modified", 125, HorizontalAlignment.Left); lv.Columns.Add("Attrib.", 50, HorizontalAlignment.Left); lv.EndUpdate( ); } // close tvw_AfterSelect

In VB.NET, go to the code window. Click on the drop-down menu at the top left of the window, select tvw, then click on the drop-down menu at the top right of the window. Click on AfterSelect. It will take you to a code skeleton for the event handler. Here is the code for the tvw_AfterSelect method in VB.NET:

Private Sub tvw_AfterSelect(ByVal sender As Object, _ ByVal e As System.Windows.Forms.TreeViewEventArgs) _ Handles tvw.AfterSelect lv.Clear( ) lv.BeginUpdate( ) dim di as new DirectoryInfo(e.Node.FullPath) dim afsi( ) as FileSystemInfo afsi = di.GetFileSystemInfos( ) ' both files & directories dim fsi as FileSystemInfo for each fsi in afsi dim lvi as new ListViewItem(fsi.Name) if ((fsi.Attributes and FileAttributes.Directory) <> 0) then lvi.ImageIndex = 1 lvi.SubItems.Add("") ' Bytes subitem else select case (fsi.Extension.ToUpper( )) case ".DOC" : lvi.ImageIndex = 4 case ".EXE" : lvi.ImageIndex = 5 case ".TXT" : lvi.ImageIndex = 6 case else : lvi.ImageIndex = 7 end select ' Bytes subitem, w/ commas ' Cast FileSystemInfo object to FileInfo object so ' the size can be obtained. lvi.SubItems.Add(CType(fsi,FileInfo).Length.ToString("N0")) end if ' Add the remaining subitems to the ListViewItem lvi.SubItems.Add(fsi.Extension) ' type lvi.SubItems.Add(fsi.LastWriteTime.ToString( )) ' modified ' Build up the Attributes string dim strAtt as String = "" if ((fsi.Attributes and FileAttributes.ReadOnly) <> 0) then strAtt += "R" end if if ((fsi.Attributes & FileAttributes.Hidden) <> 0) then strAtt += "H" end if if ((fsi.Attributes & FileAttributes.System) <> 0) then strAtt += "S" end if if ((fsi.Attributes & FileAttributes.Archive) <> 0) then strAtt += "A" end if lvi.SubItems.Add(strAtt) ' attributes lv.Items.Add(lvi) next ' end foreach lv.Columns.Add("Name", 150, HorizontalAlignment.Left) lv.Columns.Add("Bytes", 75, HorizontalAlignment.Right) lv.Columns.Add("Ext.", 50, HorizontalAlignment.Left) lv.Columns.Add("Modified", 125, HorizontalAlignment.Left) lv.Columns.Add("Attrib.", 50, HorizontalAlignment.Left) lv.EndUpdate( ) End Sub

The tvw_AfterSelect method begins by calling the ListView.Clear method to remove all the preexisting items and columns from the control. It then calls the BeginUpdate method, which prevents the control from repainting until the EndUpdate method is called after all the ListViewItems have been added. This prevents flicker.

The goal of the AfterSelect event handler is to populate the right pane with all the directories and files contained in the directory currently selected in the left pane. To do this, a DirectoryInfo object called di is instantiated from the current TreeView node. The TreeViewEventArgs passed to the event handler has a Node property, of type TreeNode, that contains the node just selected. That TreeNode itself has a FullPath property that returns the entire path from the root of the tree.

This DirectoryInfo object represented by the tree node calls the GetFileSystemInfos instance method to return an array of FileSystemInfo objects contained by the directory represented by that node. The key fact here is that the FileSystemInfo class is the base class for both FileInfo and DirectoryInfo objects, i.e., the array contains all the files and directories contained in the node.

Iterate through this array of FileSystemInfo objects, adding each directory or file in turn to the ListView. You also add the subitems for each object, used when the ListView is displayed in Details mode.

The processing of each FileSystemInfo object begins by instantiating a new ListViewItem object, given the Name property of the FileSystemInfo object. This will constitute the text displayed for that item. As shown in Table 14-27, ListViewItem has seven different constructors; this example uses the one that takes a single string as a parameter.

Next, assign the appropriate image for each item. This depends on whether the item is a directory or a file, and if a file, on the file extension.

The if statement tests to see if the object is a file or a directory, using the Attributes property of the FileSystemObject, similarly to how it was done in the TreeView examples in Example 14-3 and Example 14-4. The Attributes property is a bitwise combination of FileAttributes enumeration members, listed in Table 14-12. These attributes pertain to either files or directories. The current node's Attributes property is logically AND'ed with the Directory value of the FileAttributes enumeration: if the result is zero, then the node is not a file; otherwise, it is a directory:

if ((fsi.Attributes & FileAttributes.Directory) != 0)

if ((fsi.Attributes and FileAttributes.Directory) <> 0) then

If it is a directory, then the ImageIndex is set to 1i.e., the second image in the current image list (either imgListSmall or imgListLarge, depending on the current View mode; both image lists should have corresponding images so itdisplays correctly). At the same time, the first subitem, representing the Bytes column, is set to an empty string, since Explorer does not display the byte size of directories.

On the other hand, if the object is a file, then the image is set according to the type of file, based on the file extension. This program uses a limited selection of file types, while a real-world application would obviously use a more extensive list.

14.4.1.5 Adding SubItems to the ListView

The size subitem is added by casting the FileSystemInfo object to a FileInfo object, and then calling the ToString method on the FileInfo.Length property. This usage of the ToString method passes in a formatting string, N0 (i.e., N followed by a zero) to format the Bytes column with commas (a feature that Windows Explorer ought to incorporate):

lvi.SubItems.Add(((FileInfo)fsi).Length.ToString("N0"));

lvi.SubItems.Add(CType(fsi,FileInfo).Length.ToString("N0"))

Irrespective of whether the FileSystemInfo object is a file or a directory, three more subitem columns are added: the file extension, the time stamp for when the file or directory was last modified, and an attribute string. The latter is built up by logically AND'ing the Attributes property with each relevant member of the FileAttributes enumeration (as was done initially to determine if the FileSystemInfo object was a file or a directory) and concatenating string values, if the attribute is present for the object.

Once the subitems are added for all the ListViewItems, only then can you add the ColumnHeader objects to the Columns property. This is done using the Add method of the ListView.ColumnHeaderCollection class. This method had two overloaded forms. The first adds a ColumnHeader object, specified as a parameter. The second, used here, takes three arguments: a string displaying as the column header text, an integer specifying the initial width of the column in pixels, and a member of the HorizontalAlignment enumeration, listed in Table 12-10 of Chapter 12. In both languages, except for the trailing semicolon, the code is as follows:

lv.Columns.Add("Name", 150, HorizontalAlignment.Left); lv.Columns.Add("Bytes", 75, HorizontalAlignment.Right); lv.Columns.Add("Ext.", 50, HorizontalAlignment.Left); lv.Columns.Add("Modified", 125, HorizontalAlignment.Left); lv.Columns.Add("Attrib.", 50, HorizontalAlignment.Left);

Notice that the first column is always the ListViewItem itself and the second column is the first added subitem.

The last line of code in the tvw_AfterSelect method is a call to the EndUpdate method, which allows the ListView to redraw itself.

The program is now getting close to the real Windows Explorer. When run, it displays the two panes with the tree on the left and the list on the right, separated by a splitter. The View menu lets you set the display mode of the ListView, and when viewed in Details mode, it displays the columns of subitems you would expect. The user can resize the columns by dragging the sides of the column headers or by double-clicking the side of a column header, and the columns can be dragged to a different order. However, the program is missing some of the functionality one expects, such as the ability to open a file by double-clicking on it and the ability to sort the items by clicking on a column header. This functionality will be added next.

14.4.1.6 Activation

As described earlier, activation lets your program respond to user action, such as by opening a file.

In the constructor, you included the following line of code that set the ListView Activation property to ItemActivation.TwoClick, which is not the default behavior.

lv.Activation = ItemActivation.TwoClick;

Creating the event handler in Visual Studio .NET is slightly different, depending on the language in use. In C#, go to the Design view, click on the ListView, and then click on the yellow lightning bolt in the Properties window. Scroll to and double-click on the ItemActivate item in the list. That will automatically fill in the default event handler name, lv_ItemActivate, and create a code skeleton for the method in the code window. Then enter the following highlighted code in the code skeleton:

private void lv_ItemActivate(object sender, EventArgs e) { ListView lv = (ListView)sender; foreach (ListViewItem lvi in lv.SelectedItems) { try { Process.Start(tvw.SelectedNode.FullPath + "\" + lvi.Text); } catch { } } }

In VB.NET, go to the code window, click on the drop-down menu at the upper left of the window, and select the ListView object, lv. Then click on the drop-down menu at the upper right of the window and select the ItemActivate event. A code skeleton will be created for that event. Enter the following highlighted code in the code skeleton:

Private Sub lv_ItemActivate(ByVal sender As Object, _ ByVal e As System.EventArgs) _ Handles lv.ItemActivate lv = CType(sender, ListView) dim lvi as ListViewItem for each lvi in lv.SelectedItems try Process.Start(tvw.SelectedNode.FullPath + "" + lvi.Text) catch end try next End Sub

In either language, a ListView object is cast from the object sender passed in to the event handler. Then the SelectedItems collection of that ListView is iterated in a foreach (for each) loop. For each selected item, a fully qualified filename is constructed from the Node.FullPath property of the TreeView's SelectedNode property and the current ListViewItem's Text property. This filename used is a parameter for the static Process.Start method, which will open the default application for files with the specific extension. Files with an extension of txt will open with Notepad, files with an extension of htm will open in a browser, and files with extensions of exe will execute.

14.4.1.7 Sorting by Text property

The items in the ListView can be sorted automatically by setting the Sorting property. If the Sorting property is set to the default value of SortOrder.None, then no sorting occurs. The other two valid values are SortOrder.Ascending or SortOrder.Descending. The latter two values sort the items alphabetically by the item's Text property. In the example in this section, the Sorting property has been set to the default value of SortOrder.None.

lv.Sorting = SortOrder.None; // default

This is appropriate for a list of files and directories, since the order in which they appear in the list will be the order they are presented in the FileSystemInfo array (directories first, in alphabetical order followed by files in alphabetical order). If the Sorting property were to be set to either of the other two values, then the files and directories would be interspersed.

14.4.1.8 Sorting by clicking on a column

A common feature of ListViews is the ability to sort the list by any one of the columns in Details mode simply by clicking on the column header. Each time the column header is clicked again, the sort order toggles. In any case, the used sort algorithm is appropriate to the type of data in that subitem, whether it be a text string, a number, or a date. Although the ListView control provides several capabilities to the developer for free, this, unfortunately, is not one of them. However, it does provide the hooks that let you implement your own sorting.

Follow these steps to sort the items in a ListView by column:

  1. Handle the ListView.ColumnClick event when the user clicks on the column header.
  2. Set up for and call the ListView Sort method.

You will handle that second step first.

In Table 14-22, you see that the Sort method sorts the items in the ListView using the IComparer object specified in the ListViewItemSorter property. The IComparer interface provides a single method, Compare, which compares two objects and returns a value (listed in Table 14-33), depending upon how the objects compare. How the items are compared, and which is greater, is up to you to implement.

Table 14-33. IComparer.Compare return values

Comparison

Return value

First object less than second object

Less than zero

First object equals second object

Zero

First object greater than second object

Greater than zero

Before the Sort method can be called, an IComparer object must be created and the ListViewItemSorter property must be set to an instance of that IComparer object. To do this, you will create a nested class within the Form1 class that implements the IComparer interface:

public class SortListViewItems : IComparer {

public class SortListViewItems implements IComparer

This class will have three private member variables that contain the index of the column being sorted, the type of data contained in the column (described shortly)e.g., string data, DateTime data, or numeric dataand a Boolean that indicates whether the column should be sorted in ascending or descending order:

int columnIndex; ColumnType columnType; bool isAscending;

dim columnIndex as Integer dim colType as ColumnType dim isAscending as Boolean

The class will also have a number of public static (shared in VB.NET) Boolean variables to keep track of the current sort order for each column. These variables allow each successive click on a specific column header to toggle the sort order from ascending to descending and back:

public static Boolean isNameAscending = true; public static Boolean isBytesAscending = false; public static Boolean isExtAscending = false; public static Boolean isModifiedAscending = false; public static Boolean isAttribAscending = false;

public shared isNameAscending as Boolean = true public shared isBytesAscending as Boolean = false public shared isExtAscending as Boolean = false public shared isModifiedAscending as Boolean = false public shared isAttribAscending as Boolean = false

The constructor for the class takes three parameters and uses them to set the private member variables:

public SortListViewItems(int columnIndex, ColumnType columnType, bool isAscending) { this.columnIndex = columnIndex; this.columnType = columnType; this.isAscending = isAscending; }

public sub New(columnIndex as Integer, _ colType as ColumnType, _ isAscending as Boolean) me.columnIndex = columnIndex me.colType = colType me.isAscending = isAscending End Sub

Now comes the meat of the matterthe Compare method, which compares two objects and returns a value that indicates their relative order, as listed in Table 14-33. The criteria used to compare the two objects depends on the type of data in the column: e.g., a numeric value sorts differently from a text value. Consider the following literal values:

1

2

10

If they were sorted according to their numeric value, they would be in the order shown above. If they were to be sorted as text strings, then they would be in this order:

1

10

2

It is up to your Compare method to implement the correct sort algorithm. The type of data in the column must be passed as a parameter to the constructor and used by the Compare method to determine which sorting algorithm to use.

Three column types are possible in this application, and are specified in a public enumeration called ColumnType, declared in the Form1 class:

public enum ColumnType { Alpha, Numeric, DateTimeValue }

public enum ColumnType Alpha Numeric DateTimeValue end enum

The Compare method is never explicitly called in the code. However, when the Sort method is called on the ListView after the ListViewItemSorter property is set to the IComparer object (as seen in more detail shortly), the Compare method is called implicitly to compare each successive pair of items in the list and place them in the proper order.

The SortListViewItems class's Compare method is declared with two parameters of type Object, as required by the IComparer interface:

public int Compare(object x, object y) {

public function Compare(x as Object, y as Object) as Integer _ implements IComparer.Compare

A string representation of each object is obtained by first casting the object as a ListViewItem, and then getting the Text property of the appropriate column (indexed by the columnIndex member variable, passed in via the constructor) from the SubItems collection:

string strFirst = ((ListViewItem)x).SubItems[columnIndex].Text; string strSecond = ((ListViewItem)y).SubItems[columnIndex].Text;

dim strFirst as String = _ (CType(x,ListViewItem)).SubItems(columnIndex).Text dim strSecond as String = _ (CType(y,ListViewItem)).SubItems(columnIndex).Text

The ColumnType, passed in via the constructor, is used in a switch block (select case in VB.NET) to return the correct value, depending on the type of data in the column:

switch (columnType) { case ColumnType.Alpha : if (isAscending) return strFirst.CompareTo(strSecond); else return strSecond.CompareTo(strFirst); case ColumnType.DateTimeValue : if (isAscending) return DateTime.Parse(strFirst).CompareTo(DateTime.Parse(strSecond)); else return DateTime.Parse(strSecond).CompareTo(DateTime.Parse(strFirst)); case ColumnType.Numeric : // Special case blank byte values. if (strFirst = = "") strFirst = "-1"; if (strSecond = = "") strSecond = "-1"; if (isAscending) return Double.Parse(strFirst).CompareTo(Double.Parse(strSecond)); else return Double.Parse(strSecond).CompareTo(Double.Parse(strFirst)); default: return 0; }

select case colType case ColumnType.Alpha if isAscending then return strFirst.CompareTo(strSecond) else return strSecond.CompareTo(strFirst) end if case ColumnType.DateTimeValue if isAscending return _ DateTime.Parse(strFirst).CompareTo(DateTime.Parse(strSecond)) else return _ DateTime.Parse(strSecond).CompareTo(DateTime.Parse(strFirst)) end if case ColumnType.Numeric ' Special case blank byte values. if strFirst = "" then strFirst = "-1" end if if strSecond = "" then strSecond = "-1" end if if isAscending then return _ Double.Parse(strFirst).CompareTo(Double.Parse(strSecond)) else return _ Double.Parse(strSecond).CompareTo(Double.Parse(strFirst)) end if case else return 0 End Select

In the case of the DateTimeValue column type, the DateTime.Parse method converts the string value to a DateTime value, and in the case of the Numeric column type, the Double.Parse method is used. In all cases, the native data types represented by the ColumnType enumerationstring, double, and DateTimeare .NET value types. All value types implement a CompareTo method that behaves the same as the Compare method, returning a value based on the relative value of the two operands, as listed in Table 14-33.

Now that the IComparer object is ready, you can implement the event handler for the ColumnClick event. This is done as it was for previous event handlers in Visual Studio .NET. In C#, click on the ListView in design view, and then the lightning bolt in the Properties window, and then double-click the ColumnClick event. In VB.NET, select lv from the left drop-down menu at the top of the code window, and then select the ColumnClick event from the right drop-down menu.

In either language, a code skeleton for the event handler will be created. Add the highlighted code shown here:

private void lv_ColumnClick(object sender, ColumnClickEventArgs e) { ColumnType columnType; bool isAscending = true; string strName = ((ListView)sender).Columns[e.Column].Text; switch(strName) { case "Name": columnType = ColumnType.Alpha; SortListViewItems.isNameAscending = !SortListViewItems.isNameAscending; isAscending = SortListViewItems.isNameAscending; break; case "Bytes": columnType = ColumnType.Numeric; SortListViewItems.isBytesAscending = !SortListViewItems.isBytesAscending; isAscending = SortListViewItems.isBytesAscending; break; case "Ext.": columnType = ColumnType.Alpha; SortListViewItems.isExtAscending = !SortListViewItems.isExtAscending; isAscending = SortListViewItems.isExtAscending; break; case "Modified": columnType = ColumnType.DateTimeValue; SortListViewItems.isModifiedAscending = !SortListViewItems.isModifiedAscending; isAscending = SortListViewItems.isModifiedAscending; break; case "Attrib.": columnType = ColumnType.Alpha; SortListViewItems.isAttribAscending = !SortListViewItems.isAttribAscending; isAscending = SortListViewItems.isAttribAscending; break; default: columnType = ColumnType.Alpha; break; } lv.ListViewItemSorter = new SortListViewItems(e.Column, columnType, isAscending); lv.Sort( ); }

Private Sub lv_ColumnClick(ByVal sender As Object, _ ByVal e As System.Windows.Forms.ColumnClickEventArgs) _ Handles lv.ColumnClick dim colType as ColumnType dim isAscending as Boolean = true dim strName as String = _ CType(sender, ListView).Columns(e.Column).Text select case strName case "Name" colType = ColumnType.Alpha SortListViewItems.isNameAscending = _ not (SortListViewItems.isNameAscending) isAscending = SortListViewItems.isNameAscending case "Bytes" colType = ColumnType.Numeric SortListViewItems.isBytesAscending = _ not (SortListViewItems.isBytesAscending) isAscending = SortListViewItems.isBytesAscending case "Ext." colType = ColumnType.Alpha SortListViewItems.isExtAscending = _ not (SortListViewItems.isExtAscending) isAscending = SortListViewItems.isExtAscending case "Modified" colType = ColumnType.DateTimeValue SortListViewItems.isModifiedAscending = _ not (SortListViewItems.isModifiedAscending) isAscending = SortListViewItems.isModifiedAscending case "Attrib." colType = ColumnType.Alpha SortListViewItems.isAttribAscending = _ not (SortListViewItems.isAttribAscending) isAscending = SortListViewItems.isAttribAscending case else colType = ColumnType.Alpha end select lv.ListViewItemSorter = _ new SortListViewItems(e.Column, colType, isAscending) lv.Sort( ) End Sub

This method first declares two local variablesone for the column type, which is of type ColumnType (i.e., the enumeration previously declared) and a Boolean to indicate the sort order.

Next, the name of the column that was clicked is extracted and stored. First the sender object is cast as a ListView. Then the ColumnClickEventArgs.Column property, which returns the zero-based index of the column within the ColumnHeaderCollection, is used as the index into the Columns collection of the ListView itself passed in as sender. Finally, the strName variable is set to the Text property of Column:

string strName = ((ListView)sender).Columns[e.Column].Text;

dim strName as String = CType(sender, ListView).Columns(e.Column).Text

Once the name of the column is in hand, a switch block (select case in VB.NET) sets the column type variable, toggles the value of the Boolean that controls the sort order, and sets the value of the isAscending flag:

switch(strName) { case "Name": columnType = ColumnType.Alpha; SortListViewItems.isNameAscending = !SortListViewItems.isNameAscending; isAscending = SortListViewItems.isNameAscending; break; case "Bytes": columnType = ColumnType.Numeric; SortListViewItems.isBytesAscending = !SortListViewItems.isBytesAscending; isAscending = SortListViewItems.isBytesAscending; break; case "Ext.": columnType = ColumnType.Alpha; SortListViewItems.isExtAscending = !SortListViewItems.isExtAscending; isAscending = SortListViewItems.isExtAscending; break; case "Modified": columnType = ColumnType.DateTimeValue; SortListViewItems.isModifiedAscending = !SortListViewItems.isModifiedAscending; isAscending = SortListViewItems.isModifiedAscending; break; case "Attrib.": columnType = ColumnType.Alpha; SortListViewItems.isAttribAscending = !SortListViewItems.isAttribAscending; isAscending = SortListViewItems.isAttribAscending; break; default: columnType = ColumnType.Alpha; break; }

select case strName case "Name" colType = ColumnType.Alpha SortListViewItems.isNameAscending = _ not (SortListViewItems.isNameAscending) isAscending = SortListViewItems.isNameAscending case "Bytes" colType = ColumnType.Numeric SortListViewItems.isBytesAscending = _ not (SortListViewItems.isBytesAscending) isAscending = SortListViewItems.isBytesAscending case "Ext." colType = ColumnType.Alpha SortListViewItems.isExtAscending = _ not (SortListViewItems.isExtAscending) isAscending = SortListViewItems.isExtAscending case "Modified" colType = ColumnType.DateTimeValue SortListViewItems.isModifiedAscending = _ not (SortListViewItems.isModifiedAscending) isAscending = SortListViewItems.isModifiedAscending case "Attrib." colType = ColumnType.Alpha SortListViewItems.isAttribAscending = _ not (SortListViewItems.isAttribAscending) isAscending = SortListViewItems.isAttribAscending case else colType = ColumnType.Alpha end select

Once these variables are set, the ListView's ListViewItemSorter property can be set to a new instance of a SortListViewItems object and the called Sort method:

lv.ListViewItemSorter = new SortListViewItems(e.Column, columnType, isAscending); lv.Sort( );

lv.ListViewItemSorter = _ new SortListViewItems(e.Column, colType, isAscending) lv.Sort( )

When the Sort method is called, the items of the ListView will magically sort themselves according to the algorithm specified in the SortListViewItems class. Unlike at Hogwarts, no Sorting Hat is required.

14.4.2 Editing the Labels

The end user can edit labels associated with ListView items at runtime if the LabelEdit property is set to true and the value of the Activation property is set to ItemActivation.Standard (the default).

If the ListView.ItemActivation property is set to either nondefault value, then label editing will be disabled, regardless of the value of the LabelEdit property.

With these two properties set appropriately, double-clicking an item activates the item (discussed above), while clicking an item twice slowly puts it into edit mode. In actuality, the first click selects the item and the second click puts it into edit mode.

The ListViewItem.BeginEdit method (described in Table 14-29) allows you to put an item label into edit mode programmatically. This would be useful, for example, if a label fails validation after being edited by the user and you want it to remain in edit mode until it passes validation.

Setting the LabelEdit property to true allows editing only of the text associated with the item itself, not of any subitems.

The ListView control provides two events that are useful for label editing, BeforeLabelEdit and AfterLabelEdit, both of which are listed in Table 14-23. You can see how these events work by adding event handlers for them, using the same techniques used throughout this chapter. Once you have the code skeletons in place for each event, add the following highlighted code:

private void lv_BeforeLabelEdit(object sender, LabelEditEventArgs e) { MessageBox.Show("About to edit " + "Item:" + e.Item.ToString( ) + " " + "label:" + e.Label ); } private void lv_AfterLabelEdit(object sender, LabelEditEventArgs e) { MessageBox.Show("After edit " + "Item:" + e.Item.ToString( ) + " " + "label:" + e.Label ); }

Private Sub lv_BeforeLabelEdit(ByVal sender As Object, _ ByVal e As System.Windows.Forms.LabelEditEventArgs) _ Handles lv.BeforeLabelEdit MessageBox.Show("About to edit" & vbNewLine & _ "Item:" & e.Item.ToString( ) & vbNewLine & _ "label:" & e.Label ) End Sub Private Sub lv_AfterLabelEdit(ByVal sender As Object, _ ByVal e As System.Windows.Forms.LabelEditEventArgs) _ Handles lv.AfterLabelEdit MessageBox.Show("After edit" & vbNewLine & _ "Item:" & e.Item.ToString( ) & vbNewLine & _ "label:" & e.Label ) End Sub

While these methods will show you the before and after results of editing a label, assuming the LabelEdit and Activation properties are properly set, this code will not actually change anything. If you change the name of a file, the actual name of that file will not be changed in the file system unless you add code to specifically rename the file. That is left as an exercise for the reader.

14.4.3 Complete Code Listings

Example 14-5 and Example 14-6 list the entire code listing for the Windows Explorer clone in both C# and VB.NET. Methods that are borrowed unchanged from the TreeView examples listed in Example 14-3 (in C#) and Example 14-4 (in VB.NET) are indicated, but not listed to conserve space. Likewise, methods autogenerated by Visual Studio .NET, such as InitializeComponent, Dispose, and Main, are omitted.

Example 14-5. ListView control as part of Explorer clone in C# (csExplorerClone)

using System; using System.Drawing; using System.Collections; using System.ComponentModel; using System.Windows.Forms; using System.Data; using System.IO; using System.Diagnostics; // for Process.Start namespace csExplorerClone { public class Form1 : System.Windows.Forms.Form { private System.Windows.Forms.Splitter splitter1; private System.Windows.Forms.ListView lv; private System.Windows.Forms.TreeView tvw; private System.Windows.Forms.MainMenu mainMenu1; private System.Windows.Forms.MenuItem menuItem1; private System.Windows.Forms.MenuItem mnuSmallIcons; private System.Windows.Forms.MenuItem mnuLargeIcons; private System.Windows.Forms.MenuItem mnuList; private System.Windows.Forms.MenuItem mnuDetails; public enum ColumnType { Alpha, Numeric, DateTimeValue } private System.ComponentModel.Container components = null; public Form1( ) { InitializeComponent( ); // Use an array to add filenames to the ImageLists String[ ] arFiles = { @"C:Program FilesMicrosoft Visual Studio .NET 2003Common7" + @"Graphicsiconscomputerform.ico", @"C:Program FilesMicrosoft Visual Studio .NET 2003Common7" + @"Graphicsiconswin95clsdfold.ico", @"C:Program FilesMicrosoft Visual Studio .NET 2003Common7" + @"Graphicsiconswin95openfold.ico", @"C:Program FilesMicrosoft Visual Studio .NET 2003Common7" + @"Graphicsitmapsassortedhappy.bmp", @"C:Program FilesMicrosoft Visual Studio .NET 2003Common7" + @"GraphicsitmapsoutlineNoMaskdoc.bmp", @"C:Program FilesMicrosoft Visual Studio .NET 2003Common7" + @"GraphicsitmapsoutlineNoMaskexe.bmp", @"C:Program FilesMicrosoft Visual Studio .NET 2003Common7" + @"GraphicsitmapsoutlineNoMask xt.bmp", @"C:Program FilesMicrosoft Visual Studio .NET 2003Common7" + @"GraphicsitmapsoutlineNoMaskwindoc.bmp" }; ImageList imgListSmall = new ImageList( ); // default size 16x16 ImageList imgListLarge = new ImageList( ); imgListLarge.ImageSize = new Size(32,32); for (int i = 0; i < arFiles.Length; i++) { imgListSmall.Images.Add(Image.FromFile(arFiles[i])); imgListLarge.Images.Add(Image.FromFile(arFiles[i])); } tvw.Size = new Size(ClientSize.Width / 3, ClientSize.Height); tvw.BackColor = Color.Moccasin; tvw.HideSelection = false; tvw.ImageList = imgListSmall; tvw.ImageIndex = 1; tvw.SelectedImageIndex = 2; lv.BackColor = Color.PaleTurquoise; lv.ForeColor = Color.DarkBlue; lv.HideSelection = false; lv.SmallImageList = imgListSmall; lv.LargeImageList = imgListLarge; lv.View = View.SmallIcon; lv.Activation = ItemActivation.Standard; // default lv.MultiSelect = true; // default lv.HoverSelection = false; // default lv.Sorting = SortOrder.None; // default lv.AllowColumnReorder = true; lv.FullRowSelect = true; lv.GridLines = true; lv.HeaderStyle = ColumnHeaderStyle.Clickable; // default lv.LabelEdit = true; FillDirectoryTree( ); } // These 3 methods essentially same as in TreeViews program private void FillDirectoryTree( ) private void GetSubDirectoryNodes(TreeNode parentNode, bool getFileNames) private void tvw_BeforeExpand(object sender, TreeViewCancelEventArgs e) // This populates the list view after a tree node is selected private void tvw_AfterSelect(object sender, TreeViewEventArgs e) { lv.Clear( ); lv.BeginUpdate( ); DirectoryInfo di = new DirectoryInfo(e.Node.FullPath); FileSystemInfo[ ] afsi = di.GetFileSystemInfos( ); foreach (FileSystemInfo fsi in afsi) { ListViewItem lvi = new ListViewItem(fsi.Name); if ((fsi.Attributes & FileAttributes.Directory) != 0) { lvi.ImageIndex = 1; lvi.SubItems.Add(""); // Bytes subitem } else { switch(fsi.Extension.ToUpper( )) { case ".DOC" : lvi.ImageIndex = 4; break; case ".EXE" : lvi.ImageIndex = 5; break; case ".TXT" : lvi.ImageIndex = 6; break; default : lvi.ImageIndex = 7; break; } // Bytes subitem, w/ commas // Cast FileSystemInfo object to FileInfo object so the // size can be obtained. lvi.SubItems.Add(((FileInfo)fsi).Length.ToString("N0")); } // Add the remaining subitems to the ListViewItem lvi.SubItems.Add(fsi.Extension); // type lvi.SubItems.Add(fsi.LastWriteTime.ToString( )); // modified // Build up the Attributes string string strAtt = ""; if ((fsi.Attributes & FileAttributes.ReadOnly) != 0) strAtt += "R"; if ((fsi.Attributes & FileAttributes.Hidden) != 0) strAtt += "H"; if ((fsi.Attributes & FileAttributes.System) != 0) strAtt += "S"; if ((fsi.Attributes & FileAttributes.Archive) != 0) strAtt += "A"; lvi.SubItems.Add(strAtt); // attributes lv.Items.Add(lvi); } // end foreach lv.Columns.Add("Name", 150, HorizontalAlignment.Left); lv.Columns.Add("Bytes", 75, HorizontalAlignment.Right); lv.Columns.Add("Ext.", 50, HorizontalAlignment.Left); lv.Columns.Add("Modified", 125, HorizontalAlignment.Left); lv.Columns.Add("Attrib.", 50, HorizontalAlignment.Left); lv.EndUpdate( ); } // close tvw_AfterSelect private void mnuView_Click(object sender, EventArgs e) { MenuItem mnu = (MenuItem)sender; switch (mnu.Mnemonic.ToString( )) { case "L" : // Large Icons lv.View = View.LargeIcon; break; case "S" : // Small Icons lv.View = View.SmallIcon; break; case "T" : // List view lv.View = View.List; break; case "D" : // Detail view lv.View = View.Details; break; } } protected override void Dispose( bool disposing ) #region Windows Form Designer generated code private void InitializeComponent( ) #endregion static void Main( ) private void lv_ItemActivate(object sender, EventArgs e) { ListView lv = (ListView)sender; foreach (ListViewItem lvi in lv.SelectedItems) { try { Process.Start(tvw.SelectedNode.FullPath + "\" + lvi.Text); } catch { } } } public class SortListViewItems : IComparer // nested class { int columnIndex; ColumnType columnType; bool isAscending; public static Boolean isNameAscending = true; public static Boolean isBytesAscending = false; public static Boolean isExtAscending = false; public static Boolean isModifiedAscending = false; public static Boolean isAttribAscending = false; public SortListViewItems(int columnIndex, ColumnType columnType, bool isAscending) { this.columnIndex = columnIndex; this.columnType = columnType; this.isAscending = isAscending; } public int Compare(object x, object y) { string strFirst = ((ListViewItem)x).SubItems[columnIndex].Text; string strSecond = ((ListViewItem)y).SubItems[columnIndex].Text; switch (columnType) { case ColumnType.Alpha : if (isAscending) return strFirst.CompareTo(strSecond); else return strSecond.CompareTo(strFirst); case ColumnType.DateTimeValue : if (isAscending) return DateTime.Parse(strFirst). CompareTo(DateTime.Parse(strSecond)); else return DateTime.Parse(strSecond). CompareTo(DateTime.Parse(strFirst)); case ColumnType.Numeric : // Special case blank byte values. if (strFirst = = "") strFirst = "-1"; if (strSecond = = "") strSecond = "-1"; if (isAscending) return Double.Parse(strFirst). CompareTo(Double.Parse(strSecond)); else return Double.Parse(strSecond). CompareTo(Double.Parse(strFirst)); default: return 0; } // close switch block } // close Compare method } // close nested SortListViewItems class private void lv_ColumnClick(object sender, ColumnClickEventArgs e) { ColumnType columnType; bool isAscending = true; string strName = ((ListView)sender).Columns[e.Column].Text; switch(strName) { case "Name": columnType = ColumnType.Alpha; SortListViewItems.isNameAscending = !SortListViewItems.isNameAscending; isAscending = SortListViewItems.isNameAscending; break; case "Bytes": columnType = ColumnType.Numeric; SortListViewItems.isBytesAscending = !SortListViewItems.isBytesAscending; isAscending = SortListViewItems.isBytesAscending; break; case "Ext.": columnType = ColumnType.Alpha; SortListViewItems.isExtAscending = !SortListViewItems.isExtAscending; isAscending = SortListViewItems.isExtAscending; break; case "Modified": columnType = ColumnType.DateTimeValue; SortListViewItems.isModifiedAscending = !SortListViewItems.isModifiedAscending; isAscending = SortListViewItems.isModifiedAscending; break; case "Attrib.": columnType = ColumnType.Alpha; SortListViewItems.isAttribAscending = !SortListViewItems.isAttribAscending; isAscending = SortListViewItems.isAttribAscending; break; default: columnType = ColumnType.Alpha; break; } lv.ListViewItemSorter = new SortListViewItems(e.Column, columnType, isAscending); lv.Sort( ); } // close lv_ColumnClick private void lv_BeforeLabelEdit(object sender, LabelEditEventArgs e) { MessageBox.Show("About to edit " + "Item:" + e.Item.ToString( ) + " " + "label:" + e.Label ); } private void lv_AfterLabelEdit(object sender, LabelEditEventArgs e) { MessageBox.Show("After edit " + "Item:" + e.Item.ToString( ) + " " + "label:" + e.Label ); } } }

Example 14-6. ListView control as part of Explorer clone in VB.NET (vbExplorerClone)

imports System.IO imports System.Collections Public Class Form1 Inherits System.Windows.Forms.Form public enum ColumnType Alpha Numeric DateTimeValue end enum #Region " Windows Form Designer generated code " Public Sub New( ) MyBase.New( ) InitializeComponent( ) ' Use an array to add filenames to the ImageList dim arFiles( ) as string = { _ "C:Program FilesMicrosoft Visual Studio .NET 2003Common7" + _ "Graphicsiconscomputerform.ico", _ "C:Program FilesMicrosoft Visual Studio .NET 2003Common7" + _ "Graphicsiconswin95clsdfold.ico", _ "C:Program FilesMicrosoft Visual Studio .NET 2003Common7" + _ "Graphicsiconswin95openfold.ico", _ "C:Program FilesMicrosoft Visual Studio .NET 2003Common7" + _ "Graphicsitmapsassortedhappy.bmp", _ "C:Program FilesMicrosoft Visual Studio .NET 2003Common7" + _ "GraphicsitmapsoutlineNoMaskdoc.bmp", _ "C:Program FilesMicrosoft Visual Studio .NET 2003Common7" + _ "GraphicsitmapsoutlineNoMaskexe.bmp", _ "C:Program FilesMicrosoft Visual Studio .NET 2003Common7" + _ "GraphicsitmapsoutlineNoMask xt.bmp", _ "C:Program FilesMicrosoft Visual Studio .NET 2003Common7" + _ "GraphicsitmapsoutlineNoMaskwindoc.bmp"} dim imgListSmall as new ImageList( ) ' default size 16x16 dim imgListLarge as new ImageList( ) imgListLarge.ImageSize = new Size(32,32) dim i as Integer for i = 0 to arFiles.Length - 1 imgListSmall.Images.Add(Image.FromFile(arFiles(i))) imgListLarge.Images.Add(Image.FromFile(arFiles(i))) next tvw.Size = new Size(ClientSize.Width / 3, ClientSize.Height) tvw.BackColor = Color.Moccasin tvw.HideSelection = false tvw.ImageList = imgListSmall tvw.ImageIndex = 1 tvw.SelectedImageIndex = 2 lv.BackColor = Color.PaleTurquoise lv.ForeColor = Color.DarkBlue lv.HideSelection = false lv.SmallImageList = imgListSmall lv.LargeImageList = imgListLarge lv.View = View.SmallIcon lv.Activation = ItemActivation.Standard ' default lv.MultiSelect = true ' default lv.HoverSelection = false ' default lv.Sorting = SortOrder.None ' default lv.AllowColumnReorder = true lv.FullRowSelect = true lv.GridLines = true lv.HeaderStyle = ColumnHeaderStyle.Clickable ' default lv.LabelEdit = true FillDirectoryTree( ) End Sub Protected Overloads Overrides Sub Dispose(ByVal disposing As Boolean) #End Region ' These 3 methods essentially same as in TreeViews program private sub FillDirectoryTree( ) private sub GetSubDirectoryNodes(parentNode as TreeNode, _ getFileNames as Boolean) Private Sub tvw_BeforeExpand(ByVal sender As Object, _ ByVal e As System.Windows.Forms.TreeViewCancelEventArgs) _ Handles tvw.BeforeExpand private sub mnuView_Click(ByVal sender As Object, _ ByVal e As EventArgs) _ Handles mnuDetails.Click, mnuLargeIcons.Click, mnuList.Click, _ mnuSmallIcons.Click dim mnu as New MenuItem mnu = CType(sender, MenuItem) select case (mnu.Mnemonic.ToString( )) case "L" : ' Large Icons lv.View = View.LargeIcon case "S" : ' Small Icons lv.View = View.SmallIcon case "T" : ' List view lv.View = View.List case "D" : ' Detail view lv.View = View.Details end select end sub ' This populates the list view after a tree node is selected Private Sub tvw_AfterSelect(ByVal sender As Object, _ ByVal e As System.Windows.Forms.TreeViewEventArgs) _ Handles tvw.AfterSelect lv.Clear( ) ' remove all items & columns lv.BeginUpdate( ) dim di as new DirectoryInfo(e.Node.FullPath) dim afsi( ) as FileSystemInfo afsi = di.GetFileSystemInfos( ) ' both files & directories dim fsi as FileSystemInfo for each fsi in afsi dim lvi as new ListViewItem(fsi.Name) if ((fsi.Attributes and FileAttributes.Directory) <> 0) then lvi.ImageIndex = 1 lvi.SubItems.Add("") ' Bytes subitem else select case (fsi.Extension.ToUpper( )) case ".DOC" : lvi.ImageIndex = 4 case ".EXE" : lvi.ImageIndex = 5 case ".TXT" : lvi.ImageIndex = 6 case else : lvi.ImageIndex = 7 end select ' Bytes subitem, w/ commas ' Cast FileSystemInfo object to FileInfo object so ' the size can be obtained. lvi.SubItems.Add(CType(fsi,FileInfo).Length.ToString("N0")) end if ' Add the remaining subitems to the ListViewItem lvi.SubItems.Add(fsi.Extension) ' type lvi.SubItems.Add(fsi.LastWriteTime.ToString( )) ' modified ' Build up the Attributes string dim strAtt as String = "" if ((fsi.Attributes and FileAttributes.ReadOnly) <> 0) then strAtt += "R" end if if ((fsi.Attributes & FileAttributes.Hidden) <> 0) then strAtt += "H" end if if ((fsi.Attributes & FileAttributes.System) <> 0) then strAtt += "S" end if if ((fsi.Attributes & FileAttributes.Archive) <> 0) then strAtt += "A" end if lvi.SubItems.Add(strAtt) ' attributes lv.Items.Add(lvi) next ' end for each lv.Columns.Add("Name", 150, HorizontalAlignment.Left) lv.Columns.Add("Bytes", 75, HorizontalAlignment.Right) lv.Columns.Add("Ext.", 50, HorizontalAlignment.Left) lv.Columns.Add("Modified", 125, HorizontalAlignment.Left) lv.Columns.Add("Attrib.", 50, HorizontalAlignment.Left) lv.EndUpdate( ) End Sub Private Sub lv_ItemActivate(ByVal sender As Object, _ ByVal e As System.EventArgs) _ Handles lv.ItemActivate lv = CType(sender, ListView) dim lvi as ListViewItem for each lvi in lv.SelectedItems try Process.Start(tvw.SelectedNode.FullPath + "" + lvi.Text) catch end try next End Sub public class SortListViewItems implements IComparer dim columnIndex as Integer dim colType as ColumnType dim isAscending as Boolean public shared isNameAscending as Boolean = true public shared isBytesAscending as Boolean = false public shared isExtAscending as Boolean = false public shared isModifiedAscending as Boolean = false public shared isAttribAscending as Boolean = false public sub New(columnIndex as Integer, _ colType as ColumnType, _ isAscending as Boolean) me.columnIndex = columnIndex me.colType = colType me.isAscending = isAscending End Sub public function Compare(x as Object, y as Object) as Integer _ implements IComparer.Compare dim strFirst as String = _ (CType(x,ListViewItem)).SubItems(columnIndex).Text dim strSecond as String = _ (CType(y,ListViewItem)).SubItems(columnIndex).Text select case colType case ColumnType.Alpha if isAscending then return strFirst.CompareTo(strSecond) else return strSecond.CompareTo(strFirst) end if case ColumnType.DateTimeValue if isAscending return _ DateTime.Parse(strFirst). _ CompareTo(DateTime.Parse(strSecond)) else return _ DateTime.Parse(strSecond). _ CompareTo(DateTime.Parse(strFirst)) end if case ColumnType.Numeric ' Special case blank byte values. if strFirst = "" then strFirst = "-1" end if if strSecond = "" then strSecond = "-1" end if if isAscending then return _ Double.Parse(strFirst). _ CompareTo(Double.Parse(strSecond)) else return _ Double.Parse(strSecond). _ CompareTo(Double.Parse(strFirst)) end if case else return 0 End Select End Function end class ' nested class Private Sub lv_ColumnClick(ByVal sender As Object, _ ByVal e As System.Windows.Forms.ColumnClickEventArgs) _ Handles lv.ColumnClick dim colType as ColumnType dim isAscending as Boolean = true dim strName as String = _ CType(sender, ListView).Columns(e.Column).Text select case strName case "Name" colType = ColumnType.Alpha SortListViewItems.isNameAscending = _ not (SortListViewItems.isNameAscending) isAscending = SortListViewItems.isNameAscending case "Bytes" colType = ColumnType.Numeric SortListViewItems.isBytesAscending = _ not (SortListViewItems.isBytesAscending) isAscending = SortListViewItems.isBytesAscending case "Ext." colType = ColumnType.Alpha SortListViewItems.isExtAscending = _ not (SortListViewItems.isExtAscending) isAscending = SortListViewItems.isExtAscending case "Modified" colType = ColumnType.DateTimeValue SortListViewItems.isModifiedAscending = _ not (SortListViewItems.isModifiedAscending) isAscending = SortListViewItems.isModifiedAscending case "Attrib." colType = ColumnType.Alpha SortListViewItems.isAttribAscending = _ not (SortListViewItems.isAttribAscending) isAscending = SortListViewItems.isAttribAscending case else colType = ColumnType.Alpha end select lv.ListViewItemSorter = _ new SortListViewItems(e.Column, colType, isAscending) lv.Sort( ) End Sub Private Sub lv_BeforeLabelEdit(ByVal sender As Object, _ ByVal e As System.Windows.Forms.LabelEditEventArgs) _ Handles lv.BeforeLabelEdit MessageBox.Show("About to edit" & vbNewLine & _ "Item:" & e.Item.ToString( ) & vbNewLine & _ "label:" & e.Label ) End Sub Private Sub lv_AfterLabelEdit(ByVal sender As Object, _ ByVal e As System.Windows.Forms.LabelEditEventArgs) _ Handles lv.AfterLabelEdit MessageBox.Show("After edit" & vbNewLine & _ "Item:" & e.Item.ToString( ) & vbNewLine & _ "label:" & e.Label ) End Sub End Class

Категории