Exiting a VBA loop with multiple variables - vba

As a newbie in VBA (Excel), I am trying to make a tool, which determines what the diagnostic yield of a certain test must be; in order to be cost-effective as a pre-screening to another diagnostic test.
What I want it to do is calculate for a certain yield of test A, at what yield for test B the costs per diagnosis are the same for both tests. The code I wrote has to loop for a certain range for the diagnostic yield and exit this loop when the costs per diagnosis for test A drop under the costs per diagnosis for test B.
However, the code keeps looping for this range, but does not stop when my condition on costs is met. I tried a lot, including do while and do until statements, but it just won't work. I really hope someone could help me out! Many thanks in advance! Kirsten
Sub TGP_WES_OR_WES()
Dim Yield_A As Double
Dim Yield_B As Double
Dim Yield_A_max As Double
Dim Cost_diagnosis_A As Double
Dim Cost_diagnosis_B As Double
Yield_B = Range("C6")
Yield_A_Max = Yield_B - 0.1
Cost_diagnosis_B = Range("E15")
Cost_diagnosis_A = Range("E11")
Do While Yield_A < Yield_A_max
For Yield_A = 1 To Yield_A_max Step 0.1
Range("C5").Value = Yield_A
If Cost_diagnosis_A < Cost_diagnosis_B Then
Exit For
End If
Next Yield_TGP
Loop
Range("D1").Value = Yield_TGP
End Sub

You have a double loop (both of which appear to be doing the same thing):
Do While Yield_A < Yield_A_max
For Yield_A = 1 To Yield_A_max Step 0.1
...
Next Yield_TGP
Loop
Remove the outer do loop for starters.
The 2nd issue I see is that your loop exit conditions do not appear to depend on the loop iteration. That is to say Cost_diagnosis_A and Cost_diagnosis_B are not updated or changed by the loop. This generally indicates a design error as nearly all loop termination conditions will be dependent upon a value the loop is calculating or updating (or overall loop progress). Intuitively, your loop termination condition should incorporate Yield_A either directly or indirectly (from a downstream calculation). Perhaps you want to be updating the values of Cost_diagnosis_A and/or Cost_diagnosis_B inside your loop body, based on Yield_A?

Related

vba - Macro producing incorrect results when run, but when stepping into results are correct

I have a macro that inserts a VLOOKUP into a column. The macro has to take a number stored as text and convert it to a number, before looking up that number in another sheet.
The macro always produces the same results, such as reaching row 43 before starting to produce erroneous results however when using F8 to step through the code, these incorrect results are not produced.
The erroneous results are that the value placed into col 13 is not equal to the number stored as text. Mostly it seems as though values from rows above and below, sometimes 2 rows below are being inserted to col 13. Almost seems to me as if 2 different threads are running at 2 different speeds or something?
If anyone could have a look at the loop causing the errors I would be grateful, thanks.
For counter = 2 To NumRowsList
checker = CInt(Sheets("Sheet2").Cells(counter, 3)
Sheets("Sheet2").Cells(counter, 13).Value = checker
'Call WaitFor(0.5)
If checker < 4000 Then
Sheets("Sheet2").Cells(counter, 14) = "=VLOOKUP(M" & counter & ",Sheet4!E2:F126,2,FALSE)"
Else
Sheets("Sheet2").Cells(counter, 14) = "=VLOOKUP(M" & counter & ",Sheet5!B2:C200,2,FALSE)"
End If
Next counter
I have tried a few similar variations of this code, such as using the value stored in col 13 directly rather than using the cell reference in the VLOOKUP, always producing the same results.
I even used the waitfor function to try and create a delay hoping it may synchronise the operations, but it did not help and using a delay of more than 0.5 would cause the run time of the macro to be too big.
UPDATE:
I did not find a perfect solution, only a long hand work around. I simply combined the Vlookups onto a single sheet, and converted the numbers stored as text to numbers outside of the vba routine. This took the error away from the number calculation (just col C * 1), and then the vlookups were looking up the correct values. Thank you for the help, regardless.
you can avoid looping, checker and all those If-Then-Else, like follows
edited to account for VlookUp range depending on VlookUp value
With Worksheets("Sheet2")
.Range("N2", .Cells(NumRowsList, 14)).FormulaR1C1 = "=VLOOKUP(Value(RC3),IF(Value(RC3)<4000,Sheet4!R2C5:R126C6,Sheet4!R2C2:R200C3),2,FALSE)"
End With
The following works for me with my test data, but you'll need to see if it works for you... (also are you turning off calculation or events? I don't know if this might have an issue?)
I find it preferable to set a reference to the sheet you want to use rather than access it directly, and this may help?
Dim ws As Worksheet: Set ws = ThisWorkbook.Sheets("Sheet2")
Dim VLURange As String, checker As Long
For counter = 2 To 200 ' NumRowsList
checker = CLng(ws.Cells(counter, 3).Value)
ws.Cells(counter, 13) = checker
VLURange = IIf(checker < 4000, "Sheet4!E2:F126", "Sheet5!B2:C200")
ws.Cells(counter, 14) = "=VLOOKUP(M" & counter & ", " & VLURange & ", 2, FALSE)"
Next counter

For loop to print control characters

Alright, so I am editing a Macro for Reflection Workspace Terminal Emulator. I have a macro (it is long and not necessarily relevant, I can post the full code if anyone wants, it is about a page)
At the very end of the macro, a "Good Morning" Message is printed, and then also the value of a string variable named myName. This works fine.
What I want to do is then use a For loop to print a number of control characters (tab) equal to the amount printed in the Good Morning User. Here is the code I have so far:
Dim X As Integer
For i = 1 To i = (13 + Len(myName)) 'Good Morning + a space character will always = 13
ibmCurrentScreen.SendKeys (ControlKeyCode_Tab)
Next i
End Sub
I would use the Chr() function to print the control characters, but it seems that this particular program uses ControlKeyCode_X to print them.
The For...Next loop in VBA is a simple single variable counter based loop and cannot have complex criteria or multiple counters like in some other languages (e.g. in C# for(int i = 0, int j = 10; i < 10; i++, j--)). In VBA, therefore, the i = after To is redundant because it cannot be anything other than i.
The 'For' loop in VBA therefore only specifies the counter once. It should be:
For i = startVal To endVal [step incrementVal]
...
Next [i]
i = variable to vary
startVal = start value
endVal = end value (inclusive, i.e. To 10 will include 10 as the final value)
incrementVal = optional value to increment/decrement by each loop
(default = 1)
The counter is always incremented/decremented at the end of each loop then evaluated against endVal (i.e. for a vanilla increment by one loop, after exiting the loop it will be endValue + 1).
Note (unrelated to your situation) that you can change the value of i in the loop, e.g. to increment twice because of some special circumstance.
Specifying the variable with the Next statement is optional and has no effect on behaviour (any nested loop must always be closed before an outer loop) but is good practice for reference when you have many For...Next loops so it is clear which loop the Next is closing (although you should be religiously indenting or otherwise delineating your code)
So in your case For i = 1 To (13 + Len(myName)) instead.

What's Wrong With my logic in this Vb program?

I'm new to visual basic and I just started taking classes on it at school. I was given an assignment to write an app that tells if an input in a textbox is a Prime Number Or Not.
I have written this code snippet in Visual Studio:
Public Class PrimeNumberApp
Public Sub CheckButton_Click(sender As Object, e As EventArgs) Handles CheckButton.Click
Dim x, y As Integer
x = Val(PrimeTextBox.Text)
For y = 2 To (x - 1)
Select Case x
Case Is = (33), (77), (99)
MsgBox("Its not a prime number, try a different number!")
Exit Sub
End Select
If x Mod y = 0 Then
MsgBox("Its not a prime number, try a different number!")
Exit Sub
Else
MsgBox("Its a prime number, you're golden!")
Exit Sub
End If
Next
Select Case x
Case Is <= 0
MsgBox("I'm only accepting values above 0. :p")
Exit Sub
End Select
End Sub
I have this code snippet displaying a Message Box telling whether the input by the user is a prime number or not with the help of the Mod operator in visual basic.
If you go through the code carefully, you'll notice I had to separately create more or less escape statements for the number 33, 77 and 99.
I had to do this cause every time I typed either of those three numbers, I'd get the result that the number is a prime number which is incorrect since they're all divisible by numbers apart from themselves. Without getting things mixed up, the program displays other prime and non-prime numbers with the right Message Box, just those three.
I had spent hours trying to figure out what I had done wrong before I added the lines below to put myself out of vb misery, lol.
Select Case x
Case Is = (33), (77), (99)
MsgBox("Its not a prime number, try a different number!")
Exit Sub
End Select
But doing this isn't healthy if I really want to be awesome at coding. So here's my question, how do I get this program fixed to display the right message box when any integer is typed in the text box. Thanks.
Your code doesn't seem to work at all. It seems to always say that every number is a prime number. You loop through every possible divisor and in that loop you test to see if the input is evenly divisible by the current divisor. That part makes sense. The problem, though is that you always immediately exit the loop after the first iteration regardless of the result of the test. Pay close attention to this code from inside your loop:
If x Mod y = 0 Then
MsgBox("Its not a prime number, try a different number!")
Exit Sub
Else
MsgBox("Its a prime number, you're golden!")
Exit Sub
End If
If we remove all of the spurious details, you can tell that the logic is flawed:
If MyTest = True Then
Exit Sub
Else
Exit Sub
End If
As you can see, no matter what the result of the test is, it's always going to exit the loop. Therefore, the loop will never progress beyond the first iteration. In order to make it continue testing every possible divisor, you need to remove the Exit Sub statements and just keep track of the result in a variable. For instance, something like this:
Dim isPrime As Boolean = True
For y = 2 To x - 1
If XIsEvenlyDivisibleByY Then
isPrime = False
Exit For
End If
Next
If isPrime Then
' ...
Else
' ...
End If
Notice that once the first evenly divisible divisor is found, I have it Exit For. There's no point in searching any further since one is enough to make it not prime.
For what it's worth, you should use Integer.TryParse rather than Val and you should use MessageBox.Show rather than MsgBox. Val and MsgBox are old VB6 style functions. It's preferable to use the newer .NET-only versions.
here is an example of how to find prime numbers, for example up to 100, using 2 loops to check every single number in a given range. In this case I started by 2 because as you know 2 is the smallest of the prime numbers.
Dim i As Integer = 2, j As Integer = 2
For i = 2 To 100
For j = 2 To (i / j)
If i Mod j = 0 Then
Exit For
End If
Next
If (j > (i / j)) Then
Console.WriteLine("{0} is prime", i)
End If
Next

Macro to run through 3 conditions and provide value

This is my first time using VBA for Excel (I usually code Java and C++), and I was hoping to get some tips to start out.
I want to write a macro for a large data set that will proceed through the following list of conditions to provide a dollar result:
Collect unit size from column A (Possible values 0-8)
Determine whether single or family unit from Column B (Single- 1, Family- 0)
Collect utility code from Column C (code for type of product being assessed)
From this information, a new value will be placed in the row which determines utility costs by taking into account unit size, type of unit, and the product in question. I have thought about using nested Select Case or nested conditionals in a loop, but overall I am pretty lost.
It seems like a worksheet formula might do the trick, but it's hard to tell without knowing what the calculation is. Below is a user-defined function (UDF) that you would put in a standard module. You would call it from a cell like:
=computecosts(A2,B2,C2)
Obviously the code would change depending on how your data is laid out and what your calculation is.
Public Function ComputeCosts(rSize As Range, rFamily As Range, rCode As Range) As Double
Dim lSizeFactor As Long
Dim lFamilyFactor As Long
Dim dCodeFactor As Double
Dim rFound As Range
Const lFAMILY As Long = 0
'Size factor is a function of 0-8, namely adding 1
lSizeFactor = rSize.Value + 1
'Family factor is computed in code
If rFamily.Value = lFAMILY Then
lFamilyFactor = 3
Else
lFamilyFactor = 2
End If
'Code factor is looked up in a different sheet
Set rFound = Worksheets("Sheet2").Columns(1).Cells.Find(rCode.Value, , xlValues, xlWhole)
If Not rFound Is Nothing Then
dCodeFactor = rFound.Offset(0, 1).Value
End If
'do the math
ComputeCosts = lSizeFactor * lFamilyFactor * dCodeFactor
End Function
Thanks for the responses, they were helpful in understanding VBA for Excel. I just ended up putting possible values in a table and then using Match functions within an Index function to pick out the right value.

Inspecting a Word mail merge data source programmatically

I want to iterate over all rows of a MS-Word mail merge data source and extract the relevant data into an XML.
I'm currently using this code:
Imports Microsoft.Office.Interop
Do
objXW.WriteStartElement("Recipient")
Dim objDataFields As Word.MailMergeDataFields = DataSource.DataFields
For Each FieldIndex As Integer In mdictMergeFields.Keys
strValue = objDataFields.Item(FieldIndex).Value
If Not String.IsNullOrEmpty(strValue) Then
strName = mdictMergeFields(FieldIndex)
objXW.WriteElementString(strName, strValue)
End If
Next
objXW.WriteEndElement()
If DataSource.ActiveRecord = LastRecord Then
Exit Do
Else
DataSource.ActiveRecord = Word.WdMailMergeActiveRecord.wdNextDataSourceRecord
End If
Loop
And it turns out to be a little sluggish (About 1 second for each row). Is there any way to do it faster?
My fantasy is finding a function like MailMergeDataSource.ToDatatable and then inspecting the datatable.
Any time you're iterating through something row by row, and then doing some kind of processing on each row, is going to get a little slow.
I would be inclined to approach this problem by having a step before this which prepared the mdictMergeFields collection so that it only contained elements that were not 'null or empty', this will mean you won't have to check for that on each iteration. You could do this in process, or 'sneakily' in the background while the user is doing something else.
The other thing to try (might help!) is to change the "Do... Loop" block so that you're not checking at the end of each imported row whether or the record is the 'last record'. Instead, get a count of the records, and then compare the current index to the knowm maximum (which might be quicker)
I.E.:
Dim i, x as Integer
i = ActiveDocument.MailMerge.DataSource.RecordCount
Do While x < i
objXW.WriteStartElement("Recipient")
Dim objDataFields As Word.MailMergeDataFields = DataSource.DataFields
For Each FieldIndex As Integer In mdictMergeFields.Keys
strValue = objDataFields.Item(FieldIndex).Value
If Not String.IsNullOrEmpty(strValue) Then
strName = mdictMergeFields(FieldIndex)
objXW.WriteElementString(strName, strValue)
End If
Next
objXW.WriteEndElement()
x += 1
Loop
I don't really work with the Office Interop much, but hopefully this might offer some assistance! Post back, let me know how it goes.
/Richard.