BYO Grid
by Ross Mack - GUI Computing
Build your own simple grid from the things you can find lying around your own home and in the Visual Basic Toolbox.
During a recent project I had a need to display some data in a grid. It was a very small project and this was to be the only grid in the whole app. Also, the app would be basically used once and then more or less thrown away. I considered adding a Grid Custom Control (I have quite a fondness form TrueGrid and TrueDBGrid) but it really seemed like overkill for such a simple situation and added additional distribution problems as it would have been the only custom control in the whole app. In the end I decided to write a simple grid control in VB using a normal scroll bar control and an array of label controls. I have expanded upon it a little for the purposes of providing a more complete set of code for this article, but essentially here is what I did.
First of all you need three basic components to make the grid. The first one is a label control (or a number of label controls) to be the heading. This we place on the form where we like it and change the following properties to make it look more like the heading on a grid:
| BorderStyle: | 1 - Fixed Single |
| ForeColor: | &H8000000E& (Active Title Bar Text System Color) |
| BackColor: | &H8000000D& (Active Title Bar System Color) |
If you want a separate heading for each column simply add as many heading labels as required and configure them the same way.
The next item we need is a Vertical scroll bar. We place this at the right hand side and just below the heading labels. We set both it's Min and Max properties to 0 as this represents the range of available grid rows we will have to start off.
Lastly we add one or more labels directly below the heading label(s) that will become the first row of cells in our grid. Set the properties of these as below:
| BorderStyle: | 1 - Fixed Single |
| FontBold: | False - This is my preference, I think it looks better. |
| Index: | 0 - This makes it the first element of a control array |
| Name: | grdfldOne, grdfldTwo and so on… |
You should end up with something that looks a bit like this:
For the best visual effect you can also add a Picture control with the following properties and position it directly under the headings. Then you make the grid row labels children of the picture control and you make the scrollbar the same height as it. You should set the following property.
| BackColor: | &H8000000C& - Application Workspace. |
| Name: | picGrid |
You then have something that look like the picture below. Just like a bought one, right? Also, the advantage of using a picture box as a container is that it automatically crops the last row of labels if they don't quite fit.
Next we add a module level variable called mnGridRows. This will specify the number of grid rows currently in the grid. To be more specific it specifies the index of the last grid row and the index of the first one is always 0.
Now we need a function that adds a row of data to the grid. It might go something like this:
Sub AddGridRow (ByVal sFirst As String, ByVal sSecond As String)
Dim nNewMax As Integer
' Increment the number of rows we have
mnGridRows = mnGridRows + 1
' Load the next control array elements to be the next row.
Load grdFldOne(mnGridRows)
Load grdfldTwo(mnGridRows)
' position the new controls
grdFldOne(mnGridRows).Top = grdFldOne(mnGridRows - 1).Top + grdFldOne(mnGridRows - 1).Height - Screen.TwipsPerPixelY
grdfldTwo(mnGridRows).Top = grdfldTwo(mnGridRows - 1).Top + grdfldTwo(mnGridRows - 1).Height - Screen.TwipsPerPixelY
' Set the values of the row we want.
grdFldOne(mnGridRows - 1).Caption = sFirst
grdfldTwo(mnGridRows - 1).Caption = sSecond
' By default these controls are created invisible
grdFldOne(mnGridRows - 1).Visible = True
grdfldTwo(mnGridRows - 1).Visible = True
' reset the captions of the two new controls
' as they inherit the captions of the controls they
' are descended from
grdFldOne(mnGridRows).Caption = ""
grdfldTwo(mnGridRows).Caption = ""
' Now we calculate the new maximum value for the scroll bar
' Set it to the total number of rows
nNewMax = mnGridRows
' Subtract the number that can be seen at any one time
nNewMax = nNewMax - (picGrid.Height \ grdFldOne(0).Height)
' Only use numbers that are above 0
If nNewMax > 0 Then
scrGrid.Max = nNewMax
Else
scrGrid.Max = 0
End If
' Let's also make sure that some other scroll bar properties are set here
scrGrid.LargeChange = (picGrid.Height \ grdFldOne(0).Height)
End Sub
All the code above is pretty straight-forward. Whenever we need to add a grid row we simply load the next elements in the array of labels that form each row. And assign the values we ant to appear in that row to the second last elements. This is because we always start with one row, which we need to use first. We could use that row as a special case but always having one extra hidden row at the end is much simpler and a tiny overhead.
Notice how we position the next row directly after the previous row but one pixel up? This is so that we don't see the borders of both labels which creates quite an ugly thick line. Positioning them over each other means we only see the border of the one on top.
The maximum value of the scroll bar and the size of a large change are also recalculated here so that they are in sync with the number of rows we have.
The next thing we need to do is to respond to the user clicking in the scrollbar to change which rows are viewed. This task is greatly simplified by the use of the Picture control as a container for the grid rows as it acts like a viewport on the total array and clips any labels outside its visible area. I have written the equivalent code that does not use a picture control and trust me this is much nicer.
The code to respond to the Scroll Bar's change event I placed in a separate function, which is shown here.
Sub MoveGridRows (ByVal nTopRow As Integer)
Dim nRowSize As Integer
Dim nNum As Integer
Dim nJunk As Integer
Dim nEnd As Integer
' Precalculate as many variables as we can.
' row szie is the space we allow for each row
nRowSize = grdfldOne(0).Height - Screen.TwipsPerPixelY
' nEnd is the last visible row
nEnd = (picGrid.Height \ nRowSize) + 1 + nTopRow
For nNum = 0 To mnGridRows -1
Select Case nNum
Case nTopRow To nEnd
' In the range of rows we want to show
' set the position
grdfldOne(nNum).Top = (nNum - nTopRow) * nRowSize
grdfldTwo(nNum).Top = (nNum - nTopRow) * nRowSize
' Make sure it's visible
grdfldOne(nNum).Visible = True
grdfldTwo(nNum).Visible = True
Case Else
' Any other row we just make invisible
' Much quicker than moving everything.
grdfldOne(nNum).Visible = False
grdfldTwo(nNum).Visible = False
End Select
Next nNum
End Sub
The function loops through the control array of rows and positions and makes visible all the rows we want to show. It also makes invisible any other rows. This is much quicker to execute than positioning every row even when they are positioned outside the visible area of the picture control they are in.
This means that the Scrollbar's change event is simplified to the following code:
Sub scrGrid_Change () MoveGridRows scrGrid.Value End Sub
Now all we need is a simple routine to clear out the grid.
Sub ClearGrid ()
Dim nNum As Integer
If mnGridRows > 0 Then
' Can't unload the first row
For nNum = mnGridRows To 1 Step -1
Unload grdfldOne(nNum)
Unload grdfldTwo(nNum)
Next nNum
End If
' Clear the contents of the first row
grdfldOne(nNum).Caption = ""
grdfldTwo(nNum).Caption = ""
' Set the row counter back to 0
mnGridRows = 0
' Make sure the display is correct
MoveGridRows mnGridRows
End Sub
The ClearGrid function simply loops backwards through the array of labels that constitute the grid unloading them. It then clears out the contents of the first row which cannot be unloaded and resets the row counter and the display. Here is where we see the advantage of putting the display update code in the separate procedure, as it we can call it here and it makes sure everything is Ok, including the scrollbar's settings, without us having to recode any of it.
If you want to modify the values in the grid you can simply reference the labels that make up the grid directly or a separate wrapper function could be written to handle that depending on how you wanted to address the different columns. Of course you can also respond to all the events exposed by the labels for custom functionality. For example you could throw up some sort of edit control or a separate edit dialog for a row on the click or double click event of a row. Marking a row as selected is as simple as changing the BackColor property of all the labels in a row as a response to the click event of any one of them.
This simple grid implementation demonstrates what you can achieve in VB without using custom control to solve moderately complex problems. I have implemented this grid in both VB3 and VB5 so it is quite portable. The downloadable sample project is in VB3 and implements just the basic functionality. Have a look at it and experiment with the code to extend it to build the sort of grid you need.