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.|
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.