I have a userform that displays information about the active doc (dwg, assy, or part). I would like it to update that information automatically if the user switches to a different doc.
The only solution I have found is to require the user to click on a “refresh” button to update the info, but until the user does that, the information displayed is incorrect and that can create problems. I was hoping that there was an event that would be triggered when the userform regains focus after the user changes to a different doc, but it seems none exists.
Use events. Here’s a (hastily thrown together) simple example. You will have to add a class module along with your user form and the SW macro. The compelte macro is attached below:
'SW Macro Code
Dim swApp As SldWorks.SldWorks
Dim handler As EventHandler
Dim form As MyUserForm
Sub main()
Set swApp = Application.SldWorks
Dim mDoc As ModelDoc2
Set mDoc = swApp.ActiveDoc
Set handler = New EventHandler
Set form = New MyUserForm
handler.SetForm form
If Not mDoc Is Nothing Then
form.docTitle = mDoc.GetTitle
End If
form.Show vbModeless
End Sub
'Event Handler class module code
Dim WithEvents swApp As SldWorks.SldWorks
Dim WithEvents pDoc As PartDoc
Dim WithEvents aDoc As AssemblyDoc
Dim WithEvents dDoc As DrawingDoc
Dim myForm As MyUserForm
Private Sub Class_Initialize()
Set swApp = Application.SldWorks
End Sub
Public Sub SetForm(form As MyUserForm)
Set myForm = form
End Sub
Private Function pDoc_DestroyNotify2(ByVal DestroyType As Long) As Long
updateForm
End Function
Private Function aDoc_DestroyNotify2(ByVal DestroyType As Long) As Long
updateForm
End Function
Private Function dDoc_DestroyNotify2(ByVal DestroyType As Long) As Long
updateForm
End Function
Private Function swApp_ActiveDocChangeNotify() As Long
Dim mDoc As ModelDoc2
Set mDoc = swApp.ActiveDoc
myForm.docTitle = mDoc.GetTitle
Dim docType As swDocumentTypes_e
docType = mDoc.GetType
Select Case docType
Case swDocPART
Set pDoc = mDoc
Set aDoc = Nothing
Set dDoc = Nothing
Case swDocASSEMBLY
Set pDoc = Nothing
Set aDoc = mDoc
Set dDod = Nothing
Case swDocDRAWING
Set pDoc = Nothing
Set aDoc = Nothing
Set dDoc = mDoc
Case Else
Set pDoc = Nothing
Set aDoc = Nothing
Set dDoc = Nothing
End Select
End Function
Sub updateForm()
If swApp.GetDocumentCount = 1 Then 'Last document is closing
myForm.docTitle = "No active document"
End If
End Sub
Thank you for the quick and complete response. Can you give me a little help understanding how to implement this class module? I’ve been writing macros for 6 years, but this is new to me. My interpretation is that if a document is closed (destroyed), it will run the “updateForm” sub and that’s where I would run a public function/sub in another module? Will it work if the user neither opens or closes a document but just switches (alt-tab for example) between docs?
Any time the active document changes (switching windows, opening/closing documents) , SOLIDWORKS will call the swApp_ActiveDocChangeNotify function.
Every time a document is closed, the corresponding DestroyNotify2 function will be called. The only reason the example code is listening to the three DestroyNotify2 events is so that if no more documents are open, the user form can be updated to say ‘no active document’. Here’s an animated GIF showing it in action:
I was able to implement your code and it works great. I did have to manually create the project because it would not compile, asking for 2025 add-ins (I’m at 2024). But after transposing the code, it works great.
Now to integrate it into my project.
Thank you again. This really helps. In the past, I was hoping to implement mouse handlers, but couldn’t figure it out. I feel that with this example, I’m one step closer to figuring that out too.
You can declare variables WithEvents in a form as well as a class module. Any advantage to using a separate class module to monitor the active document vs just using events in the form itself?
Isn’t it a heck of a lot more work though? Based on the assumption that all the model interaction is already being done in code residing in the form, swApp is probably already delcared in the form’s code anyway. If you just change it to WithEvents , all you need to do is trigger the form update on ActiveDocChangeNotify.
I also don’t understand why you’re triggering the form update on destroy of the previously active document instead of in the ActiveDocChangeNotify. Unless I’m reading incorrectly, this code will only update the form when the previously active document is closed, right? I belive OP wants to update the form when the active document changes, whether the current doc was closed or not.
So… Really all they need to do is declare swApp WithEvents, then add this one function somewhere in the form code. Or am I missing something?
Private Function swApp_ActiveDocChangeNotify() As Long
If not swApp.ActiveDoc is nothing then
[whatever function/sub currently updates the form]
end if
End Function
No. It updates the form in swApp_ActiveDocChangeNotify as well:
Private Function swApp_ActiveDocChangeNotify() As Long
Dim mDoc As ModelDoc2
Set mDoc = swApp.ActiveDoc
myForm.docTitle = mDoc.GetTitle
ActiveDocChangeNotify isn’t fired when the last open document is closed. That little surprise is why I added the listeners for DestroyNotify2 for parts/assemblies/drawings so the form could reflect the fact that nothing is open.
Ah, sorry, I didn’t look at the details of your post close enough (sorta skimming bc I’ve been out of the country for 2 weeks). I see now that changing the title of the form is the only updating that the example does. I was looking for a call to updateForm in the ActiveDocChange, but didn’t notice that updateForm actually does nothing unless nothing is open.
While it’s indeed thorough, IMHO the benefit of showing in the form that no document is open is more trouble than I’d usually go to.
Yeah. It bothered me that after closing the last document the form still listed the last title. This was all just a quickly thrown together example while sitting at home half-delirious with the flu. Not my best work.
I am also interested in implementing event handlers in macros where there is no form. Is that possible?
The SW API help doesn’t document very well the methods/functions in the code shared here. I imagine that these are a standard part of VB (MS Forms 2.0?) Can you recommend a reference where I can learn more about this topic? I’m finding references that are specific to Excel and Access, but there seems to be significant differences between the API for those apps and the SW API implementation.
The event handlers don’t require forms at all. The complete list of events can be found here:
Scroll toward the bottom to the ‘Delegates’ section. Also, once you adorn a variable with the WithEvents keyword, when you select the variable in the left drop down at the top of the VBA code editor, the right drop down will list all the available event methods.
Thank you Jim and Josh. I’m gaining a much better understanding of event handlers. This is really going to expand the kinds of productivity enhancers I can create for my team.
You may also want to consider creating a custom Task Pane View instead of a separate floating form. It can make your tools look more integrated with SW itself.
Jim,
I’m successfully using the handlers you wrote, but stuck on something. When I have closed the only remaining doc that was open and that handler fires, when I ask what is the current FilePathName, I get the FilePathName of the file that was last open. Also, GetDocumentCount returns a non-zero value.
I tried inserting
Set swModel = swApp.ActiveDoc
to get it to forget the last file and hopefully return ‘nothing’, but that doesn’t work.
I don’t know what info you want to show in your scenario, but if you want it to be visible all the time, you could add to the status bar also.
The solidworks api let’s us users add 5 statusbar zones that will appear on the right of the statusbar next to the default information. SolidWorks API documentation
Thank you for the tip. I can totally use that functionality to let the user know when a macro that takes a long time to complete is busy. In the spirit of giving back, here’s some prototype code I managed to get working.
'https://help.solidworks.com/2024/English/api/sldworksapi/SOLIDWORKS.Interop.sldworks~SOLIDWORKS.Interop.sldworks.IFrame~GetStatusBarPane.html
'https://help.solidworks.com/2024/English/api/sldworksapi/SolidWorks.Interop.sldworks~SolidWorks.Interop.sldworks.IFrame_members.html
Public MyStatusIFrame As SldWorks.Frame
Public MyStatusBarPane As StatusBarPane
Set MyStatusIFrame = swApp.Frame
MyStatusIFrame.SetStatusBarText ("Something")
Set MyStatusBarPane = MyStatusIFrame.GetStatusBarPane()
MyStatusBarPane.Visible = True
MyStatusBarPane.text = "Something"
MsgBox "Pause"
MyStatusBarPane.Visible = False
Set MyStatusBarPane = Nothing
Set MyStatusIFrame = Nothing