Place a large Sub into a Workbook Open Sub - vba

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!

Related

VBA Command Button array

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

A practical example of evenly distributing n lists into a single list

I had previously asked about how to evenly distribute the items in n lists into a single list and was referred to this question: Good algorithm for combining items from N lists into one with balanced distribution?.
I made a practical example of my solution for this in VBA for Excel, since my application for this was resorting my Spotify lists which can be easily pasted into Excel for manipulation. Assumptions are that you have a headerless worksheet (wsSource) of songs with columns A, B, C representing Artist, Song, SpotifyURI respectively, a "Totals" worksheet (wsTotals) containing the sum of songs for each Artist from wsSource sorted in descending order, and a "Destination" worksheet where the new list will be created. Could I get some suggestions to improve this? I was going to get rid of the totals worksheet and have this portion done in code, but I have to go and I wanted to go ahead and put this out there. Thanks!
Sub WeaveSort()
Dim wb As Workbook
Dim wsDest As Worksheet
Dim wsSource As Worksheet
Dim wsTotals As Worksheet
Dim i As Integer
Dim iLast As Integer
Dim iDest As Integer
Dim iSource As Integer
Dim iOldRow As Integer
Dim iNewRow As Integer
Dim dDiff As Double
Dim dDiffSum As Double
Set wb = ThisWorkbook
Set wsTotals = wb.Worksheets("Totals")
Set wsSource = wb.Worksheets("Source")
Set wsDest = wb.Worksheets("Dest")
iLast = wsTotals.Range("A1").End(xlDown).Row - 1
For i = 2 To iLast
iSource = wsTotals.Range("B" & i).Value
iDest = wsDest.Range("A99999").End(xlUp).Row
If i = 2 Then
wsDest.Range("A1:C" & iSource).Value2 = wsSource.Range("A1:C" & iSource).Value2
wsSource.Range("A1:C" & iSource).Delete (xlShiftUp)
GoTo NextI
End If
dDiff = iDest / iSource
dDiffSum = 0
iNewRow = 0
For iOldRow = 1 To iSource
dDiff = iDest / iSource
dDiffSum = dDiffSum + dDiff
iNewRow = Round(dDiffSum, 0)
wsSource.Rows(iOldRow).Copy
wsDest.Rows(iNewRow).Insert xlShiftDown
iDest = iDest + 1
Next iOldRow
wsSource.Range("A1:C" & iSource).Delete (xlShiftUp)
NextI:
Next i
End Sub
Great question! I would take an object oritentated approach. Also I didn;t think it was clear what the logic was so here is my answer. Two classes and one normal module. Save these separately with the filenames ListManager.cls, List.cls, tstListManager.bas
So the ListManager.cls is this
VERSION 1.0 CLASS
BEGIN
MultiUse = -1 'True
END
Attribute VB_Name = "ListManager"
Attribute VB_GlobalNameSpace = False
Attribute VB_Creatable = False
Attribute VB_PredeclaredId = False
Attribute VB_Exposed = False
Option Explicit
Private mdic As Object
Public Sub Initialise(ByVal vLists As Variant)
Set mdic = VBA.CreateObject("Scripting.Dictionary")
Dim vListLoop As Variant
For Each vListLoop In vLists
Dim oList As List
Set oList = New List
oList.Initialise vListLoop, ""
mdic.Add mdic.Count, oList
Next
End Sub
Public Function WeaveSort() As Variant
Dim dicReturn As Object
Set dicReturn = VBA.CreateObject("Scripting.Dictionary")
Dim oNextList As List
Set oNextList = Me.WhichListHasLeastProgress
While oNextList.PercentageDone <= 1
Dim vListItem As Variant
vListItem = oNextList.GetListItem
dicReturn.Add dicReturn.Count, vListItem
oNextList.MoveNext
Set oNextList = Me.WhichListHasLeastProgress
Wend
Dim vItems As Variant
vItems = dicReturn.Items
'I don't like this bit
ReDim vRet(1 To dicReturn.Count, 1 To 1)
Dim lLoop As Long
For lLoop = 0 To dicReturn.Count - 1
vRet(lLoop + 1, 1) = vItems(lLoop)
Next lLoop
WeaveSort = vRet
End Function
Public Function WhichListHasLeastProgress() As List
Dim vKeyLoop As Variant
Dim oListLoop As List
Dim oLeastProgress As List
For Each vKeyLoop In mdic.keys
Set oListLoop = mdic.Item(vKeyLoop)
If oLeastProgress Is Nothing Then
'nothing to compare yet
Set oLeastProgress = oListLoop
Else
If oListLoop.PercentageDone < oLeastProgress.PercentageDone Then
'definitely take this new candidate
Set oLeastProgress = oListLoop
ElseIf oListLoop.PercentageDone = oLeastProgress.PercentageDone And oListLoop.Size > oListLoop.Size Then
'close thing, both showing equal progress but we should give it to the one with the bigger "queue"
Set oLeastProgress = oListLoop
Else
'no swap
End If
End If
Next
'return the answer
Set WhichListHasLeastProgress = oLeastProgress
End Function
and the List.cls file is
VERSION 1.0 CLASS
BEGIN
MultiUse = -1 'True
END
Attribute VB_Name = "List"
Attribute VB_GlobalNameSpace = False
Attribute VB_Creatable = False
Attribute VB_PredeclaredId = False
Attribute VB_Exposed = False
Option Explicit
Private mvList As Variant
Private mlCount As Long
Private mlCursor As Long
Private mvName As Variant
Public Function Initialise(ByRef vList As Variant, ByVal vName As Variant)
Debug.Assert TypeName(vList(1, 1)) <> "" ' this will break unless you specify a 2d array
Debug.Assert LBound(vList, 1) = 1 ' this ensure you got it from a sheet
mvList = vList
mlCount = UBound(mvList)
mlCursor = 1
mvName = vName
End Function
Public Function GetListItem()
GetListItem = mvList(mlCursor, 1)
End Function
Public Function Name() As Variant
Name = mvName
End Function
Public Function MoveNext() As Boolean
mlCursor = mlCursor + 1
MoveNext = (mlCursor < mlCount)
End Function
Public Function Size() As Long
Size = mlCount
End Function
Public Function PercentageDone() As Double
PercentageDone = mlCursor / mlCount
End Function
The last file is this tstListManager.bas
Attribute VB_Name = "tstListManager"
Option Explicit
Sub test()
Dim oListMan As ListManager
Set oListMan = New ListManager
Dim vLists As Variant
vLists = VBA.Array(ThisWorkbook.Sheets("Source").Range("A1:A3").Value2, _
ThisWorkbook.Sheets("Source").Range("B1:B2").Value2, _
ThisWorkbook.Sheets("Source").Range("C1:C5").Value2)
oListMan.Initialise vLists
Dim vSorted As Variant
vSorted = oListMan.WeaveSort
Dim lTotal As Long
ThisWorkbook.Sheets("Dest").Range("A1").Resize(UBound(vSorted, 1)).Value2 = vSorted
End Sub
Finally, the test data was in A1:A3 B1:B2 C1:C5
You should note I have abstracted away any Excel reading/writing logic and the pure weavesort logic is not cluttered.
Feel free to reject outright. Object orientation can be quite controversial and we think differently. :)

VBA: Initialize object on first use in Module

I kept receiving Object Variable Not Set (Error 91) with the following code in PowerPoint 2013.
I did a step through with this, and it flagged it on the Set SlideCounter = New Counter line which is quite confusing.
Module - Module1:
Dim SlideCounter As Counter
Sub showNext()
If SlideCounter Is Nothing Then
Set SlideCounter = New Counter
End If
ActivePresentation.SlideShowWindow.View.GotoSlide (SlideCounter.GetSlideNumber)
End Sub
Class Module - Counter:
'You should config the following constants
Const kBeginSlide As Integer = 2
Const kEndSlide As Integer = 4
Const kEnddingSlide As Integer = 5
'You should NOT modify anything below
Dim slides As Collection
Private Sub Class_Initialize()
Dim x As Integer
For x = kBeginSlide To kEndSlide
slides.Add (x)
Next x
End Sub
Public Function GetSlideNumber()
If slides.Count = 0 Then
GetSlideNumber = kEnddingSlide
Else
Dim slideIndex As Integer
slideIndex = GetRandomInteger(1, slides.Count)
GetSlideNumber = slides.Item(slideIndex)
slides.Remove (slideIndex)
End If
End Function
Private Function GetRandomInteger(lowerBound As Integer, upperBound As Integer)
Randomize
GetRandomInteger = Int((upperBound - lowerBound + 1) * Rnd + lowerBound)
End Function

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.

VBA. How to find position of first digit in string

I have string "ololo123".
I need get position of first digit - 1.
How to set mask of search ?
Here is a lightweight and fast method that avoids regex/reference additions, thus helping with overhead and transportability should that be an advantage.
Public Function GetNumLoc(xValue As String) As Integer
For GetNumLoc = 1 To Len(xValue)
If Mid(xValue, GetNumLoc, 1) Like "#" Then Exit Function
Next
GetNumLoc = 0
End Function
Something like this should do the trick for you:
Public Function GetPositionOfFirstNumericCharacter(ByVal s As String) As Integer
For i = 1 To Len(s)
Dim currentCharacter As String
currentCharacter = Mid(s, i, 1)
If IsNumeric(currentCharacter) = True Then
GetPositionOfFirstNumericCharacter = i
Exit Function
End If
Next i
End Function
You can then call it like this:
Dim iPosition as Integer
iPosition = GetPositionOfFirstNumericCharacter("ololo123")
Not sure on your environment, but this worked in Excel 2010
'Added reference for Microsoft VBScript Regular Expressions 5.5
Const myString As String = "ololo123"
Dim regex As New RegExp
Dim regmatch As MatchCollection
regex.Pattern = "\d"
Set regmatch = regex.Execute(myString)
MsgBox (regmatch.Item(0).FirstIndex) ' Outputs 5
I actually have that function:
Public Function GetNumericPosition(ByVal s As String) As Integer
Dim result As Integer
Dim i As Integer
Dim ii As Integer
result = -1
ii = Len(s)
For i = 1 To ii
If IsNumeric(Mid$(s, i, 1)) Then
result = i
Exit For
End If
Next
GetNumericPosition = result
End Function
You could try regex, and then you'd have two problems. My VBAfu is not up to snuff, but I'll give it a go:
Function FirstDigit(strData As String) As Integer
Dim RE As Object REMatches As Object
Set RE = CreateObject("vbscript.regexp")
With RE
.Pattern = "[0-9]"
End With
Set REMatches = RE.Execute(strData)
FirstDigit = REMatches(0).FirstIndex
End Function
Then you just call it with FirstDigit("ololo123").
If speed is an issue, this will run a bit faster than Robs (noi Rob):
Public Sub Example()
Const myString As String = "ololo123"
Dim position As Long
position = GetFirstNumeric(myString)
If position > 0 Then
MsgBox "Found numeric at postion " & position & "."
Else
MsgBox "Numeric not found."
End If
End Sub
Public Function GetFirstNumeric(ByVal value As String) As Long
Dim i As Long
Dim bytValue() As Byte
Dim lngRtnVal As Long
bytValue = value
For i = 0 To UBound(bytValue) Step 2
Select Case bytValue(i)
Case vbKey0 To vbKey9
If bytValue(i + 1) = 0 Then
lngRtnVal = (i \ 2) + 1
Exit For
End If
End Select
Next
GetFirstNumeric = lngRtnVal
End Function
An improved version of spere's answer (can't edit his answer), which works for any pattern
Private Function GetNumLoc(textValue As String, pattern As String) As Integer
For GetNumLoc = 1 To (Len(textValue) - Len(pattern) + 1)
If Mid(textValue, GetNumLoc, Len(pattern)) Like pattern Then Exit Function
Next
GetNumLoc = 0
End Function
To get the pattern value you can use this:
Private Function GetTextByPattern(textValue As String, pattern As String) As String
Dim NumLoc As Integer
For NumLoc = 1 To (Len(textValue) - Len(pattern) + 1)
If Mid(textValue, NumLoc, Len(pattern)) Like pattern Then
GetTextByPattern = Mid(textValue, NumLoc, Len(pattern))
Exit Function
End If
Next
GetTextByPattern = ""
End Function
Example use:
dim bill as String
bill = "BILLNUMBER 2202/1132/1 PT2200136"
Debug.Print GetNumLoc(bill , "PT#######")
'Printed result:
'24
Debug.Print GetTextByPattern(bill , "PT#######")
'Printed result:
'PT2200136