Drawing shapes to screen falling behind other code - excel

I am trying to create a small tutorial for one of my Excel Applications and I am running into the issue where I'm trying to draw a text shape to the screen to give advice on what to enter into an InputBox but the InputBox gets displayed before the text shape, however, when running in debug mode and stepping through the code it all works fine.
There is one userform ufNext which only contains one button, ufNext. The click event code for this button contains a Select Case clause to determine what to do each time it is clicked. The value the clause is checking is a Public variable, tutSectionsRun
Option Explicit
Private Sub btnNext_Click()
Select Case tutSectionsRun
Case 1
Call Section2
Case 2
Call Section3
Call MPFilterString
' Case N
' ...
End Select
End Sub
The code starts in Section1 which just sets the position of ufNext and shows the form then sets the global variable tutSectionsRun to 1.
The user clicks the "Next" button on the ufNext form and it calls Section2 which re-positions the form (there would normally be other code in these "Section" procedures), and sets the global variable to 2.
Again, the user clicks the "Next" button but this time there is the issue where before the shapes are drawn to the screen, I get the InputBox popping up first and only after it closes the text shape tutText is drawn to the screen.
Option Explicit
Public tutSectionsRun As Long
Sub Section1()
ufNext.Left = 550
ufNext.Top = 450
ufNext.Show
tutSectionsRun = 1
End Sub
Sub Section2()
ufNext.Left = 910
ufNext.Top = 350
tutSectionsRun = 2
End Sub
Sub Section3()
Dim tutText As Shape
Set tutText = ActiveSheet.Shapes.AddLabel(msoTextOrientationHorizontal, 600, 300, 200, 100)
tutText.TextFrame2.TextRange.Text = "Enter the string ""gr"" into the input box."
tutText.Locked = False
ufNext.Hide
tutSectionsRun = 3
End Sub
Sub MPFilterString()
Dim s As Variant
Application.ScreenUpdating = False
s = Application.InputBox("Enter string to filter out.", "Filter String.")
If s = False Then Exit Sub
End Sub
**Edit : I forgot to mention that the userform is non-modal. Otherwise execution would pause on the call to ufNext.Show and clicking next would call the event handler before the tutSectionsRun variable had been set to 1

Thanks to #BrakNicku who confirmed my suspicions in the comments, saying that the InputBox was preventing the screen from being refreshed for the text shape from ActiveSheet.Shapes.AddLabel to be displayed.
Their link to this answer offered some suggestions.
What I found was that adding either of these before the InputBox was called would force the screen to refresh but only if Application.ScreenUpdating = False was removed, or at least just moved further down in the code.
- ActiveSheet.Calculate
- ActiveWindow.SmallScroll
- Application.WindowState = Application.WindowState
I always like to call Application.ScreenUpdating = False at the top of my procedures, so I went with a different approach, thinking that the problem was that the time to refresh the screen with shapes drawn was longer than the time between the instruction to do so and the instruction to draw the InputBox to screen. So, I thought a slight delay before calling the InputBox might be a better choice for me, probably not for everyone but I felt better about doing it this way. (Application.ScreenUpdating = False is a good friend of mine and I didn't want to see her go, or even be relocated) So I just made a small wait procedure.
Sub Wait(secs As Single)
Dim finishTime As Single
finishTime = Timer + secs
Do While Timer < finishTime
DoEvents
Loop
End Sub
And called it at the top of the MPFilterString procedure. It only takes about 50ms for the shapes to show on screen but I give it 100ms to be safe.
Sub MPFilterString()
Dim s As Variant
WaitFor (0.1)
Application.ScreenUpdating = False
s = Application.InputBox("Enter string to filter out.", "Filter String.")
If s = False Then Exit Sub
' more code ...
End Sub

Related

Application.ScreenUpdate and Shapes

I have a sheet that I have a bunch of shapes (rectangle = button) that have marcos assigned to. I am using the shapes as a userform. There is one button that toggles between forms (essentially hiding all the visible shapes then showing all the shapes for another setup). The issue that I am having is that during the switch over to the other setup the shapes from the first setup still appear throughout the transition even though I am using:
Application.ScreenUpdate=False
I am trying to have a clean transition / clean switch over. Any thoughts? If my question is not clear, please let me know.
Sample Code
Sub Toggle_Form
Application.ScreenUpdating = False
Call Form1_ShowHide(False)
Call Form2_ShowHide(True)
Application.ScreenUpdating = True
Sub Form1_ShowHide(VisibleState as boolean)
For CoUnTer = 1 To 10
WS.Shapes("Shape1_" & CoUnTer).Visible = VisibleState
Next CoUnTer
End Sub
Sub Form2_ShowHide(VisibleState as boolean)
For CoUnTer = 1 To 20
WS.Shapes("Shape2_" & CoUnTer).Visible = VisibleState
Next CoUnTer
End Sub
I am using Excel 2016. Do not think it is a computer processing issue (got a pretty good laptop).

Prevent a command button from being clicked multiple times while a sub is iterating

I'm creating an Excel macro that will run when a command button is pressed (VBA 7.1), and will not allow the user to click multiple times during execution, however every trick I've found online to this end has not worked: even while locked, disabled, and with focus on a different object, the user can still cause the sub to run multiple times before the first one finishes.
My current code looks like this (run_slow_macro opens a Word document to make changes, then saves it, and can take about 30 seconds to complete.)
Private Sub CommandButton1_Click()
If CommandButton1.Enabled = False Then
MsgBox ("STOP CLICKING")
End If
Me.Frame1.SetFocus
CommandButton1.Enabled = False
CommandButton1.Locked = True
CommandButton1.Caption = "WAIT"
Call run_slow_macro
CommandButton1.Caption = "CommandButton1"
CommandButton1.Enabled = True
CommandButton1.Locked = False
End Sub
When I click the button, it locks and becomes disabled, and the caption changes as expected. However, subsequent clicks do not cause the "STOP CLICKING" messagebox to appear, yet still cause the Word document to be opened multiple times, edited, and then closed.
The command button does not become unlocked/enabled until after all executions are completed, and the "STOP CLICKING" messagebox never appears.
I'm very confused as to how it's executing "Call run_slow_macro" each time, but seems to be skipping everything before and after that line once the first execution is in progress.
I have very little experience with VBA, and have been unable to find any solutions online (the above code is the culmination of the most common recommendation's I've seen), so I appreciate any advice that can be offered.
This should disable the macro execution until it finishes. Read the code comments.
See that I'm using the variable defined as HasStarted in the run_slow_macro to tell the command button that it should exit while the macro hasn't finished.
Option Explicit
' This variable should be declared at the top of the module
Private HasStarted As Boolean
Private Sub CommandButton1_Click()
If HasStarted = True Then Exit Sub
Me.Frame1.SetFocus
CommandButton1.Enabled = False
CommandButton1.Locked = True
CommandButton1.Caption = "WAIT"
Call run_slow_macro
CommandButton1.Caption = "CommandButton1"
CommandButton1.Enabled = True
CommandButton1.Locked = False
End Sub
Public Sub run_slow_macro()
HasStarted = True
' Do something
HasStarted = False
End Sub
Let me know if it works
Take a look at the following. I believe it has your answer.
Freeze excel editing while vba is running

How to allow user to review worksheet using excel vba

My macro generates a series of reports that are 60 columns wide. I want users to be able to review the reports on screen before printing them or going on to another segment of the macro.
it there a way to set a scrollarea, have user review it, and then have the respond to a message box to continue the routine?
I tried this:
Sub reviewdata()
' Application.ScreenUpdating = False
Worksheets("Fin. Stmts").ScrollArea = ""
Application.Goto Reference:="monthlydata"
ActiveCell.Offset(2, 1).Select
ActiveWindow.FreezePanes = True
Worksheets("data. Stmts").ScrollArea = "monthlydata"
If MsgBox("End Review", vbOKOnly) = vbOK Then
End If
ActiveWindow.FreezePanes = False
Worksheets("data. Stmts").ScrollArea = ""
End Sub
the problem is that once the if, then statement is executed the user can not move around the worksheet since the routine needs a response to continue.
any insights are most appreciated.
thanks.
You can Use a Dummy Variable:
Dim dummy As Range
Set dummy = Application.InputBox("Scroll and Check. After That Select Ok!", "This is Specially created so that you can", Default:="A1", Type:=8)
Input Box that Takes in Range Allows you to Scroll in Background. Keep hitting Ok in and nothing will change, code will run as it is running at the moment.
This is a little clumsy but it sort of gets what you want. Instead of using a MsgBox use and InputBox as a range, which will allow the user to click around and scroll, as you describe. Whenever they hit okay/cancel, the macro will continue.
So probably replace your MsgBox line of code with....
Dim boom As Variant
boom = Application.InputBox("When you're done hit ""Cancel""... (""OK"" might lead to problems...)", _
"Scroll around and look at stuff", _
, , , , , 8)
I would recommend doing two macros instead, but this probably does what you need.
You can show that message in a small userform and call that userform in modeless state as shown below.
UserForm1.Show vbModeless
This way you will be able to navigate in the sheet with that message still showing.
You can also put the rest of the code in the button click event as shown below.
Option Explicit
Private Sub CommandButton1_Click()
ActiveWindow.FreezePanes = False
Worksheets("data. Stmts").ScrollArea = ""
Unload Me
End Sub

On Key Press, Whilst UserForm In Background

I know we can detect if a key is pressed e.g. KeyAscii = 8, however I have only seen this done on events whilst the userform app is the window in operation.
Is it possible to have the window in the background e.g. whilst I am using IE, I can press CTRL + 0 and my app recognizes it and does an action?
At least to my own knowledge the answer is - it's not possible, but..
1. Things we need to know
Let's get some facts straight before we do any coding:
The excel application will not be able to execute any code while the Window object is in xlMinimzed state
So basically as soon as the user will close, minimize or even tab out of the Application, we no longer will be able to detect any OnKey (or any other) events, as the currently active application (ie. browser) takes precedence over the excel Application
hence CTRL+0 will now zoom out of the page as it's the browser's default behaviour.
With that being said, we can do at least some things to get close to at least some form of usefulness out of this.
2. The Application Layout
Since you have not provided specific application details, I created this mockup:
A Worksheet consisting of a single CommandButton, that launches the UserForm1
Private Sub CommandButton1_Click()
UserForm1.Show
End Sub
A Module named Module1 which contains a simple hello world message (for the OnKey event)
Public Sub hello()
MsgBox "Hello world!"
End Sub
And the UserForm1 which contains the code for our OnKey handling
Private Sub UserForm_Activate()
With ActiveWindow
.WindowState = xlNormal
' we need to change window state to xlNormal
' xlMaximized would result in width/height property changing error
' and with xlMinimized our Onkey would not work
.Width = 0
.Height = 0
End With
Range("A1").Select
' we need a range selected, otherwise Application OnKey will not fire
Application.OnKey "^0", "Module1.hello" ' launches hello world procedure
Me.Hide
End Sub
Now that's enough to have the window (almost) minimized and respond to a keypress
3. If we want to show the UserForm while maintaing functionality
Now let's say we want the UserForm while hiding the rest of the excel Application in the background.
In order to do this, we need to:
Change the UserForm to vbModeless
To do this, select the UserForm object and show properties (F4)
Remove the Me.Hide line from our UserForm1 code
Private Sub UserForm_Activate()
With ActiveWindow
.WindowState = xlNormal
.Width = 0
.Height = 0
End With
Range("A1").Select
Application.OnKey "^0", "Module1.hello"
' Me.Hide <- remove me
End Sub
That leaves us with the following fucntionality
If there are any suggestions for improvement / optimization I'd be
happy to know as this question intrigued me quite a bit.
I'll try to keep the answer updated!

How can I run macro by first click and run another one by second click on the same button?

I have 2 macros one for show image and another to hide or delete this image.
I just need to show this image when I click on the button but when I click the second time hide this image again.
You can use the same macro for both functions:
Sub ShowAndHide()
Dim s As Shape
Set s = ActiveSheet.Shapes("Rectangle 1")
s.Visible = Not s.Visible
End Sub
If the macro runs and the Shape is not visible, it will become visible. The next time the macro runs, it will restore the Shape to hidden. Repeated clicks will produce show / hide / show / hide / show...........
In case if you want toggle between 2 macros with the same command button, the below may be used as a reference
Dim btnRun As Shape 'Declaring the command button variable
Sub Initializevariables()
Set btnRun = Worksheets("Sheet1").Shapes("Button 1") 'Initiailizing the variable
End Sub
' Ensure that you have assigned Macro 1 to the command button
Sub Macro1()
Call Initializevariables
MsgBox "Macro 1 ran"
btnRun.OnAction = "Module1.Macro2"
End Sub
Sub Macro2()
Call Initializevariables
MsgBox "Macro 2 ran"
btnRun.OnAction = "Module1.Macro1"
End Sub

Resources