I will try to be as clear as possible in the description, so here goes nothing:
I have created a code in which the user selects his excel file and then the macro copies the Sheet from that file into my macro Workbook.
MyFile = Application.GetOpenFilename()
Workbooks.Open (MyFile)
ActiveSheet.Copy After:=wbook.Sheets(1)
ActiveSheet.Name = "Selected file"
Workbooks.Open (MyFile)
ActiveWorkbook.Close SaveChanges:=False
This is working, but what I realized is, that there might be cases where the selected file has multiple Sheets.
Is there a way to write the macro in which if my selected file has 1 sheet it runs the above code and if it has more than one sheet to let me select the sheet I want and then run the rest of the code?
Edit:
I thought of another way to handle this — perhaps closer to what you were looking for . . .
It's just an expansion of the basic pause routine that I use occasionally.
This is my "regular" Pause routine (using the Timer function):
Sub Pause(seconds As Single)
Dim startTime As Single
startTime = Timer 'get current timer count
Do
DoEvents 'let Windows "catch up"
Loop Until Timer > startTime + seconds 'repeat until time's up
End Sub
...so, it gave me an idea.
Honestly, I was a little surprised to discover that this works, since it's basically running two sections of code simultaneously.
Code for WaitForUserActivity :
Here's the code I used in the demo above:
Option Explicit
Public isPaused As Boolean
Sub WaitForUserActivity() 'THE 'RUN DEMO' BUTTON runs this sub.
Dim origSheet As String
isPaused = True 'flag "pause mode" as "on"
origSheet = ActiveSheet.Name 'remember current worksheet name
MsgBox "This will 'pause' code execution until you" & vbLf & _
"click the 'Continue' button, or select a different a worksheet."
Application.StatusBar = "PAUSED: Click ""Continue"", or select a worksheet."
Do 'wait for button click or ws change
DoEvents 'yield execution so that the OS can process other events
Loop Until (Not isPaused) Or (ActiveSheet.Name <> origSheet)
If isPaused Then 'the active worksheet was changed
MsgBox "Worksheet '" & ActiveSheet.Name & "' was selected." _
& vbLf & vbLf & "Now the program can continue..."
Else 'the button was clicked
MsgBox "The 'Continue' button was clicked." _
& vbLf & vbLf & "Now the program can continue..."
End If
Application.StatusBar = "Ready"
End Sub
Sub btnContinue() 'THE 'CONTINUE' BUTTON runs this sub.
isPaused = False 'flag "pause mode" as "off"
End Sub
To run the demo:
place the above code in a regular module
make sure the workbook has at least two worksheets
create two command buttons:
one for the "Run Demo" button, assign macro: WaitForUserActivity
one for the "Continue" button, assign macro: btnContinue
click the "Run Demo" button
The key command in the code is the DoEvents Function, which "yields execution so that the operating system can process other events."
DoEvents passes control to the operating system. Control is returned after the operating system has finished processing the events in its queue and all keys in the SendKeys queue have been sent.
DoEvents is most useful for simple things like allowing a user to cancel a process after it has started, for example a search for a file. For long-running processes, yielding the processor is better accomplished by using a Timer or delegating the task to an ActiveX EXE component - and the operating system takes care of multitasking and time slicing.
Any time you temporarily yield the processor within an event procedure, make sure the procedure is not executed again from a different part of your code before the first call returns; this could cause unpredictable results.
Further details (and warnings) at the source.
Original Answer:
Some suggested solutions:
Instead of "stopping" the code you could prompt the user to specify which worksheet.
The easiest way would be with an InputBox where the user would enter an ID number or otherwise identify the worksheet.
More complicated but more robust and professional-looking would be a custom dialog box with the help of a userform. There are several examples and tutorials online such as this one.
You could "pause" execution to give the user a set amount of time to select a worksheet, with a simple timer loop, ad you could even check the worksheet name to see if the user picked a new one, something like this:
Dim startTime As Single, shtName As String
If ThisWorkbook.Worksheets.Count = 1 Then
MsgBox "There is only one worksheet in this workbook."
Else
shtName = ActiveSheet.Name 'get name of active sheet
MsgBox "You have 5 seconds to select a worksheet after clicking OK.", _
vbOKOnly + vbInformation, "Select a worksheet... fast!"
startTime = Timer
Do
DoEvents
Loop Until Timer > startTime + 5
'check if user picked a new worksheet
If ActiveSheet.Name = shtName Then
MsgBox "You didn't select a new worksheet!"
Else
MsgBox "Thanks for selecting a new worksheet!"
End If
End If
It's a little hoakey but could work, especially if proper checks to make sure you've got the correct worksheet now.
I suppose you could create an worksheet event procedure that would run when a worksheet is activated, and checked a global variable to see if your "import procedure" was running, and if so, resume your code... but that would be messy and confusing and would require the code to exist in the workbook you're "importing".
Or, better than any of those would be to programmatically/logically determine which worksheet you need based on the contents of the worksheet. Is there a title? A certain date? Maybe the newest worksheet? Something in a certain cell? There must be something that differentiates it from the others.
Hopefully this gives you some ideas towards a non-linear solution. 😉
As in whole, I would recommend ashleedawg's solution, but if you
insisted on maintaining your code structure, your code could look
something like this:
You can distinguish between amount of Sheets a Workbook has using .Count property of the Sheets object (or Worksheets if you do not want to include Charts) and use InputBox to check for the sheet you want to look for.
MyFile = Application.GetOpenFilename()
Workbooks.Open (MyFile)
If ThisWorkbook.Sheets.Count = 1 Then
ThisWorkbook.ActiveSheet.Copy After:=wbook.Sheets(1)
ThisWorkbook.ActiveSheet.Name = "Selected File"
Else
Dim checkfor As String
checkfor = InputBox("What Sheet should I execute the code for?")
Dim i As Integer
For i = 0 To ThisWorkbook.Sheets.Count
If Trim(LCase(checkfor)) = Trim(LCase(Sheets(i).Name))) Then
ThisWorkbook.Sheets(i).Copy After := wbook.Sheets(1)
ThisWorkbook.Sheets(i).Name = "Selected file"
End If
Next i
End If
Workbooks.Open (MyFile)
ActiveWorkbook.Close SaveChanges:=False
Might need some further tweaking, because I was unsure what exactly you wanted to achieve.
Related
What I have is a macro that is designed to open other workbooks and refresh the contents within those workbooks. All of these work except one of them is intermittent, and by that I mean most times it works exactly as required, but randomly it will bring up an error stating the macro cannot be found. I haven't found so far a point which I can pinpoint where it does this so it has been difficult to debug.
below is the code:
Call Shell("K:\ASA_Reporting\Audits\MDA\ListCompletedAudits.bat")
' This wait has been added to allow the command to run in Console
Application.Wait (Now() + TimeValue("00:00:10"))
ThisWorkbook.Worksheets("Bits n Pieces").Range("G14").Value = "True"
Dim wb1 As Workbook
'This is to update the 5S Audit Dashboard
Set wb1 = Workbooks.Open("K:\ASA_Reporting\Audits\Audit Dashboard v002.xlsb", True, False, , , "password")
Application.Run "'Audit Dashboard v002.xlsb'!Refresh5S"
wb1.Close savechanges:=True
Stop
The point which has a commented out section "This is to update the 5S Audit Dashboard" is the code which is where the issue is occurring. The section above that in regards to the waiting 10 seconds is to correct a different issue which is unrelated to the 5S.
I know that the Macro is correct as it works on the 5S workbook, and it does work most times through my updater, but as I've said on occasion is brings up an error stating the macro does not exist.
I have checked the code what is being called (shown below) but I cannot any errors in this.
Sub Refresh5S()
Application.ScreenUpdating = False
Application.DisplayAlerts = False
Dim datasheet As Worksheet
Set datasheet = ThisWorkbook.Worksheets("Data Sheet")
Dim wb As Workbook
Dim ws As Worksheet
Set wb=Workbooks.Open("\\bosch.com\dfsrb\dfsuk\loc\wo\dept\service\ASA_Reporting\Audits
\5S Audit Master v2.xlsx", True, True)
Set ws = wb.Worksheets("Counts")
datasheet.Range("W6:Z21").Value = ws.Range("B5:E20").Value
datasheet.Range("AB6:AC400").Value = ws.Range("H5:I399").Value
datasheet.Range("AD6:AG400").Value = ws.Range("K5:N399").Value
datasheet.Range("AH6:AH400").Value = ws.Range("O5:O399").Value
wb.Close False
MsgBox "Thank you for your patience." & vbNewLine & vbNewLine & "The figures
should update in a few seconds.", vbInformation
Application.ScreenUpdating = True
Application.DisplayAlerts = True
End Sub
I thought I had found the answer on a Microsoft help page titled "Macro in Excel Stops After A Workbook.Open Command" However that was for when you press the shift key, and that is not happening in this instance, it is being left to it's on devices.
https://support.microsoft.com/en-us/help/555263
Does anyone have any idea why it randomly decides the Macro in the 5S workbook doesn't exist?
Edit:
Tried the below and it worked for the day without issue, then today the problem came back with the following error:
The code has not been changed except for the suggestions below, which worked for a day.
The issue was in the end with Excel, as the latest updates of Office have now resolved this issue.
I come again with new query, I have made the Macro and assigned it on Workbook.open, now I want little bit changed, I want to prompt message BOX which have contains Do You want to Stop Macro ? Option YES and NO, If I clicked on Yes with in 10 seconds of workbook Open, I want to stay on same excel without executing the Macro; otherwise, run the macro if I clicked NO or if 10 seconds is completed.
VBA has a MsgBox function, but you cannot let that one time out like you want.
To get a prompt with time-out functionality, you could use the Popup method of the WScript.Shell object. You can create the Shell object with a CreateObject call, and see the MSDN documentation for the Popup method for more details on how to use it.
#Dharmendra Maybe you can try this code:
Private Sub Workbook_Open()
wbClose
End Sub
Sub wbClose()
Dim time As Integer, prompt As String
time = 10 'this is in seconds format
prompt = "This Workbook will close in " & time & " seconds." & _
vbLf & "Press OK if you want some changes to this Workbook."
With CreateObject("WScript.Shell")
Select Case .Popup(prompt, time, "Message", 0)
Case 1
Exit Sub
End Select
End With
ThisWorkbook.Close True
End Sub
Just do some revisions if you like. Thanks!
I have some code which is intended to find a button inside another worksheet via some VBA script, if the buttons text contains "Hide all Rows", then execute code, else execute other code.
The if statement works and recognizes the button text, but it doesn't seem to want to recognize the macro name in the targetworkbook. I get the error 'The item with the specified name wasn't found'
I checked the targetworkbook macro name and it is correct,
code is below, am I doing something wrong here?
Sub MapValues(targetworkbook As Workbook, TargetSheet As Worksheet)
Dim shp as shape
Set shp = TargetSheet.Shapes("Button13" & TargetSheet.Name)
With targetworkbook
If shp.TextFrame.Characters.Text = "Hide All Rows" Then
targetworkbook.Application.Run "'" & targetworkbook.Name & "'!showAllRows"
Else
targetworkbook.Application.Run "'" & targetworkbook.Name & "!hideAllRows"
targetworkbook.Application.Run "'" & targetworkbook.Name & "!showAllRows"
End If
End With
End Sub()
It looks like the problem is with the text "Button13". Double check that it's not really something like "Button 13". The easiest way to do that is in the immediate window type
?sheet1.Shapes(1).name
and continue with 2,3,4 etc. until you get your name.
My goal is to implement some of functions where I give them parameters of power, frequency and speed of an electric motor, and look in another workbook (in which I have motor data) and return the size, shaft diameter and other motor details.
As I have not mastered much VBA I tried to implement a function that simply goes to a cell in another workbook and returns the value:
Function Test() As String
Dim name As String
With Workbooks.Open("D:\ExcelTest\WbSource.xlsm").Sheets("Sheet1")
name = .Cells(2, 3)
End With
Test= name
ActiveWorkbook.Save
ActiveWorkbook.Close
End Function
The problem is that it gives me a #VALUE! error, but each variable used is defined as a string and the cells has general format (if I change cells format to text it gives me the same message).
Try as I might, I could not get workbooks.open to work in a function, even if the function calls a sub. You could open the catalogue file in the workbook open event, and close it again in the before close event.
In the VProject Explorer, right click on "ThisWorkBook," and "View code".
In the pick list at the top, select Workbook, and the sub Workbook_open() procedure should be created. If not, select "Open" in the right pick list. Put in the following:
Application.Workbooks.Open ("D:\ExcelTest\WbSource.xlsm")
ThisWorkbook.Activate 'restores the "focus" to your worksheet
Then click the right pick list and select "beforeClose" and put in
On Error Resume Next 'this keeps it from crashing if the catalogue is closed first
Workbooks("WbSource.xlsm").Close
As long as the worksheet opens the wbsource file first, the function will work.
Here is an approach with scheduling UDF execution in queue, and processing outside UDF that allows to get rid of UDF limitations. So the value from the closed workbook got via ExecuteExcel4Macro() by a link.
Put the following code into one of the VBAProject Modules:
Public Queue, QueueingAllowed, UDFRetValue
Function UDF(ParamArray Args())
If IsEmpty(Queue) Then
Set Queue = CreateObject("Scripting.Dictionary")
UDFRetValue = ""
QueueingAllowed = True
End If
If QueueingAllowed Then Queue.Add Application.Caller, (Args)
UDF = UDFRetValue
End Function
Function Process(Args)
If UBound(Args) <> 4 Then
Process = "Wrong args number"
Else
' Args(0) - path to the workbook
' Args(1) - filename
' Args(2) - sheetname
' Args(3) - row
' Args(4) - column
On Error Resume Next
Process = ExecuteExcel4Macro("'" & Args(0) & "[" & Args(1) & "]" & Args(2) & "'!R" & Args(3) & "C" & Args(4))
End If
End Function
Put the following code into ThisWorkbook section of VBAProject Excel Objects:
Private Sub Workbook_SheetCalculate(ByVal Sh As Object)
Dim Item, TempFormula
If Not IsEmpty(Queue) Then
Application.EnableEvents = False
QueueingAllowed = False
For Each Item In Queue
TempFormula = Item.FormulaR1C1
UDFRetValue = Process(Queue(Item))
Item.FormulaR1C1 = TempFormula
Queue.Remove Item
Next
Application.EnableEvents = True
UDFRetValue = ""
QueueingAllowed = True
End If
End Sub
After that you can get the values from closed workbook via worksheet formula using UDF:
=UDF("D:\ExcelTest\";"WbSource.xlsm";"Sheet1";2;3)
Anyway you can add Workbooks.Open() or any other stuff into Function Process(Args) to make it to work the way you want. The code above is just an example.
I've answered the similar questions here and here, so that descriptions might be helpful.
I suggest:
open WbSource.xlsm either manually or via VBA outside the UDF.
pass the parameters to the UDF
have the UDF search down the columns of the newly opened workbook to find the correct record
have the UDF pass the row number back to the worksheet
in the worksheet, use Match()/Index() formulas to retrieve other data.
I am just wondering if it would be possible to do error handling on an external macro. Basically what I want to achieve is I have have thousands of excel workbooks that come in daily and I want to open each of them and run the macro from them (easily done just use the Application.run feature )
Application.Run ("'" & ActiveWorkbook & "'!Export")
What I want to achieve is I want to run error resolving function if that external macro incurs an error.
This is what I have so far
Dim str_SearchFile, str_FileName, str_SearchPath As String
Dim wb_WorkBook As Workbook
Application.ScreenUpdating = False
Application.DisplayAlerts = False
str_ThisBook = ActiveWorkbook.Name 'Set the current workbook for later reference
str_SearchPath = Sheets("Control Panel").Range("E2")
str_SearchFile = Sheets("Control Panel").Range("E2") & "\*.xls*" 'Sets the file type to search for
str_NextFile = Dir(str_SearchFile, vbDirectory) 'Sets the amount of files in the directory matching the criterea (.xls)
Do While Len(str_NextFile) > 0
On Error Resume Next
Set wb_WorkBook = Workbooks.Open(Filename:=str_SearchPath & "\" & str_NextFile, Password:="")
If Err.Number = 0 Then
On Error GoTo 0
Application.Run ("'" & str_NextFile & "'!Export")
str_FileName = str_SearchPath & "\Done" & "\" & str_NextFile
wb_WorkBook.Save
wb_WorkBook.Close
FileCopy (str_SearchPath & "\" & str_NextFile), str_FileName
Kill (str_SearchPath & "\" & str_NextFile)
End If
str_NextFile = Dir
Loop
Application.ScreenUpdating = True
Application.DisplayAlerts = True
End Sub
Any Advise is very welcome!
Thank you in advance
You won't be able to get this to work the way you are trying.
The MSDN on On Error Statement indicates that it (emphasis added):
Enables an error-handling routine and specifies the location of the
routine within a procedure; can also be used to disable an
error-handling routine.
The VBE Glossary defines a procedure as:
A named sequence of statements executed as a unit. For example,
Function, Property, and Sub are types of procedures. A procedure name
is always defined at module level. All executable code must be
contained in a procedure. Procedures can't be nested within other
procedures.
This means that calling error handling before calling the macro in the other book, will be ignored in the called macro (confirmed through testing).
The only way that you would be able to enable error handling would be to actually modify the code in the workbook prior to calling the macro... which is very complicated. For your reference, here is a webpage giving an example of editing project code from VBA.
The easiest way I would deal with this is to change your external workbook's "Export" sub into a function that returns a value - Say an integer.
What you can then do is put error trapping into that function and, based upon the outcome of the procedure it can return, say:
0 = All Went Well
1 = Failed to do XXX
2 = Failed to do YYY
You could then change your code to something like this:
Select Case Application.Run ("'" & str_NextFile & "'!Export")
Case 0
MsgBox "All Went Well"
Case 1
MsgBox "Failed to do XXX"
Case 2
MsgBox "Failed to do YYY"
End Select
This will allow you to put the error trapping where it belongs and know how the procedure ran.
hope this helps