Hello. I have a small problem and I can't figure it out: how can I create an optional parameter for a form's name? For example I want to do something like this:
Private Sub Draw(ByVal Start_Pos As Point, ByVal End_Pos As Point, Optional ByVal Form_Name As Form = Cube)
End Sub
I'm not sure if what I want is possible.I just know that the code is not correct because I must specify to the program that "Cube" is a form not just a string...
A constant value is required for Optional params.
When a constant value is not possible to establish at design time for whatever reason, then the easiest trick is to set the vaue as Nothing and check if the value is nothing inside the block, if it is, then set their default value at execution time.
An example:
Private Sub Draw(ByVal startPos As Point, ByVal endPos As Point,
Optional ByVal form As Form = Nothing)
If (form Is Nothing) Then
form = Cube
End If
' ...
End Sub
An adaptation to the real problem that you'd described:
Private Sub Draw(ByVal startPos As Point, ByVal endPos As Point,
Optional ByVal formName As String = "")
If ( String.IsNullOrEmpty(formName) ) Then
formName = Cube.Name
End If
' ...
End Sub
As mentioned in documents for Optional Parameters for each optional parameter, you must specify a constant expression as the default value of that parameter.
So you can't use a form instance as default value for optional parameter.
If you need to set a form name as default value for your string optional parameter:
You can set one of your application forms full name (including namespace) as default value and then create an instance of that form using Activator.CreateInstance and Unwrap the object and use DirectCast to cast it to form and show it later.
To create other forms that their names passed as that parameter, you can use Activator.CreateInstance also you can have a Dictionary(Of String, Type) containing names and form types or Dictionary(Of String, Form) containing names and form instances and use this dictionary to get the instance of form.
Related
I have a problem that has been bugging me for a while now. Consider this code:
Public Class Class1
Dim VariableList as New List(of Object) From {MainForm.X, MainForm.Y,
SettingsForm.Z, SettingsForm.Textbox1.Text} '...etc.
Sub SetToZero()
For Each Element in VariableList
Element = 0
Next
End Sub
Sub SetToCustomValue(value As Double)
For Each Element in VariableList
Element = value
Next
End Sub
Sub LoadValuesFromFile()
Dim path As String = MainForm.GetPath()
For Each Element in VariableList
Element = File.Readline()
Next
End Sub
Sub SaveValuesToFile()
Dim path As String = MainForm.GetPath()
For Each Element in VariableList
Element = File.Writeline()
Next
End Sub
'and more similar functions/subs
As you can see, what this class does is that it takes lot of different variables from different places into a collection, and then various functions read or write values to every variable in that collection using loops. In this example, I have just a few variables, but most of the time there are dozens.
Reading the values is not a problem. Writing them, is, because when I declare that VariableList at the top of my class, that List just makes a copy of each variable, rather than maintaining a reference to it. Meaning that if, say, one of the functions modifies the MainForm.X in that List, the actual variable MainForm.X is not modified. To work with references, I would have to forgo loops, and assign every single variable manually, in every function. Which is obviously a lot of bad code. I want to declare that list of variables only once, and then use loops, like in this example code that I wrote above. My question is, how can I make such a container (List, Array, whatever) that would retain the references to the original variables in it, and make the code above possible?
There is no easy way to store pointers to variables in VB.NET. As a workaround, you can use a class to store your variables, as a class is always used as a pointer.
Here's an example of a way to achieve this with a ContainerClass which own a Dictionary of integers. One interest of this method would be that you can declare and name "variables" dynamically. In reality, they will be managed KeyValuePair. Once you have instantiated a copy of this class, you can use it to "manage" your variables by using this class as your pointer.
I included a loop which set all the integers to the same number just for fun, and to demonstrate the kind of manipulation which would end up having an effect similar to one of those described in your question.
Public Class Form2
'This is the container class which will be used to bypass the lack of pointers
'if you wanted to change a property, like the window width, it would be more difficult, but simples variables will be no trouble
Private variableContainer As New VariableContainer
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
variableContainer.AddVar("X", 5)
variableContainer.AddVar("Y", 15)
Debug.Print(variableContainer.GetVar("X"))
Debug.Print(variableContainer.GetVar("Y"))
variableContainer.SetAllVar(42)
Debug.Print("Next line will print 42")
Debug.Print(variableContainer.GetVar("X"))
End Sub
End Class
Public Class VariableContainer
'I know a public variable wouldn't need the fancy functions down there, but it's usually better to encapsulate, especially if you're working with a team
'and "future you" count as a teammate, never forget that...
Private list As New Dictionary(Of String, Integer)
Public Sub AddVar(ByVal name As String, ByVal value As Integer)
list.Add(name, value)
End Sub
Public Function GetVar(ByVal name As String) As Integer
If list.ContainsKey(name) Then
Return list(name)
Else
'I choose -1 arbitrarily, don't put too much thinking into this detail
Return -1
End If
End Function
Public Sub SetVar(ByVal name As String, ByVal num As Integer)
If list.ContainsKey(name) Then
list(name) = num
End If
End Sub
Public Sub SetAllVar(ByVal num As Integer)
Dim dict As New Dictionary(Of String, Integer)
For Each item As KeyValuePair(Of String, Integer) In list
dict.Add(item.Key, num)
Next
list = dict
End Sub
End Class
Have fun!
I have a function which helps to get data from a database. This function allows me to define the table name, required fields as a string, a where condition, order by field and optional tables to join. The function looks like this.
Public Function DBSELECT(Byval table As String, Byval fields As String, Byval where as String, Byval orderBy As String, Optional Byval join As String = Nothing)
But now I need to add a GroupBy paramter just before the join paramter, however if I were to just add that paramter it would break the code which already uses this function. Is there a way to modify this method without breaking the existing build.
I see two options:
Add the new parameter last, with default value set to Nothing.
Add new method overload with the parameter added, and make the existing one call that one with default value for groupBy provided.
You could use an Optional parameter, but then it would have to come after the join parameter.
Public Function DBSELECT(Byval table As String,
Byval fields As String, Byval where as String,
Byval orderBy As String, Byval join As String,
Optional Byval groupBy as String=Nothing)
Add the new parameter to the end as an Optional parameter with a default value.
Example:
Imports System
Public Module Module1
Public Sub Main()
Test()
Test("Goodbye")
End Sub
Public Sub Test(Optional ByVal opt1 As String = "Hello")
Console.WriteLine(opt1)
End Sub
End Module
Results:
Hello
Goodbye
In your method, you would check that the GroupBy parameter is not the default value, so you'd would know to apply it to the SQL statement.
I know that in Visual Basic, delegate function cannot contain optional parameters. But can a method take a delegate as an optional parameter?
What I want to do is this:
Delegate Sub MyDelegate(ByVal input As String)
Sub MyDelegateDefault(ByVal input As String)
'by default do nothing'
End Sub
Sub MyDelegateCustom1(ByVal input As String)
'do something here'
End Sub
In a different part of code:
Sub OtherFunction(ByVal str As String, Optional ByVal delegate As MyDelegate = AddressOf MyDelegateDefault)
delegate(str)
End Sub
Sub ParentFunction()
OtherFunction("", ) '< "" as string, nothing for optional delegate parameter'
End Sub
Note how the final function OtherFunction takes a optional delegate as second parameter.
Is this a thing? Can a delegate function be an optional parameter?
A parameter that is of a reference type can only be defaulted to null. Change the default value to null, check for the null condition, and don't call the delegate (do nothing).
Switching Byref to Byval on method calls
I have many warnings raised due to:
"Implicit conversion from xxxx to yyyy in copying the value of 'ByRef' parameter zzzz back to the matching argument."
My feeling is that it would be safe to change the function parameters from byref to byval as nothing special is being done with the reference type pointers inside these methods the reference types are simply being used and I think the behaviour would be exactly the same if running with a copy a pointer rather than the original.
Another consideration is that I have two classes which inherit from a base class. The same situation is occuring in that the byref params are causing implicit casting from the base class to the narrower concrete class. Again I can't see any problems with this code running byval either.
Does anyone have any tips regarding use of parameters in functions when dealing with reference types?
Some of the other things that are currently being passed around byref in my project are database connection objects i.e. OracleConnection and SqlConnection. Is there any good reason for passing these around byref?
Example 1
Implicit conversion from 'Object' to 'Integer' in copying the value of 'ByRef' parameter 'value' back to the matching argument.
Calling code:
cmd = New SqlCommand()
cmd.Parameters.Add(CreateParameter("Alpha", SqlDbType.Int,ParameterDirection.Input, -1, AlphaValue))
Function:
Private Function CreateParameter(ByVal parameterName As String, ByVal dbType As SqlDbType, ByVal direction As ParameterDirection, ByVal size As Integer, ByRef value As Object) As SqlParameter
Dim retParam As SqlParameter
retParam = New SqlParameter(parameterName, dbType)
retParam.Direction = direction
retParam.Size = size
retParam.Value = value
Return retParam
End Function
Example 2
Implicit conversion from 'System.Data.IDataReader' to 'System.Data.SqlClient.SqlDataReader' in copying the value of 'ByRef' parameter 'reader' back to the matching argument.
Calling code:
Dim reader As new SqlDataReader
ReleaseReader(reader)
Method:
Public Sub ReleaseReader(ByRef reader As IDataReader)
If reader IsNot Nothing Then
If Not reader.IsClosed Then
reader.Close()
End If
reader.Dispose()
End If
End Sub
When defining a method in VB.Net, or C# for that matter, the you should pass parameters by value (ByVal) unless you need to take advantage of ByRef semantics. If you are not resetting the parameter value within the method then you should definitely turn these into ByVal calls.
If you are resetting the reference but not taking advantage of it from the call site then I would write a helper method which takes the parameter ByVal and calls into the one taking it ByRef. This will remove the warning because the resulting code won't be subject to narrowing conversion errors.
For example:
Public Sub ExampleMethod(ByRef p1 As Object)
p1 = "foo"
End Sub
Public Sub ExampleMethodWrapper(ByVal p1 as Object)
ExampleMethod(p1)
End Sub
Public Sub Test()
Dim v1 As String = "hello"
Dim v2 As String = "world"
ExampleMethod(v1) ' Warning generated
ExampleMethodWrapper(v2) ' No warning
End Sub
I've some classes defined in a dll file. These are in the form of com api.
I'm trying to create an object of one of the class dynamically and than setting some property of that object.
When I set the property manually, it works, but when I try to invoke the same using reflection it gives the error that
Object does not match target type.
Following is my code
Private Sub SetObjectValue(ByVal SelectedObject As SAPbobsCOM.BoObjectTypes, ByVal ClassName As String, ByVal FieldName As String, ByVal SetValue As String, ByVal KeyValue As String)
Dim oObject As Object
Dim myAssembly As Reflection.Assembly = Reflection.Assembly.LoadFrom("interop.sapbobscom.dll")
Dim myType As Type = myAssembly.GetType(ClassName)
Dim myMember() As MemberInfo = myType.GetMember(FieldName)
Dim myProperty As PropertyInfo = CType(myMember(0), PropertyInfo)
Dim myMethod As MethodInfo = myProperty.GetSetMethod
oObject = oCompany.GetBusinessObject(SelectedObject)
oObject.GetByKey(KeyValue)
myProperty.SetValue(oObject, CDbl(SetValue), Nothing)
End Sub
It gives the error when SetValue method is called. Instead, if I use this like following it works fine:
oObject.CreditLimit = 129
oObject.Update
Where CreditLimit is a property of the given class, and update is a method which I have to call, after the value is set, so that the value in underlying database is updated.
Similarly GetByKey is used to retrieve the value of the object from the underlying database, where the value of the primary key field has to be passed.
Since there are multiple classes and each class has lots of different properties, therefore calling them dynamically will help a lot.
Thanks
Rahul Jain
Just tried doing what casper has suggested here. It gives an error saying - Member not found. (Exception from HRESULT: 0x80020003 (DISP_E_MEMBERNOTFOUND))
Rahul
Its done. Instead of vbSet, I used vbLet and it completed successfully.
Thanks
Rahul
I am kind of curious why you are doing this, as VB will do all of it for you. You simply have to declare as type object and then make the call, or are you using an option (I believe it is strict?) which prevents you from letting the compiler emit the reflection code for late-bound calls?
If you need to take a parameter, you should be able to use CallByName as well:
Private Sub SetObjectValue(ByVal SelectedObject As SAPbobsCOM.BoObjectTypes, ByVal ClassName As String, ByVal FieldName As String, ByVal SetValue As String, ByVal KeyValue As String)
Dim oObject As Object
oObject = oCompany.GetBusinessObject(SelectedObject)
oObject.GetByKey(KeyValue)
CallByName(oObject, FieldName, vbSet, CDbl(SetValue))
End Sub