Is there a way to determine the value of a property on a form that calls a method in a seperate class library - vb.net

Specifically aimed at winforms development.
I suspect that the answer to this is probably No but S.O. has a nice way of introducing me to things I didn't know so I thought that I would ask anyway.
I have a class library with a number of defined methods therein. I know from personal experimentation that it is possible to get information about the application within which the class library is referenced. What I would like to know is whether it would be possible to get information about the value of a property of a control on a form when a routine on that form calls a method in my class library without passing a specific reference to that form as a parameter of the method in the class library?
So purely as an example (because it's the only thing I can think of off the top of my head). Is there a way that a message box (if it had been so designed to do so in the first place) could 'know' from which form a call to it had been made without that form being specifically referenced as a parameter of the message box in the first place?
Thanks for any insights you might have.

To address the example of the MessageBox, in many of the cases you can use the active form. You can retrieve it by using Form.ActiveForm. Of course, as regards the properties that you can request, you are limited to the properties provided by the Form or an interface that the Form implements and that the method in the other assembly also knows. To access other properties you can use Reflection, but this approach would neither be straightforward nor would it be clean.
In a more general scenario, you could provide the property value to the method as a parameter. If it is to complex to retrieve the value of the property and the value is not needed every time, you can provide a Func(Of TRESULT) to the method that retrieves the value like this (sample for an integer property):
Public Sub DoSomethingWithAPropertyValue(propValFunc As Func(Of Integer))
' Do something before
If propertyValueIsNeeded Then
Dim propVal = propValFunc()
End If
' Do something afterwards
End Sub
You call the method like this:
Public Sub SubInForm()
Dim x As New ClassInOtherAssembly()
x.DoSomethingWithAPropertyValue(Function() Me.IntegerProperty)
End Sub

I kind of question your intentions. There's no problem sending the information to a function or the constructor.
Instead of giving the information to the class, the class would ask for the information instead using an event.
Module Module1
Sub Main()
Dim t As New Test
AddHandler t.GetValue, AddressOf GetValue
t.ShowValue()
Console.ReadLine()
End Sub
Public Sub GetValue(ByRef retVal As Integer)
retVal = 123
End Sub
End Module
Class Test
Public Delegate Sub DelegateGetValue(ByRef retVal As Integer)
Public Event GetValue As DelegateGetValue
Public Sub ShowValue()
Dim val As Integer
RaiseEvent GetValue(val)
Console.WriteLine(val)
End Sub
End Class

Related

Reference to a non-shared member requires reference

I am updating some code from a vb6 application to VB.NET.
I have a problem that occurs when I try to open a form from the main form.
It calls this function:
Public Sub optDrawSafeFile_CheckedChanged(ByVal eventSender As System.Object, ByVal eventArgs As System.EventArgs) Handles optDrawSafeFile.CheckedChanged
If eventSender.Checked Then
'--------------------------------------------------------------------------------
' JRL 11-03-06
' change the enables
UpdateGUI((False))
cboProject.SelectedIndex = frmMain.cboProjects.SelectedIndex
SelectJob()
End If
End Sub
And when it goes to execute this line:
cboProject.SelectedIndex = frmMain.cboProjects.SelectedIndex
It blows up and says this:
frmMain is declared like this:
How can I fix this error?
TL;DR
It is described in more detail in this video.
Short answer: change frmMain to My.Forms.frmMain.
cboProject.SelectedIndex = My.Forms.frmMain.cboProjects.SelectedIndex
Long answer:
In VB6, referencing a form by its name allowed you to access it both as a class and an instance of that class. The instance that you access in this manner is called the default instance. This is not possible in VB.NET. However, VB.NET includes a dynamically generated class, My.Forms, that provides functionality similar to that of default instances.
See http://msdn.microsoft.com/en-us/library/ms379610%28v=vs.80%29.aspx#vbmy_topic3 for more information about My.Forms and the "My" namespace.
A better and more object-oriented way to handle this, however, would be to pass the instance of the main form to the constructor of the frmAddMethod form and store it in an instance field.
So, within the class definition in frmAddMethod.vb:
Sub New(ByVal mainForm As frmMain)
_mainForm = mainForm
End Sub
Private _mainForm as frmMain
And when you create the frmAddMethod instance from frmMain, pass in "Me" to the constructor:
Dim addMethodForm as new frmAddMethod(Me)
"Me" is the instance of the class from which a non-shared class method was called.
This will allow you to use the _mainForm class field to access the instance of the main form from non-shared methods of frmAddMethod.
*Edited to recommend My.Forms instead of DefInstance per Plutonix's comment.
Nothing is "blowing up", your program is not crashing. Using a type name, like frmMain, where an object reference is expected is something that the VB.NET compiler allows. Specifically for the Form class, an appcompat hack for VB6 code. It is the debugger that doesn't think much of it. It merely gives you a diagnostic on your watch expression since it doesn't have the same appcompat hack as the compiler does. So doesn't know what to display.
You can use My.Forms instead to get the active form object reference. So make your watch expression:
My.Forms.frmMain.cboProjects.SelectedIndex
Only do this when you are single-stepping the code, it will still go wrong if you use Debug + Break All to break into the program. Setting a watch expression on Me.cboProject is otherwise the obvious workaround in this specific case.

vb.net Invoke and Inheritance Design

this is my first post here.
I like to have some advice on designing a new module for our software.
It's written in vb6 and i want to rewrite it in vb.net. So I want to refactor it.
It does the following:
We got many templates for OpenOffice Documents. (about 300)
Each one has an unique identifier which is in a database and comes as the input.
Dending on this number i want to create the document. e.g. calc or writer or later something else.
Then call a method named doc[template_num] or sth.
I read about invoking methods. I think this as a good way to dynamically call methods by number. Tried to do this. I think I understood now how it works.
But I dont know how to handle the document type.
I want to reuse big parts of code due to all calc documents are created equal at the beginning. But filling the cells is just bit diffrent.
I dont know how to inherit and do method calls with invoke.
Maybe someone has a little code snip for me which can explain this to.
Or maybe some other good idea how to handle this problem.
I am thankful for any new thought on this.
You could, of course, just have a giant Select Case block, like this:
Public Sub CreateDoc(templateNum As Integer)
Select Case templateNum
Case 1
CreateDoc1()
Case 2
CreateDoc2()
Case 3
CreateDoc3()
End Select
End Sub
If you had a fairly small number of case statements that would probably be the better, simpler method. If however, as you described, you have many different possible ID's to look for, then that becomes impractical and I can appreciate your desire to invoke the correct method without a big Select Case block.
However, before I get into that, I feel like I should give you another option. Perhaps you don't really need to have that many different methods. Is it possible, for instance, that half of the template ID's actually all need to be handled in the same way, and the only difference is the template file name that needs to be different. If so, it is conceivable that the Select Case block wouldn't need to be that big. For instance:
Public Sub CreateDoc(templateNum As Integer)
Select Case templateNum
Case 1 To 100
CreateWriterDoc(GetTemplateFilePath(templateNum))
Case 101 To 200
CreateCalcDoc(GetTemplateFilePath(templateNum))
End Select
End Sub
Private Sub CreateWriterDoc(templateFilePath As String)
' Launch Writer with given template file
End Sub
Private Sub CreateCalcDoc(templateFilePath As String)
' Launch Calc with given template file
End Sub
Private Function GetTemplateFilePath(templateNum As Integer) As String
' Retrieve template file path for given ID
' (from DB, Array, List, Dictionary, etc.) and return it
End Sub
To me, that seems like a much simpler solution, if such a thing is possible. If not--if you really have entirely different logic which needs to be executed for each template ID, then there are several ways to do it. The first option would be to create a list of delegates which point to the appropriate method to call for each ID. For instance, you could store them in an array, like this:
Dim createDocDelegates() As Action =
{
AddressOf CreateDoc1,
AddressOf CreateDoc2,
AddressOf CreateDoc3
}
So now, those three delegates are indexed by number (0 through 2) in an array. You can invoke them by number, like this:
createDocDelegates(1).Invoke() ' Calls CreateDoc2
Or, if your ID's aren't sequential, you may want to use a Dictionary(Of Integer, Action), instead, like this:
Dim createDocDelegates As New Dictionary(Of Integer, Action)()
createDocDelegates.Add(1, AddressOf CreateDoc1)
createDocDelegates.Add(7, AddressOf CreateDoc7)
createDocDelegates.Add(20, AddressOf CreateDoc20)
Then you can call one by ID, like this:
createDocDelegates(7).Invoke() ' Calls CreateDoc7
Another option would be to create an Interface for an object that creates a document, like this:
Public Interface IDocCreator
Sub CreateDoc()
End Interface
Then you could implement a separate class for each type of template (each implementing that same interface). For instance:
Public Class Doc1Creator
Implements IDocCreator
Public Sub CreateDoc() Implements IDocCreator
' Do work
End Sub
End Class
Public Class Doc2Creator
Implements IDocCreator
Public Sub CreateDoc() Implements IDocCreator
' Do different work
End Sub
End Class
Then, you can create a list of those objects, like this:
Dim docCreators() As IDocCreator =
{
New DocCreator1(),
New DocCreator2()
}
Or:
Dim docCreators As New Dictionary(Of Integer, IDocCreator)()
docCreators.Add(1, New DocCreator1())
docCreators.Add(7, New DocCreator7())
docCreators.Add(20, New DocCreator7())
Then you can call one like this:
docCreators(1).CreateDoc()
The IDocCreator approach is very similar to the Delegate approach. Neither is the right or wrong way to do it. It depends on your style, what you are comfortable with, and your requirements. The main advantage of the IDocCreator approach is that you could easily add more properties and methods to it in the future. For instance, lets say that you also want to somewhere store a user-friendly, descriptive name for each template. It would be very easy to add a ReadOnly Property Name As String property to the IDocCreator interface, but if you go the list-of-delegates route, that would be more difficult and sloppy.
In any of the above examples, however, you still have to add the complete list of methods or delegates somewhere. So, while it's slightly less ugly than a giant Select Case block, it may still not be enough. If so, the technology you need to use is called Reflection. The System.Reflection namespace contains much of the reflection-related functionality. Reflection allows you to dynamically access and invoke portions of your code. For instance, you can use reflection to get a list of all of the properties or methods that are defined by a given class. Or you can use reflection to get a list of types that are defined by your assembly. Using reflection, it would be possible to get a method, by string name, and then invoke it. So, for instance, if you want to call the "CreateDoc1" method on the current object, you could do it like this:
Me.GetType().GetMethod("CreateDoc1").Invoke(Me, {})
Since you are calling it by its string name, you could build the method name via concatenation:
Dim methodName As String = "CreateDoc" & templateNum.ToString()
Me.GetType().GetMethod(methodName).Invoke(Me, {})
However, if you are going to use the reflection approach, instead of using the name of the method, like that, it would be cleaner, in my opinion, to use a custom attribute to tag each method. For instance, you could create an attribute class which allows you to decorate your methods like this:
<CreateDocMethod(1)>
Public Sub CreateDoc1()
' ...
End Sub
Then, at run-time, you can use reflection to find all of the methods that have that particular attribute and then invoke the right one.
The reason that I saved the discussion on reflection for last is because it is not as efficient and it can lead to brittle code. As such, it's best to only use reflection as a last-resort. There's nothing wrong with reflection, if you really need it, but, as a general rule, if there is another reasonable way to do something, which doesn't require reflection, then you probably ought to be doing it that other way.

Does instantiating and initializing work only when tied to button click?

Beginner question. How come I can do this:
Public Class Form1
Private StudentsInMyRoom As New ArrayList
Public Class student
Public name As String
Public courses As ArrayList
End Class
Private Sub btnCreateStudent_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnCreateStudent.Click
Dim objStudent As New student
objStudent.name = "Ivan"
objStudent.courses = New ArrayList
StudentsInMyRoom.Add(objStudent)
End Sub
End Class
But I CANNOT do this:
Public Class Form1
Private StudentsInMyRoom As New ArrayList
Public Class student
Public name As String
Public courses As ArrayList
End Class
Dim objStudent As New student
objStudent.name = "Ivan"
objStudent.courses = New ArrayList
StudentsInMyRoom.Add(objStudent)
End Class
In the second example, all of the objStudent.etc get squiggly underlined and it says "declaration expected" when I hover over it. It's the same code except now it is not tied to clicking a button. Can't figure out what is the difference.
It's because the implementation needs to be in a method, the way you have it means the code couldn't possibly be executed, how would you reference this code from elsewhere?
It doesn't have to be tied to a click however:
Private Sub AnyNameYouLike
Dim objStudent As New student
objStudent.name = "Ivan"
objStudent.courses = New ArrayList
StudentsInMyRoom.Add(objStudent)
End Sub
Will work.
Rather than tell you how to fix this code directly, I'm going to explain what I think is going wrong with your thought process, so you can also do a better job writing code in the future.
What I see here is a simple misunderstanding for someone new to programming of how classes work. When you build and define a class, you are not (yet) allocating any memory in the computer, and you are not yet telling the computer to do anything. All you are doing is telling the computer about how an object might look at some point in the future. It's not until you actually create an instance of that class that anything happens:
Public Class MyClass
Public MyField As String
End Class
'Nothing has happened yet
Public myInstance As New MyClass()
'Now finally we have something we can work with,
' but we still haven't done anything
myInstance.MyField = "Hello World"
'It's only after this last line that we put a string into memory
Classes can only hold a few specific kinds of things: Fields, Properties, Delegates (events), and Methods (Subs and Functions). All of these things in the class are declarations of something, rather than the thing itself.
Looking at your samples, the code from your second example belongs inside of a method.
If you want this code to run every time you work with a new instance of your class, then there is a special method, called a constructor, that you can use. That is declared like this:
Public Class MyClass
Public MyField As String
'This is a constructor
Public Sub New()
MyField = "Hello World"
End Sub
End Class
However, even after this last example you still haven't told the computer to do any work. Again, you must create an instance of the class before the code in that constructor will run.
This is true of all code in all .Net programs anywhere. The way your program starts out is that the .Net framework creates an instance of a special object or form and then calls (runs) a specific method in that form to sort of get the ball rolling for your program. Everything else comes from there.
Eventually you will also learn about Shared items and Modules, that can (sort of) break this rule, in that you don't have to create an instance of the object before using it. But until you are comfortable working with instances, you should not worry about that too much.
Finally, I want to point out two practices in your code that professional programmers would consider poor practice. The first is ArrayLists. I can forgive this, because I suspect you are following a course of study that just hasn't covered generics yet. I only bring it up so you can know not to get too attached to them: there is something better coming. The second is your "obj" prefix. This was considered good practice once upon a time, but is no longer fashionable and now thought to be harmful to the readability of your code. You should not use these prefixes.

VB.NET Why is this subroutine declared this way?

VB.NET 2010, .NET 4
I have a basic question: I have a subroutine that I found somewhere online declared thusly:
Public Sub MyFunction(Of T As Control)(ByVal Control As T, ByVal Action As Action(Of T)) ...
I'm wondering about the (Of T As Control) part of the declaration after the sub's name. I see that T is used later in specifying the type of Control and in Action(Of T), but why is it done this way instead of just doing:
Public Sub MyFunction(ByVal Control As Control, ByVal Action As Action(Of Control)) ...
What does that part after the sub's name mean? What is its purpose? Thanks a lot and sorry for my ignorance.
That is VB.NET's generic method declaration syntax:
A generic type is a single programming
element that adapts to perform the
same functionality for a variety of
data types. When you define a generic
class or procedure, you do not have to
define a separate version for each
data type for which you might want to
perform that functionality.
An analogy is a screwdriver set with
removable heads. You inspect the screw
you need to turn and select the
correct head for that screw (slotted,
crossed, starred). Once you insert the
correct head in the screwdriver
handle, you perform the exact same
function with the screwdriver, namely
turning the screw.
(Of T) is a generic type parameter, adding As Control constrains the type of T to inherit from Control. You could write the method the second way, but you'd probably end up having to cast the Control to whatever inherited type, within the lambda expression in the Action, or in the body of MyFunction. Generics allow you to avoid that.
For example:
Sub Main()
Dim form As New Form()
Dim textBox As New TextBox
Dim listBox As New ListBox
MyFunction(textBox, Sub(c) c.Text = "Hello")
MyFunction(listBox, Sub(c) c.Items.Add("Hello"))
MyFunction2(textBox, Sub(c) c.Text = "Hello")
MyFunction2(listBox, Sub(c) CType(c, ListBox).Items.Add("Hello"))
End Sub
Public Sub MyFunction(Of T As Control)(ByVal Control As T, ByVal Action As Action(Of T))
Action(Control)
End Sub
Public Sub MyFunction2(ByVal Control As Control, ByVal Action As Action(Of Control))
Action(Control)
End Sub
It doesn't look too valuable in trivial cases, but it's invaluable for more complex cases.
As others have said, it's a constrained generic parameter. But no one has yet addressed this part of your question:
why is it done this way
The answer is in the action. If it were just declared as a Control, you wouldn't be able to do something like this, because not all controls have a .Text property*:
MyFunction(MyTextBox, Function(t) t.Text = "new value" )
The body of the function just needs to know that it's working on a control of some kind, but the Action(Of T) you pass to the function might want to know the actual type of the control.
Yes, all controls do have a .Text property. Let's pretend for a moment that some didn't

Shared method in VB.NET cannot handle this why?

(I tried with this question but this code isolates the problem better.)
I have this code:
Public Shared Sub PopulateTextFields(ByRef stuffList As List(Of Stuff))
Dim aStuff As New Stuff
For Each aStuff In stuffList
DoStuff(aStuff)
Next
End Sub
Private Sub DoStuff(ByRef theStuff as Stuff)
....
End Sub
I get the following error highlighting DoStuff(aStuff):
Cannot refer to an instance member of
a class from within a shared method or
shared member initializer without an
explicit instance of the class.
Didn't I get an explicit instance of Stuff when I wrote the Dim statement?
Don't understand what's wrong. Thanks in advance!
I think the problem lies with the Subroutine DoStuff. If both your subs lie in the same class, you are trying to refer to DoStuff from within PopulateTextFields, which is a shared sub.
In order to achieve this, you need to declare DoStuff as Shared as well.
Yes you did, but you aren't referencing aStuff you are trying to call it on the static implementation of the class, furthermore you are resetting aStuff to a separate instance through each loop iteration.. change your code to:
Public Shared Sub PopulateTextFields(ByRef stuffList As List(Of Stuff))
Dim aStuff As New Stuff
For Each aStuff In stuffList
aStuff.DoStuff(aStuff)
Next
End Sub
Private Sub DoStuff(ByRef theStuff as Stuff)
....
End Sub
And it should work, but maybe not as expected, I don't really know your intent of having a private member that handles changing a separate reference of it's own type.
It may be appropriate to change the signature of DoStuff to:
Private Sub DoStuff()
....
'Use the Me reference here to change myself
....
End Sub
and then call it as:
aStuff.DoStuff() 'Will modify this instance
You didn't share which type these methods belong to. From your confusion, I'm guessing it's part of the "Stuff" class. But it doesn't really matter. It sounds like you're forgetting one of two things:
Creating an instance of the type in the shared method doesn't somehow attach the shared method to that instance. You could create 10 or 1000 instances in the method, after all.
Passing an instance as a parameter doesn't associate the function with an instance. A parameter is not a call site.
Either way, it comes down to providing an instanced call site. Your DoStuff function is not shared, and so the compiler thinks it needs access to state provided by a specific instance of your type. That instance is the method's call site. You either need an instance of the type to call it from: SomeInstance.DoStuff(aStuff) , or if the method doesn't really need access to any type state you need to mark it shared and call it like this: Stuff.DoStuff(aStuff)