I have a Windows Forms / Visual Basic .NET application which basically acts as an editor. One of the functions it should provide its users with is the ability to run a set of rules on their current project and report any problems it finds. These rules will all be run by a BackgroundWorker object living in a form, so execution progress can be reported.
My strategy for implementing this is to give the form a bunch of private instance methods which take in the user's project data (contained in, say, a ProjectData object), run whatever check is needed at that step, and return an object containing displayable information about the test and whether it passed or failed. (Let's call this class CheckResult for discussion purposes). So, just to be clear, all of these methods would have a signature along the lines of:
Private Function SomeCheckToRun(data As ProjectData) As CheckResult
I could just define all these methods as usual and manually list them out one-by-one to be called in the BackgroundWorker's DoWork event handler, but that approach seems like it would get tedious for a potentially large number of checks. It would be nice if I could just define each method I want to run and have it decorated as such, so that a loop could automatically find each such method definition and run it.
What I'm thinking I would like to do instead is to define a custom attribute class used to indicate which instance methods are meant to be run as checks (maybe called CheckToRunAttribute), then use reflection somehow to get a list of all these methods currently implemented in the form and execute each one in sequence, perhaps by setting up a delegate to run for each one. The number of these methods in total, and how many have been executed so far, can be used by the BackgroundWorker to indicate overall progress.
So far, the structure of my code would look something like the following in my mind:
Private Sub MyBackgroundWorker_DoWork(sender As Object, e As DoWorkEventArgs) Handles MyBackgroundWorker.DoWork
' TODO: Get a list of all the <CheckToRun()> methods here,
' run each one in a loop, and report progress after each one.
End Sub
' Further down...
<CheckToRun()>
Private Function SomeCheckToRun(data As ProjectData) As CheckResult
' Check code in here.
End Function
<CheckToRun()>
Private Function AnotherCheckToRun(data As ProjectData) As CheckResult
' Check code in here.
End Function
<CheckToRun()>
Private Function YetAnotherCheckToRun(data As ProjectData) As CheckResult
' Check code in here.
End Function
' And so on...
This is not something I have much experience with doing though. I know about the Type.GetMethods() function, how to write custom attributes, and the Func(T, TResult) delegate, but I'm not sure how to put it all together for what I want.
tl;dr: Given a class with multiple private instance functions following the same signature and all marked with the same custom attribute, how can I count how many there are and then run each one?
You can use Reflection to list all the methods with your custom attribute. This is a Linq solution:
Dim methods = Me.GetType.GetMethods(Reflection.BindingFlags.NonPublic Or Reflection.BindingFlags.Instance)_
.Where(Function(m) m.GetCustomAttributes(GetType(CheckToRun), False)_
.Length > 0).ToArray()
And then run them like:
For Each method As Reflection.MethodInfo In methods
method.Invoke(Me, New Object() {methodParams})
Next
Specifically aimed at winforms development.
I suspect that the answer to this is probably No but S.O. has a nice way of introducing me to things I didn't know so I thought that I would ask anyway.
I have a class library with a number of defined methods therein. I know from personal experimentation that it is possible to get information about the application within which the class library is referenced. What I would like to know is whether it would be possible to get information about the value of a property of a control on a form when a routine on that form calls a method in my class library without passing a specific reference to that form as a parameter of the method in the class library?
So purely as an example (because it's the only thing I can think of off the top of my head). Is there a way that a message box (if it had been so designed to do so in the first place) could 'know' from which form a call to it had been made without that form being specifically referenced as a parameter of the message box in the first place?
Thanks for any insights you might have.
To address the example of the MessageBox, in many of the cases you can use the active form. You can retrieve it by using Form.ActiveForm. Of course, as regards the properties that you can request, you are limited to the properties provided by the Form or an interface that the Form implements and that the method in the other assembly also knows. To access other properties you can use Reflection, but this approach would neither be straightforward nor would it be clean.
In a more general scenario, you could provide the property value to the method as a parameter. If it is to complex to retrieve the value of the property and the value is not needed every time, you can provide a Func(Of TRESULT) to the method that retrieves the value like this (sample for an integer property):
Public Sub DoSomethingWithAPropertyValue(propValFunc As Func(Of Integer))
' Do something before
If propertyValueIsNeeded Then
Dim propVal = propValFunc()
End If
' Do something afterwards
End Sub
You call the method like this:
Public Sub SubInForm()
Dim x As New ClassInOtherAssembly()
x.DoSomethingWithAPropertyValue(Function() Me.IntegerProperty)
End Sub
I kind of question your intentions. There's no problem sending the information to a function or the constructor.
Instead of giving the information to the class, the class would ask for the information instead using an event.
Module Module1
Sub Main()
Dim t As New Test
AddHandler t.GetValue, AddressOf GetValue
t.ShowValue()
Console.ReadLine()
End Sub
Public Sub GetValue(ByRef retVal As Integer)
retVal = 123
End Sub
End Module
Class Test
Public Delegate Sub DelegateGetValue(ByRef retVal As Integer)
Public Event GetValue As DelegateGetValue
Public Sub ShowValue()
Dim val As Integer
RaiseEvent GetValue(val)
Console.WriteLine(val)
End Sub
End Class
Beginner question. How come I can do this:
Public Class Form1
Private StudentsInMyRoom As New ArrayList
Public Class student
Public name As String
Public courses As ArrayList
End Class
Private Sub btnCreateStudent_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnCreateStudent.Click
Dim objStudent As New student
objStudent.name = "Ivan"
objStudent.courses = New ArrayList
StudentsInMyRoom.Add(objStudent)
End Sub
End Class
But I CANNOT do this:
Public Class Form1
Private StudentsInMyRoom As New ArrayList
Public Class student
Public name As String
Public courses As ArrayList
End Class
Dim objStudent As New student
objStudent.name = "Ivan"
objStudent.courses = New ArrayList
StudentsInMyRoom.Add(objStudent)
End Class
In the second example, all of the objStudent.etc get squiggly underlined and it says "declaration expected" when I hover over it. It's the same code except now it is not tied to clicking a button. Can't figure out what is the difference.
It's because the implementation needs to be in a method, the way you have it means the code couldn't possibly be executed, how would you reference this code from elsewhere?
It doesn't have to be tied to a click however:
Private Sub AnyNameYouLike
Dim objStudent As New student
objStudent.name = "Ivan"
objStudent.courses = New ArrayList
StudentsInMyRoom.Add(objStudent)
End Sub
Will work.
Rather than tell you how to fix this code directly, I'm going to explain what I think is going wrong with your thought process, so you can also do a better job writing code in the future.
What I see here is a simple misunderstanding for someone new to programming of how classes work. When you build and define a class, you are not (yet) allocating any memory in the computer, and you are not yet telling the computer to do anything. All you are doing is telling the computer about how an object might look at some point in the future. It's not until you actually create an instance of that class that anything happens:
Public Class MyClass
Public MyField As String
End Class
'Nothing has happened yet
Public myInstance As New MyClass()
'Now finally we have something we can work with,
' but we still haven't done anything
myInstance.MyField = "Hello World"
'It's only after this last line that we put a string into memory
Classes can only hold a few specific kinds of things: Fields, Properties, Delegates (events), and Methods (Subs and Functions). All of these things in the class are declarations of something, rather than the thing itself.
Looking at your samples, the code from your second example belongs inside of a method.
If you want this code to run every time you work with a new instance of your class, then there is a special method, called a constructor, that you can use. That is declared like this:
Public Class MyClass
Public MyField As String
'This is a constructor
Public Sub New()
MyField = "Hello World"
End Sub
End Class
However, even after this last example you still haven't told the computer to do any work. Again, you must create an instance of the class before the code in that constructor will run.
This is true of all code in all .Net programs anywhere. The way your program starts out is that the .Net framework creates an instance of a special object or form and then calls (runs) a specific method in that form to sort of get the ball rolling for your program. Everything else comes from there.
Eventually you will also learn about Shared items and Modules, that can (sort of) break this rule, in that you don't have to create an instance of the object before using it. But until you are comfortable working with instances, you should not worry about that too much.
Finally, I want to point out two practices in your code that professional programmers would consider poor practice. The first is ArrayLists. I can forgive this, because I suspect you are following a course of study that just hasn't covered generics yet. I only bring it up so you can know not to get too attached to them: there is something better coming. The second is your "obj" prefix. This was considered good practice once upon a time, but is no longer fashionable and now thought to be harmful to the readability of your code. You should not use these prefixes.
This is probably a simple one but I can't seem to figure it out.
I have a bunch of form items created by the form designer declared as (in frmAquRun.Designer.vb)
Public WithEvents btnAquRunEvent1 As VisibiltyButtonLib.VisibilityButton
Public WithEvents btnAquRunEvent2 As VisibiltyButtonLib.VisibilityButton
... etc
And I basically want to be able to supply a number to a function access each of these fields. So I wrote this function. (in frmAquRun.vb)
Const EVENT_BUTTON_PREFIX As String = "btnAquRunEvent"
Public Function getEventButton(ByVal id As Integer) As Windows.Forms.Button
Dim returnButton As Windows.Forms.Button = Nothing
Try
returnButton = DirectCast(Me.GetType().InvokeMember(eventButtonName, Reflection.BindingFlags.GetField Or Reflection.BindingFlags.Public Or Reflection.BindingFlags.Instance, Nothing, Me, Nothing), Windows.Forms.Button)
Catch ex As Exception
End Try
Return returnButton
End Function
But it always seems to be generating field not found exceptions.
The message in the exception is "Field 'ATSIS_ControlProgram.frmAquRun.btnAquRunEvent1' not found.".
The namespace and form name in the message are correct. Any idea what i'm doing wrong?
The problem is that for WithEvents fields, VB actually creates a property that does the necessary event handler attaching and detaching. The generated property has the name of the field. The actual backing field gets renamed to _ + original name.1)
So in order for your code to work just prefix the button name by _ or use the BindingFlag that corresponds to the property getter (instead of GetField).
Alternatively, you can do this a lot easier by using the Controls collection of the form:
returnButton = DirectCast(Me.Controls(eventButtonName), Windows.Forms.Button)
But beware that this only works if the button is top-level, i.e. not nested within a container control on the form.
1) This is an implementation detail of the VB compiler but it’s portable (especially to Mono’s vbnc compiler) since the handling for WithEvents fields is described in great detail in the VB language specifications.
The problem is that the event handlers aren't really fields. As compiled they're really properties that implement add_btnAquRunEventX, remove_btnAquRunEventX and fire_btnAquRunEventX methods. There are ways of using reflection to get around this, but that's probably not the best way to approach the problem. Instead you can simply create a List<> and populate it with the event handlers, then index into that list.
I'm a little rusty in VB syntax but it should look something like this:
Dim events = New List<EventHandler>()
events.Add( btnAquRunEvent1 )
events.Add( btnAquRunEvent2 )
....
events( 0 )( null, EventArgs.Empty )
Take a step back though and evaluate why you're invoking by index. There may be a simpler way of abstracting the whole thing that doesn't involve all this indirection.
When is it appropriate to use a class in Visual Basic for Applications (VBA)?
I'm assuming the accelerated development and reduction of introducing bugs is a common benefit for most languages that support OOP. But with VBA, is there a specific criterion?
It depends on who's going to develop and maintain the code. Typical "Power User" macro writers hacking small ad-hoc apps may well be confused by using classes. But for serious development, the reasons to use classes are the same as in other languages. You have the same restrictions as VB6 - no inheritance - but you can have polymorphism by using interfaces.
A good use of classes is to represent entities, and collections of entities. For example, I often see VBA code that copies an Excel range into a two-dimensional array, then manipulates the two dimensional array with code like:
Total = 0
For i = 0 To NumRows-1
Total = Total + (OrderArray(i,1) * OrderArray(i,3))
Next i
It's more readable to copy the range into a collection of objects with appropriately-named properties, something like:
Total = 0
For Each objOrder in colOrders
Total = Total + objOrder.Quantity * objOrder.Price
Next i
Another example is to use classes to implement the RAII design pattern (google for it). For example, one thing I may need to do is to unprotect a worksheet, do some manipulations, then protect it again. Using a class ensures that the worksheet will always be protected again even if an error occurs in your code:
--- WorksheetProtector class module ---
Private m_objWorksheet As Worksheet
Private m_sPassword As String
Public Sub Unprotect(Worksheet As Worksheet, Password As String)
' Nothing to do if we didn't define a password for the worksheet
If Len(Password) = 0 Then Exit Sub
' If the worksheet is already unprotected, nothing to do
If Not Worksheet.ProtectContents Then Exit Sub
' Unprotect the worksheet
Worksheet.Unprotect Password
' Remember the worksheet and password so we can protect again
Set m_objWorksheet = Worksheet
m_sPassword = Password
End Sub
Public Sub Protect()
' Protects the worksheet with the same password used to unprotect it
If m_objWorksheet Is Nothing Then Exit Sub
If Len(m_sPassword) = 0 Then Exit Sub
' If the worksheet is already protected, nothing to do
If m_objWorksheet.ProtectContents Then Exit Sub
m_objWorksheet.Protect m_sPassword
Set m_objWorksheet = Nothing
m_sPassword = ""
End Sub
Private Sub Class_Terminate()
' Reprotect the worksheet when this object goes out of scope
On Error Resume Next
Protect
End Sub
You can then use this to simplify your code:
Public Sub DoSomething()
Dim objWorksheetProtector as WorksheetProtector
Set objWorksheetProtector = New WorksheetProtector
objWorksheetProtector.Unprotect myWorksheet, myPassword
... manipulate myWorksheet - may raise an error
End Sub
When this Sub exits, objWorksheetProtector goes out of scope, and the worksheet is protected again.
I think the criteria is the same as other languages
If you need to tie together several pieces of data and some methods and also specifically handle what happens when the object is created/terminated, classes are ideal
say if you have a few procedures which fire when you open a form and one of them is taking a long time, you might decide you want to time each stage......
You could create a stopwatch class with methods for the obvious functions for starting and stopping, you could then add a function to retrieve the time so far and report it in a text file, using an argument representing the name of the process being timed. You could write logic to log only the slowest performances for investigation.
You could then add a progress bar object with methods to open and close it and to display the names of the current action, along with times in ms and probable time remaining based on previous stored reports etc
Another example might be if you dont like Access's user group rubbish, you can create your own User class with methods for loging in and out and features for group-level user access control/auditing/logging certain actions/tracking errors etc
Of course you could do this using a set of unrelated methods and lots of variable passing, but to have it all encapsulated in a class just seems better to me.
You do sooner or later come near to the limits of VBA, but its quite a powerful language and if your company ties you to it you can actually get some good, complex solutions out of it.
Classes are extremely useful when dealing with the more complex API functions, and particularly when they require a data structure.
For example, the GetOpenFileName() and GetSaveFileName() functions take an OPENFILENAME stucture with many members. you might not need to take advantage of all of them but they are there and should be initialized.
I like to wrap the structure (UDT) and the API function declarations into a CfileDialog class. The Class_Initialize event sets up the default values of the structure's members, so that when I use the class, I only need to set the members I want to change (through Property procedures). Flag constants are implemented as an Enum. So, for example, to choose a spreadsheet to open, my code might look like this:
Dim strFileName As String
Dim dlgXLS As New CFileDialog
With dlgXLS
.Title = "Choose a Spreadsheet"
.Filter = "Excel (*.xls)|*.xls|All Files (*.*)|*.*"
.Flags = ofnFileMustExist OR ofnExplorer
If OpenFileDialog() Then
strFileName = .FileName
End If
End With
Set dlgXLS = Nothing
The class sets the default directory to My Documents, though if I wanted to I could change it with the InitDir property.
This is just one example of how a class can be hugely beneficial in a VBA application.
I use classes if I want to create an self-encapsulated package of code that I will use across many VBA projects that come across for various clients.
I wouldn't say there's a specific criterion, but I've never really found a useful place to use Classes in VBA code. In my mind it's so tied to the existing models around the Office apps that adding additional abstraction outside of that object model just confuses things.
That's not to say one couldn't find a useful place for a class in VBA, or do perfectly useful things using a class, just that I've never found them useful in that environment.
For data recursion (a.k.a. BOM handling), a custom class is critically helpful and I think sometimes indispensable. You can make a recursive function without a class module, but a lot of data issues can't be addressed effectively.
(I don't know why people aren't out peddling BOM library-sets for VBA. Maybe the XML tools have made a difference.)
Multiple form instances is the common application of a class (many automation problems are otherwise unsolvable), I assume the question is about custom classes.
I use classes when I need to do something and a class will do it best:) For instance if you need to respond to (or intercept) events, then you need a class. Some people hate UDTs (user defined types) but I like them, so I use them if I want plain-english self-documenting code. Pharmacy.NCPDP being a lot easier to read then strPhrmNum :) But a UDT is limited, so say I want to be able to set Pharmacy.NCPDP and have all the other properties populate. And I also want make it so you can't accidentally alter the data. Then I need a class, because you don't have readonly properties in a UDT, etc.
Another consideration is just simple readability. If you are doing complex data structures, it's often beneficial to know you just need to call Company.Owner.Phone.AreaCode then trying to keep track of where everything is structured. Especially for people who have to maintain that codebase 2 years after you left:)
My own two cents is "Code With Purpose". Don't use a class without a reason. But if you have a reason then do it:)
You can also reuse VBA code without using actual classes. For example, if you have a called, VBACode. You can access any function or sub in any module with the following syntax:
VBCode.mysub(param1, param2)
If you create a reference to a template/doc (as you would a dll), you can reference code from other projects in the same way.
Developing software, even with Microsoft Access, using Object Oriented Programming is generally a good practice. It will allow for scalability in the future by allowing objects to be loosely coupled, along with a number of advantages. This basically means that the objects in your system will be less dependent on each other, so refactoring becomes a lot easier. You can achieve this is Access using Class Modules. The downside is that you cannot perform Class Inheritance or Polymorphism in VBA. In the end, there's no hard and fast rule about using classes, just best practices. But keep in mind that as your application grows, the easier it is to maintain using classes.
As there is a lot code overhead in using classes in VBA I think a class has to provide more benefit than in other languages:
So this are things to consider before using a class instead of functions:
There is no class-inheritance in vba. So prepare to copy some code when you do similar small things in different classes. This happens especially when you want to work with interfaces and want to implement one interfaces in different classes.
There are no built in constructors in vba-classes. In my case I create a extra function like below to simulate this. But of curse, this is overhead too and can be ignored by the one how uses the class. Plus: As its not possible to use different functions with the same name but different parameters, you have to use different names for your "constructor"-functions. Also the functions lead to an extra debug-step which can be quite annoying.
Public Function MyClass(ByVal someInit As Boolean) As MyClassClass
Set MyClass = New MyClassClass
Call MyClass.Init(someInit)
End Function
The development environment does not provide a "goto definition" for class-names. This can be quite annoying, especially when using classes with interfaces, because you always have to use the module-explorer to jump to the class code.
object-variables are used different to other variable-types in different places. So you have to use a extra "Set" to assign a object
Set varName = new ClassName
if you want to use properties with objects this is done by a different setter. You have to use "set" instead of "let"
If you implement an interface in vba the function-name is named "InterfaceName_functionName" and defined as private. So you can use the interface function only when you cast the Variable to the Interface. If you want to use the function with the original class, you have to create an extra "public" function which only calls the interface function (see below). This creates an extra debug-step too.
'content of class-module: MyClass
implements IMyInterface
private sub IMyInterface_SomeFunction()
'This can only be called if you got an object of type "IMyInterface"
end function
private sub IMyInterface_SomeFunction()
'You need this to call the function when having an object of the type "MyClass"
Call IMyInterface_SomeFunction()
end function
This means:
I !dont! use classes when they would contain no member-variables.
I am aware of the overhead and dont use classes as the default to do things. Usually functions-only is the default way to do things in VBA.
Examples of classes I created which I found to be useful:
Collection-Classes: e.g. StringCollection, LongCollection which provide the collection functionality vba is missing
DbInserter-Class: Class to create insert-statements
Examples of classes I created which I dont found to be useful:
Converter-class: A class which would have provided the functionality for converting variables to other types (e.g. StringToLong, VariantToString)
StringTool-class: A class which would have provided some functionality for strings. e.g. StartsWith
You can define a sql wrapper class in access that is more convenient than the recordsets and querydefs. For example if you want to update a table based on a criteria in another related table, you cannot use joins. You could built a vba recorset and querydef to do that however i find it easier with a class. Also, your application can have some concept that need more that 2 tables, it might be better imo to use classes for that. E.g. You application track incidents. Incident have several attributes that will hold in several tables {users and their contacts or profiles, incident description; status tracking; Checklists to help the support officer to reply tonthe incident; Reply ...} . To keep track of all the queries and relationships involved, oop can be helpful. It is a relief to be able to do Incident.Update(xxx) instead of all the coding ...
In VBA, I prefer classes to modules when:
(frequent case) I want multiple simultaneous instances (objects) of a common structure (class) each with own independent properties.
Example:Dim EdgeTabGoogle as new Selenium.EdgeDriverDim EdgeTabBing as new
Selenium.EdgeDriver'Open both, then do something and read data to and from both, then close both
(sometimes) I want to take advantage of the Class_Initialize and Class_Terminate automatic functions
(sometimes) I want hierarchical tree of procedures (for just variables a chain of "Type" is sufficient), for better readability and Intellisense
(rarely) I want public variables or procedures to not show in Intellisense globally (unless preceded by the object name)
I don't see why the criteria for VBA would be any different from another language, particularly if you are referring to VB.NET.