VBA Macro to skip a macro that has already run - vba

I have a text form field in a protected word 2011 for mac document, that triggers a macro to run upon exit. The macro populates the form with various information but is not dependent on the test that is entered into this particular form field. I have two ideas to solve this problem. The first option would be best if it can be done, otherwise the second option would be a reasonable work-around. Can someone please help find a macro that does one of the following?
a. Will prevent the macro from running a second time if the form field is entered into again and edited?
b. Checks to see upon entry of the form field, if there is text in the field already, and if there is, prevents editing and moves to the next form field without running the upon exit macro again?
I am very new to VBA, but I think I have a handle on the basics. Here is what i have come up with for the b. solution but it does not work.
A macro that checks to see if there is text in form field names "text9", if there is, then unprotect from, go to bookmark "text10" and protect form. else, allow the user to fill in the form field and run the macro upon exit.
Sub TestSkipField()
'
' TestSkipField Macro
'
'
With GetCurrentFF
If Len(.Result) = Null Then
End If
Else
If ActiveDocument.ProtectionType <> wdNoProtection Then
ActiveDocument.UnProtect
End If
Selection.GoTo What:=wdGoToBookmark, Name:="Text10"
With ActiveDocument.Bookmarks
.DefaultSorting = wdSortByName
.ShowHidden = False
End With
ActiveDocument.Protect Type:=wdAllowOnlyFormFields, NoReset:=True
End If
End With
End Sub

I think the easiest approach to your problem is to use a global variable that answers the question "has the macro already run?".
Let's say that you have a macro like follows:
Sub myMacro()
'your stuff here
End Sub
You don't want this macro to run twice. Then, you can define a global variable on the top of your module:
Dim hasRun As Boolean 'this will be False by default
Sub myMacro()
'your stuff here
End Sub
Sub myOtherMacro()
'some other stuff here
End Sub
'etc.
And then embed a check into your macro:
Sub myMacro()
If hasRun = False Then 'if hasRun is still False, it means we never ran this code yet
'do your stuff here
hasRun = True 'set hasRun = True, so from now we know that this code has already been executed once
End If
End Sub
This should allow you not to change the structure of your code or executing check on the data, but at the same time executing the macro only once. You can follow this direction to elaborate more the execution conditions: execute it only twice, only if something happened etc.
PLEASE NOTE: you better set hasRun = True at the very end of your macro's code, in order to make sure that the value hasRun will not be True if the execution didn't arrive until the end of your desired code.

Related

VBA code crashes after saving - says the situation lies with the XML "Run-time error '429'

I made a worksheet for the company I work for to help with pricing out custom designs. A few months ago I made a macro that can save the parts to a text file that can be pulled from at a later date if we wanted to quote out the same design. Everything was working perfectly, until one day I go to open it up and got the error message
We found a problem with some content in 'File.xlsm'. Do you want us to try
to recover as much as we can?
When I click yes, it then comes up with the worksheet the macro was on completely un-formatted and says it could only open the file by repairing or removing the following part
Repaired Part: /xl/worksheets/sheet3.xml part.
This is weird because the only xml code I use is just to create a drop-down menu when the saved design names are loaded. Nothing has changed since the final revision of the code other than the amount of designs that have been saved. The boxes I had as buttons tied to macros have been deleted and none of the code for this sheet works. What it shows when I view the code now is Sheet_Thumbnails
All other macros work, the other sheets are fully functional. When I try and run the code on this sheet I get
Run-time error '429':
ActiveX component can't create object
This has to be when compiling because I can't even debug where this is happening. The best answer I get when I look this error up is that I am not using the "New" keyword when calling a file or object from somewhere else. But I have looked through my code and don't see anywhere that applies. Luckily a co-worker saved a copy off our server to her computer so I have a backup, but when I open this and run the macros then save and re-open, the same crash happens.
Here is the code with xml:
Sub MakeList(ByRef r As Range, ByRef Config As String)
r.Clear
If Not Config = "" Then
r.Select
With Selection.Validation
.Delete
.Add Type:=xlValidateList, AlertStyle:=xlValidAlertStop, Formula1:=Config
.IgnoreBlank = True
.InCellDropdown = True
.InputTitle = ""
.ErrorTitle = ""
.InputMessage = ""
.ErrorMessage = ""
.ShowInput = True
.ShowError = True
End With
End If
End Sub
Can anyone help me? I am at a total loss for why this has happened and why it keeps happening. Is it the validation part? Why would it happen after working for months?
Thank you in advance.
EDIT 1
Exporting all of the code and creating a new workbook did not solve the problem.
Thanks to Profex, the problem has been found and is in the validation. Essentially one of my lists was too long. The formula used in validation is not supposed to be beyond 255 characters. Even though Excel doesn't give any warning on this, when I would create the drop down menus, although it would populate each item from the list, after saving closing and re-opening, apparently this would corrupt the coded sheet. So now the question lies with how to add values into a drop-down menu without clearing and re-initializing with a longer list. Should I post a new question for that?
In Excel, Cell Validation Lists have a 8191 character limit (which is way too long for a user to pick from anyway).
Anything over 254 characters will corrupt the file upon save/re-open.
Here is something similar to what I have done in the past to create Dynamic Validations lists:
It uses your MakeList() subroutine, and requires another GetList() function to get the validation list for the specified cell.
Since this code is in the Workbook module, I also added another function called IsSheetTheOneICareAboutWithValidations(). If you use the WorksSheet_SelectionChange Event from in a specific sheet module, this isn't required; but you would have to change the scope of m_ValidationCell and m_ValidationList to be Public.
This code is untested and goes in the ThisWorkbook module:
Option Explicit
Private m_ValidationCell As Excel.Range
Private m_ValidationList As String
Private Sub Workbook_BeforeSave(ByVal SaveAsUI As Boolean, Cancel As Boolean)
If Not m_ValidationCell Is Nothing Then
m_ValidationList = m_ValidationCell.Validation.Value
m_ValidationCell.Validation.Delete
End If
End Sub
Private Sub Workbook_AfterSave(ByVal Success As Boolean)
If m_ValidationList <> vbNullString Then
With m_ValidationCell.Validation
.Add Type:=xlValidateList, Formula1:=ValidationList
End With
m_ValidationList = vbNullString
End If
End Sub
Private Sub Workbook_SheetSelectionChange(ByVal Sh As Object, ByVal Target As Range)
If Not m_ValidationCell Is Nothing Then
m_ValidationCell.Validation.Delete
Set m_ValidationCell = Nothing
End If
If IsSheetTheOneICareAboutWithValidations(Sh) Then
' Since we're changing the Validation each time there is a new Selection;
' It's the Active Cell that matters, not the Target range
' Add a validation list to any cell in column 4, after the header (in row 1).
If ActiveCell.Column = 4 And ActiveCell.Row > 1 Then
List = GetList(ActiveCell)
MakeList ActiveCell, List
' Should probably add this next line to you MakeList() function
Set m_ValidationCell = ActiveCell
End If
End If
End Sub
Private Function GetList(Target As Range) As String
GetList = vbNullString ' or whatever you want
End Function
Private Function IsSheetTheOneICareAboutWithValidations(Sh As Object) As Boolean
IsSheetTheOneICareAboutWithValidations = (Sh.Name = "Pricing")
End Function
Recovery
It looks like you just had a bad save. Sometimes it just corrupts the file and there isn't much you can do, other then hope you have a backup.
Right Click in the Folder > Properties > Previous Versions
If you don't have a backup, it might just help to move everything to a new file.
Create a New Workbook
Select All cells from your first sheet (click above the 1, left of the A)
Press Ctrl+C to Copy
Select All cells in your New Workbook/Sheet
Press Ctrl+V to Paste
Repeat for all Worksheets
On the VB side of things, you can just Drag the Forms/Modules/Classes from the Old file to the New One.
Issue
Did you know that all New Office documents are really just ZIP files...
Go ahead, rename the file to File.xlsm.zip
Inside the file you'll see a folder structure which should have .../xl/worksheets/sheet3.xml
This is what excel is complaining about! That file is either missing or wrong.
Code
I don't know how you're calling Makelist, so I can't verify that the range R that you are passing is valid.
Please remove the Select/Selection from your code. You don't need to select anything in the front end GUI of Excel to access/change the cells. Also you didn't check if R was Nothing.
Sub MakeList(ByRef r As Range, ByRef Config As String)
If Not r Is Nothing then
r.Clear
If Not Config = "" Then
With r.Validation
...
End With
End If
End If
End Sub

VBA code conflict

I know, I am asking an unusual question. But, please do help me.
I have a below code on Workbook that will take care of copy/paste data on sheets. It would allow me to paste data into the cells without changing format(past only values).
Basically, the code will use destination formatting. similar to "paste values". It would allow the user to paste data from any other format. So that format is consistent across sheets.
Private Sub Workbook_SheetChange(ByVal Sh As Object, ByVal Target As Range)
Dim vNewValues as Variant
NewValues = Target
Application.EnableEvents = False
Application.Undo
Target = NewValues
Application.EnableEvents = True
End Sub
Along with above code, I also have another code on the sheet that will help me to clear the contents and code is linked to a button. So, when the button is pressed it will clear the contents of the sheet.
Private Sub ResetKey_Click()
If MsgBox("Content of the sheet will be deleted and cannot be restored", vbOKCancel + vbInformation) = vbOK Then
Worksheets("User").Range("E19:I3018").ClearContents
Else
Exit Sub
End If
End Sub
Concern: I see a conflict between these codes. Because, when I click on the button I get the error that will point me to Application.Undo in the first code. I tried debugging the code but I was not able to get both to work. Please Suggest.
This will work:
Private Sub ResetKey_Click()
If MsgBox("Content of the sheet will be deleted and cannot be restored", vbOKCancel + vbInformation) = vbOK Then
Application.EnableEvents = False
Worksheets("User").Range("E19:I3018").ClearContents
Application.EnableEvents = True
Else
Exit Sub
End If
End Sub
That is, you have to suppress the Change event in other macros working on that sheet. Not elegant but doable.
To clarify what the first macro does: it saves the cell's content, undoes a user's paste or input, and then only fills in the value which was pasted, leaving the format intact. The problem with this approach is that the event handler does not return information on the action that triggered it - it could be a paste but clearing cells as well.
You can only use .Undo to undo the last action in the worksheet not to undo vba actions and must be the first line in the macro. As explained in the documentation.Application.Undo. Quote below:
This method undoes only the last action taken by the user before
running the macro, and it must be the first line in the macro. It
cannot be used to undo Visual Basic commands.

Change print method from PDF to default printer for a form printout

Hi I am trying to get the print out of a userform. While printing its always prompting to save the userform (a prompt box is being displayed). I don't want it.
I want to get the printout directly after choosing the printer without saving it.
Code below:
Private Sub CommandButton2_Click()
CommandButton2.Visible = False
CommandButton1.Visible = False
Application.Dialogs(xlDialogPrinterSetup).Show
Me.PrintForm
CommandButton2.Visible = True
CommandButton1.Visible = True
End Sub
You may not like this answer, because it involves more workt, but you probably want the userform to add the data into a formatted worksheet and then use the printout method of the worksheet.
https://msdn.microsoft.com/en-us/vba/excel-vba/articles/sheets-printout-method-excel
Worksheets.("yourWorksheet").printout _
activeprinter:= yourPDFprinter, _
PrintTofFile:= True, _
PrToFileName:= filePathAndName
If you want to do it through the UserForm, you're probably going to end up making a bunch of Windows API calls.
At the end of your code you can force a workbook to close without saving any changes, type the following code in a Visual Basic module of that workbook:
Sub Auto_Close()
ThisWorkbook.Saved = True
End Sub
Because the Saved property is set to True, Excel responds as though the workbook has already been saved and no changes have occurred since that last save.
EDIT:
Now I know what you're after I am going to presume you've created a button press for your form, so I think something like https://www.mrexcel.com/forum/excel-questions/544657-specifying-printer-printing-userform-using-vba.html . This will help you with what you want, as it will show you how to change the default print method, form PDF.

Run VBA function with a button

I defined a VBA function that returns a filesize. Now I want to invoke it with a button that's calling a different macro. My expectation is that after running the macro it'll invoke my function at the very end. My problem is that when I put a formula into a cell it will return a current filesize only the moment I enter the formula. When I edit the file, save it and reopen, the =wbksize() will still display the filesize from before my edits.
So the purpose of this macro run by a button is to refresh the filesize value. Here's my attempt to do it.
function:
Function wbksize()
myWbk = Application.ThisWorkbook.FullName
wbksize = FileLen(myWbk)
End Function
refresh:
Worksheets("Sheet2").Range("K1").Calculate
The above doesn't seem to work :/
Function works fine, but refreshing should call function.
Function wbksize() As String
myWbk = Application.ThisWorkbook.FullName
wbksize = Str(FileLen(myWbk))
End Function
Sub Refresh()
Worksheets("Sheet2").Range("K1") = wbksize
End Sub
This may or may not help you in your situation....LINK
I have never needed to use this on excel but it maybe what your looking for, you can set custom functions as 'VOLATILE' which forces excel to run them whenever ANYTHING get calculated, again i have never needed to use this so i cannot comment on any drawbacks or anything but it looks like it may work in your case.
I've tested these, and they both work fine. It depends on what you want your trigger to be: Changing the worksheet, or performing a Calculate on the worksheet.
Put either of these in your Worksheet. The first will trigger on Calculate, the second on Change.
Private Sub Worksheet_Calculate()
Dim lFileLength As Long
Application.EnableEvents = False 'to prevent endless loop
lFileLength = FileLen("\\MyFile\Path\AndName.XLS.XLS")
ThisWorkbook.Sheets("Sheet1").Range("A1").Value = CStr(lFileLength)
MsgBox "You changed THE CELL!"
Application.EnableEvents = True
End Sub
Private Sub Worksheet_Change(ByVal Target As Range)
Dim lFileLength As Long
Application.EnableEvents = False 'to prevent endless loop
lFileLength = FileLen("\\MyFile\Path\AndName.XLS")
ThisWorkbook.Sheets("Sheet1").Range("B1").Value = CStr(lFileLength)
MsgBox "You changed THE CELL!"
Application.EnableEvents = True
End Sub

Excel macro doesn't update correctly

i have created macro for excel but it seems somewhere i have done something wrong,
i want to fetch an image from a URL and then update it up to 1 second (more or less)
Sub GetPicture()
PictureURL = "This is where i put the URLi want"
Set MyPict = ActiveSheet.Pictures.Insert(PictureURL)
Cells(1).Value = Now
nextTime = Now + TimeValue("00:00:01")
End Sub
when i run the macro doesn't do anything,only when i press f5 the it updates as fast as i press f5,also what is the value to update less than 1 second ("00:00:01"),when i try ("00:00:0.5") it comes up with "run time error 13" "type mismatch"
Any help is very much apreciated.
In Excel, you can use VBA to trigger code that updates a Worksheet on specific intervals. The code below shows how you would activate a Timer each time the Worksheet is activated by a user. Whenever the Timer fires (on 1 second intervals here) this code updates Cell A1 in the ActiveSheet with the current Time.
To further customize, you would add code to the OnTimerMacro in order to update a Picture or whatever else you recurring task might be.
(Props to Hartmut Gierke for his post on the topic.)
Option Explicit
Dim Execute_TimerDrivenMacro As Boolean
Sub Start_OnTimerMacro()
Execute_TimerDrivenMacro = True
Application.OnTime Time + TimeValue("00:00:01"), ActiveSheet.Name & ".OnTimerMacro"
End Sub
Sub Stop_OnTimerMacro()
Execute_TimerDrivenMacro = False
End Sub
Public Sub OnTimerMacro()
If Execute_TimerDrivenMacro Then
' Do something e.g. put the actual time into cell A1 of the active sheet
ActiveSheet.Cells(1, 1).Value = Time
' At the end restart timer
Application.OnTime Time + TimeValue("00:00:01"), ActiveSheet.Name & ".OnTimerMacro"
End If
End Sub
Private Sub Worksheet_Activate()
'Start the timer driven method when opening the sheet
Start_OnTimerMacro
End Sub
Private Sub Worksheet_Deactivate()
'Stop the timer driven method when opening the sheet
Stop_OnTimerMacro
End Sub
if you would like the macro to repeat you have to put it in a do...until loop. The only problem, is that you can't really have the macro run all the time. There has to be a way to stop it. The do...until loop will help with this, but you have to come up with a reasonable exit from the loop. Can you give a little more background as to what you ultimately want this to do?
Also it sounds like you want the running of the macro to be triggered by something other than the pressing of F5. Can you explain when you would like to see it start?