I'm using Word 2013 to generate some objects and also delete them before a new generation. But sometimes Word crashes in such case, that all the VBA code is dropped. Here is the code for adding the generated objects:
For i = 1 To nodes
Set arrShapes(j) = docNew.Shapes.AddShape(MsoAutoShapeType.msoShapeDiamond, arrRawPoints(i, 1) - 2, arrRawPoints(i, 2) - 2, 4, 4)
arrShapes(j).title = "A" + Str(j) + "d"
arrShapes(j).Fill.ForeColor.RGB = RGB(255, 0, 0)
j = j + 1
Next i
The deleting code is as follows:
For Each sp In arrShapes
If Not (sp Is Nothing or IsEmpty(sp)) Then
tl = Left(sp.title, 1)
If tl = "A" Then
tl = Mid(sp.title, 2, Len(sp.title) - 2)
nr = Int(tl)
sp.Delete
Set arrShapes(nr) = Nothing
End If
End If
Next sp
Sometimes there occurs a crash, but if I call this routine 50 times or more, it runs perfectly. It happens that a user deletes such a generated object by hand, then I've got a crash. To find the reason, I've set a breakpoint on the 1st line within the For Each loop, but then Word crashes every time. What's wrong in this concept?
I think solution is quite obvious- when deleting you need to loop from last item to first therefore you need to switch into different kind of loop. Your (second) code could looks as follows:
For i=arrShapes.Count to 1 step -1
'you dont need if statement here
tl = Left(arrShapes.Item(i).Title, 1)
if tl = "A" then
tl = Mid(arrShapes.Item(i).Title, 2, Len(arrShapes.Item(i).Title) - 2)
'rather dont' need it any more: nr = Int(tl)
arrShapes.Item(i).Delete
'rather dont' need it any more: Set arrShapes(nr) = Nothing
end if
Next i
Not tested! Some adjustment can be required as not everything is clear in your code.
Related
I wrote an automation script that uses the following SAP GUI:
objSess.findById("wnd[0]").Maximize
objSess.findById("wnd[0]/tbar[0]/okcd").Text = "flqaf"
objSess.findById("wnd[0]").sendVKey 0
objSess.findById("wnd[0]").sendVKey 17
objSess.findById("wnd[1]/usr/cntlALV_CONTAINER_1/shellcont/shell").CurrentCellColumn = "TEXT"
objSess.findById("wnd[1]/usr/cntlALV_CONTAINER_1/shellcont/shell").SelectedRows = "0"
objSess.findById("wnd[1]/usr/cntlALV_CONTAINER_1/shellcont/shell").DoubleClickCurrentCell
objSess.findById("wnd[0]/usr/ctxtS_BELNR-LOW").Text = W_BPNumber
objSess.findById("wnd[0]/usr/ctxtS_BELNR-LOW").SetFocus
objSess.findById("wnd[0]/usr/ctxtS_BELNR-LOW").caretPosition = 10
objSess.findById("wnd[0]").sendVKey 8
objSess.findById("wnd[0]").sendVKey 0
objSess.findById("wnd[0]/tbar[0]/btn[3]").press
Everything works fine, however sometimes the problem aborts in SAP and exactly when that happens I want to capture it by writing the term "error" into an excel cell.
I tried adding this line in vba
objSheet.Cells(iRow, 5) = GuiStatusbar.Text
As well as
objSheet.Cells(iRow, 5) = objSessFindById("wnd[0]/sbar").Text
The code still runs fine but my cells in column 5 remain empty. Any ideas how to solve this?
It might help if you wait a little.
fo example:
...
objSess.findById("wnd[0]").sendVKey 0
waitTill = Now() + TimeValue("00:00:01")
While Now() < waitTill
DoEvents
Wend
if objSess.findById("wnd[0]/sbar").messageType = "E" then 'other types: W, I, S, A
objSheet.Cells(iRow, 5) = objSessFindById("wnd[0]/sbar").Text
end if
objSess.findById("wnd[0]/tbar[0]/btn[3]").press
...
Regards, ScriptMan
I'm trying to write a macro using VBA that will ask a given number of simple arithmetic questions, with each question being available for answer only for a specified amount of time.
I wish to specify somewhere in the worksheet where I can set the parameters of the game (e.g. 20 questions with 12 seconds to answer each) and then click a go button that will continually throw up input boxes every 12 seconds (and remove the previous one at the end of the 12 seconds OR when the person clicks "next" or "ok") until the 20 question quota has been reached, all the while recording the answers and time taken.
I'm quite new to VBA but have made a lot of headway using mostly this site.
So far I have the following which allows me to generate the questions and record the answers:
Sub Rand_Arith()
Dim practice As Workbook
Dim answers As Worksheet
Dim start As Worksheet
Set practice = ActiveWorkbook
Set answers = Sheets("answers")
Set start = Sheets("Start")
Dim x As Variant
Dim a As Variant
Dim b As Variant
Dim answer As Variant
Dim myanswer As Variant
Dim i As Integer
Dim correct As Integer
Dim qs As Integer 'the number of questions we want
Dim spq As Integer 'time in seconds per question
Dim totaltime As Integer
Dim operator As String
qs = start.Cells(3, 3)
spq = start.Cells(2, 3)
totaltime = spq * qs
correct = 0
If answers.Range("A1").Value = "" Then
i = answers.Range("A2").End(xlDown).Row
Else
i = 1
End If
Do Until i = qs + 1
x = Rnd
op = Int(x * 4) '0+ 1- 2* 3/
If x < 0.25 Then
a = Round(Rnd * 10, 2)
b = Round(Rnd * 10, 2)
theanswer = Round(calc(a, b, op), 2)
ElseIf x < 0.5 Then
a = Round(Rnd * 10, 2)
b = Round(Rnd * 10, 2)
theanswer = Round(calc(a, b, op), 2)
ElseIf x < 0.75 Then
a = Round(Rnd * 10, 0)
b = Round(Rnd * 10, 0)
theanswer = Round(calc(a, b, op), 1)
Else:
a = Round(Rnd * 10, 0)
b = Round(Rnd * 10, 0)
theanswer = Round(calc(a, b, op), 1)
End If
theanswer = Round(calc(a, b, op), 2)
operator = s_op(op)
myanswer = Application.InputBox("What is " & a & operator & b & " = ?")
If theanswer = myanswer Then
correct = correct + 1
End If
runningscore = correct / i
answers.Cells(i + 1, 1) = i
answers.Cells(i + 1, 2) = a
answers.Cells(i + 1, 3) = operator
answers.Cells(i + 1, 4) = b
answers.Cells(i + 1, 5) = theanswer
answers.Cells(i + 1, 6) = myanswer
answers.Cells(i + 1, 7) = runningscore
i = i + 1
Loop
End Sub
These are the function's i've made calls to:
Function calc(n1, n2, op)
If op = 0 Then calc = n1 + n2
If op = 1 Then calc = n1 - n2
If op = 2 Then calc = n1 * n2
If op = 3 Then calc = n1 / n2
End Function
Function s_op(op)
If op = 0 Then s_op = "+"
If op = 1 Then s_op = "-"
If op = 2 Then s_op = "*"
If op = 3 Then s_op = "/"
End Function
One problem I am having is that the answer that i put into the input box returns a string which, even when the values are the same, doesn't seem to register as the same as the actual answer calculated by theanswer. As a result the correct and runnningscore variables remain as 0 throughout.
I can't work out how to do the timing part. I've experimented with the following countdown timer which I've taken more or less word for word from this video: https://www.youtube.com/watch?v=sbJeGG_Xv8M
First piece of code:
Sub starttimer()
Application.OnTime Now + TimeValue("00:00:01"), "nexttick"
End Sub
Second Piece
Sub nexttick()
Dim practice As Workbook
Dim start As Worksheet
Set practice = ActiveWorkbook
Set start = Sheets("Start")
If start.Range("c4").Value = 0 Then Exit Sub
start.Range("c4").Value = start.Range("c4").Value - TimeValue("00:00:01")
If start.Range("c4").Value <= TimeValue("00:00:05") Then
start.Shapes("TextBox 3").Fill.ForeColor.RGB = RGB(255, 0, 0)
ElseIf start.Range("c4").Value <= TimeValue("00:00:10") Then
start.Shapes("TextBox 3").Fill.ForeColor.RGB = RGB(0, 0, 255)
ElseIf start.Range("c4").Value <= TimeValue("00:00:15") Then
start.Shapes("TextBox 3").Fill.ForeColor.RGB = RGB(0, 255, 0)
End If
starttimer
End Sub
Lastly:
Sub stoptimer()
On Error Resume Next
Application.OnTime Now + TimeValue("00:00:01"), "nexttick", , False
End Sub
I've not had much success here. I feel as though the countdown macros do not allow for another macro to be run alongside it?
I've also been trying to work with the Application.Wait and Application.OnTime but can't really workout how to combine the question generator I've already made with something that does even something remotely like what what I want to do.
I'm worried that I've dived into this and perhaps its not possible to do what I want with VBA (or that it is actually very difficult).
In any case, and help/constructive criticism would be great!
Furthermore, any more general opinion/advice on my approach to the problem/technique would be greatly appreciated! As mentioned, I'm only just starting out and have no experience in other languages either.
Denis,
I'm going to give you some general advise about how to complete your project, then I'll try to answer some of the specific problems you're having. Before I do that, rest assured that VBA is more than capable accomplishing your task, and, based on the code you've presented you're not in over your head.
General Advice
What you really need here Denis is a UserForm. In your IDE (the place where your code is) go to Insert > UserForm. If you cannot already see it, go to View > Toolbox. There you will see a number of controls (like buttons and text boxes) that you can drag on to your form. You can edit the code behind each control by double clicking it. So, for instance, double clicking the button control will allow you to write code that executes when the user clicks the button.
I'll leave you to figure out the rest of the controls, but using a form will give you the type of control over user interactions that I think you're after. Is it possible to do it without a Userform? Sure, but I wouldn't recommend it.
General Layout
Years ago I wrote a project very similar to yours. I set it up with a Textbox (or label) for the question, a Textbox for the user's answer, a Submit button, and a Cancel button to quit. Clicking the submit button performed the calculations and also triggered the next question.
Timing
VBA has a built in DateDiff function:
DateDiff("s", "06/27/2016 16:42:51", Now())
The first parameter, "s", denotes the unit of time. In this case seconds. In my project the only time I was interested in was the time it took the user to answer the question. For that I stored Now() in a variable at the time the question was presented and compared that to the time when the user clicked Submit using the DateDiff function. You could use it just as easily in your case to limit the duration of the quiz.
Integer Comparisons
VBA is what we call a loosely typed language, meaning that more often than not the interpreter will figure out for you whether your variables are strings or integers, or something else. That being said, some things to keep in mind.
1) It's good practice and safer to declare your variables: Dim theanswer as Integer or Dim theanswer as Double , which ever the case. Often, that will help eliminate the possibility that VBA is treating theanswer as a string and myanswer as an integer and trying to compare the two.
2) If you're pulling values from a cell in your worksheet to compare, make sure and pull Cells(x,y).value.
3) Make sure to use Debug.Print from time to time to check that the values being compared are actually what you expected. For example, Debug.print theanswer. The output from that statement can be seen in the "Immediate" window which can be enabled from the View menu.
Hopeful this will help give you some guidance with the rest of your project. I'll keep an eye on this post, so feel free to post follow up questions as they come to you. Good luck!
I am quite new to programming and VBA but I have been using a lot of help questions and answers here on stack overflow to learn! I am attempting to create a loop that will determine if a value from a "task" sheet is missing from a "preview" sheet and if so to add the number and line to the "preview" sheet from the "task" sheet. For an example of the data on each sheet:
Task Sheet
TASK VALUE description
11 task 1
12 task 2
13 task 3
Preview Sheet
PREVIEW VALUE Description
1111 preview 1
2222 preview 2
11 task 1
3333 preview 3
13 task 3
The aim is to compare each number in the preview sheet to each value in the task sheet. If a value in the task sheet is not found in the preview sheet, then it should add that value and the entire line into the preview sheet from the task sheet.
My main approach was to create a for loop that would compare each number on the "preview" sheet to each value on the "task" sheet. If it found that two values matched, it would set the variable newTask = False and then exit the nested for loop to move on to the next comparison. If it found that the value from the data sheet was not present in the main sheet, it would set newTask = True and run through until there was no more values to compare. Then, if newTask = True, it would copy and paste the value and line from the data sheet into the main sheet. This is my attempt at coding this:
Dim newTask As Boolean
iP = (Worksheets("parents").Columns("a").Cells.SpecialCells(xlCellTypeConstants).Count) - 1 'count of parent workorders
iC = (Worksheets("child").Columns("a").Cells.SpecialCells(xlCellTypeConstants).Count) - 1 ' count of child workorders
iT = (Worksheets("task").Columns("a").Cells.SpecialCells(xlCellTypeConstants).Count) - 1 ' count of task workorders
iPr = (Worksheets("preview").Columns("a").Cells.SpecialCells(xlCellTypeConstants).Count) + 1 ' this will set iPr one row below the last row on the preview page
nT = 0
Set prRng = Sheets("Preview").Range(Sheets("Preview").Cells(iPr, 1), Sheets("Preview").Cells(iPr, 11))
For n = 1 To iPr
taskWO = Worksheets("task").Cells(n + 1, 1).Value
For nT = 1 To iT
previewWO = Worksheets("preview").Cells(nT + 1, 1).Value
If previewWO = taskWO Then
newTask = False
Exit For
ElseIf previewWO <> taskWO Then
newTask = True
End If
Next nT
If newTask = True Then
Set tRng = Sheets("task").Range(Sheets("task").Cells(n + 1, 1), Sheets("task").Cells(n + 1, 11))
Sheets("Preview").Range(Sheets("Preview").Cells(iPr, 1), Sheets("Preview").Cells(iPr, 11)) = tRng.Value
Sheets("Preview").Cells(iPr, 12) = Sheets("task").Cells(n + 1, 13).Value
iPr = (Worksheets("preview").Columns("a").Cells.SpecialCells(xlCellTypeConstants).Count) + 1
End If
Next n
However, when I run this code, it keeps newTask = True, even if the values do match across the preview and task sheets. Debugging confirmed this and if it comes across similar values, it just continues over it as if they do not equal each other. This causes all values from the task sheet to copy over to the preview sheet, adding many duplicates. I also attempted a Do Until loop but get the same results: Here is my attempt at that:
Dim newTask As Boolean
iP = (Worksheets("parents").Columns("a").Cells.SpecialCells(xlCellTypeConstants).Count) - 1 'count of parent workorders
iC = (Worksheets("child").Columns("a").Cells.SpecialCells(xlCellTypeConstants).Count) - 1 ' count of child workorders
iT = (Worksheets("task").Columns("a").Cells.SpecialCells(xlCellTypeConstants).Count) - 1 ' count of task workorders
iPr = (Worksheets("preview").Columns("a").Cells.SpecialCells(xlCellTypeConstants).Count) + 1 ' this will set iPr one row below the last row on the preview page
nT = 0
taskWO = Worksheets("task").Cells(n + 1, 1).Value
Set prRng = Sheets("Preview").Range(Sheets("Preview").Cells(iPr, 1), Sheets("Preview").Cells(iPr, 11))
For n = 1 To iPr
taskWO = Worksheets("task").Cells(n + 1, 1).Value
Do Until taskWO = previewWO Or nT = iT
previewWO = Worksheets("preview").Cells(nT + 1, 1).Value
nT = nT + 1
If nT = iT Then
Set tRng = Sheets("task").Range(Sheets("task").Cells(n + 1, 1), Sheets("task").Cells(n + 1, 11))
Sheets("Preview").Range(Sheets("Preview").Cells(iPr, 1), Sheets("Preview").Cells(iPr, 11)) = tRng.Value
Sheets("Preview").Cells(iPr, 12) = Sheets("task").Cells(n + 1, 13).Value
iPr = (Worksheets("preview").Columns("a").Cells.SpecialCells(xlCellTypeConstants).Count) + 1
End If
Loop
Next n
I have done a lot of searching but I cannot find any way to get this to stop duplicating values... but I apologize if I missed a thread that has this information or help. I feel like it is something simple but I just cannot figure it out. Could I please get some help on how to correct this? If this is not the correct method, could you also please mention why the loop does not work or what went wrong so I can know in the future? Thank you for any help you provide! If you need some more information, please let me know.
Kyle, without being a complete re-work of your code, here's some code that I think might help you out. If I understand your problem correctly, the main issue you're having is that you're unable to properly determine if a value in one range exists in another. When I have this issue, I usually will use a dictionary object because it's fast, and provides an easy way to check if a value is in a list. To use a dictionary, however, you'll likely have to add a reference to it first. To do this, go to the tools menu and select "References". Scroll down until you've found "Microsoft Scripting Runtime" and select that item. After that, the following code should run just fine.
Sub Testq()
Set Dict = New Dictionary
For Each Cel In Worksheets("preview").Columns(1).SpecialCells(xlCellTypeConstants)
'Add the "Preview Values" of all the cells into your dictionary as Keys.
'Set the value of each key to the "Description" which is in the row next to it.
Dict(Trim(Cel.Value)) = Trim(Cells(Cel.Row(), 2))
Next
'Lets add in the header row of the task worksheet to prevent it from getting coppied over.
Dict(Worksheets("task").Cells(1, 1).Value) = Worksheets("task").Cells(1, 2).Value
'Now loop through all of the values in your "Task" table, checking them against the Dictionary
'to see if there are any new ones.
For Each Cel In Worksheets("Task").Columns(1).SpecialCells(xlCellTypeConstants)
If Not Dict.Exists(Trim(Cel.Value)) Then 'We have a new value.
TaskValue = Trim(Cel.Value)
Description = Trim(Worksheets("Task").Cells(Cel.Row(), 2))
Debug.Print "Yup, I found one that's missing: " & Trim(TaskValue)
'Now add the missing value to the end of your "preview" sheet.
LastRow = Worksheets("preview").Cells(Range("A:A").Rows.Count, "A").End(xlUp).Row
Worksheets("preview").Cells(LastRow + 1, 1) = Trim(TaskValue)
Worksheets("preview").Cells(LastRow + 1, 2) = Trim(Description)
End If
Next
End Sub
Since you mentioned that you're new to VBA, I'll point out that to view the output of the Debug.Print statement, you'll need to display the "Immediate" window. Do this by selecting it from the View menu. As I get a clearer picture of your project, I'll supplement this answer as needed, but for now I hope this will help you solve much of the problem you're having.
I don't have much VBA experience at all, but this is what I'm trying to do (code is below):
I have two sheets- one of dies, one of sales. Each sale has a die it comes from, with a many sales to one die relationship. I'd like to loop through all dies, and within that loop loop through all sales, and compare each of the rows to a set of criteria before outputting them.
'All dies have a type and a size. All products have a type and a size. We hope to match them.
Sub searchroute()
Dim x As Integer, y As Integer, z As Integer
x = 0 'for row offset on dies, number
y = 0 'for row offset on sales, item
z = 0 'for later use
Do Until IsEmpty(Worksheets("Dies").a2.Offset(x, 0)) = True
Do Until IsEmpty(Worksheets("Sales").a2.Offset(y, 0)) = True
If Worksheets("Dies").i2.Offset(x, 0) = Worksheets("Sales").c2.Offset(y, 0) Or Worksheets("Dies").i2.Offset(x, 0) = "Any" Then
If Worksheets("Sales").g2.Offset(y, 0) = Worksheets("Dies").j2.Offset(x, 0) Or Worksheets("Dies").j2.Offset(x, 0) = "Any" Then
'then we've got the same type and size, print output to a cell
'should figure out how to append, place the whole list in a single cell
End If
End If
y = y + 1
Loop
x = x + 1
Loop
End Sub
Stepping through this, it pops a 438 error on the first Do Until. I know this is something easy, but my mind is blank.
Thanks in advance for wisdom!
Replace Worksheets("Dies").a2. with Worksheets("Dies").Range("a2").
Same goes for all instances where you are trying to refer to a spcific cell.
I'm attempting to graph lnD and i, where i is the x-axis, and lnD is the y-axis. I have an equation that I'm putting a range of values for i into, and attempting to retrieve values of lnD.
However, I came across an odd issue. First off, here is the code. I should note that it causes my Excel to freeze up for a few seconds, but it doesn't crash or anything.:
Tracker = 0
Alpha = -1.593975
Beta = -334.6942
For i = 0 To 0.1 Step 0.01
Tracker = Tracker + 1
lnD = Beta * i + Alpha
Range("XFB" & Tracker).Value = i
Range("XFC" & Tracker).Value = lnD
Next i
I get the error "Method 'Range' of object '_Global' failed". And when I look at the columns where the data should be, it is just i = 0 and lnD = -1.593975, repeating over and over again. When I look at the value of Tracker, it has increased into the 10 thousands, and since all of the columns are full to the bottom of excel, that means the loop is actually looping. But why is i getting stuck at zero, and not increasing? Why am I getting this error?
EDIT: I should note that if you change the top line to For i = 0 to 10 step 1, it works... So does this have to do with the numbers I'm putting in?
EDIT 2: So, after getting advice that it's an error not present in the code I put here, I looked into my variable declarations. The issue ended up being that I declared i as an integer! That made it round down to zero, causing the loop to get stuck at i = 0, and never making it to a "stopping point". Just a silly mistake!
I'm not going to delete this post, only because I feel like I should put my stupidity on display. Thank you for helping, everyone!
Your code does work on my PC (after changing the columns to "A" and "B" as I'm working in Excel 2010 and my columsn don't run up all the way to where you're writing to). Your comments seem to be indicating that your actually looping over a lot more values for i than you're stating ("Tracker is in the 10,000s while i only goes through 10 steps), can something there cause a problem?
As an aside, writing single cells values to excel is very unlikely to be efficient. A lot quicker will be writing everything into an array and then writing the array to excel.
What works on my excel is (please note that I change the start of the output to "A1", be careful you don't overwrite any data):
Sub test()
' Parameters
Dim Alpha#: Alpha = -1.593975
Dim Beta#: Beta = -334.6942
Dim nmbOfSteps&: nmbOfSteps = 11&
Dim increment#: increment = 0.01
Dim startValue#: startValue = 0#
' Fill in values in an array
Dim result() As Double, cntr&
ReDim result(1 To nmbOfSteps, 1 To 2)
For cntr = 1 To nmbOfSteps
Dim iValue#: iValue = startValue + CDbl(cntr - 1&) * increment
result(cntr, 1) = iValue
result(cntr, 2) = Alpha + Beta * iValue
Next cntr
' Write the entire array in one go
ThisWorkbook.ActiveSheet.Range("A1").Resize(nmbOfSteps, 2).Value2 = result
End Sub
Try the following. Rather than moving the target range, I always find it better to fix on the top of my trget and use offset to populate the cells bwllow and to the right.
Sub testit()
Dim StartCell As Range
tracker = 0
Alpha = -1.593975
Beta = -334.6942
Set StartCell = ActiveSheet.Range("XFB1")
For i = 0 To 0.1 Step 0.01
lnD = Beta * i + Alpha
StartCell.Offset(tracker, 0).Value = i
StartCell.Offset(tracker, 1).Value = lnD
tracker = tracker + 1
Next i
End Sub