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.
Related
I have a .docm file with a Document_Open-sub, which calls another sub to open a small userform - or it's supposed to trigger and open - but it doesn't...
If I make changes to the document and save it - it still doesn't work.
If I make insignificant changes to the VBA-code in the same document (like adding a space character outside a sub) and save it - it suddenly works.
Why does it not work all the time? - and what can I change to make it work?
Additional info (I have no idea if some of it might be relevant)
In most cases the document is downloaded from an internal website and saved locally.
The document we've tested with is saved locally in a location which is trusted in Word.
In cases - where the sub isn't triggered automatically - the sub can be run with ALT+F8 (but that's not what I want).
The functionality works for some user - and not for others.
The functionality in the Document_Open-sub depends on a value read in CustomDocumentProperties.
System: Win10 - Word MSO365 build 2110
Edit - code added:
Public Sub Subrutine1()
ThisDocument.CustomDocumentProperties("prop1").Value = " " 'resetting prop1
UserForm1.Show 'prop1 is set from the userform
End Sub
Private Sub Document_Open()
If ThisDocument.CustomDocumentProperties("prop1").Value = " " Then
Call Subrutine1
End If
End Sub
You could rename your Sub "Subrutine1" to "AutoOpen."
I would recommend the following code.
Private Sub AutoOpen()
Dim myForm As UserForm1
Set myForm = New UserForm1
myForm.Show
Unload myForm
Set myForm = Nothing
End Sub
I would suggest resetting the document property as part of your userform initialization unless you sometimes use it without resetting that.
I would suggest using a document variable rather than a document property.
I suspect you would be better served with a document template rather than a document. In that case, the macro would be named "AutoNew."
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 have a Word which is used for form filling. The users are filling what is asked and some macros runs depending on what they chose or where they click. This works fine.
I recently decided to make a newer version which countains some ComboBox that are filled when the document is opened. To do that, I used the Document_Open() event.
Now here is the part I don't get : On my side, every time I do open the document, the event is triggered and the ComboBox that should get filled are filled. The problem is, so far I asked some people to test it on their own computer with their Word. Both of them came back to me saying that the ComboBox weren't filled when the document was openned.
To be more specific let's go in the code itself :
In ThisDocument, I have multiple subs that works perfectly fine and this Document_Open() event which gives me trouble.
Private Sub Document_Open()
Application.Run ("Fill.ComboBox")
End Sub
Here, Fill is the name of the Module which contains :
Sub ComboBox()
'Calling another Sub in this Module which adds the Items in the ComboBoxes
'from what parameters it is given
End Sub
Now that this has been stated, why would it work on my side but not work when another user tries it from their computer ?
Miscorsoft Word Versions :
I myself use Microsoft Office Profesionnal 2013 and so are the two users that tested it. That being said, this is intended to be working on any Word liscense from 2007 up to the current versions.
You can try in a module in your document:
Public Sub AutoOpen()
ComboBox
End Sub
I just used this in a Word 2016 document to automagically run a macro.
For documentation: https://support.microsoft.com/en-us/help/286310/description-of-behaviors-of-autoexec-and-autoopen-macros-in-word
I am working on a very large VBA project in Excel at my job. We are about 1500 lines of code for just one feature and have about a dozen more features to add. Because of this, I've been trying to break everything down so that I can keep code for each feature in separate places. OOP sucks in VBA... The problem being that these controls MUST have events fired. Of course, some events (like the TextBox_AfterUpdate event) are not available when you dynamically create controls. It's a bit convoluted because of everything that is going on, so I'll break it down the best I can:
I have a class module that represents a tab for a multipage control. When a user clicks on a tab, the Userform calls this class module and THERE I have the controls created dynamically. This way I can keep the code in that class module. I have a sub that I deemed as the "AfterUpdate" sub and put code that I needed to run there. Now the problem is to get that sub to be called at the appropriate time.
So what I did is to set up a Timer of sorts to check and see if the "ActiveControl" is said textbox. If it is not, we can assume that focus has left and we can raise that event. Here's the code I'm using:
An abbreviated version of the tab creation...
Private WithEvents cmbMarketplace As MSForms.ComboBox
Public Sub LoadTab(ByVal oPageTab As Object)
If TabLoaded Then Exit Sub
Set PageTab = oPageTab
Dim tmp As Object
Set tmp = PageTab.Add("Forms.Label.1")
tmp.Top = 6: tmp.Left = 6: tmp.Width = 48
tmp.Caption = "Marketplace:"
Set cmbMarketplace = PageTab.Add("Forms.ComboBox.1", "cmbMarketplace")
' LOAD OTHER CONTROLS '
TabLoaded = True
Start_Timer
End Sub
Then Start_Timer:
Public Sub Start_Timer()
TimerActive = True
Application.OnTime Now() + TimeValue("00:00:01"), "Timer"
End Sub
And the sub that is to be fired:
Public Sub Timer()
If TimerActive Then
' DO SOME RANDOM THINGS '
Application.OnTime Now() + TimeValue("00:00:01"), "Timer"
End If
End Sub
Does this seem like a reasonable approach to solving the problem I'm facing? I'm open to suggestions...
That's the first problem. This seems like a lot of work to accomplish this. (I'm working on getting visual studio, but I don't know if that's going to happen)
The above code will work but the "Timer" sub will not get raised at all. I get no errors if I just run the code. Everything is created, everything works as I would hope. However, if I step through the code, I eventually will get the following error:
Cannot run the macro "...xlsm!Timer". The macro may not be available in this workbook or all macros may be disabled.
Obviously neither of those suggestions are valid. Macros ARE enabled and the sub is in the same darn class module. I tried making it public, same problem. Tried "ClassModule1!Timer" to no avail. I'm at my wits end trying to figure this out. Thinking of having people write ALL this in the Userform or just giving up.
Does anybody have any suggestions on how to effectively break up large chunks of code? And does anybody have a clue why this sub will not run and seemingly cannot be found?
I understand that this is a confusing situation, so if you need more info or code examples or want to know why I have something set up the way I do, let me know.
Thanks!
Obviously neither of those suggestions are valid. Macros ARE enabled and the sub is in the same darn class module.
There's the problem: a macro cannot be in a class module. The message is entirely correct: VBA cannot see the Timer procedure, because it's not accessible.
A class module is a blueprint for an object, VBA (or any OOP language for that matter) can't do anything with a class module, without an instance of that class - i.e. an object.
Your timer callback needs to be a Public Sub in a standard module, so that it can be called directly as a macro. Public procedures of a class modules are methods, not macros.
Depending on what ' DO SOME RANDOM THINGS ' actually stands for, this may or may not require some restructuring.
1500-liner spaghetti code can be written in any language BTW.
Below is a question that I will answer myself, however it caused a GREAT deal of frustration for me and I had a lot of trouble searching for it on the web, so I am posting here in hopes of saving some time & effort for others, and maybe for myself if I forget this in the future:
For VBA (in my case, MS Excel), the Public declaration is supposed to make the variable (or function) globally accessible by other functions or subroutines in that module, as well as in any other module.
Turns out this is not true, in the case of Forms, and I suspect also in Sheets, but I haven't verified the latter.
In short, the following will NOT create a public, accessible variable when created in a Form, and will therefore crash, saying that the bYesNo and dRate variables are undefined in mModule1:
(inside fMyForm)
Public bYesNo As Boolean`
Public dRate As Double
Private Sub SetVals()
bYesNo = Me.cbShouldIHaveADrink.value
dRate = CDec(Me.tbHowManyPerHour.value)
End Sub
(Presume the textbox & checkbox are defined in the form)
(inside mModule1)
Private Sub PrintVals()
Debug.Print CStr(bYesNo)
Debug.Print CStr(dRate)
End Sub
However, if you make the slight alteration below, it all will work fine:
(inside fMyForm)
Private Sub SetVals()
bYesNo = Me.cbShouldIHaveADrink.value
dRate = CDec(Me.tbHowManyPerHour.value)
End Sub
(Presume the textbox & checkbox are defined in the form)
(inside mModule1)
Public bYesNo As Boolean`
Public dRate As Double
Private Sub PrintVals()
Debug.Print CStr(bYesNo)
Debug.Print CStr(dRate)
End Sub
mModule1 will work perfectly fine and, assuming that the fMyForm is always called first, then by the time the PrintVals routine is run, the values from the textbox and checkbox in the form will properly be captured.
I honestly cannot possibly fathom what MS was thinking with this change, but the lack of consistency is a huge suck on efficiency, learning idiosyncracies like these, which are so poorly documented that a Google search in 2013 for something that has likely been around for a decade or more is so challenging to search.
First comment:
Userform and Sheet modules are Object modules: they don't behave the same way as a regular module. You can however refer to a variable in a userform in a similar way to how you'd refer to a class property. In your example referring to fMyForm.bYesNo would work fine. If you'd not declared bYesNo as Public it wouldn't be visible to code outside of the form, so when you make it Public it really is different from non-Public. – Tim Williams Apr 11 '13 at 21:39
is actually a correct answer...
As a quick add-on answer to the community answer, just for a heads-up:
When you instantiate your forms, you can use the form object itself, or you can create a new instance of the form object by using New and putting it in a variable. The latter method is cleaner IMO, since this makes the usage less singleton-ish.
However, when in your userform you Call Unload(Me), all public members will be wiped clean. So, if your code goes like this:
Dim oForm as frmWhatever
Set oForm = New frmWhatever
Call oForm.Show(vbModal)
If Not oForm.bCancelled Then ' <- poof - bCancelled is wiped clean at this point
The solution I use to prevent this, and it is a nice alternative solution for the OP as well, is to capture all IO with the form (i.e. all public members) into a separate class, and use an instance of that class to communicate with the form. So, e.g.
Dim oFormResult As CWhateverResult
Set oFormResult = New CWhateverResult
Dim oForm as frmWhatever
Set oForm = New frmWhatever
Call oForm.Initialize(oFormResult)
Call oForm.Show(vbModal)
If Not oFormResult.bCancelled Then ' <- safe
There are other limitations to Public within Excel VBA.
MSoft documentation in learn.microsoft.com states that public variables are global to the VBA project - it's not true.
Public variables are only global to the workbook within which they are declared, and then only across standard modules. Public variables declared within workbook code are not visible in standard modules, even though standard module sub's are - which are defined to be public.
Public variables declared in one workbook's standard modules are certainly not accessible from other workbooks in the same VBA project, contrary to the MSoft documentation.