Excel - Adding animation - vba

I am quite new to Excel and so far it has been fun learning it. I have started using Excel for modelling and i have gained enough expertise at it
I now wanted to go a step ahead and do a bit of designing on Excel
I have a specific requirement. I have 10 sheets in my workbook. I want to add some sort of animation on my first sheet. I currently have a button on my sheet 1.
On click of button it unhides all the 9 sheets and allows me to see modelling stuff in them. On clicking one more time it hides all the 9 sheets again.
Now i want to add one more thing to this.
On clicking the button i want a light bulb on the sheet to turn on and display a message (along with unhiding the sheets)
On clicking one more time i want the light bulb to turn off and hide the sheets again,
Can someone help me with this animation.
Thanks,
Sachi

Like I said it is very simple. This is how your Button and Bulb looks like.
Shapes used to create the bulb
Straight Connector
Oval
Cloud
Code
Private Sub CommandButton1_Click()
If CommandButton1.Caption = "Hide" Then
'
'~~> Your code here to Hide the Sheets
'
ActiveSheet.Shapes("Oval 2").Fill.ForeColor.RGB = RGB(255, 255, 255)
CommandButton1.Caption = "Unhide"
ElseIf CommandButton1.Caption = "Unhide" Then
'
'~~> Your code here to Unhide the Sheets
'
ActiveSheet.Shapes("Oval 2").Fill.ForeColor.RGB = RGB(255, 255, 0)
CommandButton1.Caption = "Hide"
End If
End Sub

To simply make the button change a shapes fill to yellow try this:
ActiveSheet.Shapes("SHAPE NAME").Fill.ForeColor.RGB = RGB(255, 255, 0)
The same idea can be applied to change the fill back. I've been playing with actual animation myself a lot lately too, so if you want to get something a bit more fun than the above than try something like the following. (Create a shape called Oval 1 to demo it with, or change the name to your shapes name)
Sub bulb()
steps = 300
timelimit = 0.005
increments = 255 / steps
counter = 0
r = 0
g = 0
Do
DoEvents
counter = counter + 1
r = r + increments
g = g + increments
ActiveSheet.Shapes("Oval 1").Fill.ForeColor.RGB = RGB(r, g, 0)
timeout (timelimit)
Loop Until counter = steps
End Sub
Sub timeout(duration_ms As Double)
Start_Time = Timer
Do
DoEvents
Loop Until (Timer - Start_Time) >= duration_ms
End Sub
Will need tweaking to your needs of course but the possibilities playing with this are endless.

Related

How to make shape visible based on calculation in PowerPoint

I have a PowerPoint quiz which calculates a score. If the score is above 80% I would like a button(shape with name "PrintCert") to become visible so the user may print a certificate. Currently the button is always visible. Here is the code that I've tried with no success. (The first and last part of the code updates labels.)
Sub showresult()
Percentage.Caption = Int((CA.Caption) * 100 / (TQ.Caption)) & "%"
With ActivePresentation.Slides(37)
If ((CA.Caption) * 100 / (TQ.Caption)) > 79.9 Then
.Shapes("PrintCert").Visible = True
Else
.Shapes("PrintCert").Visible = False
End If
End With
SlideLayout41.PercentageCertificate.Caption = Percentage.Caption
ActivePresentation.SlideShowWindow.View.Next
End Sub
Simplest thing might be to add a new first slide with a "Start the show" action button that users click to begin the presentation. You'd apply a Run Macro action setting to the button and have it run a macro something like this:
Sub StartShow()
' set your button shapes to be invisible
With SlideShowWindows(1).Presentation.Slides(37)
.Shapes("PrintCert").Visible = False
End With
' etc, for any other shapes you want to have
' hidden to start with
' go to the actual first slide
SlideShowWindows(1).View.GotoSlide (2)
End Sub

How can I "force" Excel to count page breaks correctly?

I am supposed to export some large data ranges from Excel to Powerpoint, one page per slide, and of course I should treat the page breaks to avoid "orphan" rows or columns.
I am trying to check how many pages I would have, vertically and horizontally, with a given zoom, by reading HPageBreaks.Count and VPageBreaks.Count, and then manually define the position of each break. The idea is to have approximately the same width and height on each page.
When I debug my code step-by-step, it runs nicely, and the logic seems ok, but if I run it "freely", the page breaks are completely off. Adding some MsgBox instructions, I can see that when I read HPageBreaks.Count (or vertical) I get the wrong values. (I can check the correct ones if I do manually what the code should do).
Searching on many many forums, I see some ugly workarounds like forcing a reset of PaperSize (ws.PageSetup.PaperSize = ws.PageSetup.PaperSize). After trying some of them, what seemed to work a bit better was to turn off PrintCommunication before a change to PageSetup, and then turn it back on. This worked well on most of my sheets, but on the really large ones (~750 rows x 80 columns, almost all cells with formulas), it simply doesn't.
Here an extract of the code:
'Reset page breaks
.ResetAllPageBreaks
'Set minimum acceptable zoom factor
Application.PrintCommunication = False 'This is the ugly workaround
.PageSetup.Zoom = 60
Application.PrintCommunication = True
MsgBox "Zoom = " & .PageSetup.Zoom 'Only for debugging
'Calculate the number of pages in width
Application.PrintCommunication = False
NPagesWide = .VPageBreaks.Count + 1
Application.PrintCommunication = True
MsgBox "NPagesWide = " & NPagesWide
'Find the higher zoom factor that can fit that number of pages
Application.PrintCommunication = False
.PageSetup.Zoom = 100
Application.PrintCommunication = True
Do While .VPageBreaks.Count > NPagesWide - 1
Application.PrintCommunication = False
.PageSetup.Zoom = .PageSetup.Zoom - 5
Application.PrintCommunication = True
Loop
MsgBox "Zoom = " & .PageSetup.Zoom
'Set average width per page and initialize column pointer
If HasTitleColumns Then 'Defined earlier
PageWidth = (PrintArea.Width + TitleColumns.Width * (NPagesWide - 1)) / NPagesWide
j = TitleColumns.Columns(TitleColumns.Columns.Count).Column + 1
Else
PageWidth = PrintArea.Width / NPagesWide
j = 1
End If
'Cycle vertical page breaks
For i = 1 To NPagesWide - 1
'Set width of TitleColumns
If HasTitleColumns Then
CumulWidth = TitleColumns.Width
Else
CumulWidth = 0
End If
'Cumulate columns width until the available page width
Do While CumulWidth + .Columns(j).Width <= PageWidth
CumulWidth = CumulWidth + .Columns(j).Width
j = j + 1
Loop
'Add the break
.VPageBreaks.Add .Columns(j + 1)
Next i
Any ideas why this happens, and how can I solve it?
Thanks,
I propose general advice to the issue when VBA code works fine while hitting F8 in the debug mode, but it doesn't not work after hitting F5 to run the whole macro.
Hint 1. Use ThisWorkbook.ActiveSheet instead of ActiveWorkbook.ActiveSheet whenever possible to reference the proper sheet. Use ThisWorkbook.Application instead of just Application. It could be that you have another Addin program working on in background, switching ActiveSheet to something else that you may not be aware of. Check other macros enabled and get rid of anything that you do not use. So before anything important in your code, try to get the focus for your sheet with ThisWorkbook.Activate. See the graph on that page: http://analystcave.com/vba-tip-day-activeworkbook-vs-thisworkbook/
Hint 2. Force Excel to wait in different way then DoEvents.
Declare Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Long) 'Place this line of code at the very top of module
Sleep 1 'This will pause execution of your program for 1 ms
https://stackoverflow.com/a/3891017/1903793
https://www.fmsinc.com/microsoftaccess/modules/examples/AvoidDoEvents.asp
Or alternatively:
Application.Calculate
If Not Application.CalculationState = xlDone Then
DoEvents
End If
https://stackoverflow.com/a/11277152/1903793
Hint 3. If the above still does not work for refreshing use:
ThisWorkbook.Connections("ConectionName").Refresh
ThisWorkbook.Application.CalculateUntilAsyncQueriesDone
https://stackoverflow.com/a/26780134/1903793
Another workaround for me was to set the PrintArea new (without .PrintCommunication = false)
wks.PageSetup.PrintArea = wks.PageSetup.PrintArea
This workaround does not affect any pagesetup settings (unlike the .Zoom property)
The problem only occurs if the PageView is set to xlPageLayoutView and there is data "outside" the printarea (UsedRange is larger than PrintArea) when running the macro.
So you can check if the sheet is in PageLayoutView before you do the workaround. If the sheet is in any other view the pagebreaks.count always works fine here.

Trying to simplify a step-by-step macro in VBA

I have a spreadsheet in which there are ActiveX textboxes that are linked to specific cells. When the number in the cells change, the number in the textbox changes too. Pretty simple.
I wanted the numbers in these textboxes to be red when there is a number above zero and white when the number is zero. I've chosen white so that the zeros can't be seen at all on the printed page. I have a very basic macro that does this:
Sub textbox_change()
Worksheets("PAGE01").E21.LinkedCell = "PAGE01!AB23"
If Worksheets("PAGE01").E21.Value > 0 Then
Worksheets("PAGE01").E21.ForeColor = RGB(255, 0, 0)
Else
Worksheets("PAGE01").E21.ForeColor = RGB(255, 255, 255)
End If
Worksheets("PAGE01").E22.LinkedCell = "PAGE01!AB24"
If Worksheets("PAGE01").E22.Value > 0 Then
Worksheets("PAGE01").E22.ForeColor = RGB(255, 0, 0)
Else
Worksheets("PAGE01").E22.ForeColor = RGB(255, 255, 255)
End If
End Sub
As you can see, the code for each textbox is completed one at a time. How can I combine these so that the macro changes these colors at once, without having to have a block of code for each textbox?
Note: I've only used two textboxes in the example, but I would like to have up to 48 of these boxes total. That would be A LOT of redundancy when I'm positive that there's a much shorter way to take care of these pesky color changes.
Can you kind folks help out? Very much appreciated...
You can loop over the sheet's OLEObjects collection and check for textboxes:
Sub Tester()
Dim t
For Each t In Worksheets("PAGE01").OLEObjects
'is it a textbox ?
If t.progID = "Forms.TextBox.1" Then
'check the name begins with "E"
If t.Name Like "E*" Then
'Set the forecolor: using the built-in color constants,
' but you could instead use specific RGB() values
t.Object.ForeColor = IIf(t.Object.Value > 0, vbRed, vbWhite)
End If
End If
Next t
End Sub

Advancing two slides in powerpoint VBA

I am trying to advance slides based on a response. I want to go forward 2 slides if the user selects easy and one if they select hard. This is my current code. The Nextpage script isn't working AND I would prefer for it to be usable for multiple questions--I can't seem to get it to work with something like slide +1 or slide +2 (or ++).
Sub Start()
ActivePresentation.Slides(2).Shapes("selection_hard").Visible = False
ActivePresentation.Slides(2).Shapes("selection_easy").Visible = False
ActivePresentation.SlideShowWindow.View.Next
End Sub
Sub Shoe_Easy()
ShoeAnswer = "Easy"
ActivePresentation.Slides(2).Shapes("selection_hard").Visible = False
ActivePresentation.Slides(2).Shapes("selection_easy").Visible = True
'ActivePresentation.SlideShowWindow.View.GotoSlide (11)
End Sub
Sub Shoe_Hard()
ShoeAnswer = "Hard"
ActivePresentation.Slides(2).Shapes("selection_hard").Visible = True
ActivePresentation.Slides(2).Shapes("selection_easy").Visible = False
'ActivePresentation.SlideShowWindow.View.GotoSlide (12)
End Sub
Sub Nextpage()
If ActivePresentation.Slides(2).Shapes("selection_hard").Visisble = True Then
ActivePresentation.SlideShowWindow.View.GotoSlide (3)
ElseIf ActivePresenation.Slides(2).Shapes("selection_easy").Visible = True Then
ActivePresenation.SlideShowWindow.View.GotoSlide (4)
End If
End Sub
Assuming that "response" means clicking on one of two shapes (Easy or Hard), this will do it. You just need to make sure that the text in the shape and the code below match up and that you assign the HandleClick macro as a RunMacro action setting to each of the shapes (assign them to two of them then copy/paste the shapes elsewhere as needed).
There are a few extra hoops to jump through to get this working on a Mac; shout if you need it to work there too.
Sub HandleClick(oSh As Shape)
' Did they click the Easy or Hard button?
' oSh contains a reference to the shape they clicked
' Look a the text in oSh to decide where to go next:
Select Case UCase(oSh.TextFrame.TextRange.Text)
Case Is = "EASY"
SlideShowWindows(1).View.GotoSlide (oSh.Parent.SlideIndex + 2)
Case Is = "HARD"
SlideShowWindows(1).View.GotoSlide (oSh.Parent.SlideIndex + 1)
Case Else
' Do nothing
End Select
End Sub
This immediately advances the slide as soon as it's clicked. If you want the user to be able to choose an answer and then advance, you'd need a different approach.
Instead of advancing immediately as above, you'd set the value of a global variable to, say, "EASY" or "HARD", depending on the user's selection.
Then in a separate macro assigned to your forward button, you'd advance one or two slides depending on the value of the global variable.
I think something like this might help:
Nextpage()
Dim currentSlide as Slide
Set currentSlide = ActivePresentation.SlideshowWindow.View.Slide
If ActivePresentation.Slides(2).Shapes("selection_hard").Visisble = True Then
ActivePresentation.SlideShowWindow.View.GotoSlide currentSlide.SlideIndex + 1
ElseIf ActivePresenation.Slides(2).Shapes("selection_easy").Visible = True Then
ActivePresenation.SlideShowWindow.View.GotoSlide currentSlide.SlideIndex + 2
End If
End Sub

Word crashes on removing a Shape with VBA from a header

(disclaimer: i'm not a VBA programmer by occupation)
Attached to buttons in the Ribbon I have code to toggle the company logo in a Word Document.
One button for the logo type A, a second button for logo type B and a third for no logo (logo is preprintend on paper)
First I remove the logo with removeLogo and then i add it the requested logo with setLogoAt.
The first button click is fine (e.g. for Logo Type A), a logo is added to the header of the document. When i click an other button (e.g for Logo Type B) Word crashes (probably on removing the current logo)
What is wrong with my code (or less probably: with Word?)
Sub setLogoAt(left As Integer, path As String)
Dim logoShape As Shape
Dim anchorLocation As Range
Dim headerShapes As Shapes
Set logoShape = ActiveDocument. 'linebreks for readability
.Sections(1)
.Headers(wdHeaderFooterPrimary)
.Shapes
.AddPicture(FileName:=path, LinkToFile:=False,
SaveWithDocument:=True, left:=0,
Top:=0, Width:=100, Height:=80)
logoShape.name = "CompanyLogo"
logoShape.RelativeHorizontalPosition = wdRelativeHorizontalPositionPage
logoShape.RelativeVerticalPosition = wdRelativeVerticalPositionPage
logoShape.Top = CentimetersToPoints(0.1)
logoShape.left = CentimetersToPoints(left)
End Sub
Sub removeLogo()
Dim headerShapes As Shapes
Set headerShapes = ActiveDocument.Sections(1).Headers(wdHeaderFooterPrimary).Shapes
Dim shapeToDelete As Shape
If (headerShapes.Count > 0) Then
If Not IsNull(headerShapes("CompanyLogo")) Then
Set shapeToDelete = headerShapes("CompanyLogo")
End If
End If
If Not (shapeToDelete Is Nothing) Then
shapeToDelete.Delete
End If
End Sub
edit
I steped trough my code. All is fine until I reach the line shapteToDelete.Delete in removeLogo. Here Word crashes hard, even while debugging. I'm using Word 2007 (and that is a requirement)
edit2
I cleared all macros, all normals.dot, all autoloading templates, then created a new document with the two routines above and this test method:
Sub test()
setLogoAt 5, "C:\path\to\logo.jpg"
removeLogo
setLogoAt 6, "C:\path\to\logo.jpg"
End Sub
When I run test it crashes in removeLogo at shapeToDelete.Delete.
Edit 3
I 'solved' the problem by first making the headers/footers view the active view in Word, then deleting the Shape and then returning to normal view. Very strange. It works but as a programmer I'm not happy.
Another potential solution is to try and select the shape first and then delete the selection:
shapeToDelete.Select
Selection.Delete
You would probably want to switch off screen updating if this works, else you'll get flickering as Word moves around the document.
I've experienced this problem before and normally with an automation error: "The object invoked has disconnected from its clients". I haven't yet found a solution.
However a good workaround is to hide the shape rather than delete it.
So:
shapeToDelete.Visible = False
This works:
I only have 2 boxes to hide so this isn't generic
Private Sub btnPrint_Click()
Dim hdrShapes As Shapes
Dim S As Shape
Dim aTohide(2) As String
Dim iNdx, i As Integer
iNdx = 0
' Hide buttons and print
Set hdrShapes = ActiveDocument.Sections(1).Headers(wdHeaderFooterPrimary).Shapes
' GET BUTTON NAMES (ACTUALLY TEXT BOXES
For Each S In hdrShapes
If S.Type = msoTextBox Then
aTohide(iNdx) = S.Name
iNdx = iNdx + 1
End If
Next
' now hide , use the arrays as the for each statement crashes
For i = 0 To 1
hdrShapes(aTohide(i)).Visible = msoFalse
Next
' print it
With ActiveDocument
.PrintOut
End With
' and unhide the buttons
For i = 0 To 1
hdrShapes(aTohide(i)).Visible = msoTrue
Next
Set hdrShapes = Nothing
End Sub