Get a list of observable properties - oop

i know how to use the command 'properties' to get a list of all properties that an object has.
Is there a way to get a list of only the properties that are observable?

Use metaclass information, and look for the properties that have the SetObservable attribute:
mc = meta.class.fromName('MyClass');
idx = [mc.PropertyList.SetObservable];
pnames = {mc.PropertyList.Name};
pnames = pnames(idx)
Same can be done for GetObservable attribute

Related

Fabric.js: How to serialize/deserialize some global data? How to extend the canvas object?

How can I add additional properties to the canvas object so that they get serialized/deserialized? E.g. I'd like to store a name for the data that I get with canvas.toJSON() and that I store on the server-side.
Does it make sense to extend the canvas this way? Or would you recommend to create a "meta object" that contains my additianal data and one property for the canvas json?
You can pass an array with additional properties into Canvas.toJSON() method like this:
const canvas = new fabric.Canvas('c')
canvas.myProp = 'MyCanvas'
const serializedCanvas = canvas.toJSON(['myProp'])
console.log(serializedCanvas.myProp) // 'MyCanvas'
Note that this array is also passed into every object's _toObject() method during the serialization, so it makes sense to keep the custom property list to a minimum.

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")

Reference to object properties

I have a list of objects of type say Person, and I want to export Person records to an excel-sheet (I am using a proprietary excel component for VB.NET). Using a form with checkboxes the user can specify which Person properties should be exported.
Instead of having an enormous if-then-else tree where I check to see if each checkbox (corresponding to a property) has been checked, I have a data structure where for each property in Person I keep a boolean (checked/unchecked) and the name of the property as a string. I then use two for-loops like this:
For Each p As Person In Persons
...
For Each item As ExportColumnData In ExportColumnTable
...
If item.Checked Then
...
Dim o As Object = CallByName(p, item.PropertyName, CallType.Get, Nothing)
SaveValueToExcelSheet(o)
...
End If
...
Next
...
Next
However, this is not type-safe since I am using CallByName supplying PropertyName as a string. Is there a more elegant and type-safe way I can achieve the same thing? I need some way (other than a string) to reference the properties of these Person objects.
The CallByName function uses reflection to find and execute the property getter by string name, so you are right that it is unsafe in the sense that there will be no compile-time checking done to ensure that the properties by those names actually do exist in the Person type.
Unfortunately, short of a big If/Else block, or something similar, there is no "safe" way to do this in a way which will allow for compile-time type checking. If you want it to check that at compile-time, you need to call the property by name directly in code, and if you are doing that, it will have to be in a big conditional block of some sort.
There are things you could do to minimize or shift the location of the ugliness. For instance, you could create an enumeration of all the Person properties and add a method to the Person class which returns the property value given the enumeration item using a big Select Case block. That would make the logic reusable but not really any less ugly. Not only that, but doing it that way kind of puts the type-checking responsibility on your code, not the compiler.
Alternatively, you could, for instance, set the tag of each CheckBox control to a delegate which takes a Person object and returns the correct property value for that option from the given Person object. Then, in the loop, you could just call the delegate in the tag to retrieve the value. For instance, if you had a delegate like this:
Private Delegate Function GetPersonProperty(x As Person) As Object
Then you could set the Tag of the CheckBox controls like this:
chkFullName.Tag = New GetPersonProperty(Function(x As Person) x.FullName)
chkAge.Tag = New GetPersonProperty(Function(x As Person) x.Age)
Then, in your loop, you could invoke the delegate in the Tag to get the value, like this:
Dim myDelegate As GetPersonProperty = CType(item.Tag, GetPersonProperty)
Dim value As Object = myDelegate.Invoke(p)
But that's rather overly-complicated for such a simple task.
In the end, if the compile-time type checking is really important, I'd just bite the bullet and make the big conditional block. If it's not really that important, I'd just stick with the reflection and put some decent exception handling in the code.
You say you already have a class where you store the information about the properties of your Person class. You can use this to store the PropertyInfos as well.
Here's an example:
Class Person
Public Property Name As String
Public Property Age As Integer
End Class
Class ExportProperty
Public Property [Property] As PropertyInfo
Public Property Export As Boolean
End Class
Sub Main()
'' Create a List(Of ExportProperty) from all public properties of Person
Dim properties = GetType(Person).GetProperties() _
.Select(Function(p) New ExportProperty With { .[Property] = p}) _
.ToList()
'' Say we want to export only the Age field
properties.Single(Function(p) p. [Property].Name = "Age").Export = True
'' Create a person instance to export
Dim pers = New Person With { .Name = "FooBar", .Age = 67 }
'' Only export the properties with Export = True
For Each prop in properties.Where(Function(p) p.Export)
'' Use the PropertyInfo.GetValue-method to get the value of the property
''
Console.WriteLine(prop.[Property].GetValue(pers, Nothing))
Next
End Sub
Your solution is perfectly fine, as long as the contents in ExportColumnData are correct. If these are computed dynamically at runtime, you're fine.
Otherwise, or alternatively, you can do the following: use Type.GetProperties to get a list of PropertyInfo objects. You can then use these instead of a mere String to extract property values in your loop:
Dim o As Object = item.PropertyInfo.GetValue(p, Nothing)

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.