|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Visual Basic Intrinsic Control
Routines Pure VB: Simulating a Matrix Checkbox Control Array with a Picture Box |
||
Posted: | Wednesday January 15, 2003 | |
Updated: | Monday December 26, 2011 | |
Applies to: | VB4-16, VB4-32, VB5, VB6 | |
Developed with: | VB6, Windows XP | |
OS restrictions: | None | |
Author: | Larry Serflaten, VBnet - Randy Birch | |
Related: |
Pure VB: Simulating a Matrix Checkbox Control Array with a MSFlexGrid | |
Prerequisites |
None. |
|
A newsgroup poster recently requested help when his project started causing VB to lock up. As a compiled exe the project the application ran to a point, then disappeared without raising an error. Investigation determined the developer was using his own usercontrol consisting of a picture box populated with a user-defined number of check boxes and labels in a set of control arrays. The developer's goal was to produce a clickable matrix of checkboxes the program would use to perform a list of calculations. Each check indicated an equation to be evaluated where the column and row values were to be used as two of the values in the equation. Some equations had a large set of values to display for the column or row selections, and some equations had more than two variables to select which required multiple usercontrols in an array to display all the possible selections. Each new matrix control added to the control array produced a corresponding increase in the total number of Checkbox and Label controls. The problem was the cumulative size of the control arrays -- the developer was required to allow the end-user to specify experiment conditions whereby the application would create the required check box/label arrays. The problem surfaced as the application attempted to create six usercontrols, where each usercontrol contained 2601 checkboxes and 102 labels (a 6 x 51^2 matrix of controls). Larry Serflaten responded to the question with an interesting and highly workable solution to the resource problem, and implementing Larry's solution would also provide a boost to the application's overall performance. The solution utilized precisely one picture box and used the control's intrinsic methods to simulate a check box control array matrix. Larry's code is reproduced here with permission.
The number of columns (COLCELLS) and rows (ROWCELLS) is adjustable as
demanded by the initial design criteria, as is the cell size (CELLTWIPS) to better
suit aesthetics or fit the matrix into available screen space. The size of the
matrix label and checkmark fonts scale to fit the cell size based upon
the CELLTWIPS value. Together, these three values are used to create an empty grid drawn into the picture box. Each
'cell' of the 'grid' is clickable, visibly toggling on/off the respective
cell by drawing or removing the chosen check mark character determined
through the x/y positions provided through mouse events. Each
selection/de-selection of a cell is actually toggling the appropriate
array element in a module level user-defined type representing the grid
data. In addition to using far fewer resources and eliminating the load delay prevalent when forms contain an excessive number of controls, this method will also boost overall application performance when calculations are being made by providing the user's selections in a suitable data structure instead of thousands of checkbox properties. The Matrix button's click event code shows how to access the data; pressing this button will add each checked cell's column and row values to individual lines in the Listbox shown on the form. The command buttons, combo and listbox were added to Larry's basic matrix method in order to highlight additional functionality the demo provides which you may wish to utilize in your implementation of this design. The code for the basic matrix grid does not rely on those controls being present. |
BAS Module Code |
None. |
|
Form Code |
To a new form, toss on a picture box (Picture1), a list (List1), a combo (Combo1) and four command buttons (Command1 through Command4) -- the form's Load event sizes and positions those controls. Add the following code: |
|
Option Explicit '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' Copyright ©1996-2011 VBnet/Randy Birch, All Rights Reserved. ' Some pages may also contain other copyrights by the author. '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' Distribution: You can freely use this code in your own ' applications, but you may not reproduce ' or publish this code on any web site, ' online service, or distribute as source ' on any media without express permission. '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' 'adjustable values Private Const ROWCELLS = 18 'number of Rows Private Const COLCELLS = 28 'number of Columns Private Const CELLTWIPS = 240 'cell size in twips (min ~150) 300=20 pixels Private Const BANDCOLOR = &HFFEFEF 'color of every 10th row/col 'UDT's holding row/column array Private Type DataRow Rows() As Byte End Type Private Type DataCol Cols() As DataRow End Type 'form-level variables Private Data As DataCol 'the array of cells to create Private CellSize As Long 'working var containing CELLTWIPS Private ColCount As Long 'working var containing COLCELLS Private RowCount As Long 'working var containing ROWCELLS Private MouseCount As Long 'tracks mouse activity Private CellChar As String 'character selected for cell checks Private Dirty As Boolean 'true if grid has been checked Private Sub Form_Load() 'Dynamic adjustments RowCount = ROWCELLS ColCount = COLCELLS CellSize = CELLTWIPS 'working var Dirty = False 'set up controls With Picture1 .ScaleMode = vbTwips .Move 200, 200, CellSize * (ColCount + 2), CellSize * (RowCount + 2) .BackColor = vbWhite .AutoRedraw = True End With With List1 .Move Me.ScaleLeft + Picture1.Width + 300, Picture1.Top, 1500, Picture1.Height End With With Command1 .Move 200, Form1.ScaleTop + 400 + Picture1.Height, 1350, 345 .Caption = "Select All" End With With Command2 .Move Command1.Left + Command1.Width, Command1.Top, 1350, 345 .Caption = "Select None" End With With Command3 .Move Command2.Left + Command2.Width, Command1.Top, 1350, 345 .Caption = "Invert Selection" End With With Command4 .Move List1.Left, Command1.Top, 1350, 345 .Caption = "Matrix" End With With Combo1 .Font.Name = "Marlett" .Font.Size = 10 .AddItem "a" .AddItem "g" .AddItem "i" .AddItem "n" .AddItem "r" .Move Command3.Left + Command3.Width + 120, _ Command1.Top + (Command1.Height - .Height) \ 2, 950 .ListIndex = 0 End With With Me .Width = Picture1.Width + List1.Width + (.Width - .ScaleWidth) + 500 .Height = Picture1.Height + (.Height - .ScaleHeight) + (Command1.Height * 2.5) End With 'set up the data and draw new grid Call BuildArray Call DrawDisplay End Sub Private Sub Picture1_MouseDown(Button As Integer, Shift As Integer, X As Single, Y As Single) 'If the user happens to move the mouse when 'trying to select just one cell, then that 'cell is toggled in the MouseMove and again 'at MouseUp, resulting in no change from its 'original state. Using a counter for mouse 'movement allows the user to 'accidentally' 'move the mouse between MouseDown and MouseUp, 'and still register it as a mark. MouseCount = 0 End Sub Private Sub Command1_Click() 'Select All Dim dx As Long Dim dy As Long For dx = 1 To ColCount For dy = 1 To RowCount Data.Cols(dx).Rows(dy) = 255 FillCell dx, dy Next 'dy Next 'dx End Sub Private Sub Command2_Click() 'Clear All Dim dx As Long Dim dy As Long For dx = 1 To ColCount For dy = 1 To RowCount Data.Cols(dx).Rows(dy) = 0 FillCell dx, dy Next 'dy Next 'dx Dirty = False End Sub Private Sub Command3_Click() 'Invert Dim dx As Long Dim dy As Long For dx = 1 To ColCount For dy = 1 To RowCount FillCell dx, dy, True Next 'dy Next 'dx End Sub Private Sub Command4_Click() 'Matrix Dim dx As Long Dim dy As Long With List1 .Clear .AddItem "col" & vbTab & "row" .AddItem "-----------------------------" For dx = 1 To ColCount For dy = 1 To RowCount If Data.Cols(dx).Rows(dy) <> 0 Then .AddItem dx & vbTab & dy End If Next 'dy Next 'dx End With End Sub Private Sub Combo1_Click() Dim dx As Long Dim dy As Long If Combo1.ListIndex > -1 Then CellChar = Combo1.List(Combo1.ListIndex) If Dirty Then For dx = 1 To ColCount For dy = 1 To RowCount FillCell dx, dy Next 'dy Next 'dx End If 'Dirty End If 'If Combo1.ListIndex End Sub Private Sub Picture1_MouseMove(Button As Integer, Shift As Integer, X As Single, Y As Single) Static px As Long, py As Long 'previous X & Y Dim dx As Long, dy As Long 'dynamic X & Y MouseCount = MouseCount + 1 If Button = vbLeftButton Then 'limit to one change per cell while dragging dx = X \ CellSize dy = Y \ CellSize If (px <> dx) Or (py <> dy) Then px = dx py = dy MouseCount = 0 Picture1_MouseUp Button, Shift, X, Y End If 'if px <> dx End If 'if Button End Sub Private Sub Picture1_MouseUp(Button As Integer, Shift As Integer, X As Single, Y As Single) Dim dx As Long Dim dy As Long dx = X \ CellSize dy = Y \ CellSize 'bail if out of bounds If (MouseCount > 2) Or _ (dx < 0) Or (dx > ColCount) Or (dy < 0) Or (dy > RowCount) Then Exit Sub Else 'ensure next MouseMove will be out of bounds MouseCount = 3 End If If (dx = 0 And dy > 0) Then 'toggle Row dy (based on nearest cell) FillCell 1, dy, True For dx = 2 To ColCount Data.Cols(dx).Rows(dy) = Data.Cols(1).Rows(dy) FillCell dx, dy Next ElseIf (dy = 0 And dx > 0) Then 'toggle Col dx (based on nearest cell) FillCell dx, 1, True For dy = 2 To RowCount Data.Cols(dx).Rows(dy) = Data.Cols(dx).Rows(1) FillCell dx, dy Next ElseIf dx Or dy Then 'toggle cell FillCell dx, dy, True End If End Sub Private Sub BuildArray() Dim cnt As Long Erase Data.Cols ReDim Data.Cols(1 To ColCount) For cnt = 1 To ColCount ReDim Data.Cols(cnt).Rows(1 To RowCount) Next cnt End Sub Private Sub DrawDisplay() Dim r As Long Dim c As Long Dim cell As Long 'As VB's graphic methods do not work 'when used in contracted form inside a 'With statement block. Specific references 'to Picture1 are required on any graphic 'method calls With Picture1 'set font for row/col headings .Font.Name = "Trebuchet MS" .Font.Size = 1 + ((CellSize \ 120) * 4) 'draw Col lines r = RowCount * CellSize For c = 1 To ColCount If c Mod 10 = 0 Then For cell = 1 To RowCount DrawCellColor c, cell, BANDCOLOR Next End If Picture1.Line (c * CellSize, CellSize)-Step(0, r), &HC0C0C0 CenterCellText CStr(c), c, 0 Next 'draw Row lines c = ColCount * CellSize For r = 1 To RowCount If r Mod 10 = 0 Then For cell = 1 To ColCount DrawCellColor cell, r, BANDCOLOR Next End If Picture1.Line (CellSize, r * CellSize)-Step(c, 0), &HC0C0C0 CenterCellText CStr(r), 0, r Next 'black border Picture1.Line (CellSize, CellSize)-Step(ColCount * CellSize, RowCount * CellSize), vbBlack, B 'set font to display checkmarks .Font.Name = "Marlett" .Font.Size = 1 + ((CellSize \ 120) * 5) .Font.Bold = False End With End Sub Private Sub DrawCellColor(ByVal X As Long, ByVal Y As Long, K As Long) Picture1.Line (X * CellSize + Screen.TwipsPerPixelX, _ Y * CellSize + Screen.TwipsPerPixelY)-Step(CellSize - Screen.TwipsPerPixelX * 3, _ CellSize - Screen.TwipsPerPixelY * 3), K, BF End Sub Private Sub FillCell(ByVal X As Long, ByVal Y As Long, Optional Toggle As Boolean = False) Dim tgl As Byte Dim bnd As Long tgl = Data.Cols(X).Rows(Y) If Toggle Then tgl = tgl Xor 255 Data.Cols(X).Rows(Y) = tgl End If If (X Mod 10 = 0) Or (Y Mod 10 = 0) Then bnd = BANDCOLOR Else bnd = vbWhite End If DrawCellColor X, Y, bnd If tgl Then CenterCellText CellChar, X, Y Dirty = True End Sub Private Sub CenterCellText(sText As String, X As Long, Y As Long) 'Determine the X and Y print position 'for the character. With Picture1 .CurrentX = (X * CellSize) + ((CellSize - .TextWidth(sText)) \ 2) + Screen.TwipsPerPixelX .CurrentY = (Y * CellSize) + ((CellSize - .TextHeight(sText)) \ 2) + Screen.TwipsPerPixelY End With Picture1.Print sText End Sub |
Comments |
Set the ROWCELLS and COLCELLS constants to the desired values and run. |
|
|
|
|||||
|
|||||
|
|||||
Copyright ©1996-2011 VBnet and Randy Birch. All Rights Reserved. |