how to use Option Strict On and Late Binding - vb.net

I am trying to get some code to compile after switching Option Strict On. However I am using some Interop with VB6 and passing in a form object ByRef so Form.Caption fails and I can't convert it to type Form because a VB.NET Form doesn't have a caption property.
How can I can get the following to compile with Option Strict ON:
Public Sub EditFormLegacy(ByRef objForm As Object)
objForm.Caption = objForm.Caption + " Edited"
End Sub
Is there any way to switch option strict off for specific methods?

You can't turn it off for a method, but you can turn if off for a form or class. Just put "option strict off" at the top of the form. Per MSDN - "If used, the Option Strict statement must appear in a file before any other source code statements." HTH

You really want to leave option Strict on, so I guess you should try a workaround. For example, get the form (with the caption) to store it's Caption in a seperate string, which can be recalled by the new class loading in the form.

Related

What exactly are parentheses around an object reference doing in VB.NET?

I have some procedures that take Control object references as a parameter.
I have a bunch of Controls throughout my project of varying derived types such as Button, TextBox, PictureBox, ListBox, etc.
I was calling the procedure and passing the reference as normal:
Procedure(controlRef)
I changed some of the Warning Notifications in my project configuration. I'm guessing it was changing the Implicit Conversion Notification from 'None' to 'Warning' that caused warnings similar to the following to appear everywhere these procedures were called:
"Implicit conversion from 'Control' to 'Button' in copying the value of 'ByRef' parameter 'parControl' back to the matching argument."
This makes sense, I'm doing an Implicit Conversion, but hang on a second, I'm passing a Button in to a Control parameter, not a Control to a Button like it says, I'm slightly confused what's happening here.
Anyway, I take a look at the "Show potential fixes" and there is no fix suggestion, only Suppress or Configure options, okay. So I do a explicit cast using DirectCast(controlRef, Control) to see if that'll remove the warning on Implicit Conversion, which it does, but it gets replaced by a Redundant Cast warning, again, this makes sense. So I remove the cast using the potential fixes and the argument in the procedure call is left with parentheses around it and no more warnings.
Procedure((controlRef))
What is going on here exactly?
Since the signature for Procedure is Sub Procedure(ByRef param As Control) and you're passing a Button to the method, the compiler is correctly warning you about an implicit conversion.
Imagine that this were the definition of Procedure:
Sub Procedure(ByRef param As Control)
param = New Label()
End Sub
And if you called it this way:
Dim button = New Button()
Procedure(button)
Then you're effectively calling this code:
Dim button As Button = New Button()
button = New Label()
Hence the compiler warning.
If you change the signature to Sub Procedure(ByVal param As Control) then there is no possibility of assignment back to the calling variable and the warning will go away.
The use of the extra parenthesis forces the call to be ByVal instead of ByRef. See https://learn.microsoft.com/en-us/dotnet/visual-basic/programming-guide/language-features/procedures/how-to-force-an-argument-to-be-passed-by-value
This is not an answer to the question but it may be a solution to the actual problem. It also requires significant code, so I decided an answer was the best option.
One has to wonder why you have declared that parameter ByRef in the first place. Many people do so when it is not required because, as in VB6, they think that it will prevent an object being copied. That is not the case because reference type objects, i.e. class instances, don't get copied when passed by value anyway. That's the whole point of reference types, i.e. the value of a variable is a reference, not an object, so passing by value only copies the reference, not the object. If you are not assigning anything to that parameter inside the method then it should be declared ByVal.
If you are assigning to the parameter inside the method then the solution is to declare the method to be generic. That way, the parameter won't be Control but will actually be the type you pass in. In its simplest form, that would be:
Private Sub Procedure(Of T)(ByRef control As T)
'...
End Sub
That's probably not enough though, because that would allow you to pass any object as an argument. To restrict the method to only accept controls:
Private Sub Procedure(Of TControl As Control)(ByRef control As TControl)
'...
End Sub
Now you will only be able to pass a control to the method but, inside the method, the parameter will be treated as the actual type of the argument you passed, e.g. if you pass a Button then TControl is fixed to be Button. If you need to create a control of the appropriate type inside the method then you need another restriction too, which enables you to assume a parameterless constructor, e.g.
Private Sub Procedure(Of TControl As {New, Control})(ByRef control As TControl)
control = New TControl With {.Location = New Point(100, 100),
.Size = New Size(50, 25)}
'...
End Sub
That code means that, inside the method, you know that the type of the parameter is Control or derived from that type and that you can create new instances by invoking a parameterless constructor.

How to convert Timer.Enabled to type Control

After converting code from VB6 to VB.NET I got the following resultant code:
Designer Form Code:
Public WithEvents Timer1 As Microsoft.VisualBasic.Compatibility.VB6.TimerArray
Me.Timer1 = New Microsoft.VisualBasic.Compatibility.VB6.TimerArray(components)
Code Implementation:
Private Function GetBaseControl(ByRef a_type As String) As System.Windows.Forms.Control Implements GetBaseControl
Select Case a_type
Case "Web"
GetBaseControl = ctBrowser1(0)
Case "Timer"
GetBaseControl = Timer1(0)
End Select
End Function
Here the Error I've received:
Value of type 'Boolean' cannot be converted to 'System.Windows.Forms.Control' at Line GetBaseControl = Timer1(0).
That works fine in VB6 Though !!
If this ever worked in VB6, the code was following some very poor practices to return the Boolean Enabled instead of the actual Timer component. It implies, at minimum, that Option Strict was off, and that's really bad.
In this case, Timers in .Net are no longer considered Controls at all. Instead, they are Components. You'll want to re-think how this code functions. An example of how the code is used might let us recommend a different (better) approach to the problem.
In this case, I suspect re-thinking this to use overloaded methods (which was not idiomatic for vb6) will produce better results, especially with regards to preserving type safety rather than passing strings around.
Note: this answer made more sense before the question was edited the next day

Marshalling a .Net function that returns Double() to consume in VBA

Here is my function in .Net:
<Runtime.InteropServices.ComVisibleAttribute(True)>
Public Function Unhex(hex As String) As Double()
Dim GetArr As Double() = HexStringToDoubleArray(hex)
Return GetArr
End Function
Here is how I would like to use it in VBA:
Dim ret() As Double
ret = LinkToComLib.Unhex("EDC531...")
There are hundreds of examples of how to pass arrays into .Net (eg), but the only one I found showing the opposite is this MS page, and it doesn't show it being used on the VBA (or even COM) side. Perhaps I am using the wrong search terms. In any event:
Can I use the MarshalAs to export the Double() from .Net, or will I need to use Marshal.Copy or similar (as I suspect, as it is managed)?
If I do have to Copy, is the proper return type then IntPtr?
Am I correct in thinking that Dim ret() As Double is a pointer to a malloc'ed array or perhaps SAFEARRAY? Is that the proper type to use in VBA in this case?
Would creating the array with the proper size (it's always 492!) in VBA and then passing that to the function help in any way? Deallocing perhaps?
If anyone has a pointer to an example of this - a double (or int) array being passed out of .Net along with the corresponding VBA code, I can likely take it from there. But if someone has answers for the above, VB.Net or C# as they like, I'd appreciate it.
You need to decorate the return with <MarshalAs(UnmanagedType.SafeArray)> attribute.
VB.Net Example:
Imports System.Runtime.InteropServices
<ComClass(ArrayExample.ClassId, ArrayExample.InterfaceId)> _
Public Class ArrayExample
' These GUIDs provide the COM identity for this class and its COM interfaces.
Public Const ClassId As String = "e510d899-dad1-412b-94ea-6c726fe9f9da"
Public Const InterfaceId As String = "ef3498f0-22b4-4c2a-aeb1-22936c9757eb"
Public Function Unhex(hex As String) As <MarshalAs(UnmanagedType.SafeArray)> Double()
Dim GetArr As Double() = {2.0R, 5.0R}
Return GetArr
End Function
End Class
VBA Usage:
Sub t()
Dim c As ExampleComArrayReturn.ArrayExample
Set c = New ExampleComArrayReturn.ArrayExample
Dim arr() As Double
arr = c.Unhex("AABB")
End Sub
Edit: Forgot to mention that this uses the ComClassAttribute Class to have the compiler generate the interfaces for your class.
Edit 2 in response to follow-up question.
To debug your COM library project, go to the Debug tab of project properties. Select "Start External Program" and set it to run Excel. You can also specify the Workbook to open in the "Command line Arguments". Now when you click on the "Start" button, Excel will be launched and break points in your code will be triggered.
Edit 3:
To address the issue of targeting .Net 3.5, you can use a slightly less convenient method of attaching the debugger to the Excel process. If you are using VS2008, the method described above will work. New VS versions will need to attach to the process. There may be a way to specify this info in the vproj.user file, but I have not found the magic property type to allow direct launching using a specific framework version.
Depending on your VS version the "Attach To Process" item will either be under the Tools (VS2013) or the Debug (VS2017) menu or you can use the shortcut cntrl-alt-p.
Obviously start Excel and load your Workbook. Then in VS launch the Attach to Process dialog. Click the "Select" button and then click on the "Debug these type" radiobutton. Select the "Managed (v3.5, v3.0, v2.0) code" type and click the "OK" button. Then select the Excel process and click "Attach".

Option Strict On issues where generic type isn't known until runtime

I have the following code that has worked fine for months, but I forgot to create this class with Option Strict On so now I am going back to clean up my code correctly, however I haven't been able to figure out a way around the following issue.
I have a local variable declared like this:
Private _manageComplexProperties
Now with option strict, this isn't allowed due to no As clause which I understand, however the reason that it is like this is because the instance of the class that will be assigned to it takes a type parameter which isn't known until run time. This is solved by the following code:
Private _type As Type
*SNIP OTHER IRRELEVANT VARIABLES*
Public Sub Show()
Dim requiredType As Type = _
GetType(ManageComplexProperties(Of )).MakeGenericType(_type)
_manageComplexProperties = Activator.CreateInstance(requiredType, _
New Object() {_value, _valueIsList, _parentObject, _unitOfWork})
_result = _manageComplexProperties.ShowDialog(_parentForm)
If _result = DialogResult.OK Then
_resultValue = _manageComplexProperties.GetResult()
End If
End Sub
Again option strict throws a few errors due to late binding, but they should be cleared up with a cast once I can successfully declare the _manageComplexProperties variable correctly, but I can't seem to get a solution that works due to the type parameter not known until run time. Any help would be appreciated.
Declare your variable as Object
Private _manageComplexProperties as Object
And then you will have to persist with reflection, e.g. to call ShowDialog method:
Dim method As System.Reflection.MethodInfo = _type.GetMethod("ShowDialog")
_result = method.Invoke(_manageComplexProperties, New Object() {_parentForm})
You have to use option infer on on the top of your vb file. It enables local type inference.
Using this option allows you to use Dim without the "As" clausule, it is like
var in C#.
IntelliSense when Option Infer and Option Strict are off
IntelliSense when Option Infer is on (as you can see it has type inference)
If you do not want to use option infer on, you will have to declare the variable matching the type of the one returned by Activator.CreateInstance

VBA User form with ThemeColorScheme and late binding

I'd like to run a user form with status bar.
I show my form with code bolow.
How should I declare variables and assigning new values to those variables?
Very important: I have to use late binding in my project.
Sub RunMyUserForm()
With MyUserForm
.LabelProgress.Width = 0
.TextBox1 = 1
'to make the progress bar color match the workbook's current theme:
.LabelProgress.BackColor = ActiveWorkbook.Theme.ThemeColorScheme.Colors(msoThemeAccent1)
.Show vbModeless
End With
End Sub
Thank you in advance for your help!
Updated information:
When I try to run my macro with "Option Explicit", it doesn't work (Compile error: Variable not defined - part of code msoThemeAccent1 is marked as yellow color). That's why I asked for help in defining the variables.
When I try to run my macro without "Option Explicit", it dosen't work (Err.Description: "Value is out of range", Err.Number: -2147024809)
When I try to run my macro with early binding (reference to "MS Office Object Library" via Tools/References in VBE) everything works perfect with (and without) "Option Explicit".
Your compiler is seeing msoThemeAccent1 as a variable, and it is undeclared. This is why your code won't run with Option Explicit and also why your code raises an error when you disable Option Explicit. .Colors is a 1-based collection (?) so when you call:
ActiveWorkbook.Theme.ThemeColorScheme.Colors(msoThemeAccent1)
It is compiling to:
ActiveWorkbook.Theme.ThemeColorScheme.Colors(0)
Which raises an expected, albeit cryptic error.
I used some code to check the value of this constant, but in hindsight I should have just referred to the documentation:
http://office.microsoft.com/en-us/excel-help/HV080559557.aspx
This should fix it for you
ActiveWorkbook.Theme.ThemeColorScheme.Colors(5)
Alternatively, if you need to rely on this value in several places in your code, you could declare a variable (public or private, scope depends on what you need it for).
Public Const myAccentColor1 as Long = 5
And then, in your code you could:
ActiveWorkbook.Theme.ThemeColorScheme.Colors(myAccentColor1)
Revised
I understand now, without reference to MS Office Object Library this makes sense. This is one of the few libraries that I maintain a reference to in my XLSB file, so my confusion was a result of the fact that I thought I was using an appropriate late-binding, but I was really using early-binding.