VB6 Error Handling in Properties -> Best practise? - properties

im using CodeSmart 2013 to analyze my VB6 projects.
The review function says i should add error handling (or at least "on error resume next") to my class-properties.
My properties typically look like this (in 99% cases):
Public Property Let PLZ(ByVal strPlz As String)
myStrPLZ = strPlz
End Property
Public Property Get PLZ() As String
PLZ = myStrPLZ
End Property
When i automatically add error handling it would look like this:
Public Property Let PLZ(ByVal strPlz As String)
'<EhHeader>
On Error GoTo PLZ_Err
'</EhHeader>
myStrPLZ = strPlz
'<EhFooter>
Exit Property
PLZ_Err:
MsgBox Err.Description & vbCrLf & _
"in TNV.frmSucheTeilnehmer.PLZ " & _
"at line " & Erl, _
vbExclamation + vbOKOnly, "Application Error"
Resume Next
'</EhFooter>
End Property
Public Property Get PLZ() As String
'<EhHeader>
On Error GoTo PLZ_Err
'</EhHeader>
PLZ = myStrPLZ
'<EhFooter>
Exit Property
PLZ_Err:
MsgBox Err.Description & vbCrLf & _
"in TNV.frmSucheTeilnehmer.PLZ " & _
"at line " & Erl, _
vbExclamation + vbOKOnly, "Application Error"
Resume Next
'</EhFooter>
End Property
is anyone practically doing error handling in properties? is this best practise? because this would be a lot of additional code to my projects (code overview decreases imho)
Thx for help!
Greetings from germany
SLimke

You should add error handling in just the same way as you would in any method (Sub/Function).
My rule of thumb is that if the method is only a couple of lines long and doesn't do anything exotic then there is no error handling required. The same can be said of a property Get/Set
Yes adding error handling to all your property getters and setters generates a lot of code so one option could be to use centralised error handling. See this post for some discussion of this: Centralized error handling in VB6

Don't take these guidelines (especially from a program written by someone with their own opinion of what "best practice" entails). I've never used it so I can't speak (nor do I doubt) it's quality... just saying.
MarkJ is also right. If you don't need any validation on property setting then just make them public variables. If you later need to add validation, making them into private variables and having the public Property Get/Let/Set instead won't break the interface and no other code changes are needed.
As for adding error handlers in every class property get/let? Why? Personally, I assume if someone is going to use my class they will at least take a moment to make sure they know what the !##$ they are doing and how to use it. If they set a property so incorrectly that it causes a runtime (or compile-time) error then they will know right away.
If the value for setting the property is coming from the user, i.e.
MyClass.SomeProperty = Input("Enter something: ")
Then I personally think the error code should be placed there. i.e.
On Error GoTo StupidUser
MyClass.SomeProperty = Input("Enter something: ")
Exit Sub
ErrorHandler:
MsgBox "You did not supply a correct value!", vbExclamation
Otherwise, what are you going to do when you handle an error in your class? Pass it on to a centralized error handler which will just create more code to follow and end up with the same result? The user being notified/written to a log/etc.?
I (again, personally... programming isn't black & white) prefer to put error handling code in the caller routine... the procedure that's using the class, setting properties, etc.
Any sort of "code optimizer/checker" is written by a person or multiple, and even reputable ones will bring up debate among those who use it on how it should work.
TL;DR: some recommendations can be safely ignored.

Why not use public fields instead of these boilerplate properties?
Public PLZ As String
If you later need to add logic, no problem! You can convert public fields to properties without breaking clients.

Related

Proper release of COM objects in code

I have just started to migrate some code from VBA to VB.Net. So I am an absolute beginner in VB.Net – but I want to do things right. Maybe some of my questions are stupid but I guess that is because I am a beginner.
So as a first exercise I have developed my first piece of code (see below). Now I thought I have to release ALL COM objects again. Two of them throw errors already while writing the code. And others throw errors at runtime.
But the funny thing is: Weather I release the rest of the COM objects or not (by making the relevant not yet commented lines of Marshal.Release to comments as well – then all lines starting with Marshal.Release are comment lines) the behavior of the code is absolutely the same to my eyes.
Can anybody tell me where I can see/find the difference?
The internet tells me that there must be a difference?
But I guess I just don’t understand (till now).
Besides this many more questions are in my head:
Does every “Dim” statement create a COM Object - that has to be released later on?
If not how do I detect whether a COM object has been created or not? Which “Dim” statements create COM object and which don't?
In this example: Dim ActiveWindow As Object = Nothing Try ActiveWindow = Me.HostApplication.ActiveWindow() Catch End Try
Is
Marshal.ReleaseComObject(ActiveWindow)
identical to
Marshal.ReleaseComObject(Me.HostApplication.ActiveWindow())?
According to this:
http://www.codeproject.com/Tips/235230/Proper-Way-of-Releasing-COM-Objects-in-NET
Would it not be better to release each "level" separately like this:
Marshal.ReleaseComObject(Me.HostApplication.ActiveWindow())
Marshal.ReleaseComObject(Me.HostApplication)
Marshal.ReleaseComObject(Me)
Overall: Am I trying to release too much? Or is it correct / good practie?
And what does "GC.Collect()" and "… = Null" have to do with all this? I have not used it at all. Should I better use it? Why? ( "... = Null" I have seen here:
http://www.codeproject.com/Tips/162691/Proper-Way-of-Releasing-COM-Objects-in-NET)
Why do I get “ShapeCount was not declared …” - Error if I try to do “Marshal.ReleaseComObject(ShapeCount)”? The same with “ShRange”. I think these are COM objects as well?!?
How do I notice when is the best time to release the COM object again? When I process/debug my code step by step with F11 will it be possible for me to determine the best (soonest) point of release? So far I have no “feeling” about when the COM object is not needed anymore and I can release it.
Any help and explanations very welcome.
Here is the code I am talking about:
Imports System.Runtime.InteropServices
Imports System.ComponentModel
Imports System.Windows.Forms
Imports AddinExpress.MSO
Imports PowerPoint = Microsoft.Office.Interop.PowerPoint
'Add-in Express Add-in Module
<GuidAttribute("D75C609E-7632-400F-8A6F-6A6E6E744E75"),
ProgIdAttribute("MyAddin8.AddinModule")> _
Public Class AddinModule
Inherits AddinExpress.MSO.ADXAddinModule
#Region " Add-in Express automatic code "
[…]
#End Region
Public Shared Shadows ReadOnly Property CurrentInstance() As AddinModule
Get
Return CType(AddinExpress.MSO.ADXAddinModule.CurrentInstance, AddinModule)
End Get
End Property
Public ReadOnly Property PowerPointApp() As PowerPoint._Application
Get
Return CType(HostApplication, PowerPoint._Application)
End Get
End Property
Private Sub AdxRibbonButton2_OnClick(sender As Object, control As IRibbonControl, pressed As Boolean) Handles AdxRibbonButton2.OnClick
MsgBox(GetInfoString2())
End Sub
Friend Function GetInfoString2() As String
Dim ActiveWindow As Object = Nothing
Try
ActiveWindow = Me.HostApplication.ActiveWindow()
Catch
End Try
Dim Result As String = "No document window found!"
If Not ActiveWindow Is Nothing Then
Select Case Me.HostType
Case ADXOfficeHostApp.ohaPowerPoint
Dim Selection As PowerPoint.Selection =
CType(ActiveWindow, PowerPoint.DocumentWindow).Selection
Dim WindowViewType As PowerPoint.PpViewType = PowerPoint.PpViewType.ppViewNormal
Dim SlideRange As PowerPoint.SlideRange = Selection.SlideRange
Dim SlideCountString = SlideRange.Count.ToString()
If WindowViewType = 9 And SlideCountString < 2 Then
Dim ShRange As PowerPoint.ShapeRange = Nothing
Try
ShRange = Selection.ShapeRange
Catch
End Try
If Not ShRange Is Nothing Then
Dim ShapeCount = ShRange.Count.ToString()
Result = "You have " + ShapeCount _
+ " shapes selected."
Else
Result = "You have 0 shapes selected."
End If
End If
'Marshal.ReleaseComObject(ShapeCount)
'Marshal.ReleaseComObject(ShRange)
'Marshal.ReleaseComObject(WindowViewType)
'Marshal.ReleaseComObject(SlideCountString)
Marshal.ReleaseComObject(SlideRange)
Marshal.ReleaseComObject(Selection)
Case Else
Result = AddinName + " doesn't support " + HostName
End Select
'Marshal.ReleaseComObject(Me.HostType)
'Marshal.ReleaseComObject(Result)
Marshal.ReleaseComObject(Me.HostApplication.ActiveWindow())
Marshal.ReleaseComObject(Me.HostApplication)
'Marshal.ReleaseComObject(Me)
End If
Return Result
End Function
End Class
The ReleaseComObject method of the Marshal class decrements the reference count of the specified Runtime Callable Wrapper (RCW) associated with the specified COM object, it doesn't release an object. It comes from the COM nature.
Typically you need to release every object returned from the Office (PowerPoint in your case) object model. Exceptions are objects passed to event handlers as parameters.
You may read more about that and find answers to your multiple questions in the When to release COM objects in Office add-ins developed in .NET article.
FinalReleaseComObject calls ReleaseComObject til it returns 0 which means release of COM object. Calling them in reverse order as in Excel objects(Application, Workbook, Worksheet) is the proper way to dispose of COM objects that are related.
Exception Condition
ArgumentException
o is not a valid COM object.
ArgumentNullException
o is null.

Powerpoint Visual Basic code not working

Recently I have been using a lot of macro visual basic in my PowerPoint. Everything was working fine and then I needed to add a Try and Catch statement. It seemed not to work but I thought maybe it changed in newer versions. Now after trying to make a dictionary in visual basic using PowerPoint the same thing happened. It didn't work and the text was red.
This is the code for the dictionary:
Dim test As New Dictionary(Of String, Integer)
test.Add("1", 1)
Is there anyway of adding dictionaries to it when using PowerPoint? or is this code wrong? The same thing with try and catch statements?
You need to ensure the reference to System.Collections.Generic is declared.
At the top of your code add:
Imports System.Collections.Generic
You are very unclear on the end result but try something like this.
Sub test()
On Error Goto Handler
Dim test AS New Collection
test.Add "1", "27"
test.Add 15, "Monkeys"
Exit Sub
Handler:
Msgbox "Error Number: " & Err & " Occurred. Its message text is: " & Error(Err)
End Sub
VBA Does not have Try and Catch you must handle all errors with On Error GoTo then you can process the Errors by the Error Number. See Here
Also note I used a Collection which uses an Item and Key structure. Not sure what you are going for with this but you can retrieve Items by Key in a Collection which might be fine for you. e.g.
test.Item(1) 'will return "1" by index
test.Item("27") 'will return "1" by key
test.Item(2) ' will return 15 by index
test.Item("Monkeys") 'will return 15 by key

How to call a WCF service in my client application form

I am new to VB.NET and am completely lost on a project. I currently have a service that contains many methods such as GetCustomersAll, GetCustomersByPhone, etc. I am now trying to consume the service with my Windows Form Application. Currently I have a service reference set up but I am just lost when it comes to programming the button in my form. I would like the button to call on the GetCustomersAll method from my service but every time I fix one error another one presents itself. So far this is the code I have:
Private Sub btnAllCustomers_Click(sender As Object, e As EventArgs) Handles btnAllCustomers.Click
Dim MyService As Demo2ServiceReference.Service1Client = New Demo2ServiceReference.Service1Client
Dim MyResult As Demo2ServiceReference.Customer
MyResult = MyService.GetCustomersAll()
lblAllCustomers.Text = "Customer Id: " & MyResult.CustomerId & ", Customer Name: " & MyResult.FirstName & " " & MyResult.LastName & "<br/>"
End Sub
When I run this bit of code I get an error on "MyService.GetCustomersAll()" which says "Value of type '1-dimensional array of DemoWindowsApplication.Demo2ServiceReference.Customer' cannot be converted to 'DemoWindowsApplication.Demo2ServiceReference.Customer'.
**When I change MyResult to an array (Demo2ServiceReference.Customer()) I then get errors on my returns inside my label. MyResult.CustomerId and the rest turn into errors stating "CustomerId" is not a member of "System.Array".
This is all guess work. I honestly have no idea where to start when it comes to coding this button. If anyone has some advice or suggestions it would be much appreciated. If I have left out important information needed to assist me with this please let me know.

Restrict type in a Collection inside a class module

I have a collection inside a class module. I'd like to restrict the object type that is "addable" to this collection, i.e. collection should only ever accept objects of one given type and nothing else.
Is there any way to enforce the type of objects added to a collection?
From what I can tell, there is no built-in way to do this. Is the solution then to make this collection private, and build wrapper functions for the methods usually accessible for Collections, i.e. Add, Remove, Item, and Count?
I kinda hate having to write 3 wrapper functions that add no functionality, just to be able to add some type enforcement to the Add method. But if that's the only way, then that's the only way.
There is no way to avoid wrapper functions. That's just inherent in the "specialization through containment/delegation" model that VBA uses.
You can build a "custom collection class", though. You can even make it iterable with For...Each, but that requires leaving the VBA IDE and editing source files directly.
First, see the "Creating Your Own Collection Classes" section of the old Visual Basic 6.0 Programmer's Guide:
http://msdn.microsoft.com/en-us/library/aa262340(v=VS.60).aspx
There is also an answer here on stackoverflow that describes the same thing:
vb6 equivalent to list<someclass>
However, those are written for VB6, not VBA. In VBA you can't do the "procedure attributes" part in the IDE. You have to export the class module as text and add it in with a text editor. Dick Kusleika's website Daily Dose of Excel (Dick is a regular stackoverflow contributer as you probably know) has a post from Rob van Gelder showing how to do this:
http://www.dailydoseofexcel.com/archives/2010/07/04/custom-collection-class/
In your case, going to all that trouble - each "custom collection" class needs its own module - might not be worth it. (If you only have one use for this and it is buried in another class, you might find that you don't want to expose all of the functionality of Collection anyway.)
This is what I did. I liked Rob van Gelder's example, as pointed to by #jtolle, but why should I be content with making a "custom collection class" that will only accept one specific object type (e.g. People), forever? As #jtolle points out, this is super annoying.
Instead, I generalized the idea and made a new class called UniformCollection that can contain any data type -- as long as all items are of the same type in any given instance of UniformCollection.
I added a private Variant that is a placeholder for the data type that a given instance of UniformCollection can contain.
Private mvarPrototype As Variant
After making an instance of UniformCollection and before using it, it must be initialized by specifying which data type it will contain.
Public Sub Initialize(Prototype As Variant)
If VarType(Prototype) = vbEmpty Or VarType(Prototype) = vbNull Then
Err.Raise Number:=ERR__CANT_INITIALIZE, _
Source:=TypeName(Me), _
Description:=ErrorDescription(ERR__CANT_INITIALIZE) & _
TypeName(Prototype)
End If
' Clear anything already in collection.
Set mUniformCollection = New Collection
If VarType(Prototype) = vbObject Or VarType(Prototype) = vbDataObject Then
' It's an object. Need Set.
Set mvarPrototype = Prototype
Else
' It's not an object.
mvarPrototype = Prototype
End If
' Collection will now accept only items of same type as Prototype.
End Sub
The Add method will then only accept new items that are of the same type as Prototype (be it an object or a primitive variable... haven't tested with UDTs yet).
Public Sub Add(NewItem As Variant)
If VarType(mvarPrototype) = vbEmpty Then
Err.Raise Number:=ERR__NOT_INITIALIZED, _
Source:=TypeName(Me), _
Description:=ErrorDescription(ERR__NOT_INITIALIZED)
ElseIf Not TypeName(NewItem) = TypeName(mvarPrototype) Then
Err.Raise Number:=ERR__INVALID_TYPE, _
Source:=TypeName(Me), _
Description:=ErrorDescription(ERR__INVALID_TYPE) & _
TypeName(mvarPrototype) & "."
Else
' Object is of correct type. Accept it.
' Do nothing.
End If
mUniformCollection.Add NewItem
End Sub
The rest is pretty much the same as in the example (plus some error handling). Too bad RvG didn't go the whole way! Even more too bad that Microsoft didn't include this kind of thing as a built-in feature...
I did almost the same code of Jean-François Corbett, but I adapted because for some reason wasn't working.
Option Explicit
Public pParametro As String
Private pColecao As New Collection
Public Sub Inicializar(ByVal parametro As String)
pParametro = parametro
End Sub
Public Sub Add(NewItem As Object)
If TypeName(NewItem) <> pParametro Then
MsgBox "Classe do objeto não é compatível à coleção"
Else
pColecao.Add NewItem
End If
End Sub
Public Property Get Count() As Long
Count = pColecao.Count
End Property
Public Property Get Item(NameOrNumber As Variant) As Variant
Set Item = pColecao(NameOrNumber)
End Property
Sub Remove(NameOrNumber As Variant)
pColecao.Remove NameOrNumber
End Sub
Then, when i want to create an instance from CCollection I make like the code bellow:
Set pFornecedores = New CCollection
pFornecedores.Inicializar ("CEmpresa")
Where CEmpresa is the class type from the object I want
Yes. The solution is to make your collection private and then make public wrapper functions to add, remove, getitem and count etc.
It may seem like hassle to write the additional code but it is a more robust solution to encapsulate the collection like this.

vba: passing a variable into error handles

i have a statement:
on error go to label
however i would like to pass into the label the variable which caused the error
is this possible?
You can use Err to get the error No and Description
Sub|Function SomeName()
On Error GoTo Err_SomeName ' Initialize error handling.
' Code to do something here.
Exit_SomeName: ' Label to resume after error.
Exit Sub|Function ' Exit before error handler.
Err_SomeName: ' Label to jump to on error.
MsgBox Err.Number & Err.Description ' Place error handling here.
Resume Exit_SomeName ' Pick up again and quit.
End Sub|Function
First, I think you mean:
on error goto label
And no, you can't pass variables using a goto command. However, you can check the Err.Description for details, and if you are raising your own errors, you can do this:
' Raise a custom error.
Err.Raise Number:=vbObjectError + 1000, _
Source:="TestRaiseCustomError", _
Description:="My custom error description."
So if you are raising your own error, you could set Source to the field that caused the problem.
Refer to the Use the Err Object's Raise Method to Raise Custom Errors section at this link for more info.
I can't think of a clever way to do it. I normally have an error handeling class/function that way I can use "on error goto" to pass the error to the bottom block then call the error handeling function. The advantage of this is it's nice to have a centralised error handler but also you can customise it so in my case I pass the name of the procedure thats crashed. It's not pretty but you could if you really wanted to pass either a collection of variables (dependant on how many you have) or set up something to identify the variable based on the line number (which you'd have to add manauly...)
on error goto err
'Code
err:
ErrorHandeler err, "String with Procedure name"
Declare global variables and use them in the code and in your error code.
Public global_variable1 As Integer
Public global_variable2 As String
Private Sub Btn1234_Click()
....
end sub
Err_abcd:
....
End Sub