Pass a ComboBox as a parameter - vba

I need to pass a ComboBox to a method in a different class module. First I defined the method in the class module as:
Public Sub initializeCombo(ByVal s As String, ByRef c As ComboBox)
And called it from a form:
initializeCombo(s, frmMyForm.cboBox)
This results in a type mismatch. This works if I define the method in the code behind the form, but not in a separate class module.

It's a mismatch because OptionButton is not ComboBox. Try to change it into ByRef c As OptionButton.
You can figure that out by replacing ByRef c As ComboBox with c As Variant, then set a breakpoint on the first line and check on the watch window what is being passed. This usually helps understand what's going on when the object passed is not the expected type.

Related

How to access the Format function in a ComboBox derived class?

I am writing a Combobox custom control and cannot use the Format() function. The editor marks the format line as an error.
Public Class TestCombo
Inherits ComboBox
Protected Overrides Sub OnDrawItem(ByVal e As DrawItemEventArgs)
Dim MyStr = Format(5459.4, "##,##0.00") ' The error is here.
End Sub
End Class
After a little exploring, I found that Combobox uses ListControl as an event.
How can I tell the editor to address the function and not the event?
As you noticed, the ComboBox class has an event called Format (which is inherited from ListControl). So, when you try to call the Format() function inside ComboBox, the compiler thinks that you are trying to use the event because it has the narrowest scope, hence, the error.
To get around this, you may explicitly call the module name (i.e., Strings) where the Format() function is declared:
Dim MyStr = Strings.Format(5459.4, "##,##0.00")
Alternatively, you may use String.Format() or ToString() (which is the standard way in .NET):
Dim MyStr2 = String.Format("{0:##,##0.00}", 5459.4)
Dim MyStr3 = 5459.4.ToString("##,##0.00")

Adding an extension method "Move" to generic List(Of T)": T is not defined

I would like to add the "Move" method as an extension method to the "List(Of...)".
I would like to add this to the generic list, not to a specific list.
My approach is this:
Imports System.Runtime.CompilerServices
Module ExtensionMethods
<Extension()>
Public Sub Move(ByRef uBase As List(Of T), ByVal index As Integer, ByVal newIndex As Integer)
Dim item As T = uBase.Item(index)
uBase.RemoveAt(index)
uBase.Insert(newIndex, item)
End Sub
End Module
The compiler doesn't accept the "T" in the lines "uBase As List(Of T)" and in " Dim item As T ="
What should be used here?
Thank you very much!
First, don't use ByRef on the target parameter. I'll expand on that later, because I want to skip to what will fix your compilation error.
Second, in order to have a type argument T in List(Of T), it has to exist in the method definition, so you need (Of T) on the method.
Imports System.Runtime.CompilerServices
Module ExtensionMethods
<Extension()>
Public Sub Move(Of T)(ByVal uBase As List(Of T), ByVal index As Integer, ByVal newIndex As Integer)
' ^^^^^^
Dim item As T = uBase.Item(index)
uBase.RemoveAt(index)
uBase.Insert(newIndex, item)
End Sub
End Module
Rule: An extension method should never** accept the target instance using ByRef.
**Exception to the rule: Certain value (Structure) types may need to be passed by reference to achieve reference type-like behavior (although value types should be immutable if at all possible) or to achieve better performance (in C#, you use the in keyword so the compiler prevents mutation of the instance).
Take this extension method, for example:
Module ExtensionMethods
<Extension()>
Public Sub ConfuseMe(Of T)(ByRef list as List(Of T))
list = New List(Of T)
End Sub
End Module
Dim myList As List(Of Integer)
Dim myList2 = myList ' A copy of the reference, but only one list object involved.
myList.Add(0)
myList.Add(1)
myList.Add(2)
myList.ConfuseMe() ' Call an extension method that can MODIFY myList
myList no longer points to the same instance. myList2 points to the original instance while myList points to the new one created in ConfuseMe. There's no reason the caller should expect that to happen.
So why would you ever do something like this? You probably wouldn't. But based on some of the comments and the confusion between references vs. references to references, I could see it accidentally happening. Using ByVal prevents it from ever becoming a difficult-to-track-down bug.
While it's possible in an extension method, you can't do that with a regular instance method.
Class TestClass
Sub ConfuseMe()
Me = New TestClass() ' Not possible on a Class
End Sub
EndClass
Dim x As New TestClass()
x.ConfuseMe() ' You wouldn't expect 'x' to refer to a different instance upon return
You can't do that. It won't allow you to assign to Me (again, value types are the exception), and you wouldn't expect x to point to a new instance after a call like this.
By the same token, it doesn't make sense to do it in an extension method, where the purpose is to behave like an instance method. And since you don't need to change the caller's variable, there's no need to take a reference to it. Just deal with the direct reference to the object instance by accepting it with ByVal.

WriteOnly Property ByRef

I don't understand how I can pass an argument byref in VB.NET.
I tried this:
Private m_Form As frmMain
Public WriteOnly Property MyForm() As Form
Set(ByRef value As Form)
m_Form = value
End Set
End Property
But VB.NET does not like the "Byref" argument.
Can somebody help?
Thank you!
The ByRef modifier cannot be used in property setters.
It can only be declared in method'ss and constructor's signatures. There it specifies that the underlying variable of an argument can be modified in the called method.
In the following example the ByRef modifier causes the field named "underlyingVariable" to take the new value. By passing the variable by Value, it would not get modified and therefore would be null:
Private underlyingVariable As Object
Public Sub New()
MyMethod(underlyingVariable)
End Sub
Public Sub MyMethod(ByRef o As Object)
o = New Object()
End Sub
You cannot pass things by reference with setters. It must be ByVal. From the VB.NET Specification:
ยง9.7.2 If a parameter list is specified, it must have one member, that member must have no modifiers except ByVal, and its type must be the same as the type of the property.
I don't think it particularly makes sense to use ByRef in a property setter. Using ByRef implies that you may want to change the reference of what invokes the setter.
Form is a reference type (class), so you want to pass it by value. Otherwise you are passing a reference of a reference type.

Passing Method as Delegate Parameter Issues

I'm used to programming in C# so I have no idea how to approach delegates and passing methods in VB
The error that I am getting is: Argument not specified for parameter 'message' of 'Public Sub ReceiveMessage(message As String)'
Here is the constructor of the class that I am trying to pass to:
Delegate Sub ReceiveDelegate(message As String)
Public ReceiveMethod As ReceiveDelegate
Sub New(ByRef receive As ReceiveDelegate)
ReceiveMethod = receive
End Sub
This is the method that I am trying to pass to that constructor:
Public Sub ReceiveMessage(message As String)
MessageBox.Show(message)
End Sub
I'm using it as such:
Dim newClass As New Class(ReceiveMessage)
The purpose of this, is that once the class receives data from a network device, it can call the corresponding method on the Form asynchronously.
You need to create the delegate object and use the AddressOf operator, like this:
Dim newClass As New Class(New ReceiveDelegate(ReceiveMessage))
However, if you don't explicitly create the delegate object, VB.NET will automatically determine the right type, based on the signature, and create it for you, so you can just do it like this:
Dim newClass As New Class(AddressOf ReceiveMessage)
The latter is obviously less typing, but the former is more explicit. So, take your pick. Both ways are perfectly acceptable and common.

Interface does not behave like an Object?

I have a little problem with an interface. A bunch of my classes implement the ILayoutObject interface. A method declares a variable as ILayoutObject (defaulting it as Nothing) and then runs some code which decides which object it should be. The problem is, the evaluation code runs in a method which receives the variable as a parameter and assigns an object to it. With objects, this would be no problem. The object would be affected by the changes in the method and everything would be OK. Howeverm, when using an interface, the variable in the calling code remains Nothing and behaves like a normal variable. Does anyone have any ideas on how to circumvent that? Alas, due to code structure I am unable to use ByRef or functions :(
Here is some code:
Protected LayoutHandler As Dictionary(Of String, Action(Of Constants.OptionsEntryStructure, ILayoutElements)) = New Dictionary(Of String, Action(Of Constants.OptionsEntryStructure, ILayoutElements)) From
{
{Constants.KeyLayoutType, AddressOf KeyLayoutType}
}
Sub MakeLayOuts
Dim LayoutElement As ILayoutElements = Nothing
Dim Value = "SomeValues"
Dim key = "Key"
LayoutHandler(key)(Value, LayoutElement)
' LayoutElement remains nothing.....
End Sub
Protected Sub KeyLayoutType(elem As Constants.OptionsEntryStructure, Layout As ILayoutElements)
Layout = New LayoutObject 'which would implement the interface
End Sub
You need to declare the parameter as ByRef if you want to alter the object to which the variable in the calling code points to:
Protected Sub KeyLayoutType(elem As Constants.OptionsEntryStructure, ByRef Layout As ILayoutElements)
Layout = New LayoutObject 'which would implement the interface
End Sub
This is true with any reference type (classes). The fact that they are referenced with an interface makes no difference.
If you can't use ByRef, and you can't use a function to return the new object, then your only other real option would be to request a type of object which has the layout object as a property. For instance:
Public Interface ILayoutElementContainer
Public Property LayoutElement As ILayoutElements
End Interface
Protected Sub KeyLayoutType(elem As Constants.OptionsEntryStructure, Container As ILayoutElementContainer)
Container.LayoutElement = New LayoutObject 'which would implement the interface
End Sub