Setting default property fails because it is read only? - vb.net

This code was converted from VB6 to VB.Net:
Public prvMainForm = VB6Form
If prvMainForm IsNot Nothing Then
CObj(prvMainForm).StatusBar.Panels(1) = "Initializing Folders..."
End If
(My code is quite long so I've just added this if block which is where the actual error occurs.)
The error is seen on the single line inside the If statement:
Property 'Item' is 'ReadOnly'

StatusBar.Panels(1) returns a MSComctlLib.Panel.
StatusBar.Panels(1) = "Initializing Folders..." is valid in VB6 because of default properties.
Default properties in VB.NET must have parameters. A parameterless property cannot be default and therefore cannot be omitted. Thus, .Panels(1) = "..." is understood by VB.NET as an attempt to replace the entire Panel in the Panels property, which is not allowed.
You can look up the name of the default property in the VB6 object browser, which turns out to be Property _ObjectDefault As String, so you should be able to do:
CObj(prvMainForm).StatusBar.Panels(1).[_ObjectDefault] = "Initializing Folders..."
As you have observed, assigning Text should do the same:
CObj(prvMainForm).StatusBar.Panels(1).Text = "Initializing Folders..."

Related

VB.Net readonly property being automatically initialised without Get code execution

I don't understand this, please help. Here is my code:
Class MyCookie
Private _CookieName As String
ReadOnly Property CookieName As String
Get
If String.IsNullOrWhiteSpace(_CookieName) Then _CookieName = "Test"
Return _CookieName
End Get
End Property
End Class
I put a breakpoint inside the property Get procedure. When I initialise MyCookie class, MyCookie.CookieName already has "Test" in it but the breakpoint is never hit!
What am I missing?
What other value would the debugger display for that property? The debugger has to execute the property to get a value to display in the IDE but your application hasn't executed the property so the breakpoint isn't activated. Breakpoints are only activated by YOU executing the code, not the IDE. If you were to use the debugger to watch the _CookieName field instead of the CookieName property then you'd see exactly what you would expect to see.

vb.net modifying values of custom attributes in a class

I had an idea to use custom attributes on the properties in a class for databinding purposes in a winforms interface. For example, setting and changing the backcolor, forecolor, and tooltip on a textbox with invalid data. I find that I can bind up the control properties of txtTest for backcolor, etc., to a custom attribute such as BackColorAttr decorating a property in the class such as Name, with no problem. The property value itself is bound to the Text of the textbox, two-way binding of that works just fine, and the initial backcolor, forecolor, etc., are set from the initial values of the custom attributes just the way I had hoped. I'm doing all this through a BindingHelper class that reduces all the coding to a couple of generic methods.
Where I'm stumped is manipulating the values of the custom attributes at a later time. Changing the backcolor to red, for example. Nothing I've tried seems to work. Has anybody tried something like this, or have some guidance as to how I might proceed?
I dont quite follow the first part or what binding has to do with colors or Attributes, but thats not how Attributes work. They are not Property wrappers and Properties, Methods and Types have no idea of the Attributes associated with them (and vice-versa). They are meta data compiled into the assembly. As such, you cant change the value in any meaningful way.
Test class and test Attribute:
Public Class BarState
Inherits Attribute
Public Property State As String
Public Sub New(t As String)
State = t
End Sub
End Class
Public Class Foo
<BarState("red")>
Public Property Name As String
End Class
Since State is a property, test if we can set it:
Dim f As New Foo
' get props for the Type
Dim pi As PropertyInfo = f.GetType.GetProperty("Name")
Dim attr = pi.GetCustomAttributes(GetType(BarState), False)
If attr.Length > 0 Then
' get prop info for the State property on the Attr Type
Dim pa As PropertyInfo = attr(0).GetType.GetProperty("State")
' change it
CType(attr(0), BarState).State = "GREEN"
' or
'pa.SetValue(attr(0), "GREEN", Nothing)
' print it (prints "GREEN" but it does not persist)
Console.WriteLine(CType(attr(0), BarState).State)
End If
'get the attr again as you might do next time thru
attr = pi.GetCustomAttributes(GetType(BarState), False)
' print the value (Print "red")
Console.WriteLine(CType(attr(0), BarState).State)
The first print will be "GREEN" but that is only for this instance - it does not persist. The next time you get it, it reverts to "red". Since an Attribute is a Type, we can try to Reflection to change the value using pa.SetValue(attr(0), "GREEN", Nothing) which is commented out. It still wont persist because "red" is compiled into the assembly which is what your starting point will always be.
It might seem like you could keep a Dictionary or collection of the attribute instances for all the properties on all the types. That could work except, they all look alike, so you would have to create a hash to track which Attribute instance goes with what Property on what Type.
And you'd have to keep that collection in sync with the underlying instance objects. The Attribute instance wont know the instance it came from is gone and so the state setting should revert, so your Attribute manager would need to handle that.
You might look into "weavers" which use attributes to tag things (like a value range) then rewrite the assembly to weave in range checks for the tagged properties. Sort of sounds like what you are after I dont know what else they might do along the lines of what you describe.

Using CallByName to set Item(x) property

As a bit of background, I have a .net <-> COM object bridge that uses VB.net as a middleman, with a lot of reflection to get the job done.
I've run into a hurdle where I'm needing to use CallByName() to set a pretty standard property which is defined as
Public Default Property Item (
index As Integer
) As String
Get
Set
which would normally be called as .Object(1) = "new value", however the bridge code at the moment tries to get .Object(1) as an object then call Set on it using CallByName() (which obviously doesn't work).
With other collections I am happily able to use CallByName() to make method calls .Clear() and .Add("new value") but this property doesn't have these methods and besides, I'd like to solve it for a more generic approach so that code from the other side of the bridge can call the .Object directly.
Is someone able to suggest a way to Set an array-type property directly using CallByName(), or perhaps suggest an alternative reflection function that can be called to achieve this?
The default property can be used as a normal property, using its name. So, given a class:
Class Foo
Default Public Property Item(index As Integer) As String
Get
'...
End Get
Set(value As String)
'...
End Set
End Property
End Class
These three property assignments all have the same effect:
Dim Bar As New Foo
Bar(1) = "x"
Bar.Item(1) = "x"
CallByName(Bar, "Item", CallType.Set, 1, "x")
For array-type properties, the parameter(s) are passed to CallByName before the value when setting.
You did not show how you were using CallByName on that property, which leaves us to guess what is wrong. The syntax of .Object(1) = "new value" is also a little confusing: does the leading dot means that Object itself is some sort of collection on some other Type?
The basic answer lies in looking at the declaration, not how it is used normally. The fact that you can omit "Item" normally because it is the Default, does not apply here:
'foo(1) ==> foo.Item(1) = "Ziggy" ==>
CallByName(foo, "Item", CallType.Set, 1, "Ziggy")
The procName argument would be the property name, Item in this case. CallType.Set means you want the prop setter (Let or Set seem to both work). The first argument would be the index of the item to set/get, the last would be the data to pass.
If .Object is supposed to mean you are trying to reference a collection property, then the answer is about the same:
'foo.bars(1) ==> foo.Bars.Item(1) = "Zoey" ==>
CallByName(foo.Bars, "Item", CallType.Set, 1, "Zoey")

Call set of parent property when setting child properties

So in my class 'myInfo' I have an aliased property 'HeaderInfo' that is a property as a class, where it is actually the Header of a much deeper class.
Private _header As myHeader
Public Property HeaderInfo() AS myHeader
Get
Return _header
End Get
Set(ByVal value As myHeader)
_header = value
Someotherclass.Foo.Bar.AnotherThing.Header = _header
End Set
End Property
myHeader is a class with properties like 'Name', 'ID', etc. that are all strings. So when I reference this property in something like a Windows Form, I do
Dim info As New myInfo()
info.HeaderInfo.ID = "ID HERE"
info.HeaderInfo.Name = "Name here"
It works to the extent that the instance of the info.HeaderInfo is setting all its properties correctly, but
Someotherclass.Foo.Bar.AnotherThing.Header = _header
never gets set inside the myInfo.HeaderInfo 'Set', because I'm not directly setting the property, I'm setting its subproperties in assumption that it would propagate. Am I not understanding how properties with a custom type work? Is there a way to propagate this?
To make this happen automatically, you would need to alter the setter for the properties in your myHeader type, and for that to work your type instances have to know about the specific instance of your myInfo type.
Let's look at why this doesn't work they way you hoped. To do that, I'll break apart this statement:
info.HeaderInfo.ID = "ID HERE"
When that statement is executed, first the info variable must be de-referenced to get the object instance it refers to.1 When we have that object, we have to get (not set) the HeaderInfo property, so that we have a reference to the your myHeader object instance. Once we have the myHeader object, we call the setter on the ID property to complete the assignment.
Hopefully that clears up why this works the way it does. You do access the HeaderInfo property, but you only ever use the getter.
1Side note: if you ever see the "Object reference not set to instance of object" this is what it's talking about: a variable or property that you did not expect in an expression was Nothing/null.

In VB6 what is the difference between Property Set and Property Let?

I have just created several Property Set methods, and they didn't compile. When I changed them to Property Let, everything was fine.
I have since studied the documentation to find the difference between Property Set and Property Let, but must admit to being none the wiser. Is there any difference, and if so could someone offer a pointer to a proper explanation of it?
Property Set is for objects (e.g., class instances)
Property Let is for "normal" datatypes (e.g., string, boolean, long, etc.)
Property Let is more versatile than Property Set. The latter is restricted to object references only. If you have this property in a class
Private m_oPicture As StdPicture
Property Get Picture() As StdPicture
Set Picture = m_oPicture
End Property
Property Set Picture(oValue As StdPicture)
Set m_oPicture = oValue
End Property
Property Let Picture(oValue As StdPicture)
Set m_oPicture = oValue
End Property
You can call Property Set Picture with
Set oObj.Picture = Me.Picture
You can call Property Let Picture with both
Let oObj.Picture = Me.Picture
oObj.Picture = Me.Picture
Implementing Property Set is what other developers expect for properties that are object references but sometimes even Microsoft provide only Property Let for reference properties, leading to the unusual syntax oObj.Object = MyObject without Set statement. In this case using Set statement leads to compile-time or run-time error because there is no Property Set Object implemented on oObj class.
I tend to implement both Property Set and Property Let for properties of standard types -- fonts, pictures, etc -- but with different semantics. Usually on Property Let I tend to perform "deep copy", i.e. cloning the StdFont instead of just holding a reference to the original object.
Property Set is for object-like variables (ByRef) whereas Property Let is for value-like variables (ByVal)