I've made two macros in SolidWorks VBA (one to save PDF files and one to save DXF files), and I want to share some common code. This should be easy, by calling subprocedures from the other modules (e.g. call module.sub()).
I have two modules (one for PDFs and one for DXFs), and one "shared" module that the other two call.
Here's some of my PDF-saving code, in the "pdf" module. The "dxf" module is the same except it calls shared_module.shared_sub("dxf") instead of shared_module.shared_sub("pdf").
Sub save_pdf()
' Calls the shared module to save a PDF file this time
call shared_module.shared_sub("pdf")
End Sub
Here's some of my "shared_module" code:
Dim swApp As SldWorks.SldWorks
Dim swModel As ModelDoc2
Sub shared_sub(byval file_extension as String)
' get the solidworks application object
Set swApp = Application.SldWorks
' get the current opened document object
Set swModel = swApp.ActiveDoc
' do some shared stuff here, with the "file_extension" string
...
End Sub
My problem is, when I create or edit the macro button, the "Method:" dropdown menu is empty... Any ideas why?
If I have just one macro/module/main() subroutine, it shows up. But when I add other modules, it's blank.
I checked this answer and this answer, but they don't help for this problem.
I've solved the problem, simply by putting all my subs into the same module. It seems if there's more than one module, the "create macro button" dropdown doesn't know where to look for the "methods"...
No big deal to have them all in one module, instead of having separate modules.
Related
I'd like to do what feels like a fairly simple task, and I've found the specific API Help pages which should make it clear, but, I can't actually make things work.
The Key steps that I would like to achieve are:
Rename the active document
Update References to this document to accommodate new name
Save active document.
This help page shows the Usage for renaming the doc, and under the "Remarks" heading, includes links to the next two steps, mentioning them off hand as if implementing them would be easy.
https://help.solidworks.com/2020/English/api/sldworksapi/SolidWorks.Interop.sldworks~SolidWorks.Interop.sldworks.IModelDocExtension~RenameDocument.html?verRedirect=1
The trouble is, I'm a bit of a VBA beginner - usually I get by with the 'record' function, and then tidying things up from there - but undertaking the steps above manually doesn't result in anything being recorded at all for one reason or another.
Assuming I am able to pass in the item to be renamed (I'll define a variable at the start of the Sub for this e.g. swModel = swApp.ActiveDoc), and the new name (NewName = "NEW NAME HERE"), How would I translate the Help API into a Sub that I can actually run?
Two of them suggest declaring as a Function, and one as a Public Interface - I've never used these before - do these just run in a standard Module? Do I need to write a 'master Sub' to call the different functions sequentially, or could these be included directly in the sub, if they're only to be used once?
[Feeling a little lost - it's demoralizing when the help files aren't all that helpful]
Let me know if there's any more information missing that I can add to improve my question - as I said, I'm fairly new to this coding thing...
The "record" function is sometimes a good point to start but there are a lot of functions it can't recognize while you execute them manually.
The API Help is then useful to find out how to use a specific function.
In almost every example the use of a specific method (e.g. RenameDocument) is only shown abstract. There is always a instance variable which shows you the object-type needed to call this method. So you can use these in every sub you want, but beforehand need access to the specific instance objects.
For your example the RenameDocument method is called with an object of the type IModelDocExtension. First thing for you to do is to get this object and then you can call the method as described in the help article.
Under Remarks in the article you find additional information for what you maybe have to do before or after calling a method.
For your example it is mentioned that the renaming takes permanently place after saving the document.
And finally here is what you want to do with some VBA code:
Dim swApp As SldWorks.SldWorks
Dim swModel As ModelDoc2
Sub main()
' get the solidworks application object
Set swApp = Application.SldWorks
'get the current opened document object
Set swModel = swApp.ActiveDoc
' get the modeldocextension object
Dim swModelExtension As ModelDocExtension
Set swModelExtension = swModel.Extension
Dim lRet As Long
lRet = swModelExtension.RenameDocument("NEW NAME")
If lRet = swRenameDocumentError_e.swRenameDocumentError_None Then
MsgBox "success renaming"
Else
MsgBox "failed with error: " & lRet
End If
End Sub
Afterwars you have to process the return value to check for errors described in this article: https://help.solidworks.com/2020/English/api/swconst/SolidWorks.Interop.swconst~SolidWorks.Interop.swconst.swRenameDocumentError_e.html
I need a to create a macro on my Word which would save the printed document automatically to another location on my computer. I have looked through hundreds of options online and here also but couldn't exactly what I was looking for. Saving it to another location is easy but it should make a copy only when the the document is in the print queue. Can anyone help me out here? Need it for my employee monitoring.
Use the Application.DocumentBeforePrint event which is triggered everytime before the opened document is printed.
The following code needs to be placed in a class module, and an instance of the class must be correctly initialized.
Option Explicit
Public WithEvents App as Word.Application
Private Sub App_DocumentBeforePrint(ByVal Doc As Document, ByRef Cancel As Boolean)
Doc.SaveAs2 FileName:="your path"
End Sub
Code 1: Put this code into a class module called "EventClassModule".
According to Using events with the Application object you need to register the event handler before it will work.
Option Explicit
Dim ThisWordApp As New EventClassModule
Public Sub RegisterEventHandler()
Set ThisWordApp.App = Word.Application
End Sub
Code 2: Put this code into a normal module (not a class module).
The event DocumentBeforePrint will work after you registered the event handler by running RegisterEventHandler, this is recommended to run whenever the document is opened. Therefore we use the Document.Open event in ThisDocument:
Option Explicit
Private Sub Document_Open()
RegisterEventHandler
End Sub
Code 3: Put this code into "ThisDocument".
Then save, close and re-open your document. If you print it now, the event DocumentBeforePrint will execute right before printing.
Edit according comment:
Image 1: Make sure your class module is named correctly.
I currently use word templates in my business to help create customer mailings. There's a existing userform coding on our templates that insert the proper return address, phone number, legal entity, etc... onto the letter. Lately we've been having issues with a subset of users (seemingly random, we've been through exhaustive testing and cant find rhyme/reason) who do not receive the popup to choose whatever items they need when they enable macros. In testing we've found that the users have been able to go into vbasic and run the userform manually. I'd like to build in a hotkey option to initialize the userform, please help! Currently, the ThisDocument object has this code to run the userform:
Private Sub Document_New()
Channel_Select.Show
End Sub
From what I've been able to find online, something like the OnKey Command from excel could be used, but I cant find the Word version. Does anyone have experience in this?
I found the answer on a thread about keybinding, but I can't refind that thread. I added 2 new modules to my document, module1 has:
Option Explicit
Sub AddKeyBinding()
With Application
' \\ Do customization in THIS document
.CustomizationContext = ThisDocument
' \\ Add keybinding to this document Shorcut: Alt+r
.KeyBindings.Add KeyCode:=BuildKeyCode(wdKeyAlt, wdKeyR), _
KeyCategory:=wdKeyCategoryCommand, _
Command:="TestKeybinding"
End With
End Sub
Module 2
Option Explicit
' \\ Test sub for keybinding
Sub TestKeybinding()
Channel_Select.Show
End Sub
Run the AddKeyBinding macro, then alt+r will launch the userform on the document
Consider the following code:
Public Sub VBACompilerIsMad()
Dim Ap As Application
Dim Wb As Workbook
Dim Ws As Worksheet
Debug.Print Ap.XXX ' No compile error
Debug.Print Wb.XXX ' No compile error
Debug.Print Ws.XXX ' Compile error
End Sub
When I compile this, I get a compiler error for referring to an inexisting member of Worksheet. However, if I comment out the last line, there is no compiler error, even though neither Application nor Workbook have a method or property XXX. It is as if I declared Ap and Wb as Object variables.
Why does the compiler treat Application / Workbook differently from Worksheet?
Are there any other classes like this, that the compiler seems to treat as if they were Object?
As I have been explained (kudos go respectively), this is a COM feature.
By default COM assumes an interface is extensible, that is, it allows adding members at run time. If that is not the desired behaviour, one can apply the [nonextensible] attribute to the interface definition, which declares the interface only accepts methods explicitly defined in the type library.
dispinterface _Application and dispinterface _Workbook do not have this flag set in the Excel type library, dispinterface _Worksheet does.
Similarly, ADO's dispinterface _Connection does not have [nonextensible], dispinterface _Command does.
To learn which are extensible, add a reference to TypeLib Info in the project's References and run:
Dim t As tli.TLIApplication
Set t = New tli.TLIApplication
Dim ti As tli.TypeLibInfo
Set ti = t.TypeLibInfoFromFile("excel.exe")
Dim i As tli.InterfaceInfo
For Each i In ti.Interfaces
If (i.AttributeMask And tli.TYPEFLAG_FNONEXTENSIBLE) <> tli.TYPEFLAG_FNONEXTENSIBLE Then
Debug.Print i.Name
End If
Next
You will see that almost all interfaces are extensible here, so most of them get pushed out of the debug window and you will only see the last ones. Change the <> to = to print those that are not extensible, there are much less of them.
A bit of a hypothesis:
You can call a stored procedure on an ADODB.Connection object like a native method (at the bottom).
(The examples for this on several msdn sites look oddly messed up).
So there is some mechanism like 'anonymous/dynamic methods' in VBS/VBA.
It may be a similar mechanism activated here for Application and Workbook classes - although I don't see where and how exactly.
A test supports the basic idea:
I have tested this with a reference to Microsoft ActiveX Data Objects 2.8 Library:
Public Sub testCompiler()
Dim cn As ADODB.Connection
Dim cmd As ADODB.Command
Debug.Print cn.XXX
Debug.Print cmd.XXX
End Sub
cn.XXX does not throw a compile error, cmd.XXX does.
GSerg's answer is indeed outstanding, I love the whole COM type library IDL and how some attributes there can govern the behaviour in the Excel VBA IDE. Long may this arcane knowledge of COM be handed down! And, I realise this question has been bountied to give that answer more rep but when a bounty is set it appears on my radar and I have a view on this matter.
So although GSerg's answer gives the mechanism it does not give the rationale, i.e. it gives the how but not the why. I'll attempt to answer the why.
Some of the answer why is already given by Martin Roller (OP) in his comments about Application and WorksheetFunction. This, to me, is a convincing reason to keep Application extensible and I'll not consider Application further.
Let us turn to Workbook and Worksheet and we best start with some code to demonstrate, so you will need to begin with two fresh workbooks, call them MyWorkbook.xlsm and OtherWorkbook.xlsm. So some instructions:
In OtherWorkbook.xlsm go the code module ThisWorkbook and paste the code
Option Explicit
Public Function SomeFunctionExportedOffOtherWorkbook() As String
SomeFunctionExportedOffOtherWorkbook = "Hello Matt's Mug!"
End Function
In MyWorkbook.xlsm go the Sheet1 code module and paste the code
Option Explicit
Public Function SomeFunctionExportedOffCodeBehindSheet1() As String
SomeFunctionExportedOffCodeBehindSheet1 = "Hello Martin Roller!"
End Function
Now, in the VBA IDE change the codename of Sheet1 to codebehindSheet1
Now, in a new standard module in MyWorkbook.xlsm add the following code
Sub TestingObjectLikeInterfacesOfWorkbookAndCodeBehindWorksheet_RunMany()
'* For this example please rename the 'CodeName' for Sheet1 to be "codebehindSheet1" using the IDE
Debug.Assert ThisWorkbook.Worksheets.Item("Sheet1").CodeName = "codebehindSheet1"
Dim wb As Workbook
Set wb = Application.Workbooks.Item("OtherWorkbook")
'* Workbook dispinterface needs to not marked with nonextensible attribute
'* so that it doesn't trip up over exported function in another workbook
'* below SomeFunctionExportedOffOtherWorkbook is defined in the ThisWorkbook module of the workbook "OtherWorkbook.xlsm"
Debug.Print wb.SomeFunctionExportedOffOtherWorkbook
'*Not allowed --> Dim foo As Sheet1
'*have to call by the 'code behind' name which is usually Sheet1 but which we changed to illustrate the point
Debug.Print codebehindSheet1.SomeFunctionExportedOffCodeBehindSheet1
End Sub
Now run this code above.
You've probably read the code and hopefully understood the point I'm making but let me spell it out. We need Workbook to remain extensible because it may contain a reference to another workbook which may be exporting a method or function and we'd like no compile errors.
However, for the Worksheet, to do a similar export we again add code to the code behind module but there is a difference in referencing the module: one grabs a reference to that code behind module by using its VBA code name, most people do not change this from Sheet1 (that is why you were invited to change it above).
So the interface obtained by the code behind module name needs to extensible and not the Excel.Worksheet interface.
P.S. Anyone got a copy of TLI.dll?
As a workaround it could still be possible to create your own interface and implement this interface. Then declare a variable as INewInterface and all the compiler messages will be there :). Here simple example with custom interface for a UserForm. HTH
Interface
Public CancelButton As MSForms.CommandButton
Public DataList As MSForms.ListBox
Public CommandBox As MSForms.TextBox
Implementation
Implements IMyForm
Private Property Set IMyForm_CancelButton(ByVal RHS As MSForms.ICommandButton)
End Property
Private Property Get IMyForm_CancelButton() As MSForms.ICommandButton
End Property
Private Property Set IMyForm_CommandBox(ByVal RHS As MSForms.IMdcText)
End Property
Private Property Get IMyForm_CommandBox() As MSForms.IMdcText
End Property
Private Property Set IMyForm_DataList(ByVal RHS As MSForms.IMdcList)
End Property
Private Property Get IMyForm_DataList() As MSForms.IMdcList
End Property
Usage
Note: MyForm is existing VBA Form which has been added to the project.
I have VS 2012 with the main project as a Windows form application and an added project as a excel workbook. I have some code that are exactly the same on both projects so I am trying to save time by sharing that code.
From my excel project I added a reference to my windows form project and imported the namespace. I can access the public functions on my main project, but I cannot seem to be able to access my public subs.
I also tried creating a module a adding a link between the projects but that would cause me to also update the code in two places. Besides, I think that creating a link may also cause some issues at deployment.
For example, in my windows form project I have the following I want to access from my second project
Public Sub closeXLApp()
'This sub is called to close the application without
'saving any changes to the workbook. The sub closes
'the app, workbook and sheet and performs some garbage clean up
'as well making sure that the opened Excel instance is cleared from memory.
xlBook.Close(SaveChanges:=False)
xlApp.Quit()
System.Runtime.InteropServices.Marshal.ReleaseComObject(xlSheet)
System.Runtime.InteropServices.Marshal.ReleaseComObject(xlBook)
System.Runtime.InteropServices.Marshal.ReleaseComObject(xlApp)
xlSheet = Nothing
xlBook = Nothing
xlApp = Nothing
GC.Collect()
End Sub
On my second project I created a reference and imported the namespace as:
Imports Compensation_Template_Welcome_Page
So when I try to access the above public sub from my second project as:
Private Sub btnMinCloseProject_Click(sender As Object, e As EventArgs) Handles btnMinCloseProject.Click
'This procedure runs when the btnMinCloseProject is clicked. The
'procedure calls the function to close workbook without saving changes.
closeXLApp()
End Sub
I get an error saying that the sub is not declared or not accessible due to its protection level.
Is there a better way to accomplish this? Even if is a longer route, I just want it to make it efficient in the long run.
Thanks
You're sub closeXLApp() is not static (shared), so you need to create an instance of the class where this sub is containted and then call the sub.