VBA Command Button array - vba

I'm currently working on a project where I'll be selecting up to 5 items to compare to each other, with the results being displayed in up to a 5x5 dynamic grid. My objective is to have this grid be composed of command buttons such that the caption of each button is the percent similarity between the row and column items, and on clicking the button, the units that are common between the row and column items will be displayed in a message box.
I more or less know how to generate the actual array of buttons. However, everything I've read suggests that I need to create a class to handle the button clicks, since I don't feel like making 20 subroutines that all have the same code in them. I have not been able to get this class to work properly, and I could use some tips. Here's what I have so far.
In a class module named DynButton:
Public Withevents CBevents as MSForms.CommandButton
Private Sub CBevents_Click()
DisplayOverlappedUnits 'Sub that will display the units that are the same
'between items i and j- may use Application.Caller
End Sub
And in the userform itself:
Private Sub Userform_Initialize()
Dim NumItems as integer
Dim ComparisonArray() as DynButton
Dim ctlButton as MSForms.CommandButton
'QuestionList() is a public type that stores various attributes of the
'items I'm comparing.
'This code determines how many items were selected for comparison
'and resets the item array accordingly.
NumItems=0
For i=1 to 5
If QuestionList(i).Length>0 Then
NumItems=Numitems+1
QuestionList(NumItems)=QuestionList(i)
End If
Next
Redim ComparisonArray(1 to NumItems, 1 to NumItems)
For i = 1 to NumItems
For j=1 to NumItems
Set ctlButton=Me.Controls.Add("Forms.CommandButton.1", Cstr(i) & Cstr(j) & cb)
With ctlButton
.Height= CB_HEIGHT 'These are public constants defined elsewhere.
.Width= CB_WIDTH
.Top= TOP_OFFSET + (i * (CB_HEIGHT+ V_PADDING))
If i = j Then .visible = False
.Caption= CalculateOverlap(i,j) 'Runs a sub that calculates the overlap between items i and j
End With
Set ComparisonArray(i,j).CBevents = ctlButton
Next
Next
End Sub
Currently, I get a "Object with or Block variable not set" when I hit the Set ComparisonArray line, and I'm stymied. Am I just missing something in the class module? Thanks in advance for the help.
Edited to add: I tried to model the class code in part off of this article, but again I haven't got it to work yet. http://www.siddharthrout.com/index.php/2018/01/15/vba-control-arrays/

Private Sub Userform_Initialize()
Dim NumItems as integer
Dim ComparisonArray() as DynButton '<<<< should be a Global variable
As soon as Userform_Initialize completes, ComparisonArray() will go out of scope and no longer exist: you need to make that a Global variable in your form so it will be around to handle any events.

Your code seems correct and interesting. The only (bug) I could see is:
Redim ComparisonArray(1 to NumItems, 1 to NumItems)
...
Set ComparisonArray(i,j).CBevents = ctlButton
The problem is that your array holds null references. You have not created your DynButton objects yet. You must explicitly creat the objects in your array.
Redim ComparisonArray(1 to NumItems, 1 to NumItems)
For i = 1 to NumItems
For j = 1 to NumItems
Set ComparisonArray(i,j) = new DynButton
Next
Next
...
Set ComparisonArray(i,j).CBevents = ctlButton
Also, declare the array ComparisonArray as a member object of the form, not as a local variable in Form_Initialize.

Only copy paste
Option Private Module
Option Explicit
Private Const i_total_channels As Integer = 100
Sub createArrayOfbuttons()
Application.ScreenUpdating = False
f_create_buttons 5, 5, 30, 5, True
End Sub
Sub clearArrayOfButtos()
Application.ScreenUpdating = False
f_clear_array_of_buttons
End Sub
Private Function f_create_buttons(Optional posLeft As Integer = 0, Optional posTop As Integer = 0, _
Optional sizeSquare As Integer = 20, Optional distBetween As Integer, Optional buttonColor As Boolean = False)
'create customized buttons to channel choice.
Dim i_ch_amount_x As Integer
Dim i_ch_amount_y As Integer
Dim i_size_X 'size of square button
Dim i_size_Y 'size of square button
Dim i_stp_X As Integer 'step in X
Dim i_stp_Y As Integer 'step in Y
Dim i_dist_bte_buttons As Integer 'distance between buttons, in X and Y
Dim i_pos_ini_X As Integer 'initial position
Dim i_pos_ini_Y As Integer
Dim it_x As Integer 'iterator
Dim it_y As Integer 'iterator
Dim amount As Integer 'channel acumulator
Dim FO_color As Integer 'index from 1 to 12 to change background color of button
f_clear_array_of_buttons
i_pos_ini_X = posLeft
i_pos_ini_Y = posTop
'create dimensions of square
i_size_X = sizeSquare
i_size_Y = i_size_X 'to create a square Y need same size of X
'distance between squares
i_dist_bte_buttons = i_size_X + distBetween 'to shift distance change laste value of expression
i_stp_X = i_pos_ini_X
i_stp_Y = i_pos_ini_Y
i_ch_amount_x = Int(Sqr(i_total_channels)) 'total channels in switch (i_ch_amount_y * i_ch_amount_x)
i_ch_amount_y = i_ch_amount_x
amount = 1
FO_color = 1
For it_y = 1 To i_ch_amount_x
For it_x = 1 To i_ch_amount_y
f_create_button amount, i_stp_X, i_stp_Y, CSng(i_size_X), CSng(i_size_Y), FO_color
i_stp_X = i_stp_X + i_dist_bte_buttons
amount = amount + 1
If buttonColor Then
FO_color = FO_color + 1
End If
If FO_color > 12 Then 'return FO to 1
FO_color = 1
End If
Next it_x
i_stp_X = i_pos_ini_X
i_stp_Y = i_stp_Y + i_dist_bte_buttons
Next it_y
amount = 0
i_ch_amount_x = 0
i_ch_amount_y = 0
i_size_X = 0
i_size_Y = 0
i_stp_X = 0
i_stp_Y = 0
i_pos_ini_X = 0
i_pos_ini_Y = 0
i_dist_bte_buttons = 0
FO_color = 0
End Function
Private Function f_create_button(index As Integer, posLeft As Integer, posRight As Integer, _
Box_width As Single, Box_height As Single, Optional FO As Integer)
ActiveSheet.Shapes.AddShape(msoShapeRectangle, posLeft, posRight, Box_width, Box_height). _
Select
With Selection
.Name = "ch_" & index
.Text = index
.Font.Name = "Arial"
.Font.Bold = True
If FO = 9 Then
.Font.Color = vbWhite
Else
.Font.ColorIndex = xlAutomatic
End If
.Font.Size = 10
.Interior.Color = fiber_color(FO)
.HorizontalAlignment = xlCenter
.VerticalAlignment = xlCenter
End With
End Function
Public Function fiber_color(Optional FO As Integer = 1) As Long
'use with a index in FO from 1 to 12
Select Case FO
Case 1
fiber_color = 65280 'green
Case 2
fiber_color = 65535 'yellow
Case 3
fiber_color = 16777215 'white
Case 4
fiber_color = 16711680 'blue
Case 5
fiber_color = 255 'red
Case 6
fiber_color = 16711823 'violt
Case 7
fiber_color = 19350 'brown
Case 8
fiber_color = 13353215 'pink
Case 9
fiber_color = 0 'black
Case 10
fiber_color = 16711680 'cinza
Case 11
fiber_color = 32767 'orange
Case 12
fiber_color = 16776960 'aqua
Case Else
fiber_color = 65280 'verde
End Select
End Function
Private Function f_clear_array_of_buttons()
Dim i_ch_amount_x As Integer
Dim it As Integer
i_ch_amount_x = i_total_channels
On Error GoTo sair
If ActiveSheet.Shapes.Count <> 0 Then
For it = 1 To i_ch_amount_x
ActiveSheet.Shapes("ch_" & it).Delete
Next it
End If
sair:
i_ch_amount_x = 0
it = 0
End Function

Related

Place a large Sub into a Workbook Open Sub

I have a very large macro that is made up of about 6 or more subs. But i want to call this whole macro from another application by placing the macro in a private sub workbook_open() thereby making it an automatic macro! the problem i have is how do i place this macro with the boundaries of a private sub and end sub. basically this is part of the macro...
Private Sub Workbook_open()
End Sub
'//============================================================================
'// COPYRIGHT DASSAULT SYSTEMES 2001
'//============================================================================
'// Generative Shape Design
'// point, splines, loft generation tool
'//============================================================================
Const Cst_iSTARTCurve As Integer = 1
Const Cst_iENDCurve As Integer = 11
Const Cst_iSTARTLoft As Integer = 2
Const Cst_iENDLoft As Integer = 22
Const Cst_iSTARTCoord As Integer = 3
Const Cst_iENDCoord As Integer = 33
Const Cst_iERRORCool As Integer = 99
Const Cst_iEND As Integer = 9999
Const Cst_strSTARTCurve As String = "StartCurve"
Const Cst_strENDCurve As String = "EndCurve"
Const Cst_strSTARTLoft As String = "StartLoft"
Const Cst_strENDLoft As String = "EndLoft"
Const Cst_strSTARTCoord As String = "StartCoord"
Const Cst_strENDCoord As String = "EndCoord"
Const Cst_strEND As String = "End"
'------------------------------------------------------------------------
'To define the kind of elements to create (1: create only points
'2: creates points and splines
'3: Creates points, splines and loft
'------------------------------------------------------------------------
Function GetTypeFile() As Integer
Dim strInput As String, strMsg As String
choice = 0
While (choice < 1 Or choice > 3)
strMsg = "Type in the kind of entities to create (1 for points, 2 for points and splines, 3 for points, splines and loft):"
strInput = InputBox(Prompt:=strMsg, _
Title:="User Info", XPos:=2000, YPos:=2000)
'Validation of the choice
choice = CInt(strInput)
If (choice < 1 Or choice > 3) Then
MsgBox "Invalid value: must be 1, 2 or 3"
End If
Wend
GetTypeFile = choice
End Function
'------------------------------------------------------------------------
'Get the active cell
'------------------------------------------------------------------------
Function GetCell(iindex As Integer, column As Integer) As String
Dim Chain As String
Sheets("Feuil1").Select
If (column = 1) Then
Chain = "A" + CStr(iindex)
ElseIf (column = 2) Then
Chain = "B" + CStr(iindex)
ElseIf (column = 3) Then
Chain = "C" + CStr(iindex)
End If
Range(Chain).Select
GetCell = ActiveCell.Value
End Function
Function GetCellA(iRang As Integer) As String
GetCellA = GetCell(iRang, 1)
End Function
Function GetCellB(iRang As Integer) As String
GetCellB = GetCell(iRang, 2)
End Function
Function GetCellC(iRang As Integer) As String
GetCellC = GetCell(iRang, 3)
End Function
'------------------------------------------------------------------------
'Syntax of the parameter file
'------------------------
'StartCurve -> to start the list of points defining the spline
' double , double , double
' double , double , double -> as many points as necessary to define the spline
'EndCurve -> to end the list of points defining the spline
'
'
'Example:
'--------
'StartCurve
' -10.89, 10 , 46.78
'1.56, 4, 6
'EndCurve -> spline composed of 2 points
'------------------------------------------------------------------------
Sub ChainAnalysis(ByRef iRang As Integer, ByRef X As Double, ByRef Y As Double, ByRef Z As Double, ByRef iValid As Integer)
Dim Chain As String
Dim Chain2 As String
Dim Chain3 As String
Chain = GetCellA(iRang)
Select Case Chain
Case Cst_strSTARTCurve
iValid = Cst_iSTARTCurve
Case Cst_strENDCurve
iValid = Cst_iENDCurve
Case Cst_strSTARTLoft
iValid = Cst_iSTARTLoft
Case Cst_strENDLoft
iValid = Cst_iENDLoft
Case Cst_strSTARTCoord
iValid = Cst_iSTARTCoord
Case Cst_strENDCoord
iValid = Cst_iENDCoord
Case Cst_strEND
iValid = Cst_iEND
Case Else
iValid = 0
End Select
If (iValid <> 0) Then
Exit Sub
End If
'Conversion string -> double
Chain2 = GetCellB(iRang)
Chain3 = GetCellC(iRang)
If ((Len(Chain) > 0) And (Len(Chain2) > 0) And (Len(Chain3) > 0)) Then
X = CDbl(Chain)
Y = CDbl(Chain2)
Z = CDbl(Chain3)
Else
iValid = Cst_iERRORCool
X = 0#
Y = 0#
Z = 0#
End If
End Sub
'------------------------------------------------------------------------
' Get CATIA Application
'------------------------------------------------------------------------
'Remark:
' When KO, update CATIA registers with:
' CNEXT /unregserver
' CNEXT /regserver
'-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
Function GetCATIA() As Object
Set CATIA = GetObject(, "CATIA.Application")
If CATIA Is Nothing Then
Set CATIA = CreateObject("CATIA.Application")
CATIA.Visible = True
End If
Set GetCATIA = CATIA
End Function
'------------------------------------------------------------------------
' Get CATIADocument
'------------------------------------------------------------------------
Function GetCATIAPartDocument() As Object
Set CATIA = GetCATIA
Dim MyPartDocument As Object
Set MyPartDocument = CATIA.ActiveDocument
Set GetCATIAPartDocument = MyPartDocument
End Function
'------------------------------------------------------------------------
' Creates all usable points from the parameter file
'------------------------------------------------------------------------
Sub CreationPoint()
'Get CATIA
Dim PtDoc As Object
Set PtDoc = GetCATIAPartDocument
' Get the HybridBody
Dim myHBody As Object
Set myHBody = PtDoc.Part.HybridBodies.Item("GeometryFromExcel")
Dim iLigne As Integer
Dim iValid As Integer
Dim X As Double
Dim Y As Double
Dim Z As Double
Dim Point As Object
iLigne = 1
'Analyze file
While iValid <> Cst_iEND
'Read a line
ChainAnalysis iLigne, X, Y, Z, iValid
iLigne = iLigne + 1
'Not on a startcurve or endcurve -> valid point
If (iValid = 0) Then
Set Point = PtDoc.Part.HybridShapeFactory.AddNewPointCoord(X, Y, Z)
myHBody.AppendHybridShape Point
End If
Wend
'Model update
PtDoc.Part.Update
End Sub
'------------------------------------------------------------------------
' Creates all usable points and splines from the parameter file
'------------------------------------------------------------------------
'-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
'Limitations:
' ============================> NO MORE THAN 500 POINTS PER SPLINE
'-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
Sub CreationSpline()
'Limitation : points per spline
Const NBMaxPtParSpline As Integer = 500
'Get CATIA
Dim PtDoc As Object
Set PtDoc = GetCATIAPartDocument
'Get HybridBody
Dim myHBody As Object
Set myHBody = PtDoc.Part.HybridBodies.Item("GeometryFromExcel")
Dim iRang As Integer
Dim iValid As Integer
Dim X1 As Double
Dim Y1 As Double
Dim Z1 As Double
Dim index As Integer
Dim PassingPtArray(1 To NBMaxPtParSpline) As Object
Dim spline As Object
Dim ReferenceOnPoint As Object
Dim SplineCtrPt As Object
iValid = 0
iRang = 1
'Analyze file
While iValid <> Cst_iEND
'reinitialization of point array of the spline
index = 0
'Remove records before StartCurve
While ((iValid <> Cst_iSTARTCurve) And (iValid <> Cst_iEND))
ChainAnalysis iRang, X1, Y1, Z1, iValid
iRang = iRang + 1
Wend
If (iValid <> Cst_iEND) Then
'Read until endcurve -> Spline completed
While ((iValid <> Cst_iENDCurve) And (iValid <> Cst_iEND))
ChainAnalysis iRang, X1, Y1, Z1, iValid
iRang = iRang + 1
'valid point
If (iValid = 0) Then
index = index + 1
If (index > NBMaxPtParSpline) Then
MsgBox "Too many points for a spline. Point deleted"
Else
Set PassingPtArray(index) = PtDoc.Part.HybridShapeFactory.AddNewPointCoord(X1, Y1, Z1)
myHBody.AppendHybridShape PassingPtArray(index)
End If
End If
Wend
'Start building spline
'Are there enough points ?
If (index < 2) Then
MsgBox "Not enough points for a spline. Spline deleted"
Else
Set spline = PtDoc.Part.HybridShapeFactory.AddNewSpline
spline.SetSplineType 0
spline.SetClosing 0
'Creates and adds points to the spline
For i = 1 To index
Set ReferenceOnPoint = PtDoc.Part.CreateReferenceFromObject(PassingPtArray(i))
' ---- Version Before V5R12
' Set SplineCtrPt = PtDoc.Part.HybridShapeFactory.AddNewControlPoint(ReferenceOnPoint)
' spline.AddControlPoint SplineCtrPt
' ---- Since V5R12
spline.AddPointWithConstraintExplicit ReferenceOnPoint, Nothing, -1, 1, Nothing, 0
Next i
myHBody.AppendHybridShape spline
End If
End If
Wend
PtDoc.Part.Update
End Sub
Sub LookForNextSpline(ByRef iRang As Integer, ByRef spline As Object, ByRef iValid As Integer, ByRef iOKSpline)
'Limitation number off point per spline
Const NBMaxPtParSpline As Integer = 500
'Get CATIA
Dim PtDoc As Object
Set PtDoc = GetCATIAPartDocument
'Get HybridBody
Dim myHBody As Object
Set myHBody = PtDoc.Part.HybridBodies.Item("GeometryFromExcel")
Dim X1 As Double
Dim Y1 As Double
Dim Z1 As Double
Dim index As Integer
Dim PassingPtArray(1 To NBMaxPtParSpline) As Object
Dim ReferenceOnPoint As Object
Dim SplineCtrPt As Object
iValid = 0
iOKSpline = 0
'reinitialization of point array of the spline
index = 0
'Remove records before StartCurve
While ((iValid <> Cst_iSTARTCurve) And (iValid <> Cst_iEND))
ChainAnalysis iRang, X1, Y1, Z1, iValid
iRang = iRang + 1
Wend
If (iValid <> Cst_iEND) Then
'Read until endcurve -> Spline completed
While ((iValid <> Cst_iENDCurve) And (iValid <> Cst_iEND))
ChainAnalysis iRang, X1, Y1, Z1, iValid
iRang = iRang + 1
'valid point
If (iValid = 0) Then
index = index + 1
If (index > NBMaxPtParSpline) Then
MsgBox "Too many points for a spline. Point deleted"
Else
Set PassingPtArray(index) = PtDoc.Part.HybridShapeFactory.AddNewPointCoord(X1, Y1, Z1)
myHBody.AppendHybridShape PassingPtArray(index)
End If
End If
Wend
'Start building spline
'Are there enough points ?
If (index < 2) Then
MsgBox "Not enough points for a spline. Spline deleted"
Else
Set spline = PtDoc.Part.HybridShapeFactory.AddNewSpline
'Creates and adds points to the spline
For i = 1 To index
Set ReferenceOnPoint = PtDoc.Part.CreateReferenceFromObject(PassingPtArray(i))
' ---- Version Before V5R12
' Set SplineCtrPt = PtDoc.Part.HybridShapeFactory.AddNewControlPoint(ReferenceOnPoint)
' spline.AddControlPoint SplineCtrPt
' ---- Since V5R12
spline.AddPointWithConstraintExplicit ReferenceOnPoint, Nothing, -1, 1#, Nothing, 0#
Next i
myHBody.AppendHybridShape spline
spline.SetSplineType 0
spline.SetClosing 0
iOKSpline = 1
End If
End If
End Sub
okay so VBA takes it that private sub is just one of the declarations in the code not that it is supposed to hold the whole code within it.. please any help will be appreciated.
Hopefully I helped answer the question with my edits, but there are a couple things I would change, and suggest.
Change Private Sub Workbook_open() to Private Sub Workbook_Open()
Instead of putting all of the Subs into one Private Sub, use them to Call each other. (Suggested by A. S. H. and SJR in comments)
It would look something like this:
Const Cst_iSTARTCurve As Integer = 1
Const Cst_iENDCurve As Integer = 11
Const Cst_iSTARTLoft As Integer = 2
Const Cst_iENDLoft As Integer = 22
Const Cst_iSTARTCoord As Integer = 3
Const Cst_iENDCoord As Integer = 33
Const Cst_iERRORCool As Integer = 99
Const Cst_iEND As Integer = 9999
Const Cst_strSTARTCurve As String = "StartCurve"
Const Cst_strENDCurve As String = "EndCurve"
Const Cst_strSTARTLoft As String = "StartLoft"
Const Cst_strENDLoft As String = "EndLoft"
Const Cst_strSTARTCoord As String = "StartCoord"
Const Cst_strENDCoord As String = "EndCoord"
Const Cst_strEND As String = "End"
Private Sub Workbook_Open()
CreationPoint
'or Call CreationPoint
End Sub
This will not only work normally, but will also make your code more legible! Though this isn't the top priority, it will definitely help when working in teams. Good luck!

Excel VBA, choosing chart color based on series value comparison

I have some code I have used to color excel charts with for quite a few years and it has worked well, (although there are likely better ways to do it). The charts contain 2 series, the first series with a value and the second with a goal. The goal does not get colored but the vba loops through the first series and colors according to hard coded goals in the vba.
The problem I have now is that I have added a chart that has a goal that can change month to month so having the hard coding doesn't work. How can I use the same theory but compare series 1 data directly to series 2 data to determine the color, (Case Is series 1 point > series 2 point, etc). I have tried a number of ways without success so any assistance would be greatly appreciated. below is the code for the proven technique.
Private Sub Worksheet_Activate()
Dim cht As Object
Dim p As Object
Dim V As Variant
Dim Counter As Integer
For Each cht In ActiveSheet.ChartObjects
Counter = 0
V = cht.Chart.SeriesCollection(1).Values
For Each p In cht.Chart.SeriesCollection(1).Points
Counter = Counter + 1
Select Case V(Counter)
'Case Is = 1
'p.Shadow = False
'p.InvertIfNegative = False
'p.Fill.OneColorGradient Style:=msoGradientVertical, Variant:=3, _
' Degree:=0.78
'p.Fill.Visible = True
'p.Fill.ForeColor.SchemeColor = 5
Case Is < 0.98
p.Shadow = False
p.InvertIfNegative = False
p.Fill.OneColorGradient Style:=msoGradientVertical, Variant:=3, _
Degree:=0.78
p.Fill.Visible = True
p.Fill.ForeColor.SchemeColor = 3
'Case Is < 0.98
'p.Shadow = False
'p.InvertIfNegative = False
'p.Fill.OneColorGradient Style:=msoGradientVertical, Variant:=4, _
' Degree:=0.38
'p.Fill.Visible = True
'p.Fill.ForeColor.SchemeColor = 6
Case Is <= 1
p.Shadow = False
p.InvertIfNegative = False
p.Fill.OneColorGradient Style:=msoGradientVertical, Variant:=3, _
Degree:=0.78
p.Fill.Visible = True
p.Fill.ForeColor.SchemeColor = 10
End Select
Next
Next
End Sub
Did you try something like:
Case Is > .SeriesCollection(2).Values()(Counter)
Also revised to get rid of some apparent redundancy (if need a loop and a counter variable, e.g., when looping several collections/arrays in parallel), it seems better IMO to just loop by index, rather than For Each _object_ with a separate counter.
Private Sub Worksheet_Activate()
Dim cht As Object
Dim p As Object
Dim V As Variant
Dim Counter As Integer
For Each cht In ActiveSheet.ChartObjects
Counter = 0
With cht.Chart
V = .SeriesCollection(1).Values
For Counter = 1 to.SeriesCollection(1).Points.Count
'Assign your Point object, if needed elsewhere
Set p = .SeriesCollection(1).Points(Counter)
Select Case V(Counter)
Case Is > .SeriesCollection(2).Values()(Counter)
'DO STUFF HERE.
'Add other cases if needed...
End Select
Next
End With
Next
End Sub
And unless you need the values in an array V for some other reason, this can be further reduced:
Private Sub Worksheet_Activate()
Dim cht As Object
Dim p As Object
Dim val1, val2
Dim Counter As Integer
For Each cht In ActiveSheet.ChartObjects
Counter = 0
With cht.Chart
For Counter = 1 to.SeriesCollection(1).Points.Count
'Assign your Point object, if needed elsewhere
Set p = .SeriesCollection(1).Points(Counter)
' extract specific point value to variables:
val1 = .SeriesCollection(1).Values()(Counter)
val2 = .SeriesCollection(2).Values()(Counter)
Select Case V(Counter)
Case val1 > val2
'DO STUFF HERE.
'Add other cases if needed...
End Select
Next
End With
Next
End Sub
Edited with final code, The gradient needed 2 refreshes to completely fill in, (I would have to hit another tab and then go back), so I added a loop to run the code through twice and now it updates perfect the first time. Hopefully this helps others. This allows for a completely dynamic chart. Again, thank you David.
Private Sub Worksheet_Activate()
Dim cht As Object
Dim p As Object
Dim V As Variant
Dim Counter As Integer
Dim L As Integer
For L = 1 To 2
For Each cht In ActiveSheet.ChartObjects
Counter = 0
With cht.Chart
V = cht.Chart.SeriesCollection(1).Values
For Counter = 1 To .SeriesCollection(1).Points.Count
Set p = .SeriesCollection(1).Points(Counter)
Select Case V(Counter)
'Blue Gradient
'Case Is = .SeriesCollection(2).Values()(Counter)
'p.Shadow = False
'p.InvertIfNegative = False
'p.Fill.OneColorGradient Style:=msoGradientVertical, Variant:=3, _
' Degree:=0.78
'p.Fill.Visible = True
'p.Fill.ForeColor.SchemeColor = 5
'Red Gradient
Case Is < .SeriesCollection(2).Values()(Counter)
p.Shadow = False
p.InvertIfNegative = False
p.Fill.OneColorGradient Style:=msoGradientVertical, Variant:=3, _
Degree:=0.78
p.Fill.Visible = True
p.Fill.ForeColor.SchemeColor = 3
'Yellow Gradient
'Case Is < .SeriesCollection(2).Values()(Counter)
'p.Shadow = False
'p.InvertIfNegative = False
'p.Fill.OneColorGradient Style:=msoGradientVertical, Variant:=4, _
' Degree:=0.38
'p.Fill.Visible = True
'p.Fill.ForeColor.SchemeColor = 6
'Green Gradient
Case Is >= .SeriesCollection(2).Values()(Counter)
p.Shadow = False
p.InvertIfNegative = False
p.Fill.OneColorGradient Style:=msoGradientVertical, Variant:=3, _
Degree:=0.78
p.Fill.Visible = True
p.Fill.ForeColor.SchemeColor = 10
End Select
Next
End With
Next
Next L
End Sub

Compare the last 2 strings of the name of the label to the first 2 string of the variable

I have 20 labels with names like lbl_A1, lbl_A2, lbl_A3, lbl_A4, lbl_B1, lbl_B2, lbl_B3, lbl_B4... until lbl_E4. Each labels forecolor should change according to the value in the database, 0 = red, 1 = yellow, 2 = green.
'I arrayed all the values of it, each item contains its specific value
Dim lightFunctions = New Integer() {a1_LightFunction, b1_LightFunction, c1_LightFunction, d1_LightFunction, e1_LightFunction _
, a2_LightFunction, b2_LightFunction, c2_LightFunction, d2_LightFunction, e2_LightFunction _
, a3_LightFunction, b3_LightFunction, c3_LightFunction, d3_LightFunction, e3_LightFunction _
, a4_LightFunction, b4_LightFunction, c4_LightFunction, d4_LightFunction, e4_LightFunction}
'Loop through each item of array and get the value
For Each lightFunctionsValue As Integer In lightFunctions
'Loop through each label in my form
For Each c As Label In Me.Controls.OfType(Of Label)()
'This is my problem, I don't know how to make this that if it detects that for example the label's name ends with A1 then it should get the value of a1_LightFunction the if it is 0 it should be red
If c.Name.EndsWith("") and lightFunctionsValue = 0 Then c.ForeColor = color.red
If c.Name.EndsWith("") and lightFunctionsValue = 1 Then c.ForeColor = color.yellow
If c.Name.EndsWith("") and lightFunctionsValue = 2 Then c.ForeColor = color.green
Next
Next
I believed that if I do it like that I can avoid so many if's condition like this
If a1_LightFunction = 0 Then
lbl_A1.ForeColor = Color.Red
End If
If b1_LightFunction = 0 Then
lbl_B1.ForeColor = Color.Red
End If
If c1_LightFunction = 0 Then
lbl_C1.ForeColor = Color.Red
End If
If d1_LightFunction = 0 Then
lbl_D1.ForeColor = Color.Red
End If
If e1_LightFunction = 0 Then
lbl_E1.ForeColor = Color.Red
End If
And so on and so forth until it reaches ....
If e4_LightFunction = 2 Then
lbl_E4.ForeColor = Color.Green
End If
All you may need is a little abstraction and a way to link labels with some sort of key:
Private lblCol As Dictionary(Of String, Label)
...
Dim lbls As Label() = {Label2, Label3, Label4, Label5}
Dim keys As String() = {"lbl_a1", "lbl_c1", "lbl_b4", "lbl_d3"}
lblCol = New Dictionary(Of String, Label)
For n As Int32 = 0 To keys.Count - 1
lblCol.Add(keys(n), lbls(n))
Next
Then a generic updater:
Private Sub UpdateLabel(lbl As Label, n As Int32)
Select Case n
Case 0
lbl.BackColor = Color.Red
Case 1
lbl.BackColor = Color.Yellow
Case 2
lbl.BackColor = Color.Green
End Select
End Sub
Loop thru all of them:
For Each kvp In lblCol
UpdateLabel(kvp.Value, RNG.Next(0, 3))
Next
To find them use the key, which in your original method was the name:
Dim find = "lbl_a1"
UpdateLabel(lblCol(find), RNG.Next(0, 3))
The key needs to be something simple from the DB to allow you to set up the link/map and make it simple to find the right control.
RNG is a Random object, you would use the DB values.

How can I list all the combinations that meet certain criteria using Excel VBA?

Which are the combinations that the sum of each digit is equal to 8 or less, from 1 to 88,888,888?
For example,
70000001 = 7+0+0+0+0+0+0+1 = 8 Should be on the list
00000021 = 0+0+0+0+0+0+2+1 = 3 Should be on the list.
20005002 = 2+0+0+0+5+0+0+2 = 9 Should not be on the list.
Sub Comb()
Dim r As Integer 'Row (to store the number)
Dim i As Integer 'Range
r = 1
For i = 0 To 88888888
If i = 8
'How can I get the sum of the digits on vba?
ActiveSheet.Cells(r, 1) = i
r = r + 1
End If
Else
End Sub
... Is this what you're looking for?
Function AddDigits(sNum As String) As Integer
Dim i As Integer
AddDigits = 0
For i = 1 To Len(sNum)
AddDigits = AddDigits + CInt(Mid(sNum, i, 1))
Next i
End Function
(Just remember to use CStr() on the number you pass into the function.
If not, can you explain what it is you want in a bit more detail.
Hope this helps
The method you suggest is pretty much brute force. On my machine, it ran 6.5min to calculate all numbers. so far a challenge I tried to find a more efficient algorithm.
This one takes about 0.5s:
Private Const cIntNumberOfDigits As Integer = 9
Private mStrNum As String
Private mRng As Range
Private Sub GetNumbers()
Dim dblStart As Double
Set mRng = Range("a1")
dblStart = Timer
mStrNum = Replace(Space(cIntNumberOfDigits), " ", "0")
subGetNumbers 8
Debug.Print (Timer - dblStart) / 10000000, (Timer - dblStart)
End Sub
Private Sub subGetNumbers(intMaxSum As Integer, Optional intStartPos As Integer = 1)
Dim i As Integer
If intStartPos = cIntNumberOfDigits Then
Mid(mStrNum, intStartPos, 1) = intMaxSum
mRng.Value = Val(mStrNum)
Set mRng = mRng.Offset(1)
Mid(mStrNum, intStartPos, 1) = 0
Exit Sub
End If
For i = 0 To intMaxSum
Mid(mStrNum, intStartPos, 1) = CStr(i)
subGetNumbers intMaxSum - i, intStartPos + 1
Next i
Mid(mStrNum, intStartPos, 1) = 0
End Sub
It can be sped up further by about factor 10 by using arrays instead of writing directly to the range and offsetting it, but that should suffice for now! :-)
As an alternative, You can use a function like this:
Function isInnerLowr8(x As Long) As Boolean
Dim strX As String, inSum As Long
isInnerLowr8 = False
strX = Replace(CStr(x), "0", "")
For i = 1 To Len(strX)
Sum = Sum + Val(Mid(strX, i, 1))
If Sum > 8 Then Exit Function
Next i
isInnerLowr8 = True
End Function
Now change If i = 8 to If isInnerLowr8(i) Then.

Force fit column of flexgrid

What is best way to force fit the columns of msflexgrid in vb6?
so, that all columns are visible and they occupies maximum width of grid!
I've tried this code but it does not properly fit last column inside the grid, can anyone suggest what could be problem?
Public Sub **FlexGrid_AutoSizeColumns (** ByRef pGrid As MSHFlexGrid, _
ByRef pForm As Form, _
Optional ByVal pIncludeHeaderRows As Boolean = True, _
Optional ByVal pAllowShrink As Boolean = True, _
Optional ByVal pMinCol As Long = 0, _
Optional ByVal pMaxCol As Long = -1, _
Optional ByVal pBorderSize As Long = 8, _
Optional fitToScreen As Boolean = False **)**
Dim lngMinCol As Long, lngMaxCol As Long, lngCurrRow As Long
Dim lngMinRow As Long, lngMaxRow As Long, lngCurrCol As Long
Dim lngMaxWidth As Long, lngCurrWidth As Long
Dim fntFormFont As StdFont
Dim totalWidth As Integer
totalWidth = 0
Set fntFormFont = New StdFont
Call CopyFont(pForm.Font, fntFormFont)
Call CopyFont(pGrid.Font, pForm.Font)
With pGrid
lngMinCol = pMinCol
lngMaxCol = IIf(pMaxCol = -1, .Cols - 1, pMaxCol)
lngMinRow = IIf(pIncludeHeaderRows, 0, .FixedRows)
lngMaxRow = .Rows - 1
For lngCurrCol = lngMinCol To lngMaxCol
lngMaxWidth = IIf(pAllowShrink, 0, pForm.ScaleX(.ColWidth(lngCurrCol), vbTwips, pForm.ScaleMode))
For lngCurrRow = lngMinRow To lngMaxRow '..find widest text (in scalemode of the form)
lngCurrWidth = pForm.TextWidth(Trim(.TextMatrix(lngCurrRow, lngCurrCol)))
If lngMaxWidth < lngCurrWidth Then lngMaxWidth = lngCurrWidth
Next lngCurrRow
lngMaxWidth = pForm.ScaleX(lngMaxWidth, pForm.ScaleMode, vbTwips)
.ColWidth(lngCurrCol) = lngMaxWidth + (pBorderSize * Screen.TwipsPerPixelX)
totalWidth = .ColWidth(lngCurrCol) + totalWidth
Next lngCurrCol
End With
Call CopyFont(fntFormFont, pForm.Font)
If fitToScreen = True Then
Dim i As Integer
Dim gridWidth As Long
gridWidth = pGrid.Width
For i = 0 To pGrid.Cols - 1
pGrid.ColWidth(i) = Int(gridWidth * pGrid.ColWidth(i) / totalWidth)
Next
End If
End Sub
One way I could think is to resize your columns (with visibility) to fit into the max width found in a column (text). The function returns either 0 or a double value. As long as the returned max column width is not zero, we may adjust the current grid column width accordingly. If zero then it remains the same.
Dim i, j, as Integer
Dim maxWidth as Double
For i = 0 to MsFlexGrid1.Rows - 1
For j = 0 to MsFlexGrid1.Cols - 1
maxWidth = maxColWidth(j)
If maxWidth > 0 then
MsFlexGrid.ColWidth(j) = maxWidth
End If
Next j
Next i
Private Function maxColWidth(coNum as Integer) as Double
Dim i, Max as Integer
Max = 0
With MsFlexGrid1
For i = .FixedRows to .Rows-1
If TextWidth(.TextMatrix(i, colNum)) > Max Then
Max = TextWidth(.TextMatrix(i, colNum))
End If
Next i
maxColWidth = Max
End With
End Function
to distribute the remaining space over the columns, divide it by the number of columns and add it to each column
'1 form with :
' 1 msflexgrid : name=MSFlexGrid1
Option Explicit
Private Sub Form_Load()
Dim intCol As Integer
'example form and grid configuration
Move 0, 0, 10000, 5000
With MSFlexGrid1
.FixedRows = 0
.FixedCols = 0
.Rows = 10
.Cols = 10
For intCol = 0 To .Cols - 1
.ColWidth(intCol) = (intCol + 1) * 107
Next intCol
End With 'MSFlexGrid1
End Sub
Private Sub Form_Resize()
MSFlexGrid1.Move 0, 0, ScaleWidth, ScaleHeight
End Sub
Private Sub MSFlexGrid1_Click()
DistributeWidth
End Sub
Private Sub DistributeWidth()
Dim intCol As Integer, intColSel As Integer
Dim lngWidth As Long
Dim lngRemaining As Long
Dim lngExpand As Long
With MSFlexGrid1
intColSel = .Col 'remember selected column
.Col = 0 'select first column to ...
lngWidth = .Width - .CellLeft * 2 '... take flexgrid-borders into account
.Col = intColSel 'select column again
lngRemaining = lngWidth - InUse 'calculate the remaining space
If lngRemaining > 0 Then
lngExpand = lngRemaining \ .Cols 'distribute the remaining space over the columns
For intCol = 0 To .Cols - 1
.ColWidth(intCol) = .ColWidth(intCol) + lngExpand
Next intCol
lngExpand = lngRemaining Mod .Cols
.ColWidth(.Cols - 1) = .ColWidth(.Cols - 1) + lngExpand 'since we are working with longs, apply the remaining fraction to the last column
Else
'what to do with lack of space? Shrink columns or expand grid or nothing?
End If
End With 'MSFlexGrid1
End Sub
Private Function InUse() As Long
'calculate how much of the gridwidth is already in use by the columns
Dim intCol As Integer
Dim lngInUse As Long
With MSFlexGrid1
lngInUse = 0
For intCol = 0 To .Cols - 1
lngInUse = lngInUse + .ColWidth(intCol)
Next intCol
End With 'MSFlexGrid1
InUse = lngInUse
End Function
The above example somehow does not always fill the area completely, although i think the logic is correct and i can't see anything missing ...
i guess this gives a similar result to what you have? or is it slightly better?