Excel VBA-Getting a blank message box with data derrived from an array - vba

I am getting a blank message box for an array which should automatically have and display the following values:
00:00:00
01:00:00
02:00:00
and so on...
Here is my code
Dim i As Integer
i = 0
Dim sampleArr(0 To 24) As Variant
Dim a As Integer
a = 0
Do Until i > 23
sampleArr(a) = i & ":00:00"
a = a + 1
i = i + 1
MsgBox sampleArr(a)
Loop
Please tell me what's wrong with this code

You update the value of sampleArr(a), then increment a. So to get the just-updated value you need to use the pre-incremented value: a-1.
MsgBox sampleArr(a-1)

Put the Msgbox first before you increment a and i.
MsgBox sampleArr(a)
a = a + 1
i = i + 1

It's not entirely clear what you're trying to achieve here, (especially with a and i being identical. Presumably the msgbox is only actually in there to prove you've created the array correctly and will be removed later?
That said, as everyone is pointing out, you're incrementing your pointer before displaying the entry. The simplest way to fix that is to put the display line in immediately after creating the element.
I've also formatted i in order to produce the exact output you've requested.
Also, I suspect your array only needs to go 0 To 23 if this is some kind of time selector?
So, fixing your issue looks like:
Dim i As Integer
i = 0
Dim sampleArr(0 To 23) As Variant
Dim a As Integer
a = 0
Do Until i > 23
sampleArr(a) = Format(i, "00") & ":00:00"
MsgBox sampleArr(a)
a = a + 1
i = i + 1
Loop
However, you could just do the following:
Dim i As Integer
Dim sampleArr(0 To 23) As Variant
For i = 0 To 23
sampleArr(a) = Format(i, "00") & ":00:00"
MsgBox sampleArr(a)
Next
Beyond this, if you want to store the values in the array as TIME rather than a text representation of the time (useful for calculations etc.) then replace the sampleArr line with
sampleArr(a) = TimeSerial(i, 0, 0)

Related

Trying to Add a Footnote on the last Page of an Excel File using a loop

I'm currently working on an invoice which could span multiple pages and I want the Signature to appear near the bottom of the last page of the invoice.
My idea:
If the invoice is only one page long I would like to place the signature on Row 39.
If there is data in Row 39,then Place the Footnote at the end of the next page which is Row 86 (add 47 rows).
Continue doing that until an empty row is found. So if Row 86 has Data add another 47 Rows and place the footer in Row 133.
I'm having some trouble figuring out how to get the loop to work, I know how to get a loop to work when you're using a count Do while i > (insert amount here) but I don't know how to do it until it finds an empty row.
Don't do it exactly that way. Start with the last used row (plus one), then use a loop to find the next appropriate row after that.
lastRow = activesheet.usedrange.rows.count + 1
sigRow = 39
while sigRow < lastRow
sigRow = sigRow + 47
wend
activesheet.cells(sigRow, c) = signature
Now, this is pseudocode, so you will need to adapt it to your specific use. For instance, you have to define the signature, and tell it what column to put it in (c is the placeholder). You may want to explicitly name the worksheet instead of just using activesheet.
Ended up Solving it
Public Sub Signature()
Dim Signature_Line As String
Dim Signature_Labels As String
Dim Signed As Integer
Dim Signature_Row As Integer
Signature_Line = " _________________________ _________________________ ______________________________"
Signature_Labels = " Name Date Signature"
Signed = 0
Signature_Row = 39
Do Until Signed = 1
If IsEmpty(ActiveSheet.Range("A" & Signature_Row - 1)) = True Then
ActiveSheet.Range("A" & Signature_Row).Value = Signature_Line
ActiveSheet.Range("A" & Signature_Row + 1).Value = Signature_Labels
Signed = Signed + 1
Else
Signature_Row = Signature_Row + 47
End If
Loop
End Sub

Pass user input from excel cells to an Array

I am very new to VBA, so I apologize if this is a very simple question. I am trying to pass user input data into an array. Actually, 4 different arrays. All 4 arrays can have up to 3 elements, but could only need one at any given time. They are then sorted a specific way via For Loops and then will output the sendkeys function to the active window (which will not be excel when it is running). I have the for loops figured out and it is sorting the way i need it to. I just need to be able to get the user input into those arrays and then output them to a phantom keyboard (i.e. sendkeys). I appreciate any help or advice!
FYI, I have declared the arrays as strings and the variables as long... the message boxes are there to just test the sort, they are not very important
For i = 0 To UBound(SheetPosition)
If j = UBound(Position) Then
j = 0
End If
For j = 0 To UBound(Position)
If k = UBound(Direction) Then
k = 0
End If
For k = 0 To UBound(Direction)
If l = UBound(Temper) Then
l = 0
End If
For l = 0 To UBound(Temper)
MsgBox(i)
MsgBox(SheetPosition(i))
MsgBox(j)
MsgBox(Position(j))
MsgBox(k)
MsgBox(Direction(k))
MsgBox(l)
MsgBox(Temper(l))
Next
Next
Next
Next
you could use Application.InputBox() method in two ways:
Dim myArray As Variant
myArray = Application.InputBox("List the values in the following format: " & vbCrLf & "{val1, val2, val3, ...}", Type:=64) '<--| this returns an array of 'Variant's
myArray = Split(Application.InputBox("List the values in the following format: " & vbCrLf & "val1, val2, val3, ...", Type:=2), ",") '<--| this returns an array of 'String's
Yes, you could get the input from the user using Input boxes:
myValue = InputBox("Give me some input")
Or forms, which is the preferred method. Unfortunately, forms take some time to develop and are best deployed through Excel add-ins, which also require time to learn how to setup.
Here is a good tutorial on using the SendKeys method:
http://www.contextures.com/excelvbasendkeys.html
The usual way of getting data from cells into an array would be:
Dim SheetPosition As Variant
SheetPosition = Range("A1:A3").Value
or perhaps
Dim SheetPosition As Variant
SheetPosition = Range("A1:A" & Cells(Rows.Count, "A").End(xlUp).Row).Value
A few things to note:
The array needs to be dimensioned as a Variant.
The dimension of the array will be rows x columns, so in the first example above SheetPosition will be dimensioned 1 To 3, 1 To 1, and in the second example it might be dimensioned 1 To 5721, 1 To 1 (if the last non-empty cell in column A was A5721)
If you need to find the dimensions of a multi-dimensioned array, you should use UBound(SheetPosition, 1) to find the upper bound of the first dimension and UBound(SheetPosition, 2) to find the upper bound of the second dimension.
Even if you include Option Base 0 at the start of your code module, the arrays will still be dimensioned with a lower bound of 1.
If you want a single dimensioned array and your user input is in a column, you can use Application.Transpose to achieve this:
Dim SheetPosition As Variant
SheetPosition = Application.Transpose(Range("A1:A3").Value)
In this case SheetPosition will be dimensioned 1 To 3.
If you want a single dimensioned array and your user input is in a row, you can still use Application.Transpose to achieve this, but you have to use it twice:
Dim SheetPosition As Variant
SheetPosition = Application.Transpose(Application.Transpose(Range("A1:C1").Value))
FWIW - Your If statements in the code in the question are not achieving anything - each of the variables that are being set to 0 are going to be set to 0 by the following For statements anyway. So your existing code could be:
For i = LBound(SheetPosition) To UBound(SheetPosition)
For j = LBound(Position) To UBound(Position)
For k = LBound(Direction) To UBound(Direction)
For l = LBound(Temper) To UBound(Temper)
MsgBox i
MsgBox SheetPosition(i)
MsgBox j
MsgBox Position(j)
MsgBox k
MsgBox Direction(k)
MsgBox l
MsgBox Temper(l)
Next
Next
Next
Next

for loop : string & number without keep adding &

I'm learning for loop and I cannot get this problem fixed.
The problems are in the following codes.
dim rt as integer = 2
dim i As Integer = 0
dim currentpg as string = "http://homepg.com/"
For i = 0 To rt
currentpg = currentpg & "?pg=" & i
messagebox.show(currentpg)
next
'I hoped to get the following results
http://homepg.com/?pg=0
http://homepg.com/?pg=1
http://homepg.com/?pg=2
'but instead I'm getting this
http://homepg.com/?pg=0
http://homepg.com/?pg=0?pg=0
http://homepg.com/?pg=0?pg=0?pg=0
Please help me
Thank you.
You probably need something like this:
Dim basepg as string = "http://homepg.com/"
For i = 0 To rt
Dim currentpg As String = basepg & "?pg=" & i
messagebox.show(currentpg)
Next
Although a proper approach would be to accumulate results into a List(Of String), and then display in a messagebox once (or a textbox/file, if too many results). You don't want to bug user for every URL (what if there are 100 of them?). They would get tired of clicking OK.
First of all, you went wrong while copying the output of the buggy code. Here is the real one.
http://homepg.com/?pg=0
http://homepg.com/?pg=0?pg=1
http://homepg.com/?pg=0?pg=1?pg=2
It does not work because currentpg should be a constant but it is changed on each iteration.
Do not set, just get.
MessageBox.Show(currentpg & "?pg=" & i)
Or you can use another variable to make it more readable.
Dim newpg As String = currentpg & "?pg=" & i
MessageBox.Show(newpg)
Also, your code is inefficient. I suggest you to change it like this.
Dim iterations As Integer = 2
Dim prefix As String = "http://homepg.com/?pg="
For index As Integer = 0 To iterations
MessageBox.Show(prefix & index)
Next

Excel VBA: Looking for Advice Avoiding an Infinite Loop

Imgur Album with screens of worksheets: http://imgur.com/a/6rFWF
Long story short, I am writing an Excel VBA utility that will assign two types of security shifts (called coverages and weekend duties) to security staff members. Basically, I have a worksheet with all of the staff members and their various availability information in it (the top image in the imgur album) and a worksheet with all of the coverage dates in it (the bottom image in the imgur album). Note that I don't have an image of the weekend duty dates as it looks similar to the coverage dates (but with the Friday and Saturday shifts).
The utility basically assigns a random staff member to each date, checking to make sure it doesn't violate any of their availability requirements. Unfortunately, I realize that I am creating a large chance for an infinite loop to occur. In my own testing, there has only been 1 attempt out of around 15-16 that did not enter an infinite loop near the end. So I'm looking for your help to account for this so the utility doesn't eat itself.
Here is the "pseudo-code" for the procedure in question.
'Loop for Column A in the Coverage Slips sheet (image 2 in imgur album)
Do Until (CoverageRowNumber = LastCoverageSlipRow + 1)
Get a Random Staff Member by RNG
If staff member still needs more shifts (see Requirements columns) Then
If staff member does not have an "X" under the day of the week Then
If staff member does not have a matching date conflict Then
Assign the coverage
Increase CoverageRowNumber
End If
End If
End If
Loop
'Loop for Column B in the coverage slips sheet (image 2 in imgur album)
Do Until...
Same as the loop above
Loop
Edit: Disregard that I have the dates in two columns for now. I'll be fixing that once I solve the problem of this post...it's an easy fix and will cut the code almost in half.
The problem is that as the utility gets near the end of the list of dates, it often runs into the scenario where the only staff members left cannot sit that specific shift (whether because of day of the week or specific date). In the event that it runs into this scenario, I can see a couple of acceptable options (though I don't know how I'd go about programming them):
Undo all of the work that the utility did and start over until it can get lucky and find a solution that works. This would save me some time doing manual placements for the last few shifts but might take a very long time. Additionally, I'd have to store all of the original values and then paste them back into the spreadsheet anytime it starts over.
Simply stop assigning shifts and just exit the procedure. I will be able to manually place the last few shifts by moving a few people around. I sure is a lot less work than manually assigning 200 shifts by hand like I've been doing it the past few years.
Do you guys have any thoughts that could be of help here? I'm not even sure how I could have the procedure check to see if there are any available options or not, but either way there's got to be a way to detect (and deter) this infinite loop before it crashes the program.
Sorry for the novel, and thanks in advance for any help!
Edit: In an effort to provide a little more clarity, I figured I'd copy and paste the actual code below:
'------------------------------------------------------------'
'Create ws variables for each worksheet
Dim wsConflicts As Worksheet
Dim wsCoverageSlips As Worksheet
Dim wsWDSlips As Worksheet
Dim wsCoverageOutput As Worksheet
Dim wsWDOutput As Worksheet
'------------------------------------------------------------'
Public Function SetSheets()
'Assign the worksheets to the ws variables
Set wsConflicts = Worksheets("Conflicts")
Set wsCoverageSlips = Worksheets("Coverage Slips")
Set wsWDSlips = Worksheets("WD Slips")
Set wsCoverageOutput = Worksheets("Coverage Output")
Set wsWDOutput = Worksheets("WD Output")
'Display a message (debugging)
'MsgBox "The sheets have been assigned successfully"
End Function
'------------------------------------------------------------'
Public Function ColumnLetter(ColumnNumber As Integer) As String
Dim n As Long
Dim c As Byte
Dim s As String
n = ColumnNumber
Do
c = ((n - 1) Mod 26)
s = Chr(c + 65) & s
n = (n - c) \ 26
Loop While n > 0
ColumnLetter = s
End Function
'------------------------------------------------------------'
Sub AssignCoverages()
'Fill the ws variables
Call SetSheets
'Set the first and last row numbers
Dim FirstStaffMemberRow As Integer
FirstStaffMemberRow = 3
Dim LastStaffMemberRow As Integer
LastStaffMemberRow = wsConflicts.UsedRange.Rows.Count
'Count the number of required coverages and weekend duties
Dim RequiredCoverages As Integer
Dim RequiredWDs As Integer
For i = FirstStaffMemberRow To LastStaffMemberRow
RequiredCoverages = RequiredCoverages + wsConflicts.Range("B" & i).Value
RequiredWDs = RequiredWDs + wsConflicts.Range("C" & i).Value
Next i
'Display a message (debugging)
MsgBox "You currently have " & RequiredCoverages & " required coverages and " & RequiredWDs & " required weekend duties."
'Count the number of coverage slips and weekend duty slips
Dim FirstCoverageSlipRow As Integer
FirstCoverageSlipRow = 1
Dim LastCoverageSlipRow As Integer
LastCoverageSlipRow = wsCoverageSlips.UsedRange.Rows.Count
Dim NumCoverageSlips As Integer
NumCoverageSlips = (LastCoverageSlipRow - FirstCoverageSlipRow + 1)
Dim FirstWDSlipRow As Integer
FirstWDSlipRow = 1
Dim LastWDSlipRow As Integer
LastWDSlipRow = wsWDSlips.UsedRange.Rows.Count
Dim NumWDSlips As Integer
NumWDSlips = (LastWDSlipRow - FirstWDSlipRow + 1)
'Check to make sure there are enough required shifts for slips
If RequiredCoverages <> NumCoverageSlips Then
MsgBox "The number of shifts you require (Columns B & C on Conflicts sheet) does not match the number of slips you've entered. You have " & RequiredCoverages & " required coverages and " & NumCoverageSlips & " coverage slips. You have " & RequiredWDs & " required weekend duties and " & NumWDSlips & " weekend duty slips. Please correct this error and retry."
Exit Sub
Else
'Debugging
'MsgBox "The number of shifts you require (Columns B & C on Conflicts sheet) matches the number of slips you've entered. You have " & RequiredCoverages & " required coverages and " & NumCoverageSlips & " coverage slips. You have " & RequiredWDs & " required weekend duties and " & NumWDSlips & " weekend duty slips."
End If
'Massive loop to assign coverages to random staff members
Dim NumRemainingCoverages As Integer
NumRemainingCoverages = NumCoverageSlips
Dim SlipRowNumber As Integer
SlipRowNumber = FirstCoverageSlipRow
'Loop for Column A
Do Until (SlipRowNumber = LastCoverageSlipRow + 1)
'Get a random staff member row
StaffMemberRow = GetRandomStaffMemberRow(FirstStaffMemberRow, LastStaffMemberRow)
'Check to make sure the staff member has remaining required coverages
If wsConflicts.Range("B" & StaffMemberRow).Value > 0 Then
'Check to make sure the staff member can sit the day of the week
Dim CurrentDate As Date
CurrentDate = wsCoverageSlips.Range("A" & SlipRowNumber).Value
Dim CurrentDay As Integer
CurrentDay = Weekday(CurrentDate)
Dim CurrentDayColumn As String
If CurrentDay = 1 Then CurrentDayColumn = "D"
If CurrentDay = 2 Then CurrentDayColumn = "E"
If CurrentDay = 3 Then CurrentDayColumn = "F"
If CurrentDay = 4 Then CurrentDayColumn = "G"
If CurrentDay = 5 Then CurrentDayColumn = "H"
If CurrentDay = 6 Then CurrentDayColumn = "I"
If CurrentDay = 7 Then CurrentDayColumn = "J"
If wsConflicts.Range(CurrentDayColumn & StaffMemberRow).Value = "" Then
'Check to make sure the staff member does not have a date conflict
Dim ColumnNumber As Integer
Dim ColumnLetterText As String
Dim CoverageDateConflicts As Integer
CoverageDateConflicts = 0
For ColumnNumber = 11 To 20
ColumnLetterText = ColumnLetter(ColumnNumber)
Dim CoverageSlipDate As Date
If IsDate(wsConflicts.Range(ColumnLetterText & StaffMemberRow).Value) = True Then
CoverageSlipDate = wsConflicts.Range(ColumnLetterText & StaffMemberRow).Value
Else
CoverageSlipDate = DateValue("01/01/1900")
End If
If CurrentDate = CoverageSlipDate Then
CoverageDateConflicts = CoverageDateConflicts + 1
End If
Next ColumnNumber
If CoverageDateConflicts = 0 Then
'Assign the coverage
Dim BlankCoverageOutputRow As Integer
BlankCoverageOutputRow = wsCoverageOutput.UsedRange.Rows.Count + 1
wsCoverageOutput.Range("A" & BlankCoverageOutputRow).Value = wsConflicts.Range("A" & StaffMemberRow).Value
wsCoverageOutput.Range("B" & BlankCoverageOutputRow).Value = CurrentDate
'Reduce the staff member's required coverages by 1
Dim CurrentRequirements As Integer
CurrentRequirements = wsConflicts.Range("B" & StaffMemberRow).Value
wsConflicts.Range("B" & StaffMemberRow).Value = CurrentRequirements - 1
'Reduce the number of remaning coverages by 1
NumRemainingCoverages = NumRemainingCoverages - 1
'Increase the slip row number by 1
SlipRowNumber = SlipRowNumber + 1
'Message box for debugging
'MsgBox "Coverage Date (" & CurrentDate & ") assigned to " & wsConflicts.Range("A" & StaffMemberRow).Value & "."
End If 'End date check
End If 'End day check
End If 'End requirements check
Loop 'End loop for column A
End Sub
'------------------------------------------------------------'
Public Function GetRandomStaffMemberRow(FirstStaffMemberRow As Integer, LastStaffMemberRow As Integer)
'Pick a random number between the first staff member row and the last
Call Randomize
GetRandomStaffMemberRow = Int((LastStaffMemberRow - FirstStaffMemberRow + 1) * Rnd + FirstStaffMemberRow)
End Function
The question is too open for a detailed answer, so I try with some guidelines. I hope it helps.
I would use a class Solution with the following members:
Solution.ReadInputFromSheet() reads the table from the sheet into the class members
Solution.GenerateRandom() creates a new random solution. Try to find a balance between smart (add some logic to avoid totally random solutions) and speed (don't get stuck, exit after trying 10 or 50 random numbers that don't work), but speed is more important
Solution.Quality() As Double calculates the quality of the solution. For example a solution that is not valid returns 0, if Joe has 10 consecutive shifts returns 20, if the shifts are better distributed returns 100.
Solution.WriteOnSheet() write the data from the class members into the sheet.
Solution.Clone() As Solution() creates a new Solution instance with the same data
Make a cycle that creates a solution, checks if its quality is better than the best quality solution found so far, if it is better keep it, otherwise go and calculate another solution.
Set BestS = New Solution
BestS.ReadInputFromSheet
BestS.GenerateRandom()
Set S = New Solution
S.ReadInputFromSheet
For I = 1 To 10000
S.GenerateRandom()
If S.Quality() > BestS.Quality() Then Set BestS = S.Clone()
Next I
BestS.WriteOnSheet
Instead of 10000 you can use Timer to run it for a finite number of seconds, or make a button to interrupt it when you come back from lunch break.
A faster solution generator function is better than risking of getting stuck with one difficult (or impossible) solution.
For a smarter solution generator function I need more details on the rules.
So I went ahead and developed my own solution to this problem--it's not perfect and it's probably not the best way to handle the scenario. But it works, and it solved my problem in a matter of minutes instead of hours learning other methods.
Basically, I created two new "counter" variables. The first is FailedAttempts. Every time the procedure tries a random staff member but runs into a conflict, it increments FailedAttempts by 1. Every time the random staff member is a successful match (no conflicts), it resets FailedAttempts to 0. If at any time FailedAttempts = 100, it immediately exits the loop and starts over. In other words, if it tries 100 random staff members in a row without finding a match, I assume it's not going to find a match and just cut my losses.
The second variable, Assignments, is incremented by 1 every time that the procedure makes a successful assignment. When this number equals the number of shifts that the procedure is supposed to assign, it immediately exits the loop.
To do this, I had to use a couple of forbidden 'GoTo' commands (I wasn't sure how else to exit the loop. You can exit a For loop with Exit For but I believe this is invalid for Do While loops. I ended up only needing two GoTo's, one for exiting the loop and one to go back to the beginning of the procedure. I also made sure that the cells in the worksheet that change during the procedure are reset to their original state before it retries the assignment procedure.
I'll save everyone the trouble of reading through the extended version of the code, but in 'pseudo-code' form it looks like this:
Retry: 'Label for GoTo command
Do Until (CoverageRowNumber = LastCoverageSlipRow + 1)
Get a Random Staff Member by RNG
If staff member still needs more shifts (see Requirements columns) Then
If staff member does not have an "X" under the day of the week Then
If staff member does not have a matching date conflict Then
'Assign the coverage
'Increase CoverageRowNumber
Assignments = Assignments + 1
Else
FailedAttempts = FailedAttempts + 1
End If
Else
FailedAttempts = FailedAttempts + 1
End If
Else
FailedAttempts = FailedAttempts + 1
End If
If FailedAttempts > 100 Then
GoTo ExitLoop
End If
Loop
ExitLoop: 'Label for GoTo command
If Assignments <> NumCoverageSlips Then
GoTo Retry
End If
'Do rest of procedure
Again, there may be (and certainly is) a more elegant and "correct" way of accomplishing the task at hand. This method worked for me with the given environment. Thanks to those who provided solutions--even though I ended up going a different direction they provided great food for thought and helped me learn a bunch of new methods (especially the class idea from #stenci).
Thanks all.

Append text to existing row in datatable

I'm trying to make a calendar in vb.net and I have come across this problem. I want to append some text into an existing datatable row. When I watch my debugger it says:"In order to evaluate an indexed property, the property must be qualified and the arguments must be explicitly supplied by the user.".
Dim aantalRijen As Integer = 1
For x = 0 To 6
Dim dttopdrachten As New DataTable
dttopdrachten = opdrachtendao.getOpdrachtenByDate(Today.AddDays(x))
If dttopdrachten.Rows.Count > aantalRijen Then
aantalRijen = dttopdrachten.Rows.Count
End If
Next
For z = 0 To aantalRijen - 1
Dim r As DataRow
r = dttAgenda.NewRow()
dttAgenda.Rows.InsertAt(r, z)
Next
For i = 0 To 6
Dim aantalItems As Integer = 0
Dim dttopdrachten As New DataTable
dttopdrachten = opdrachtendao.getOpdrachtenByDate(Today.AddDays(i))
aantalItems = dttopdrachten.Rows.Count
For j = 0 To aantalItems - 1
Dim info As String = dttopdrachten.Rows(j).Item(0).ToString & vbCrLf & dttopdrachten.Rows(j).Item(2).ToString & vbCrLf & dttopdrachten.Rows(j).Item(3).ToString & vbCrLf & dttopdrachten.Rows(j).Item(4).ToString & vbCrLf & dttopdrachten.Rows(j).Item(5).ToString & vbCrLf & dttopdrachten.Rows(j).Item(6).ToString
dttAgenda.Rows(j).Item(i) = info
Next
Next
dgvAgenda.DataSource = dttAgenda
In the code above, I first count how many rows I have to make. Afterwards I add the amount of rows to the datatable (columns are added before). Until here it works, but then when I keep debugging I get the error. I tried googling but nothing could help me so far.
Seem problem has been solved without changing anything. So if someone want to make a calendar. Here's the solution ;)