Screen not updated after N iterations - vba

I run a very simple "Snake"-like game on my excel, which means I need the screen to be updated continuously, inside my main-while loop. The "snake" (a colored cell basically) is moving randomly within a 20x20 table.
It works well for several seconds, however, ay some point screen stops updating; ans excel stops responding, while still running the code. This leads to a crash.
I remplaced while loop with a For loop, which avoids crashing, but still, the screen stops updating until the loop is ended. When the loops stops, the screen is updated one last time.
I also included "Sleep" statements after each function, however, this doesn't help.
Here is the main code:
Sub main()
Dim table(20, 20) As Integer
Dim index_move As Integer
'Position class contains only X and Y parameters
Dim index_actual_head_pos As Position
Set index_actual_head_pos = New Position
Dim i As Long
index_actual_head_pos.x = 5
index_actual_head_pos.y = 15
Application.ScreenUpdating = True
For i = 1 To 50 'this remplaces the initial while loop
'next line generates a random movement, bounded by a 20x20 matrix
index_move = generer_movement(index_actual_head_pos)
'next line updates the "snake" position params
Call update_position(index_actual_head_pos, index_move)
'next line deletes previous snake and draws a new one
Call draw(index_actual_head_pos)
Sleep (200)
Next i
End Sub
Please, note that the problem is really screen updating. As stated before, I don't know how to force excel to update screen continuously
Many thanks,
Denis

You should probably abandon that Sleep(200) and let the Excel application object do some processing for the 200 milli-seconds instead.
dim dTill as double 'should be at the top with the other var declarations
dtill = Timer + 0.200
do while timer < dTill and timer > 0.200: DoEvents: loop
The timer > 0.200 is important because Timer resets at midnight and you wouldn't want it to stall everything for 24 hours if you crossed midnight during that ¹⁄₅ second.

Related

Create Millisecond Loops in Excel VBA

Since I just found out about Excel Macros, I want to try to simulate moving objects. I would like to run some looped code every 'frame' of my project. I can make an infinite loop in Excel VBA with this code:
Do While True:
'code
Loop
However, this crashes Excel. Is there a way to make an infinite loop that runs every ten milliseconds or so, something like this:
Dim timer as Timer
If timer = 10 Then
'code
timer = 0
End If
EDIT: Your answers are very good, but not exactly what I'm looking for. I want to be able to run other code at the same time; a bit like Javascript's
setInterval(function(){}, 200);
which can run multiple functions simultaneously.
You can use an API call and Sleep.
Put this at the top of your module:
Declare Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Long)
Then you can call it in a procedure like this:
Do While True:
Sleep 10 'wait 0.01 seconds
Loop
If the code is in 64bit OS, you will need to use PtrSafe. See https://support.microsoft.com/en-us/help/983043/compile-error-the-code-in-this-project-must-be-updated-for-use-on-64
Your original method is crashing Excel because it is creating an infinite loop with no exit condition.
The second method doesn't work because your system clock time (given by Timer) will never be 10, if you use debug.Print(Timer) in your Immediate Window you will see its value.
Here is some commented code to execute some actions based on a timer. Please please PLEASE make sure you retain the runtime condition for exiting the while loop, infinite loops are the devil and really you should have some other exit code in here somewhere!
Sub timeloop()
Dim start As Double: start = Timer ' Use Timer to get current time
Dim t As Double: t = start ' Set current time "t" equal to start
Dim interval As Double: interval = 1 ' Interval for loop update (seconds)
Dim nIntervals As Long: nIntervals = 0 ' Number of intervals passed
' Use this While loop to avoid an infinite duration! Only runs for "runtime" seconds
Dim runtime As Double: runtime = 10
Do While t < start + runtime
' Check if a full interval has passed
If (t - start) / interval > nIntervals Then
nIntervals = nIntervals + 1
' Do stuff here ---
Debug.Print (t)
' -----------------
End If
t = Timer ' Update current time
Loop
End Sub

Visual Basic: Image moves up and and then back down after a button click

I am attempting to make a character appear to jump straight up in the air and then come back down and return to the same level he started at. (y=100) The code below seems to make the program fight itself and move him up and down at the same time.
I have tried countless methods and all of them resulted in the guy either going up and not coming back down or flying off the page.
Private Sub btnJump_Click(sender As Object, e As EventArgs) Handles btnJump.Click
tmrJump.Start()
End Sub
Private Sub tmrJump_Tick(sender As Object, e As EventArgs) Handles tmrJump.Tick
For intCounterUp As Integer = 100 To 15
picSpaceRunner.Location = New Point(intCounterX, intCounterY)
intCounterY = intCounterUp
Next intCounterUp
For intCounterDown As Integer = 15 To 100
picSpaceRunner.Location = New Point(intCounterX, intCounterY)
intCounterY = intCounterDown
Next intCounterDown
End Sub
End Class
The code is running with no delay, so you're at the mercy of the machine.
I'm not a professional game coder, so I couldn't explain the intricacies of modern game engines. However, one of the basic ideas I learned a long time ago is to control your game/animation loop. Consider the frames per second.
In your code, it could be as simple as adding a delay within each loop iteration. If you want the character to complete his jump in 2 seconds (1 second up, 1 second down), then divide 1000 (1 sec = 1000 ms) by the number of iterations in each loop and delay by that amount. For example, you have 85 iterations, so each iteration would take approximately 12 ms.
If you don't mind blocking a thread, you can do this very easily with Threading.Thread.Sleep(12). If blocking is an issue, you'll likely want to use an external timer.
I found this link during a Google search. He explains how to set up a managed game loop in VB.Net.
http://www.vbforums.com/showthread.php?737805-Vb-Net-Managed-Game-Loop
UPDATE: Per OP's comment...
To do this using timers, you'll want to manipulate the character object directly within the Timer event handler (Tick). You wouldn't use loops at all.
Set the Timer's Interval to the value discussed earlier - the number of ms corresponding to how long it takes to move 1 pixel. Then, in the Timer's Tick handler, set the character object's Location equal to a new Point with the new value. Also in the Tick handler, check your upper bound (15), then reverse the process until it hits the lower bound (100).
For example,
Private Sub tmrJump_Tick(sender As Object, e As EventArgs) Handles tmrJump.Tick
If (intCounterY > 15 And blnGoingUp == True) Then
picSpaceRunner.Location = new Point(intCounterX, intCounterY - 1);
End If
... Remaining Code Goes Here ...
End Sub
Do not put the loop in the timer_tick. Increase or decrease the height by set interval instead and then check if the image had reached the maximum or minimum height.

Code Execution Sequence

I am a PLC programmer who is currently using a variant of VB to control a motor.
I want to call a function that will execute moves and not return to the main code until the move has been completed. Currently here is what I have:
Program 'Main Program
While 1
If move_req = 1
Function MoveMotor
End If
Wend
End Program
Function MoveMotor
MoveABS 10 ' Move to encoder position 10mm
move_complete = 1
While move_req = 1
'Do Nothing
Wend
End Function
For some reason this code isn't working and the move command is being sent over and over again. Could this be because the main program continues to run when the function is running? Is that how VB works? I am used to thinking of code sequence in terms of PLC's where they scan through everything repeatedly at a certain frequency.
Whenever the move is complete, there must be a way for it to be detected by the program. It looks like you want move_req to be set to zero when this happens, but I cannot see what would cause that. How does the machine signal the program that it's finished moving?
A second point is that when you have a loop that waits while it checks for variable change, it can cause a CPU spike. You can put a pause in the loop with some thing like System.Threading.Thread.Sleep(100) where 100 is milliseconds to pause.

Label.Text doesn't change in a loop

I'm trying to change a text that a label displays during each iteration of a Do While loop in Visual Basic. One label (which displays an integer) works fine, however the other stays blank until the loop finishes and displays the final result. What could be the problem?
Private Sub btnCalc_Click(ByVal sender As System.Object, ByVal e As System.EventArgs)
Handles btnOblicz.Click
Dim W As Single
Dim L As Integer
Dim J As Integer
Dim Y As Double
W = Val(txtW.Text)
L = InputBox("Input L")
J = 0
If W > 0 And W < 100 Then
Do
Y = (2 * Math.Sqrt(W)) / (L - J)
J = J + 1
lblJ.Text = Str(J)
lblY.Text = Str(Y)
MsgBox("Next...")
Loop Until Y < 0
Else
MsgBox("No calculations, because the number is less than zero or greater than a hundred.")
End If
End Sub
Application.DoEvents(), as suggested by others, can work. However, you should be aware that it has some negative side-effects, such as the potential to create StackOverflowExceptions.
The right way to solve this problem is to use a BackgroundWorker component.
As to why this happens... remember that all windows programs work by having, at their core, a loop which checks for messages from the user and operating system (things like mousemove events, clicks, keystrokes, etc). When you set the text property of a label, you are not telling the label to re-draw itself on the screen. Instead, you are posting an event to the operating system that your program's message loop must then receive and process. As new events come in, the message loop (or pump) sends those events to the proper method.
Your btnCalc_Click() function is one such method. If your function is running, it was called by the core windows messaging loop, which is now waiting for your method to complete and return control: it's blocked. The loop cannot continue receiving and dispatching methods until your function completes, and therefore nothing in your program's interface can be updated.
This worked fine for me in small copy I did using VS2010
It may be that a message pump is required. Try this :
System.Windows.Forms.Application.DoEvents()
Probably, your window function which processes OS event message is inaccesible while your loop executes. Try explicitly call dispatching messages. E.g. insert after assigning labels text inside your loop
Application.DoEvents

Constantly updating "Status Form" locks over time

This is my first time using VBA in Outlook so please bear with me
I've created a basic Macro that does various things to folders. Since this takes a while I decided to make a status window that says what its currently doing. I simply keep setting one of the label's values with this
Function UpdateStatus(Message As String)
StatusForm.StatusUpdate.Caption = Message
StatusForm.Repaint
End Function
The issue is that after it runs for a bit (5-15 seconds) the window and the rest of outlook locks; the form no longer updates and has a "(Not Responding)" in its window title.
I feel like I'm somehow dead locking the UI thread but I'm at a loss on how to work around it. Commenting out Repaint not surprisingly doesn't let it update at all, but outside of that I don't know where to look
Any suggestions?
Try adding DoEvents in your computationally intensive loop. This yields the executing code to the UI thread so that other things can get done when you have computationally intensive stuff going on in the background. Office is single-threaded, so you can block the UI when you are running macros.
Sample:
Sub LockUI()
Dim x
x = Timer
Do While Timer - x < 5
'Blocks the UI for 5 seconds
Loop
End Sub
Sub LockUI2()
Dim x
x = Timer
Do While Timer - x < 5
'Doesn't block the UI
DoEvents
Loop
End Sub