Can someone help me fix this error?
Option Strict On disallows late binding
Here's the code that's causing the error:
Dim SF6StdData As BindingSource = New BindingSource()
' ...
If StrComp(SF6StdData.Current("O2AreaCts").ToString, "") = 0 Then
AreaCts(3) = 0
Else
AreaCts(3) = Convert.ToDouble(SF6StdData.Current("O2AreaCts").ToString)
End If
I need to rewrite the code so it will not have any errors. I know I could fix this by setting Option Strict to Off in the project properties, but I really don't want to do that. Is there some other way?
Late binding is not allowed when Option Strict is on. If you need to perform late binding, the only options are either to use reflection or to shut off Option Strict. The one saving grace, however, is that you don't have to shut off Option Strict for the whole project. You can leave it on for the project and then just add the line Option Strict Off at the top of any code files where you need to perform late binding. It's not a great solution, but it's better than affecting the whole project.
Also, since the Option Strict placed at the top of a file applies just to that file, it doesn't even have to apply to an entire class. If you split your class into multiple Partial Class files, then you could have Option Strict set differently for each of those files. For instance, if you put most of your class in a file with Options Strict On, and then just put one method in a Partial Class in a separate file with Option Strict Off, then only that one method would be compiled loosely. The rest of the class would be compiled using the strict rules.
You need to make the BindingSource act as a strongly-typed data source. See the remarks in the documentation: http://msdn.microsoft.com/en-us/library/system.windows.forms.bindingsource.aspx
This is an old post, but I struggled with the error "Option Strict On disallows late binding". Maybe another answer will help someone else. The problem may be coming when you try to convert the data in your SF6StdData bindingsource to a string. You can probably solve the problem by defining a local variable with the desired type, and then using Ctype to extract the data into the correct type. Here's an example of how I solved a similar problem.
This code gave me the late-binding error:
Friend Function CountNumCheckedInGroupbox(ByVal gbox As GroupBox, ByRef nameschecked() As String) As Integer
Dim numchecked As Integer = 0
For Each ctrl In gbox.Controls
If TypeOf ctrl Is CheckBox Then
If ctrl.Checked = True Then
nameschecked(numchecked) = ctrl.Text
numchecked += 1
End If
End If
Next
Return numchecked
End Function
The late binding error occurred where I referenced "ctrl.Checked" and "ctrl.Text"
Instead of referencing "ctrl" directly, I defined a variable cbox that is typed as a Checkbox. Then I extracted the information from "ctrl" into cbox. Now the code does not show late-binding errors:
Friend Function CountNumCheckedInGroupbox(ByVal gbox As GroupBox, ByRef nameschecked() As String) As Integer
Dim numchecked As Integer = 0
Dim cbox As CheckBox
For Each ctrl In gbox.Controls
If TypeOf ctrl Is CheckBox Then
cbox = CType(ctrl, CheckBox)
If cbox.Checked = True Then
nameschecked(numchecked) = cbox.Text
numchecked += 1
End If
End If
Next
Return numchecked
End Function
If you declared AreaCts without a type, ex:
Dim AreaCts as Array
Try
Dim AreaCts() as Double
This fixed my late binding error.
Related
My database has the formname, control, and control property type value stored.
I would like to have a line of code like this.
Forms(i%).Controls(ControlName$)).controlpropertytype$ = NewValue
I am currently using a select case structure to handle the various property types. It would be much simpler to have a single statement take care of it.
Using a helper function, you can achieve this with one line of code. Here's an example of setting a TextBox on Form1 to the value 'aaa':
Option Explicit
Private Sub Test()
CallByName FindForm("Form1").Controls("Text1"), "Text", VbLet, "aaa"
End Sub
Public Function FindForm(ByVal Name As String) As Form
Dim f As Form
For Each f In Forms
If UCase(f.Name) = UCase(Name) Then
Set FindForm = f
Exit Function
End If
Next
End Function
While this is an interesting exercise, I would not recommend this approach. It assumes the form and the control can both be found, but if they can't be found this one-liner will crash your app.
Here's documentation for CallByName.
i have a control named SuperValidator1 on every form with SuperValidator type. i want to find this control and enable it using its name because the name is consistent in all forms. so this is the code i came up with :
Dim validator As SuperValidator
Dim frm As Form = Me.ParentForm
Dim ctrl As Control()
ctrl = frm.Controls.Find("SuperValidator1", True)
Dim singleCtrl As Control = ctrl(0)
validator = TryCast(singleCtrl, SuperValidator) '< ERROR LINE
it throws editor error : Value of Type 'Control' cannot be converted to 'SuperValidator'
i tried CType and DirectCast but it is the same. according to this i should be able to cast any data type. what is wrong and what should i do ?
btw SuperValidator is from DevComponents.DotNetBar.Validator
thanks
Since SuperValidator is a component you must get it from your form's component collection. However at runtime components don't seem to inherit a name, so finding the exact one might be tricky.
As far as I know your only options are:
A) Get the first SuperValidator you can find, or
B) Match its properties (if possible).
Either way you do it you must iterate through the Me.components.Components collection:
Dim validator As SuperValidator = Nothing
For Each component In Me.components.Components
If component.GetType() Is GetType(SuperValidator) Then
validator = DirectCast(component, SuperValidator)
'Perform additional property checking here if you go with Option B.
End If
Next
Here is a test that uses a control I have on a form. Changed your logic slightly. Give it s try and see what results you have.
Dim validator As RichTextBox ' SuperValidator
Dim frm As Form = Me ' .ParentForm
Dim ctrl() As Control = frm.Controls.Find("RichTextBox1", True) ' ("SuperValidator1", True)
If ctrl.Length > 0 Then
validator = TryCast(ctrl(0), RichTextBox) ' , SuperValidator) < ERROR LINE
Else
Stop
End If
I face a problem with Ambiguous match found
What I am trying to do is described in :GetType.GetProperties
In two words I am trying to run through all the properties of a control and find if user had made any changes to control's properties , then I take only the changed properties and store the values for these properties
I followed the suggestions but I get an error for propery Padding when the control is a TabControl (the tabControl has 2 tabPages).
Ok with help from Ravindra Bagale I manage to solve it:
The problem wasn't the new modifier but my stupidity:
In MSDN is says:
Situations in which AmbiguousMatchException occurs include the following:
A type contains two indexed properties that have the same name but different numbers of parameters. To resolve the ambiguity, use an overload of the GetProperty method that specifies parameter types.
A derived type declares a property that hides an inherited property with the same name, by using the new modifier (Shadows in Visual Basic). To resolve the ambiguity, use the GetProperty(String, BindingFlags) method overload and include BindingFlags.DeclaredOnly to restrict the search to members that are not inherited.
So I used BindingFlags.DeclaredOnly and the problem solved:
Private Sub WriteProperties(ByVal cntrl As Control)
Try
Dim oType As Type = cntrl.GetType
'Create a new control the same type as cntrl to use it as the default control
Dim newCnt As New Control
newCnt = Activator.CreateInstance(oType)
For Each prop As PropertyInfo In newCnt.GetType().GetProperties(BindingFlags.DeclaredOnly)
Dim val = cntrl.GetType().GetProperty(prop.Name).GetValue(cntrl, Nothing)
Dim defVal = newCnt.GetType().GetProperty(prop.Name).GetValue(newCnt, Nothing)
If val.Equals(defVal) = False Then
'So if something is different....
End If
Next
Catch ex As Exception
MsgBox("WriteProperties : " & ex.Message)
End Try
End Sub
I have to apologize.
My previous answer was wrong.
With BindingFlags.DeclaredOnly I don't get the properties that I wanted.
So I had to correct the problem with other way.
The problem occurs because two properties have the same name.
So I searched where the same named properties are different and I found that they have: have different declaringType,MetadataToken and PropertyType.
So I change the way I get the value and problem solved:
Dim val = cntrl.GetType().GetProperty(prop.Name, prop.PropertyType).GetValue(cntrl, Nothing)
Dim defVal = newCnt.GetType().GetProperty(prop.Name,prop.PropertyType).GetValue(newCnt,Nothing)
Sorry if I misguided someone.
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.
I have a COM-visible method which looks something like the following:
Public Sub SomeMethod(someControl as Object)
On Error Goto ErrHandler
Dim someSpecificControl as SpecificControl
MsgBox TypeOf someControl is Control
MsgBox TypeOf someControl is SpecificControl
On Error Resume Next
Set someSpecificControl = someControl
On Error Goto ErrHandler
if someSpecificControl is Nothing then
Exit Sub
end if
' do stuff to the control
End Sub
Other components would call this method (i.e. via COM) and pass in a control of type SpecificControl.
My problem is that when run via the debugger, the parameterized control doesn't appear to be of the right type i.e. it exits the sub-routine after the 'cast' fails when I would have expected it not to.
Using TypeOf I have verified that the parameterized object is of type Control (as above) but I cannot work out why it was passed in - apparently - incorrectly. It seems to be behaving correctly when run outside the debugger - but I can't be sure (hence this question).
Can anyone shed any light on this? Could the control have been - somehow - corrupted in the boxing-unboxing process? Is there a better way of doing this?
Edit: I used TypeName as suggested by Kris Erickson and got some interesting results:
MsgBox TypeName(someControl)
MsgBox "someControl is of type SpecificControl: " & TypeOf someControl is SpecificControl
MsgBox "someControl is of type UserControl: " & TypeOf someControl is UserControl
MsgBox "someControl is of type Control: " & TypeOf someControl is Control
I get:
SpecificControl
someControl is of type SpecificControl: False
someControl is of type UserControl: False
someControl is of type Control: True
I guess the only way I have around this is to avoid passing in a UserControl as a parameter.
I'm using VBControlExtender as parameter type
Public Sub SomeMethod(someControl as VBControlExtender)
then I get the references like this
Dim someSpecificControl as SpecificControl
Dim someSpecificControlExt as VBControlExtender
Set someSpecificControl = someControl.object
Set someSpecificControlExt = someControl
Then use someSpecificControlExt to access Left, TabIndex, TabStop, Name, Move, etc. properties of the extender and someSpecificControl to access specific methods/properties of my user control.
FYI, the behaviour of your code depends on whether the user control is implemented in the current project or referenced in an ocx. I'm using Matt Curlands direct user control access hack too, which allows me to do this
Dim someSpecificControl as DirectSpecificControl
so that someSpecificControl props/methods are accessed early-bound.
This is how I get someSpecificControlExt (the extender) from the control:
Public Function GetExtendedControl(oCtl As IUnknown) As VBControlExtender
Dim pOleObject As IOleObject
Dim pOleControlSite As IOleControlSite
On Error Resume Next
Set pOleObject = oCtl
Set pOleControlSite = pOleObject.GetClientSite
Set GetExtendedControl = pOleControlSite.GetExtendedControl
On Error GoTo 0
End Function
This is how I get the internal UserControl of the VB6 user control:
Public Function GetUserControl(oObj As Object) As UserControl
Dim pControl As UserControl
Call CopyMemory(pControl, ObjPtr(oObj), 4)
Set GetUserControl = pControl
Call CopyMemory(pControl, 0&, 4)
End Function
The reference GetUserControl returns has a very weird implementaion of QueryInterface -- it seems UserControl interface is specifically dummied to E_NOTIMPLEMENTED.
I don't know why this happens, but I do know that UserControl's are semi magic in VB6. If you pass a UserControl into a function by its actual type, it loses all of its UserControl base class (I know, VB6 doesn't have inheritance but when creating a UserControl you expect certain features).
So if you
Private Sub AdjustControlProperties(oControl as MyUserControl)
...
End Sub
Once the control leaves your your subroutine, it will behave as a Control, not as a UserControl (you will have no access to UserControl properties anymore, and attempts to access them will cause an error). Very strange bug in VB6, and one that caused much pulling of hair to work it out.
Private Sub AdjustControlProperties(oControl as Object)
...
End Sub
And everything is fine. My guess is that you are correct, and UserControls are boxed, unboxed as Control's not UserControls. The only solution to type checking, is to use
TypeName()
to know what type it is, as that does not get corrupted.