ou create an “owner-draw” ListBox or ComboBox when you want to bypass the control’s automatic item display in order to do something special, such as display an image for each item or display a list in which the items aren’t all the same size. The .NET framework makes it simple to create these custom item lists. In this article, you’ll see how to populate list and combobox controls with items you draw yourself.
The only thing you need to do to create an owner-drawn list or combo box is to set the DrawMode property to either OwnerDrawFixed, or OwnerDrawVariable. The DrawMode property has three possible settings: Normal (the default), in which the system handles displaying the items automatically; OwnerDrawFixed, which you should use when you want to draw the items yourself, but they’re all the same height and width; and OwnerDrawVariable, which you use to draw items that vary in height or width. When you select the OwnerDrawFixed setting, you must implement a DrawItem method, which the ListBox calls whenever it needs to draw an item. When you select the OwnerDrawVariable setting, you must implement both the DrawItem and a MeasureItem method. The MeasureItem method lets you set the size of the item to be drawn. When you select the Normal setting, the system does not fire either the MeasureItem or the DrawItem methods.
There are a couple of restrictions. You can’t create variable-height items for multicolumn ListBoxes, and CheckedListBoxes don’t support either of the owner-drawn DrawMode settings.
Example 1: Listing Files and Folders
Suppose you want to list the files and directories in a folder along with the associated system icons appropriate to the type of file. There are several steps required to accomplish the task:
- Validate the requested directory path
- Retrieve the files and subfolders from a directory
- Iterate through them, retrieving their types and names
- Find the appropriate icon for each file type
- Draw the items for the ListBox containing the appropriate icon and text
Figure 1: The ListFilesAndFolders form contains an owner-draw ListBox that displays folder and file names, along with their system-associated icons. |
Figure 1 depicts a Web form with the finished ListBox control that displays all the files in a specified directory along with their corresponding system icons. To use the example, enter a directory path in the first text field. The form ensures that the entered path is valid, and then follows the steps listed above to fill a ListBox shown in a separate dialog. The user can double-click an item in the list, or select an item and click OK. The constructor for the dialog form (ListFilesAndFolders) requires a path string.
The first step in the application’s logic is to validate the path string the user enters into the main form. The System.IO.DirectoryInfo class has an Exists method that returns True if the directory exists:
Dim di As DirectoryInfo di = New DirectoryInfo(Me.txtPath.Text) If Not di.Exists Then txtPath.ForeColor = System.Drawing.Color.Red Beep() End If
The code turns the TextBox text red and plays a warning sound if the entered path is invalid; otherwise, it creates a new instance of the ListFilesAndFolders form, passing the validated path string to its constructor.
Dim frmFiles As New _ ListFilesAndFolders(Me.txtPath.Text)
The ListFilesAndFolders form contains a ListBox, an OK button, and a Cancel button.
The ListFilesAndFolders form constructor calls a FillList method that retrieves the files and folders in the specified path and fills a ListBox control with the icons and names, suspending the control’s display until the method completes.
Sub FillList(ByVal aPath As String) Dim fsi As FileSystemInfo lstFiles.BeginUpdate() lstFiles.Items.Clear() files = New DirectoryInfo(aPath).GetFileSystemInfos For Each fsi In files lstFiles.Items.Add(fsi) Next lstFiles.EndUpdate() End Sub
The DirectoryInfo.GetFileSystemInfos method returns an array of FileSystemInfo objects. The code iterates through the returned array and adds each item to the ListBox’s Items collection.
The act of adding the items to the ListBox doesn’t cause the ListBox to draw them; instead, the ListBox fires a DrawItem event whenever the ListBox needs to display an item. In this case, you want to draw an icon and the name of a FileSystemInfo object that represents a file or folder. Because this is an owner-draw control, you need to create the DrawItem method to create the item display.
Private Sub lstFiles_DrawItem( _ ByVal sender As Object, _ ByVal e As System.Windows.Forms.DrawItemEventArgs) _ Handles lstFiles.DrawItem ' the system sometimes calls this method with ' an index of -1. If that happens, exit. If e.Index < 0 Then e.DrawBackground() e.DrawFocusRectangle() Exit Sub End If ' create a brush Dim aBrush As Brush = System.Drawing.Brushes.Black ' get a reference to the item to be drawn Dim fsi As FileSystemInfo = _ CType(lstFiles.Items(e.Index), FileSystemInfo) ' create an icon object Dim anIcon As Icon ' use a generic string format to draw the filename Dim sFormat As StringFormat = _ StringFormat.GenericTypographic ' get the height of each item Dim itemHeight As Integer = lstFiles.ItemHeight ' call these methods to get items to highlight ' properly e.DrawBackground() e.DrawFocusRectangle() ' retrieve the appropriate icon for this file type anIcon = IconExtractor.GetSmallIcon(fsi) ' draw the icon If Not anIcon Is Nothing Then e.Graphics.DrawIcon(anIcon, 3, _ e.Bounds.Top + ((itemHeight - _ Me.ImageList1.Images(0).Height) 2)) anIcon.Dispose() End If ' if the item is selected, ' change the text color to white If (e.State And _ Windows.Forms.DrawItemState.Selected) = _ Windows.Forms.DrawItemState.Selected Then aBrush = System.Drawing.Brushes.White End If sFormat.LineAlignment = StringAlignment.Center e.Graphics.DrawString(fsi.Name, lstFiles.Font, _ aBrush, 22, e.Bounds.Top + _ (e.Bounds.Height 2), sFormat) End Sub
In the DrawItem method, the code calls a shared GetSmallIcon method exposed by the IconExtractor class (see Listing 1), which, when passed a FileSystemInfo object, calls the Win32 SHGetFileInfo API to extract the icon for the file type represented by that object. The IconExtractor class exposes two public shared methods, GetLargeIcon and GetSmallIcon, both of which simply call a private GetIcon method that returns the large (32 x 32) and small (16 x 16) icon versions, respectively.
Public Shared Function GetSmallIcon( _ ByVal fsi As FileSystemInfo) As Icon Return IconExtractor.GetIcon _ (fsi, SHGFI_SMALLICON) End Function Public Shared Function GetLargeIcon( _ ByVal fsi As FileSystemInfo) As Icon Return IconExtractor.GetIcon _ (fsi, SHGFI_LARGEICON) End Function Private Shared Function GetIcon( _ ByVal fsi As FileSystemInfo, _ ByVal anIconSize As Integer) As Icon Dim aSHFileInfo As New SHFILEINFO() Dim cbFileInfo As Integer = _ Marshal.SizeOf(aSHFileInfo) Dim uflags As Integer = SHGFI_ICON Or _ SHGFI_USEFILEATTRIBUTES Or anIconSize Try SHGetFileInfo(fsi.FullName, fsi.Attributes, _ aSHFileInfo, cbFileInfo, uflags) Return Icon.FromHandle(aSHFileInfo.hIcon) Catch ex As Exception Return Nothing End Try End Function
The GetSmallIcon and GetLargeIcon methods both accept a FileSystemInfo object. Internally, the GetIcon method uses the FileSystemInfo object to pass the file name and file attributes to the SHGetFileInfo API call. After drawing the icon, the DrawItem event handler calls the Graphics.DrawString method to place the file name on the image next to the icon. The ListBox calls the DrawItem method repeatedly, for each item in its Items collection.
The DrawItemEventArgs argument to the DrawItem event handler exposes an Index property whose value is the index of the item to be drawn. Watch out! The system raises the DrawItem event with an index value of -1 when the Items collection is empty. When that happens you should call the DrawItemEventArgs.DrawBackground() and DrawFocusRectangle() methods and then exit. The purpose of raising the event is to let the control draw a focus rectangle so that users can tell it has the focus, even when there are no items present. The code traps for that condition, calls the two methods, and then exits the handler immediately.
Users can select an item and close the ListFilesAndFolders form either by selecting an item and then clicking the OK button, or by double-clicking an item. Either way, the form sets a public property called SelectedItem, sets another public property called Cancel and then closes. The main form then displays the filename of the selected item in the Result field.
Example 2: Drawing Items with Variable Width and Height
Here's another example. The items you create don't all have to be the same width and height. To create an owner-drawn list or combo box with variable item heights and widths, set the DrawMode property to OwnerDrawVariable. Then, implement a method that handles the MeasureItem event, which accepts a sender (Object), and a System.Windows.Forms.MeasureItemEventArgs argument. The sample form frmColorListBox displays all the known system colors and their names in a combo box. The items themselves vary between 20 and 40 pixels in height. It's contrived and ugly (see Figure 2), but serves to illustrate the point.
About Our Editorial ProcessAt DevX, we’re dedicated to tech entrepreneurship. Our team closely follows industry shifts, new products, AI breakthroughs, technology trends, and funding announcements. Articles undergo thorough editing to ensure accuracy and clarity, reflecting DevX’s style and supporting entrepreneurs in the tech sphere. See our full editorial policy. About Our JournalistCharlie Frank Charlie has over a decade of experience in website administration and technology management. As the site admin, he oversees all technical aspects of running a high-traffic online platform, ensuring optimal performance, security, and user experience. View Author Walmart’s fintech startup to disrupt BNPL market Cameron Wiggins April 26, 2024 5:38 PM From humble cart to thriving business: The Nitro Bar’s success story Rashan Dixon April 26, 2024 3:50 PM Adapting business strategies amidst unpredictable economies April Isaacs April 26, 2024 3:11 PM Singaporean entrepreneur’s journey to grocery chain success Cameron Wiggins April 26, 2024 3:06 PM Rhode Island coffee startup finds success April Isaacs April 26, 2024 1:39 PM Erel Margalit’s enduring impact on Israeli tech industry Noah Nguyen April 26, 2024 1:12 PM Bilt Rewards transform US rental sector Cameron Wiggins April 26, 2024 8:58 AM Chinese e-commerce platform upsets US market Noah Nguyen April 26, 2024 8:54 AM Stripe refines business model for fintech competitiveness Johannah Lopez April 26, 2024 7:13 AM Addressing challenges in Israel’s tech sector Rashan Dixon April 25, 2024 5:58 PM Gender disparity challenged by women-centric coworking spaces April Isaacs April 25, 2024 5:56 PM From cart to empire: The Nitro Bar’s rise Cameron Wiggins April 25, 2024 5:23 PM FlexAI nets $30 million to simplify AI computing infrastructure Noah Nguyen April 25, 2024 3:36 PM Google fires 28 protesting employees, maintains professional ethics Johannah Lopez April 25, 2024 1:49 PM Growing interest in small business ownership among millennials Johannah Lopez April 25, 2024 1:31 PM India’s rise in space exploration with private corporations Johannah Lopez April 25, 2024 8:42 AM Walmart partners with fintech One for installment payment service Johannah Lopez April 25, 2024 7:59 AM Biden administration bans noncompete agreements nationwide April Isaacs April 25, 2024 7:01 AM Media mogul poised for $1 billion boost if shares surge Cameron Wiggins April 24, 2024 5:19 PM Bitcoin miners’ strategy stabilizes market pre-halving Noah Nguyen April 24, 2024 5:12 PM Digital banking tackles fraud with advanced methods Johannah Lopez April 24, 2024 1:55 PM Wealth accumulation: Path to personal freedom and societal impact Cameron Wiggins April 24, 2024 1:25 PM Japan’s finance minister warns of currency market intervention Rashan Dixon April 24, 2024 1:14 PM TabaPay set to acquire Synapse’s assets April Isaacs April 24, 2024 11:58 AM Baton Rouge Entrepreneurship Week 2024 announced Cameron Wiggins April 24, 2024 11:36 AM |