VBA - Passing variable between modules - vba

I'm new to VB6, and trying to write some macro in use for CorelDraw.
I have a variable that need to be passed from Class module to Standard module, in my Class Module "SaveOptClass" I have a public variable called IsSaved and it's set on the class module:
Public IsSaved As Boolean
Public Sub SaveFile()
If <some triggers> Then
IsSaved = True
End If
In Standard module:
Sub DoSave()
Dim SaveClass As SaveOptClass
Set SaveClass = New SaveOptClass
If SaveClass.IsSaved = True Then
ActiveDocument.Save
Else
Form1.Show
End If
End Sub
Basically I'm trying to pass "IsSaved" boolean value from class module to standard. (If IsSaved is true, save the document or else display a form.)
I have tested that the boolean is True when I executed the code, but I can't get the state to pass to the other module.
Is there something I miss here? Thanks in advance.

As already answered by #shahkalpesh the problem is that you're not using a meaningful instance of SaveOptClass.
In my opinion the best way to design this kind of dependency is by mean of a parameter in the routine is using it and avoid as much as possible the use of global variables.
In your case this brings to this rewriting:
' in someOtherModule
Public Sub DoSave(saveOptObj as SaveOptClass)
If saveOptObj.IsSaved Then ' = True is unnecessary
ActiveDocument.Save
Else
Form1.Show
End If
End Sub
The client code could be:
private saveOptObj as SaveOptClass
Public Sub SaveFile()
If <some triggers> Then
saveOptObj.IsSaved = True
End If
' ....
someOtherModule.DoSave(saveOptObj)
' ...
Consider also, at this point, a renaming of DoSave, given that the actions taken suggest different semantics. In similar cases is preferable moving the If Else logic in the caller. Anyway, if you prefer to group actions with different semantics in the same routine you'd better use namings like DoSaveOr<SomethingElse>.

Related

Terminate method not called in Access 2021

Anyone else finding that their Terminate() method in Access isn't being called?
Here's my code for my cBusyPointer class with the comments removed for brevity:
Option Compare Database ' Use database order for string comparisons
Option Explicit ' Declare EVERYTHING!
Option Base 1 ' Arrays start at 1
Public Sub show()
DoCmd.hourGlass True
End Sub
Private Sub Class_Terminate()
DoCmd.hourGlass False
End Sub
Typical usage is:
Sub doTehThings()
Dim hourGlass As New cBusyPointer
hourGlass.show()
' Do all teh things
End Sub
In previous versions, this class would restore the hourglass whenever the object went out of scope and was destroyed.
I even tried to manually destroy my hourglass object:
Sub doTehThings()
Dim hourGlass As cBusyPointer
Set hourGlass = New cBusyPointer
hourGlass.show()
' Do all teh things
Set hourGlass = Nothing
End Sub
The only way to fix this is to add a hide() method and call that.
Has anyone else encountered this problem?
I cannot replicate the issue. The Terminate() method is called upon reaching the Set hourGlass = Nothing.
A couple of points:
Dim hourGlass As New cBusyPointer
This will create a new instance every time you call the hourGlass variable even to set it to Nothing. See the answer in the link below for additional info:
What's the difference between Dim As New vs Dim / Set
You should always use Dim/Set when working with objects.
hourGlass.show()
This does not even compile in VBA. Subs do not accept parentheses even when arguments are being expected, unless they are preceded with the Call keyword.
Lastly, the cleanest way to reference an object is to access it using the With statement which ensures the object is terminated when the End With statement is reached.
With New cBusyPointer
.show
End With

VBA Settings Dialog using MVP - do I need a model?

I've been reading up on many examples of MVP (Model-View-Presenter) and their variations (Passive view, Supervising view) to try and make my solutions more robust (and reusable) in VBA (using Excel as the host in this instance). The problem I've found is finding good, simple examples in VBA that are not complete overkill for the (hopefully) simple examples I need.
I'm attempting to create a "settings" dialogue that stores certain configuration in a worksheet (this is my "repository").
Here's my main procedure, triggered by the user:
Private Sub ShowImportSelector()
Dim importPresenter As DataImportPresenter
Set importPresenter = New DataImportPresenter
importPresenter.LoadConfig
If importPresenter.Show = -1 Then Exit Sub
importPresenter.SaveConfig
' begin processing...
If (CStr([Settings.SelectedVersion].Value2) = "QQ") Then
' ...
End If
End Sub
Here is my "presenter" (here I use range names for the source, and config destination):
Option Explicit
Private m_importForm As FImport
Private Sub Class_Initialize()
Set m_importForm = New FImport
End Sub
Public Sub LoadConfig()
m_importForm.SetAvailableVersions "tblVERSION"
m_importForm.SetAvailableSalesOrgs "tblSALESORG"
m_importForm.SetAvailableCategories "tblCATEGORY"
m_importForm.ToolName = "Forecast"
End Sub
Public Sub SaveConfig()
[Settings.SelectedVersion].Value2 = m_importForm.SelectedVersion
[Settings.SelectedSalesOrg].Value2 = m_importForm.SelectedSalesOrg
[Settings.SelectedCategory].Value2 = m_importForm.SelectedCategory
End Sub
Public Function Show() As Integer
m_importForm.Show vbModal
Show = m_importForm.Result
End Function
And now the "View" (a VBA Form):
Option Explicit
Private m_selectedVersion As String
Private m_selectedSalesOrg As String
Private m_selectedCategory As String
Private m_toolName As String
Private m_dialogueResult As Long
Public Property Get ToolName() As String
ToolName = m_toolName
End Property
Public Property Let ToolName(ByVal value As String)
m_toolName = value
ToolNameLabel.Caption = value
End Property
Public Property Get Result() As Long
Result = m_dialogueResult
End Property
Public Property Get SelectedVersion() As String
SelectedVersion = m_selectedVersion
End Property
Public Property Get SelectedSalesOrg() As String
SelectedSalesOrg = m_selectedSalesOrg
End Property
Public Property Get SelectedCategory() As String
SelectedCategory = m_selectedCategory
End Property
Public Sub SetAvailableVersions(ByVal value As String)
VersionSelector.RowSource = value
End Sub
Public Sub SetAvailableSalesOrgs(ByVal value As String)
SalesOrgSelector.RowSource = value
End Sub
Public Sub SetAvailableCategories(ByVal value As String)
CategorySelector.RowSource = value
End Sub
Private Sub SaveSelections()
m_selectedVersion = VersionSelector.value
m_selectedSalesOrg = SalesOrgSelector.value
m_selectedCategory = CategorySelector.value
End Sub
Private Sub CloseButton_Click()
m_dialogueResult = -1
Me.Hide
End Sub
Private Sub ImportButton_Click()
SaveSelections
m_dialogueResult = 0
Me.Hide
End Sub
At this point, I have become confused with the possible directions I could go in terms of adding a model to the above - question is: is this even needed for this simple example?
MVP architecture makes cleaner code, but cleaner code isn't the primary purpose of MVP; achieving loose coupling, higher cohesion, and testability is.
If loosely-coupled components and unit-testable logic isn't a requirement, then full-blown MVP is indeed overkill, and having the "model" exposed as properties on the "view" is definitely good enough, as it already helps making your "presenter" not need to care about form controls. You're treating the form as the object it's begging to be, and pragmatically speaking this could very well be all you need. I'd make the Show method return an explicit Boolean though, since it's implicitly used as such.
On the other hand, if you are shooting for decoupling and testability, then extracting the model from the view would only be step one: then you need to decouple the presenter from the worksheet, and maybe introduce some ISettingsAdapter interface that abstracts it away, such that if/when the configuration needs to go to a database or some .config file, your presenter code doesn't need to change in any way... but this requires designing the interfaces without having any particular specific implementation in mind, i.e. something that works without changes regardless of whether the data is on a worksheet, in some flat file, or in some database table.
MVP demands a paradigm shift: MVP isn't procedural programming anymore, it's OOP. Whether OOP is overkill for your needs depends on how much coupling you're willing to live with, and how frail this coupling is making your code in the face of future changes. Often, abstraction is enough: using named ranges instead of hard-coded range addresses is one way of improving the abstraction level; hiding the worksheet behind an adapter interface implemented by a worksheet proxy class (whatever you do, never make a worksheet module implement an interface: it will crash) is another - depends where your threshold for "overkill" is, but if you do achieve full decoupling and write the unit tests, nobody can blame you for going overboard: you're just following the industry best-practices that every programmer strives for, improving your skills, and making it much easier to later take that code and rewrite it in .NET, be it VB or C#. I doubt anyone would argue that full-blown MVP is overkill in .NET/WinForms.

How do I make a function return a value to be used immediately as part of the calling script?

I want to make a function that I can wrap around certain things and change the output based on the current user.
Assume there is a master user called "av". If the person currently using the DB is "av", then I need to alter the VBA code to allow him better priveleges.
Public Function masteruser(input_parm)
If GetUser = "av" Then
masteruser = ""
Else
masteruser = input_parm
End Function
I plan on using the function above like this:
Public Sub blah_FormTimer()
'rest of code
masteruser(Application.Quit)
'rest of code
End Sub
In the sub above, if the current user is "av", then the Application.Quit will effectively be removed. If not, then the code reads Application.Quit like normal.
How do I handle the input parameter in such a way as to return it to work with the rest of the VBA script like normal?
You can first create a wrapper sub/function, because Application.Quit is a method call and not a function call:
Public Sub QuitApplication()
Application.Quit
End Sub
And then use the following code:
Public Function masteruser(input_parm As String)
If GetUser = "av" Then
masteruser = ""
Else
Application.Run input_parm
End If
End Function
And of course
Public Sub blah_FormTimer()
'rest of code
masteruser "QuitApplication"
'rest of code
End Sub
You can of course create a function that creates a module, creates a wrapper sub/function, then pastes the code that you pass it in that function and executes it, and then deletes that module, but I wouldn't suggest it.
You can also make one especially for method calls, which would prevent the need for making wrapper functions for them:
Public Function masterusermethod(objObject as object, strMethod As String)
If GetUser = "av" Then
masteruser = ""
Else
CallByName objObject, strMethod, VbMethod
End If
End Function
(If anyone knows a more efficient way to do this, I'd like to know)

VBA - Class property with reserved keyword as the name

In VBA, is there any known mechanism to fool the compiler into allowing the use of reserved keywords as names for class properties? For example, I would like to create a property called Select in one of my class modules. However, the compiler flags my declaration as an error. Below is the syntax I used:
Public Property Get Select() As Database.SQLStatements
End Property
Database is my VBA project name and SQLStatements is one of the class modules I created. Also, I'm running the code in MS Access 2010.
You can do that and use any keyword/reserved word in your VBA. But then it will make your code a lot messy and very hard to read/debug/maintain.
If you have a bool property named If in your class you will end up with something like this If .If  Then, well, good luck reading that. Also code maintenance like Find/Replace/rename etc. will need extra caution and more work.
Anyhow, if you are willing to go through all this pain, here is how you do it.
After the keywords/reserved words add a invisible blank space using ALT+0160 and that's it. VBA will consider it a perfectly legal name. e.g. If .
Also, you will have to either use intellisense for using these propertynames or manually type the altcode everywhere. That's extra typing.
clsTest
Option Explicit
Private m_sSelect As String
Private m_bIF As Boolean
Public Property Get Select () As String '~~> Select () is actually typed as SelectALT+0160()
Select  = m_sSelect
End Property
Public Property Let Select (ByVal sNewValue As String)
m_sSelect = sNewValue
End Property
Public Property Get If () As Boolean
If  = m_bIF
End Property
Public Property Let If (ByVal bNewValue As Boolean)
m_bIF = bNewValue
End Property
Test Module
Option Explicit
Sub demo()
Dim objTestClass As clsTest
Set objTestClass = New clsTest
With objTestClass
.Select  = "It works. But it will, for sure, create readibility/maintenance issues."
.If  = False
End With
MsgBox objTestClass.Select 
'/ See how hard it will to read/debug this sort of code
With objTestClass
If .If  Then '~~> This line here :)
MsgBox "If prop value is TRUE"
Else
MsgBox "If prop value is FALSE"
End If
End With
End Sub
ALT+0160 <> Space

VBA global class variable

My obstacle is trying to get multiple subs to recognize class variables. When I try to declare them globally, I get a compile error: "Invalid outside procedure". Then, when I run a public function or sub to declare the variables, they remain undefined in the other subs. I want multiple subs to recognize the variables because their values are supposed to be altered via UserForm, and then utilized in a different sub.
If it could work in this manner, great, but I understand that my design could fundamentally be flawed. Please advise!
This is my Class definition, inserted as a Class module named "cRSM":
Option Explicit
Private pName As String
Private pDesiredGrowth As Double
'Name of RSM
Public Property Get Name() As String
Name = pName
End Property
Public Property Let Name(Value As String)
pName = Value
End Property
'Growth property
Public Property Get DesiredGrowth() As Double
DesiredGrowth = pDesiredGrowth
End Property
Public Property Let DesiredGrowth(Value As Double)
If Value > 0 And Value < 1 Then
pDesiredGrowth = Value
End If
End Property
This is invalid procedure error (which I put in the Global Declarations section):
'Bedoya
Dim Bedoya As cRSM
Set Bedoya = New cRSM
Bedoya.Name = "Bedoya"
And this is the "variable not defined error" (within a private sub):
Private Sub Add_Click()
**Bedoya.DesiredGrowth** = Txt2.Value
Thank you for your time
In a standard module (I name mine MGlobals), put
Public Bedoya As cRSM
Then in another standard module (I name mine MOpenClose), put
Sub Initialize()
If Not Bedoya Is Nothing Then
Set Bedoya = New cRSM
End If
End Sub
Any default properties you want set should be set in the Class_Initialize procedure. In any procedure that you want to use Bedoya, use
Initialize
and it will instantiate the global variable if necessary. The only difference between this and the New keyword is that you can't accidentally instantiate the variable with this method. You either call Initialize or you don't. A lot of VBA developers use New, but almost never do for that reason.
If I understood well You want a global object.
You can put the declaration in module like
public Bedoya As cRSM
then you have create the object ... you can use a global event inside the Workbook like
Private Sub Workbook_Open()
Set Bedoya = New cRSM
Bedoya.initialize("Bedoya") 'a method to initialize private variables
End Sub
Now you can use the global object. You have to restart the excel file or run this method manually.
Is not good style to use global variables, but sometimes is the more easy to do :P
What you want to do nowadays is done using singleton Software Pattern, but this is for other day hehehe