By Robert Delwood
For presenting large amounts of information at one time, lists are the best. For .NET, these are listboxes and datagridviews. Listboxes are well known and are generally single column scrollable lists. Datagridviews tend to be multiple columns, presenting different information about the same subject for each row. For example, they may display a person's first and last name, address, age, and perhaps employee ID number. In both cases, they can be double clickable, taking an action on the selected row.
Programming these are similar and with a little extra work, can be extremely versatile with fast operations. This article addresses only listboxes but datagridviews are similar. For the project, start a VB.NET Windows Form application (in Visual Studio, click File|New Project|Visual Basic|Windows|Windows Form Application). On the form, drag a Button, and a ListBox. Resize the listbox to fit the form. I recommend setting one of the listbox's properties, Anchor, to Top, Left, Bottom, and Right. This adjusts the list box as the form is resized. Double click Button1 to show the code behind, and paste the following complete code. This will form the basis of the project.
Public Class Form1 Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click TestListBox1() End Sub Private Sub TestListBox1() End Sub End Class
The simplest way of formatting a listbox is by brute force, adding each item explicitly. Display the list by clicking Button1, defined by the TestListBox1 method.
Public Class Form1 Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click TestListBox1() End Sub Private Sub TestListBox1() ListBox1.Items.Clear() 'Makes sure the list box is cleared each time. ListBox1.Items.Add("William Rehnquist") ListBox1.Items.Add("Warren E. Burger") ListBox1.Items.Add("Earl Warren") ListBox1.Items.Add("Fred M. Vinson") End Sub End Class
Run the application (click Start), and click Button1, the listbox displays the four names. It's important to note that the four lines here represents the ability to collect the data, but that's not the focus of this article. The source could be a database, a file, the user entering data, or a programmatic algorithm to generate the information. Obviously it is impractical and unreasonable to use this individual line approach for large or extremely large amounts of information. For my examples, I'll use several ways based on the need of the example, but it is up to you to determine the information's source. How ever the information is gathered, it is added to the listbox with the ListBox1.Items.Add method.
A more versatile approach uses loops. I added a Label item to list the number of lines in the listbox. Drag a Label onto the windows form.
Public Class Form1 Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click TestListBox1() End Sub Private Sub TestListBox1() ListBox1.Items.Clear() 'Makes sure the list box is cleared each time. For i As Integer = 0 To 1000 ListBox1.Items.Add(CInt(Math.Ceiling(Rnd() * 12)) + 1) Next Label1.Text = String.Format("Items in the list: {0}", ListBox1.Items.Count) End Sub End Class
This looping approach is more efficient but notice three points about it.
First, it requires a systematic source of information, here by generating a random number.
Second, there is a noticeable lag in completing the operation. If it's not noticeable, change the loop value from 1000 to perhaps 10,000, 100,000, or more.
Third, it's not very flexible. That is, you can't sort, or select information. There is a listbox Sort property that automatically lists the information in alphabetic order. This is useful for the simple operations, just as listing names, but I'm not going to use it.
The flexibility is a problem. In these examples, application is designed for the data. That is, the application does nothing more than displays the data. Real applications work the other way around. The data is the important factor and the application responds to it in different ways, such as adding or deleting entries, sorting, or analyzing them. For example, you may have a database of employee information, or a list of stock merchandise, whose information will be used different ways, including, but not limited solely to, displaying the information. For this, we will use an in memory database, such as an array or list collection.
Public Class Form1 Dim gChiefsInformation As List(Of String) = New List(Of String)() Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load gChiefsInformation.Add("William Rehnquist") gChiefsInformation.Add("Warren E. Burger") gChiefsInformation.Add("Earl Warren") gChiefsInformation.Add("Fred M. Vinson") TestListBox1() End Sub Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click TestListBox1() End Sub Private Sub TestListBox1() ListBox1.Items.Clear() For Each item As String In gChiefsInformation ListBox1.Items.Add(item) Next Label1.Text = String.Format("Items in the list: {0}", ListBox1.Items.Count) End Sub End Class
Here, I've added a List collection, adding the four names to it. This is more representative of the situation, since the information can be used conveniently throughout the application. The following displays the list. I've also added the call in the form's load routine so it runs automatically now.
This is where we depart from the conventions. Using loops to enter information is slow and, now, is unneeded. Instead, there is a property called DataSource that takes a list or array and automatically applies that to the listbox. The same application the new way:
Public Class Form1 Dim gChiefsInformation As List(Of String) = New List(Of String)() Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load gChiefsInformation.Add("William Rehnquist") gChiefsInformation.Add("Warren E. Burger") gChiefsInformation.Add("Earl Warren") gChiefsInformation.Add("Fred M. Vinson") TestListBox1() End Sub Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click TestListBox1() End Sub Private Sub TestListBox1() ListBox1.DataSource = gChiefsInformation Label1.Text = String.Format("Items in the list: {0}", ListBox1.Items.Count) End Sub End Class
There are several implications. The first is this runs faster. Adding information is generally 16 times faster than using a loop. This may be trivial for up to 1000 items, but becomes significant at 100,000 with 1.8 seconds to load versus 27 seconds. In some instances it's possible to list a million points (for example, in scientific applications, such as recording the coefficients of an electrical function or points in a space, you can easily have millions or even billions of data points).
The second implication is that it gives you versatility in sorting the information. By using a collection, such as a list or array, you can use built in functions, like the Sort method, which is not the same as the Listbox's Sort property.
Public Class Form1 Dim gChiefsInformation As List(Of String) = New List(Of String)() Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load gChiefsInformation.Add("William Rehnquist") gChiefsInformation.Add("Warren Burger") gChiefsInformation.Add("Earl Warren") gChiefsInformation.Add("John Marshall") gChiefsInformation.Add("Harlan Stone") gChiefsInformation.Add("John Jay") gChiefsInformation.Add("Fred Vinson") TestListBox1() End Sub Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click TestListBox1() End Sub Private Sub TestListBox1() gChiefsInformation.Sort() 'gChiefsInformation.Reverse() ListBox1.DataSource = gChiefsInformation Label1.Text = String.Format("Items in the list: {0}", ListBox1.Items.Count) End Sub End Class
Uncommenting the Reverse line reverses the order. However, the real power comes with LINQ. LINQ is .NET's query language, but it goes beyond just a SQL query. Microsoft has four LINQ types (called LINQ providers): LINQ to SQL, LINQ to XML, LINQ to DataSet, and LINQ to Objects. We're interested in the last provider. LINQ to Objects enables you to query in-memory collections and arrays. It is available by default in all Visual Studio projects but separately as the System.Linq namespace. You could do the same sort with LINQ. Replace TestListBox1 with this:
Private Sub TestListBox1() ListBox1.DataSource = gChiefsInformation.OrderBy(Function(x) x.Substring(0, x.Length)).ToList Label1.Text = String.Format("Items in the list: {0}", ListBox1.Items.Count) End Sub
Or with a little more work, sort by last name. Replace the one line with this one:
ListBox1.DataSource = gChiefsInformation.OrderBy(Function(x) (x.Split(" "))(x.Split(" ").Length - 1)).ToList
Or even the number of letters in their name:
ListBox1.DataSource = gChiefsInformation.OrderBy(Function(x) x.Length).ToList
The last one may be interesting but hardly useful. The boring fact is there isn't much you can do with having just their names. To see the real power of LINQ, we need to have more information. And for that, we have to introduce objects. These represent their full name, last name, and the year appointed to the seat.
Public Class Form1 Dim gChiefsInformation As List(Of JusticesNames) = New List(Of JusticesNames)() Private Sub Form1_Shown(sender As Object, e As EventArgs) Handles MyBase.Shown gChiefsInformation.Add(New JusticesNames("William Rehnquist", "Rehnquist", 1986)) gChiefsInformation.Add(New JusticesNames("Warren Burger", "Burger", 1969)) gChiefsInformation.Add(New JusticesNames("Earl Warren", "Warren", 1953)) gChiefsInformation.Add(New JusticesNames("John Marshall", " Marshall ", 1801)) gChiefsInformation.Add(New JusticesNames("Harlan Stone", "Stone", 1941)) gChiefsInformation.Add(New JusticesNames("John Jay", "Jay", 1789)) gChiefsInformation.Add(New JusticesNames("Fred Vinson", "Vinson", 1946)) ListBox1.DisplayMember = "FirstName" TestListBox1() End Sub Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click TestListBox1() End Sub Private Sub TestListBox1() ListBox1.DataSource = gChiefsInformation Label1.Text = String.Format("The list contains: {0}", ListBox1.Items.Count) End Sub Class JusticesNames Public Property FirstName As String Public Property LastName As String Public Property YearInOffice As Integer Public Sub New(first As String, last As String, year As Integer) Me.FirstName = first Me.LastName = last Me.YearInOffice = year End Sub End Class End Class
Besides using objects, an important addition is the line. With multiple fields for each object, you must define the field to display:
ListBox1.DisplayMember = "FirstName"
Now we can use LINQ. Replace the method with this one.
ListBox1.DataSource = gChiefsInformation.OrderBy(Function(x) x.YearInOffice).ToList
You get the names listed in ascending order that they took office. This is for descending order:
ListBox1.DataSource = gChiefsInformation.OrderByDescending(Function(x) x.YearInOffice).ToList
There are two factors at work here. First, the LINQ statement creates a new collection, based on the original list:
ChiefsInformation.OrderByDescending(Function(x) x.YearInOffice).ToList
Then we assign that to the DataSource. In fact, it has to be explicitly cast as a list (by the ToList method). Second, the original list is kept intact. That means we can keep making all the LINQ calls we need without endangering the original list. That makes for good programming practice.
The more complete form of a LINQ call, and may be easier to work with, is the expanded form. This form looks more like the start of a conventional SQL call:
Dim queryResults = From justices In gChiefsInformation
The justices name is only a local variable and can be called anything. A more complete example:
Private Sub TestListBox1() Dim queryResults = From justices In gChiefsInformation Where justices.YearInOffice > 1961 Select justices Order By justices.YearInOffice ListBox1.DataSource = queryResults.ToList Label1.Text = ListBox1.Items.Count End Sub
This returns those justices starting after 1961 and from among those, are in ascending order. Many programmers are familiar with this format from SQL programming and may be more natural to figure out. It may also be tempting to list all the information, such as name, date confirmed, and the number of years in office, but remember this is a listbox, intended to present a small amount of information, such as only the name. Fully listing additional information is what a datagridview tool does. The procedures are identical for using a DataSource property, but the set up and overhead is slightly different. The datagridview tool is not covered here.
To make this display interactive, add three radio buttons. Each radio button controls the display of the list. You could also label the radio buttons' Text property in the design time's property list. I've also added a field, the length in the office.
Public Class Form1 Dim gChiefsInformation As List(Of JusticesNames) = New List(Of JusticesNames)() Private Sub Form1_Shown(sender As Object, e As EventArgs) Handles MyBase.Shown gChiefsInformation.Add(New JusticesNames("John Roberts", "Roberts", 2005, 11.5)) gChiefsInformation.Add(New JusticesNames("William Rehnquist", "Rehnquist", 1986, 18.7)) gChiefsInformation.Add(New JusticesNames("Warren Burger", "Burger", 1969, 17.2)) gChiefsInformation.Add(New JusticesNames("Earl Warren", "Warren", 1954, 15.5)) gChiefsInformation.Add(New JusticesNames("John Marshall", " Marshall ", 1801, 34)) gChiefsInformation.Add(New JusticesNames("Harlan Stone", "Stone", 1941, 4.9)) gChiefsInformation.Add(New JusticesNames("John Jay", "Jay", 1789, 5)) gChiefsInformation.Add(New JusticesNames("Fred Vinson", "Vinson", 1946, 7.3)) RadioButton1.Text = "Sort: By Name" RadioButton2.Text = "By Date In Office" RadioButton3.Text = "No Sort" ListBox1.DisplayMember = "FirstName" RadioButton3.PerformClick() End Sub Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click TestListBox1() End Sub Private Sub TestListBox1() Dim queryResults = (From justices In gChiefsInformation Where justices.YearInOffice > 1961 And justices.YearsTenure > 10 Select justices Order By justices.YearsTenure).ToList ListBox1.DataSource = queryResults 'ListBox1.DataSource = gChiefsInformation.OrderByDescending(Function(x) x.YearsTenure).ToList End Sub Class JusticesNames Public Property FirstName As String Public Property LastName As String Public Property YearInOffice As Integer Public Property YearsTenure As Integer Public Sub New(first As String, last As String, year As Integer, tenure As Integer) Me.FirstName = first Me.LastName = last Me.YearInOffice = year Me.YearsTenure = tenure End Sub End Class Private Sub RadioButton1_CheckedChanged(sender As Object, e As EventArgs) Handles RadioButton1.CheckedChanged Dim queryResults = (From justices In gChiefsInformation Select justices Order By justices.LastName).ToList ListBox1.DataSource = queryResults End Sub Private Sub RadioButton2_CheckedChanged(sender As Object, e As EventArgs) Handles RadioButton2.CheckedChanged Dim queryResults = (From justices In gChiefsInformation Where justices.YearsTenure > 8 Select justices Order By justices.YearInOffice).ToList ListBox1.DataSource = queryResults End Sub Private Sub RadioButton3_CheckedChanged(sender As Object, e As EventArgs) Handles RadioButton3.CheckedChanged ListBox1.DataSource = gChiefsInformation End Sub Private Sub ListBox1_DataSourceChanged(sender As Object, e As EventArgs) Handles ListBox1.DataSourceChanged Application.DoEvents() Label1.Text = String.Format("The list contains: {0}", CType(ListBox1.DataSource, List(Of JusticesNames)).Count) End Sub End Class
The last thing changed is that the list box count now appears in an event handler. Each object has a set of predefined handlers that are triggered by certain events, such as a mouse click, a mouse double click, the form being resized and so on. If the event code is completed, whenever it occurs the code executes. For example, if you wanted to take an action based on double clicking a justice's name, you would add a MouseDoubleClick event for the list box. In this case, the event is DataSourceChanged. As you might guess, this is trigged by assigning a list to the DataSource property. Here, the application updates the display of the list count without us having to explicitly make that call anymore.