The one calls form 2 as a dialog, and passed by ref a "pointer" to the base class(abstract).
//Form 1 calling form two. And Passing a ref object
Dim CreateForm As New frmCreate(Robot)
//Second Forms Overloaded New
Public Sub New(ByRef Robot As cRobot)
InitializeComponent()
thisRobot = Robot
End Sub
Select Case (cbType.SelectedIndex)
Case 0
lblOther.Text = "Bullet Proof Value"
Dim SecRobot = New cSecurityRobot
SecRobot.Name = txtName.Text
SecRobot.Temperature = nudTemp.Value
SecRobot.Threshold = nudThreshold.Value
SecRobot.BulletproofValue = nudOther.Value
thisRobot = SecRobot
Case 1
lblOther.Text = "Special Moves"
Dim SpRobot = New cSportsRobot
SpRobot.Name = txtName.Text
SpRobot.Temperature = nudTemp.Value
SpRobot.Threshold = nudThreshold.Value
SpRobot.SpecialMoves = nudOther.Value
thisRobot = SpRobot
Case 2
lblOther.Text = "Domestic Skills"
Dim SerRobot = New cServiceRobot
lblOther.Text = "Domestic Skills"
SerRobot.Name = txtName.Text
SerRobot.Temperature = nudTemp.Value
SerRobot.Threshold = nudThreshold.Value
SerRobot.DomesticSkills = nudOther.Value
thisRobot = SerRobot
Case Else
lblOther.Text = "Bullet Proof Value"
Dim SecRobot = New cSecurityRobot
SecRobot.Name = txtName.Text
SecRobot.Temperature = nudTemp.Value
SecRobot.Threshold = nudThreshold.Value
SecRobot.BulletproofValue = nudOther.Value
thisRobot = SecRobot
End Select
Form 2 assigns some values and terminates, but there is always a NULL Exception occurring
Let's take a look at your constructor:
Public Sub New(ByRef Robot As cRobot)
InitializeComponent()
thisRobot = Robot '<-- Problem is here
End Sub
On the line indicated above, you are making a copy of the reference, and so the ByRef no longer helps you.
Thinking about how to get around this problem, you might be able to do it by nesting the Robot inside another class:
Public Class RobotContainer
Public Property Robot As Robot
End Class
Pass a RobotContainer instance to your constructor in the normal (ByVal) way and keep a reference to that entire object in your class. Now, both your frmCreate type and the calling code have a reference to the same object. When you update the Robot property on that object, it will be updated for both locations.
But really, this whole design doesn't smell right. Normally I would suggest a method that return the created robot, rather than trying to assign it to an outside location directly, but I understand that working with Windows Forms controls this may not be option. To suggest a better solution we'd need to see a lot more of your code.
Hmm... looking back at this I wanted to do something to make the RobotContainer more useful:
Public Class ReferenceContainer(Of T)
Public Property Item As T
End Class
No, the "ByRef"-ness is only relevant for the method in which the parameter is declared. The value of the thisRobot variable is still just a reference value. Changing the value of that variable later on won't change the caller's variable.
Related
I am getting Runtime Error 13 when trying to update an object stored in a collection. Here is a minimal example.
The class (Class2) of the objects to be stored in the collection.
Option Explicit
Private pHasA As Boolean
Private pHasB As Boolean
Private pSomeRandomID As String
Property Get HasA() As Boolean
HasA = pHasA
End Property
Property Get HasB() As Boolean
HasB = pHasB
End Property
Property Let HasA(propValue As Boolean)
pHasA = propValue
End Property
Property Let HasB(propValue As Boolean)
pHasB = propValue
End Property
Property Let RandomID(propValue As String)
pSomeRandomID = propValue
End Property
Sub SetHasValues(key As String)
Select Case key
Case "A"
pHasA = True
Case "B"
pHasB = True
End Select
End Sub
Minimal code that reproduces the error:
Option Explicit
Private Sub TestCollectionError()
Dim classArray As Variant
Dim classCollection As Collection
Dim singleClass2Item As Class2
Dim iterator As Long
classArray = Array("A", "B", "C")
Set classCollection = New Collection
For iterator = LBound(classArray) To UBound(classArray)
Set singleClass2Item = New Class2
singleClass2Item.RandomID = classArray(iterator)
classCollection.Add singleClass2Item, classArray(iterator)
Next iterator
Debug.Print "Count: " & classCollection.Count
singleClass2Item.SetHasValues "A" ' <-- This code works fine.
Debug.Print "New Truth values: " & singleClass2Item.HasA, singleClass2Item.HasB
For iterator = LBound(classArray) To UBound(classArray)
classCollection(classArray(iterator)).RandomID = classArray(iterator)
classCollection(classArray(iterator)).SetHasValues classArray(iterator) '<-- Type mismatch on this line.
Next iterator
'***** outputs
'''Count: 3
'''New Truth values: True False
' Error dialog as noted in the comment above
End Sub
While the code above appears a little contrived, it is based on some real code that I am using to automate Excel.
I have searched for answers here (including the following posts), but they do not address the simple and non-ambiguous example that I have here. The answers that I have found have addressed true type mismatches, wrong use of indexing or similar clear answers.
Retrieve items in collection (Excel, VBA)
Can't access object from collection
Nested collections, access elements type mismatch
This is caused by the fact, that the parameter of your procedure SetHasValues is implicitely defined ByRef.
Defining it ByVal will fix your problem.
#ADJ That's annoying, but perhaps the example below will allow you to start making a case for allowing RubberDuck.
I've upgraded your code using ideas and concepts I've gained from the rubberduck blogs. The code now compiles cleanly and is (imho) is less cluttered due to fewer lookups.
Key points to note are
Not relying on implicit type conversions
Assigning objects retrieved from collections to a variable of the type you are retrieving to get access to intellisense for the object
VBA objects with true constructors (the Create and Self functions in class2)
Encapsulation of the backing variables for class properties to give consistent (and simple) naming coupled with intellisense.
The code below does contain Rubberduck Annotations (comments starting '#)
Updated Class 2
Option Explicit
'#Folder("StackOverflowExamples")
'#PredeclaredId
Private Type Properties
HasA As Boolean
HasB As Boolean
SomeRandomID As String
End Type
Private p As Properties
Property Get HasA() As Boolean
HasA = p.HasA
End Property
Property Get HasB() As Boolean
HasB = p.HasB
End Property
Property Let HasA(propValue As Boolean)
p.HasA = propValue
End Property
Property Let HasB(propValue As Boolean)
p.HasB = propValue
End Property
Property Let RandomID(propValue As String)
p.SomeRandomID = propValue
End Property
Sub SetHasValues(key As String)
Select Case key
Case "A"
p.HasA = True
Case "B"
p.HasB = True
End Select
End Sub
Public Function Create(ByVal arg As String) As Class2
With New Class2
Set Create = .Self(arg)
End With
End Function
Public Function Self(ByVal arg As String) As Class2
p.SomeRandomID = arg
Set Self = Me
End Function
Updated test code
Private Sub TestCollectionError()
Dim classArray As Variant
Dim classCollection As Collection
Dim singleClass2Item As Class2
Dim my_item As Variant
Dim my_retrieved_item As Class2
classArray = Array("A", "B", "C")
Set classCollection = New Collection
For Each my_item In classArray
classCollection.Add Item:=Class2.Create(my_item), key:=my_item
Next
Debug.Print "Count: " & classCollection.Count
Set singleClass2Item = classCollection.Item(classCollection.Count)
Debug.Print "Initial Truth values: " & singleClass2Item.HasA, singleClass2Item.HasB
singleClass2Item.SetHasValues "A" ' <-- This code works fine.
Debug.Print "New Truth values: " & singleClass2Item.HasA, singleClass2Item.HasB
For Each my_item In classArray
Set my_retrieved_item = classCollection.Item(my_item)
my_retrieved_item.RandomID = CStr(my_item)
my_retrieved_item.SetHasValues CStr(my_item)
Next
End Sub
The 'Private Type Properties' idea comes from a Rubberduck article encapsulating class variable in a 'This' type. My take on this idea is to use two type variable p and s (Properties and State) where p holds the backing variables to properties and s hold variables which represent the internal state of the class. Its not been necessary to use the 'Private Type State' definition in the code above.
VBA classes with constructors relies on the PredeclaredID attribute being set to True. You can do this manually by removing and saving the code, using a text editor to set the attributer to 'True' and then reimporting. The RUbberDuck attribute '#PredeclaredId' allows this to be done automatically by the RubberDuck addin. IN my own code the initialiser for class2 would detect report an error as New should not be used when Classes are their own factories.
BY assigning and intermediate variable when retrieving an object from a class (or even a variant) you give Option Explicit the best change for letting you n=know of any errors.
An finally the Rubberduck Code Inspection shows there are still some issues which need attention
If you want to insert a variable Form2, I use this ..
Dim Variable As New Form2
But if I have the same functions with names in two forms, I'll do it using if.
if 1 = 1 Then
Dim Variable As New Form2
Else
Dim Variable As New Form3
End If
That's perfectly fine, but if I start using this variable in the code below, an error occurs if I use without conditions, everything is fine, use when the condition seemed to understand what to do.
Variable.DataGridView1.Rows.Add(row)
Object reference not set to instance of an object
You need to declare your variable first and then assign it to a particular type:
Dim Variable As Form = Nothing
If 1 = 1 Then
Variable = New Form2()
Else
Variable = New Form3()
End If
But usually you would do this in an object oriented approach by each form implementing a common interface.
You can then do this:
Dim Variable As IForm
If 1 = 1 Then
Variable = New Form2() 'Form2 implements IForm
Else
Variable = New Form3() 'Form3 implements IForm
End If
I'm writing a program that has two forms. One form gets the user to enter multiple values, and then does some calculations. Then it passes that information to another form However I can't figure out how to do it. Here is a relevant part of my code. To head some confusion, I am trying to pass 11 values, also initially, form 2 is not shown, and then when the values are passed from form 1 to form 2, then form 1 goes away and form 2 is the only one that shown
NOTE: This is not all my code, I don't believe all my code is required (I have 1000 lines right now) However this is the code with the information I want to be passed to the other form.
A lot of people are apparently saying that this is a duplicate of another question, however that question, he seems to already know how to pass the variables, but is just having issues with it (and even with looking at his, i cant figure it out)
Private Sub btnSubmit_Click(sender As Object, e As EventArgs) Handles btnSubmit.Click
'declarations
Dim intNormal As Integer
Dim intChildren As Integer
Dim intBonanza As Integer
Dim intDiamond As Integer
Dim intPictureFrame As Integer
Dim intKite As Integer
Dim intCrazyT As Integer
Dim intLetterX As Integer
Dim int2PostageStamp As Integer
Dim intPick7 As Integer
Dim intJackpot As Integer
Validate()
If txtNormal1.Enabled = False Then
intNormal = intNormInput
Else
intNormal = CalcNormalBooks()
End If
If txtChildren1.Enabled = False Then
intChildren = intChildInput
Else
intChildren = calcChildrensBooks()
End If
If txtBonanza1.Enabled = False Then
intBonanza = intBonInput
Else
intBonanza = calcBonanza()
End If
If txtSpecial1.Enabled = False Then
intSpecial = intSpeInput
Else
intSpecial = calcSpecialBooks(intSpecial)
End If
If txtDiamond1.Enabled = False Then
intDiamond = intDiaInput
Else
intDiamond = calcDiamond(intSpecial)
End If
If txtPictureFrame1.Enabled = False Then
intPictureFrame = intPicInput
Else
intPictureFrame = calcPictureFrame(intSpecial)
End If
If txtKite1.Enabled = False Then
intKite = intKiteInput
Else
intKite = calcKite(intSpecial)
End If
If txtCrazyT1.Enabled = False Then
intCrazyT = intCrazyInput
Else
intCrazyT = calcCrazyT(intSpecial)
End If
If txtLetterX1.Enabled = False Then
intLetterX = intLettInput
Else
intLetterX = calcLetterX(intSpecial)
End If
If txt2PostageStamp1.Enabled = False Then
int2PostageStamp = intPostInput
Else
int2PostageStamp = CalcPostageStamp(intSpecial)
End If
If txtPick71.Enabled = False Then
intPick7 = intPickInput
Else
intPick7 = calcPick7(intSpecial)
End If
If txtJackpot1.Enabled = False Then
intJackpot = intJackInput
Else
intJackpot = calcJackpot()
End If
End Sub
Since I had almost the same requiremnt lately here is my solution:
Custom Event which fires when your 2nd Form is closing
Public Event HotKeyFormClosed As EventHandler(Of HotKeyFormClosedEventArgs)
Custom EventArgs class where you store your values you want to pass to Main Form
Public Class HotKeyFormClosedEventArgs
Inherits EventArgs
'Your properties here
Public Sub New(...) 'your params here
MyBase.New()
'set your properties here
End Sub
End Class
On 2nd Form handle FormClosed event and pass your values to EventArgs
Private Sub HotKey_FormClosed(sender As Object, e As System.Windows.Forms.FormClosedEventArgs)
RaiseEvent HotKeyFormClosed(Me, New HotKeyFormClosedEventArgs(...)) 'your params here
End Sub
On Main Form handle your custom event (here HotKeyFormClosed) and extract its values
AddHandler frmHotKey.HotKeyFormClosed, AddressOf HotKey_FormClosed;
...
Private Sub HotKey_FormClosed(sender As Object, e As HotKeyFormClosedEventArgs)
'Do stuff with values from e
End If
I have chosen the Event approach since it decouples the two forms from another.
One could easily duplicate the information on both forms, make them public and access it directly thru an object instance.
But I like the observable approach from the events more due to it gives mor flexibility (additonal forms using the same events etc.)
P.S.: I wrote my code in c# and blind entered the VB code here so be gracious.
The values/variables that a method expects to receive (specified in the method's signature) are called Parameters.
The values sent to a method when the method is called are called Arguments.
As long as the arguments used when calling a method match the parameters for that method, those values can be passed.
For example (and I'll try to apply this to your context), if you want to create an instance of a form that takes certain values, you can specify those parameters in the form's New event, like so:
Public Sub New(someInt As Integer)
'do something with someInt here
End Sub
Then when you call this method you'd pass it the arguments, like so:
Dim myInt As Integer = 10
Dim newForm As myForm = New myForm(myInt)
When I say the arguments need to match the parameters, that means the number of values, the order of those values, and the value types must be the same (or in the case of numbers the parameter's type must be the same or larger than the argument's type).
As long as that is true, then it shouldn't really matter how you pass these - you could pass 11 individual arguments, you just have to make sure you are matching the argument to the parameter.
Hope that helps!
This question is with VB2008 Express.
I'm making a usercontrol which uses a structured property. Both the control and the overall project have an identical structure. The problem is that within the main project, attempts to assign a place to this property results in: "Reference to a non-shared member requires an object reference."
I have no clue what that means, nor how to deal with it. Microsoft's help babbles something like: "You are referencing a non shared member, so an object reference will be required."
Well gee Microsoft, I read the error description so no shiznit... But what's that mean? (I came from VB6 and I'm self-taught by example from there so please go easy on me.)
Of course I could assign each individual piece of the structure as its own property, such as "Street" "City", etc. but there are reasons I prefer to do it in one step, as it is validated all at once by the usercontrol upon assignment.
Any help getting my usercontrol and my main project to pass a "place" to each other?
Public Structure Place
Public PlaceName As String
Public Street As String
Public Apt As String
Public City As String
Public State As String
Public Zip As String
Public VerifiedStatus As Integer
Public Lat As Single
Public Lng As Single
End Structure
Public Property CurrentPlace() As Place
Get
Dim ThisPlace As New Place
ThisPlace.Street = Trim(Me.txtStreet.Text)
ThisPlace.Apt = Trim(txtAptNo.Text)
ThisPlace.City = Trim(txtCity.Text)
ThisPlace.State = Trim(lblState.Text)
ThisPlace.Zip = Trim(txtZip.Text)
ThisPlace.Lat = MyLat
ThisPlace.Lng = MyLng
ThisPlace.PlaceName = ""
'This control doesn't take placenames but they exist in the structure.
ThisPlace.VerifiedStatus = MyVerifiedStatus
Return ThisPlace
End Get
Set(ByVal value As Place)
AsLoadedApt = Trim(value.Apt)
AsLoadedCity = Trim(value.City)
AsLoadedLat = value.Lat
AsLoadedLng = value.Lng
AsLoadedState = Trim(value.State)
AsLoadedStreet = Trim(value.Street)
AsLoadedVerifiedStatus = value.VerifiedStatus
AsLoadedZip = Trim(value.Zip)
txtStreet.Text = AsLoadedStreet
txtAptNo.Text = AsLoadedApt
txtCity.Text = AsLoadedCity
lblState.Text = AsLoadedState
txtZip.Text = AsLoadedState
MyVerifiedStatus = AsLoadedVerifiedStatus
MyLat = AsLoadedLat
MyLng = AsLoadedLng
Call ShowStatus()
End Set
End Property
With your structure inside the control and the usercontrol file as part of the project, the structure will be exposed as a type by qualifying it as part of the usercontrol:
Dim NewPlace As New UserControl1.Place
Now since you're using the same structure, the NewPlace object can be used to set the CurrentPlace property
With NewPlace
.Apt = "Apt"
.City = "City"
.Lat = 0
.Lng = 0
.State = "State"
.Street = "Street"
.Zip = "Zip"
End With
UserControl11.CurrentPlace = NewPlace
If it's part of a different project in the same solution add the qualification for the project as well.
Rather than giving the very specific case (which I did earlier), let me give a general example. Let's say that I have a function, called callingFunction. It has one parameter, called parameter. Parameter is of an unknown type. Let us then say that I wish to copy this parameter, and return it as a new object. For example, in pseudo code, something along the lines of...
Function callingFunction(ByVal parameter As Object) As Object
Dim newObj As New Object
'newObj has the same value as parameter, but is a distinctly different object
'with a different reference
newObj = parameter
return newObj
End Function
EDIT: Additional Information
The first time I posted this question, I received only one response - I felt that perhaps I made the question too specific. I guess I will explain more, perhaps that will help. I have an ASP page with 10 tables on it. I am trying, using the VB code behind, to come up with a single solution to add new rows to any table. When the user clicks a button, a generic "add row" function should be called.
The difficulty lies in the fact that I have no guarantee of the contents of any table. A new row will have the same contents as the row above it, but given that there are 10 tables, 1 row could contain any number of objects - text boxes, check boxes, etc. So I want to create a generic object, make it of the same type as the row above it, then add it to a new cell, then to a new row, then to the table.
I've tested it thoroughly, and the only part my code is failing on lies in this dynamic generation of an object type. Hence why I asked about copying objects. Neither of the solutions posted so far work correctly, by the way. Thank you for your help so far, perhaps this additional information will make it easier to provide advice?
You can't do this in general. And it won't be a good idea, for example, if parameter is of a type which implements the singleton pattern. If parameter is of a type which supports copying, it should implement the ICloneable interface. So, your function could look like this:
Function MyFunc(ByVal parameter As Object) As Object
Dim cloneableObject As ICloneable = TryCast(parameter, ICloneable)
If Not cloneableObject Is Nothing Then
Return cloneableObject.Clone()
Else
Return Nothing
End If
End Function
You could implement something like this:
Dim p1 As Person = New Person("Tim")
Dim p2 As Object = CloneObject(p1)
Dim sameRef As Boolean = p2 Is p1 'false'
Private Function CloneObject(ByVal o As Object) As Object
Dim retObject As Object
Try
Dim objType As Type = o.GetType
Dim properties() As Reflection.PropertyInfo = objType.GetProperties
retObject = objType.InvokeMember("", System.Reflection.BindingFlags.CreateInstance, Nothing, o, Nothing)
For Each propertyInfo As PropertyInfo In properties
If (propertyInfo.CanWrite) Then
propertyInfo.SetValue(retObject, propertyInfo.GetValue(o, Nothing), Nothing)
End If
Next
Catch ex As Exception
retObject = o
End Try
Return retObject
End Function
Class Person
Private _name As String
Public Property Name() As String
Get
Return _name
End Get
Set(ByVal value As String)
_name = value
End Set
End Property
Public Sub New()
End Sub
Public Sub New(ByVal name As String)
Me.Name = name
End Sub
End Class
Here's a simple class that will work for most objects (assumes at least .Net 2.0):
Public Class ObjectCloner
Public Shared Function Clone(Of T)(ByVal obj As T) As T
Using buffer As MemoryStream = New MemoryStream
Dim formatter As New BinaryFormatter
formatter.Serialize(buffer, obj)
buffer.Position = 0
Return DirectCast(formatter.Deserialize(buffer), T)
End Using
End Function
End Class