Passing variable from module to user form - vba

I've searched a lot of pages and don't have a clear answer to this.
I have VBA code which does a lot of processing of Word revisions and I'd like to display a userform to inform the user of what's happening.
I track two variables (TotalRevisionCount and RevisionIndex) which count the total number of revisions being processed, and the individual revision number respectively. I've declared these as public variables in my main module.
I'd like my userform to display "Processing revision 2 of 36". I think the code to do this is really simple but I just can't join the dots.
Help?
***UPDATE
This is a cut down version of the calling code:
Sub TestSub()
updateTotal = 10000
PlsWaitForm.Show (False)
For i = 1 To 10000
UpdateNum = i
PlsWaitForm.Repaint
Next i
Unload PlsWaitForm
End Sub
...and this is what I have in my userform:
Sub DisplayStuff()
PlsWaitText.Caption = "Currently processing " & UpdateNum & " of " UpdateTotal & " records."
End Sub

Your code is not calling DisplayStuff in the loop, if you step through your code you would notice that the code did not reach DisplayStuff at all and thus, unable to update the Caption.
Code in Module1:
Public UpdateTotal As Long
Public UpdateNum As Long
Sub TestSub()
UpdateTotal = 10000
PlsWaitForm.Show (False)
For i = 1 To 10000
UpdateNum = i
PlsWaitForm.DisplayStuff
Next i
Unload PlsWaitForm
End Sub
Code in PlsWaitForm:
Public Sub DisplayStuff()
Me.Caption = "Currently processing " & UpdateNum & " of " & UpdateTotal & " records."
End Sub
Note: To reiterate what I said in my comment, if all you want to display is this message to your user of its progress, you can consider using Application.StatusBar instead.

On the form, implement a function to update the displayed information on the form, like this:
Public Function UpdateDisplayInfo()
labCurrentSelectedCell.Caption = Sheet1.selectedCellAddress
If Not Me.Visible Then
Me.Show False
End If
DoEvents
End Function
In code doing the processing, insert a call to UserFormName.UpdateDisplayInfo() at a convenient point in the processing loop, like this:
Public selectedCellAddress as string
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
Me.selectedCellAddress = Target.Address
UserForm1.UpdateDisplayInfo
End Sub

The missing piece was the RePaint statement. So the final module code looks like this:
Sub TestSub()
updateTotal = 100000
PlsWaitForm.Show (False)
For i = 1 To 1000000
UpdateNum = i
PlsWaitForm.DisplayStuff
Next i
Unload PlsWaitForm
End Sub
...and the final form code looks like this:
Public Sub DisplayStuff()
PlsWaitForm.PlsWaitText.Caption = "Currently processing " & UpdateNum & " of " & updateTotal & " records."
PlsWaitForm.Repaint
End Sub
Thanks for all your help and patience, everyone.

Related

Finding out if a one of several forms are open in MS Access

I need to iterate through a list of forms to see if they are open and then do something with them if they are. The following works:
Public Sub isloadedtester()
Dim iForm As Variant
For Each iForm In CurrentProject.AllForms
Debug.Print iForm.name & ": " & CurrentProject.AllForms(iForm.name).IsLoaded
Next
End Sub
but it loops through all forms. So I thought the following should work to loop through only the forms I care about:
Public Sub isloadedtester2()
Dim iForm As Variant
Dim list
list = Array(Form_some, Form_another)
For Each iForm In list
Debug.Print iForm.name
Debug.Print ".isloaded: " & CurrentProject.AllForms(iForm.name).IsLoaded
Debug.Print "direct: " & SysCmd(acSysCmdGetObjectState, acForm, iForm.name)
Debug.Print "by fn: " & IsLoaded(iForm.name)
Next
End Sub
Public Function IsLoaded(FormName As String, Optional aType As AcObjectType = acForm)
IsLoaded = (SysCmd(acSysCmdGetObjectState, aType, FormName) <> 0)
End Function
However, the second version always sees forms as loaded, no matter which of several ways to read loaded state I use:
.isloaded: True
direct: 1
by fn: True
It's almost like assigning a form to an array makes VBA load the form.
Should I use something other than an array for this? I realize I could do this as an array of names that I also loop through to see if one fits, but that seems awkward, so I first wanted to see if I am just doing something slightly different than it needs to be.
If you only want open forms, then use the Forms collection. It includes only open forms
Dim intFrm As Integer
If Forms.Count > 0 Then
For intFrm = 0 To Forms.Count - 1
'Debug.Print Forms(intFrm).NAME
If Forms(intFrm).Name IsInYourList Then
DoSomething
End IF
Next intFrm
End If
If you have a list of form names, you can do it the "old-fashioned" way with SysCmd
Public Function IsLoaded(FName As String, Optional aType As AcObjectType = acForm)
IsLoaded = (SysCmd(acSysCmdGetObjectState, aType, FName) <> 0)
End Function
and
For Each iForm In list
Debug.Print iForm.Name & ": " & IsLoaded(iForm.Name)
Next

PowerPoint How to Run Macro and Hyperlink at The Same Time?

I'm trying to make a choose your own type of game in powerpoint. The game includes a point system that I did using a macro when the user is clicks an option. I also need a hyperlink to jump to the right slide in accordance to the option that was picked.
I tried creating 1 macro for every button, assigning where to slide to jump to, but this becomes solution way too messy since, by the end of the game, there would be hundreds of buttons = hundreds of macro.
Dim numCorrect As Integer
Dim numIncorrect As Integer
Dim userName As String
Dim printableSlideNum As Long 'ADDED
Sub GetStarted()
Initialize
ActivePresentation.SlideShowWindow.View.Next
End Sub
Sub Initialize()
numCorrect = 0
numIncorrect = 0
ActivePresentation.SlideShowWindow.View.Next
End Sub
Sub Right()
numCorrect = numCorrect + 10
End Sub
Sub Wrong()
numIncorrect = numIncorrect + 25
End Sub
Sub Feedback()
MsgBox "You spent " & numCorrect + numIncorrect & " minutes to solve the issue" & vbCr & _
"Out of the " & numCorrect + numIncorrect & ", you wasted " & numIncorrect & " minutes by choosing the wrong options"
End Sub
Sub JumpTo(lSlideIndex As Long)
SlideShowWindows(1).View.GotoSlide (lSlideIndex)
End Sub
Sub ONEA()
Right
JumpTo 6
End Sub
I had an idea of creating a macro that jumped to a slide number according to the name of the shape, but don't know whether it is possible or not.
Example: option B jumps to slide 60
the shape name would be 60
the vba will match the shape name and jumps to slide 60
Any help/ideas for this is appreciated!
Note: my VBA skills is essentially zero.
To name your shape see How to name an object within a PowerPoint slide?
Sub JumpToSlide(oShp As Shape)
lSlideIndex = CLng(oShp.Name)
SlideShowWindows(1).View.GotoSlide (lSlideIndex)
End Sub
To assign this macro to the shape you can right-click on the shape --> Hyperlink --> Run a Macro --> Select "JumpToSlide"

How do I show a progress bar whilst my sub routine is running?

I am trying to work out how to get my progress bar to show the progress of the subroutine below. Does anyone have any ideas how to get the ball rolling on this one?
Any help or advice is appreciated.
Private Sub CommandButton8_Click()
Dim StartTime As Double
Dim MinutesElapsed As String
'Remember time when macro starts
StartTime = Timer
'*****************************
'Direct Data
Sheet4.Activate
Call Test1
Call Test2
Call Test3
Call Test4
'Return to Welcome Page
Sheet8.Activate
'*****************************
'Determine how many seconds the code took to run
MinutesElapsed = Format((Timer - StartTime) / 86400, "hh:mm:ss")
'Notify user in seconds
MsgBox "This code ran successfully in " & MinutesElapsed & " minutes",
vbInformation
End Sub
Here's what I use for a progress bar:
Application.StatusBar = "Processing... " & CInt(nRow / nLastRow * 100) & "% " & String(CInt(nRow / nLastRow * 100), ChrW(9609))
DoEvents ' optional
nRow is the increment number
nLastRow is the value for 100% complete
When the loop is complete, reset it like this:
Application.StatusBar = ""
The logic behind a progressbar is this:
have a userform with a Label (or image or button) with the image of your example photo. Put it in a frame. The width of the frame is initialised to zero and grows as your number grows by either calling a Public Sub inside the userform, of directly Userform1.Frame1.width=Percent*MaxWidth/100.
Sometime you would want to add a doevents, wich i use only every X cycles (if clng(percent) mod X = 0 then doevents , for example).
Step 1: design a progress dialog box. For the following example, it must have the name frmProgress and have two text labels lblMsg1 and lblMsg2
Step 2: insert the following code in the dialog's code module:
Sub ShowProgressBox()
Me.Show vbModeless
End Sub
Sub SetProgressMsg(msg1, msg2)
If (msg1 <> "") Then Me.lblMsg1 = msg1
If (msg2 <> "") Then Me.lblMsg2 = msg2
DoEvents
End Sub
Sub SetProgressTitle(title)
Me.Caption = title
DoEvents
End Sub
Sub EndProgressBox()
Unload Me
End Sub
You can call these functions from your code to show progress messages.
Step 3: in your code, at the beginning of the lengthy operation, call
frmProgress.ShowProgressBox
Step 4: during the lengthy operation, regularly call the set function to show the user information, for example in a loop:
frmProgress.SetProgressMsg "Searching in "+ myArray(i), "Working..."
Step 5: Once done, close the progress box:
frmProgress.EndProgressBox

Application.OnTime stops firing after changing the active document

I am trying to implement an autosave function for all documents created from a certain template. In this template I have created the following for tests:
Dim doc As Document
Dim count As Integer
Private Sub Document_Open()
count = 1
Set doc = ActiveDocument
SaveTime
End Sub
Sub SaveTime()
Application.OnTime When:=Now + TimeValue("00:00:15"), _
name:="DoSave"
End Sub
Sub DoSave()
doc.SaveAs2 "c:\test\testsave" & count & ".docx"
count = count + 1
SaveTime
End Sub
Now if I open a document thats created by this template the autosaving works every 15 seconds as intended (15sec + the counter with different name is just for testing).
BUT as soon as I create a new document in Word or open another file the autosaving in the first document stops working and also doesnt come back if I continue to work in the document.
How can I make the autosave work no matter of which document is active? Like this the feature would only work if only one Document is open at a time, which I can not garantuee of course.
Shot in the dark here, but you are dimming doc within the scope of the module (from what I can tell). It is possible that opening the new document causes this to go out of scope. It would be best to just pass the document between routines.
Something like this:
' This will declare count within the scope of the module
Private count As Integer
Private Sub Document_Open()
count = 1
SaveTime ActiveDocument
End Sub
Sub SaveTime(doc as Document)
Application.OnTime When:=Now + TimeValue("00:00:15"), "'DoSave doc'"
End Sub
Sub DoSave(doc as Document)
doc.SaveAs2 "c:\test\testsave" & count & ".docx"
count = count + 1
SaveTime
End Sub
I haven't used OnTime much, so if the syntax above doesnt allow arguments to be passed, here is a version using a Private doc variable.
' This will declare count and doc within the scope of the module
Private count As Integer
Private doc as Document
Private Sub Document_Open()
count = 1
SaveTime
End Sub
Sub SaveTime()
Application.OnTime When:=Now + TimeValue("00:00:15"), "DoSave"
End Sub
Sub DoSave()
doc.SaveAs2 "c:\test\testsave" & count & ".docx"
count = count + 1
SaveTime
End Sub
I hope this helps. I mostly work within Excel for VBA so I apologize if I am way off the mark here.
EDIT:
I figured it out. What is happening is every time your save event fires it is saving the active document as a new file, but it is not creating a new instance. For example, if test is your first document then:
Test Opens > Event Fires > Test becomes Test2.docx ( Event Fires > Test2 becomes Test3 etc.
Since the code is still stored in memory somehow (this is the part I dont fully understand, but it is, from what I can tell, is what's happening) the event still fires. The problem is, opening a new document somehow refreshes this and the event cancels.
There is a simple workaround, see below:
Option Explicit
Private doc As Document
Private count As Integer
Private Sub Document_Open()
count = 1
Set doc = ActiveDocument
SaveTime
End Sub
Sub SaveTime()
Application.OnTime Now + TimeValue("00:00:15"), "DoSave"
End Sub
Sub DoSave()
doc.Save
Application.Documents.Add doc.FullName
ActiveDocument.SaveAs2 "c:\test\testsave" & count & ".docx"
ActiveDocument.Close
count = count + 1
SaveTime
End Sub
This creates a new instance of the document, saves this new instance, and then closes it. This leaves the old instance in-tact and running.
I hope that all makes sense. Admittedly, I am not clear on why the code remains in memory even when the document holding the code no longer exists.

worksheet_calculate() is not working

I have added a private sub worksheet_calculate() in a sheet called Main. I have a value in column AP with formulas derived from other sheets and if that number is greater than value in X I want to display a message as a warning that it's over, but the code is not working any suggestions why?
Private Sub Worksheet_Calculate()
If Sheets("Main").Range("AP7").value > Sheets("Main").Range("x7").value Then
MsgBox "You Are Over Pieces Suggested"
End If
End Sub
Try this.
Private Sub Worksheet_Calculate()
If Range("AP7").Value > Range("X7").Value Then
MsgBox "You Are Over Pieces Suggested."
End If
End Sub
EDITED####
Edited the original code to run as a Worksheet_Calculate rather than a Change.
Working on trying to set the ranges to columns for you now.
EDIT#########
I flippin love a challenge. Try This.
Private Sub Worksheet_Calculate()
Set Target = Range("AP:AP").SpecialCells(xlCellTypeFormulas)
If Target Is Nothing Then Exit Sub
For Each c In Target
If c > Range("X" & c.Row) Then
MsgBox "You Are Over Pieces Suggested - Cell " & "AP" & c.Row
End If
Next
End Sub
Consider using Data Validation on cell AP7 using a "Custom" formula of: =AP7<=$X$7
Fill in the Error Alert tab on the validation menu: Stop; "You Are Over Pieces Suggested". I think this might achieve what you want without any macros. In fact, it can prevent an invalid number from being entered in the first place.