Populate IEnumerable through XAML neatly? - xaml

I am trying to populate a dependency property from XAML. The dependency property is an IEnumerable<KeyAction> where KeyAction is a complex type.
<loc:MyType.KeyActions>
<loc:KeyAction Action="Show" Key="Space" Modifiers="LeftCtrl" />
<loc:KeyAction Action="Hide" Key="Escape" />
</loc:MyType.KeyActions>
Now, this causes the KeyAction property to be 'declared twice' because XAML interprets each item as a candidate for the property, instead of items in a list.
For this to work, it would need to look something like:
<loc:CompletionPopupView.KeyActions>
<sys:List`KeyAction>
<loc:KeyAction Action="Show" Key="Space" Modifiers="LeftCtrl" />
<loc:KeyAction Action="Hide" Key="Escape" />
</sys:List`KeyAction>
</loc:CompletionPopupView.KeyActions>
And I'd need to add namespaces, and the generic syntax is probably even more terrible, if even doable. Is there any way to make the first example work?

There are two different ways you can make this work. One looks exactly like your first example, but requires changes to your class and doesn't operate quite the way you're asking for (which might or might not be a problem to you); the other acts exactly like you're asking but is a little more verbose. You can decide which one is better for you.
Option 1: Adding to a collection
XAML has magic shorthand syntax for initializing collections, using the exact syntax you show in your first example. However, it only works if the property type implements IList. (Yes, that's really the non-generic IList -- not normally a big deal though, all the generic collections that ship with .NET implement both IList<T> and IList.)
So you could do your first example, but only if your KeyActions property was declared as a type that implements IList. For example, you could change your property to:
public ObservableCollection<KeyAction> KeyActions { get {...} }
And then just put multiple child elements inside your property, and it would add them to the collection:
<loc:MyType.KeyActions>
<loc:KeyAction Action="Show" Key="Space" Modifiers="LeftCtrl" />
<loc:KeyAction Action="Hide" Key="Escape" />
</loc:MyType.KeyActions>
This isn't quite like what you asked for though, because the XAML doesn't create a new collection -- it adds to the existing collection. So if you choose this option, your class needs to instantiate the collection in its constructor (KeyActions = new ObservableCollection<KeyAction>();), so that you don't get a null reference exception when you start trying to Add elements to it.
Option 2: Creating a new collection
If you do need to create a new collection and assign it to your property, that's doable too. Unfortunately, XAML2006 (the flavor still used by WPF) only supports generics on the root element of the entire document -- so you can't instantiate a List<T> and assign it to a property.
But that's okay; you can use the same workaround that WPF does. Just create your own non-generic class that descends from a generic list.
public class KeyActionCollection : ObservableCollection<KeyAction> {}
Then you can instantiate it in XAML:
<loc:CompletionPopupView.KeyActions>
<loc:KeyActionCollection>
<loc:KeyAction Action="Show" Key="Space" Modifiers="LeftCtrl" />
<loc:KeyAction Action="Hide" Key="Escape" />
</loc:KeyActionCollection>
</loc:CompletionPopupView.KeyActions>
If you choose this option, you can keep your property declared as IEnumerable<KeyAction> if you wish. The custom list is only necessary to populate the list from XAML.
You could even combine both approaches: make the property read/write and of type KeyActionCollection, instantiate it in your constructor, and then XAML can choose whether to add to the existing collection using the shorthand syntax, or create a new collection; and runtime code could make the same choice.

Related

What data types cause object collection to behave as it does?

I understand arrays. I know some java from 15 years ago, and I know about classes, objects, instances, variables, static variables and constructors. Not so familiar with these things in VB.
I don't understand object collections..
Suppose I draw a listbox, and name it lstbox1
I see that I can say lstbox1.items.item(0) or lstbox1.items(0)
The fact that I can say listbox1.items(0) puzzles me a little bit. If an object collection is not an array, then it's clearly not an object either.
This link https://msdn.microsoft.com/en-us/library/yb7y698k(v=vs.90).aspx says Collection is an object.
But then what is items(0) items is not a class so that can't be calling a constructor... and items is not a method, it's a property that is an instance of object collection, so I can't see how the (0) works.. I know what it refers to the first object, the element with index 0, but I don't understand how that can work. I know blah(0) would work if blah was an array. And I am sure lstbox1.items is not an array of object collections it's just 1 object collection.
Is it a data structure like an Array, that has its own syntax.. for example one can say dim blah as Integer() or dim blah() as Integer and thus define it without even stating the class Array. Is ObjectCollection a bit like that? It does seem to allow (index) after an instance of it.
VB has a concept called Default Properties. In the case of an ItemCollection type (and a number of other types, as well), the Item property is the default property for the collection. This allows you to use the shorthand from the question.
It's basically just a bit of syntactical sugar. When you say, lstbox1.items(0), it's just shorthand for lstbox1.items.item(0).
Also, don't mistake the various collection types for simple arrays. They will have similar syntax, but every collection type has it's own quirks and use cases, and it's generally worth your time to look at the documentation for the specific type you're working with. Don't assume something is an array, just because you can access the items by index.
Think about this just as a "language feature". This is what really called indexer property. It is implemented as default property in vb.net. In c# implementation is different. The data structure behind it could be anything you want - array, list, dictionary, hashtable. The fact is - it lets you access something by supplying a parameter without calling property syntax. myParentObject(1) instead of myParentObject.GetChildObject(1)
In VB, default property must be indexed.
listbox1 . Items . item(0) --> listbox1 - main object that has property Items , which is a collection. This collection has property item, which is default property or indexer. Item is a property exposing single object from underlying collection.

XAML bind to Collection[index].ObjectProperty

I have an observable collection of objects in my VM. I want to bind to a property of a specific item in the list in a text block, something like this:
Binding="{MyVMCollection[0].Description}"
But this syntax does not work. Is it possible to do what I am after and if so, how?
Thanks!
You're missing the Binding keyword and I think you also need to use Path.
Binding="{Binding Path=MyVMCollection[0].Description}"
The type of the object needs to be a type where an array index would normally work for this to work. I'm not sure the exact constraints but use Type[] if in doubt.
eg. If it is some weird enumerable type like IOrderedEnumerable<T> (or some wierd LINQy type) then something like {Binding List[0]} won't work.

Changing DataTemplate with Property Binding in Silverlight

I'm trying to implement this using Eugene Akinshin's code from here: http://forums.silverlight.net/t/237947.aspx/1
It seems like a really nice way to bind to already-existing properties and means the configuration can all be defined in XAML.
However, I can't get it to work.
I'm defining the templates to use like this:
<Converters:TemplateSelectorConverter x:Key="templateConverter">
<Converters:TemplateSelectorCase TemplateReference="Minimised" Template="{StaticResource SmallOrders}"/>
<Converters:TemplateSelectorCase TemplateReference="Restored" Template="{StaticResource MediumOrders}"/>
<Converters:TemplateSelectorCase TemplateReference="Maximised" Template="{StaticResource LargeOrders}"/>
</Converters:TemplateSelectorConverter>
and then setting the item template of my ListBox like this:
ItemTemplate="{Binding CurrentState, Converter={StaticResource templateConverter}}"
CurrentState is a string of either 'Minimised', 'Maximised' or 'Restored' (I've edited the linked example to have a string as the key rather than an int) and is set to 'Minimised' by default, but all I get is a list of Cannot create [my object type] in my ListBox.
The templates definitely work as I can expose the View in the ViewModel and set the DataTemplate in code, and there are visual states that rely on the same CurrentState property which work, so I know the View can access the property correctly. Unfortunately, if I breakpoint the Convert() method in the converter, it never gets hit.
All suggestions greatly appreciated!
Not sure if you have figured this out or not but placement of the Converter definition in the Resource in relationship to the DataTemplate will be the difference between it working and not working.
The Converter needs to be placed prior to the Data Template.

CF9 ORM Populating an entity with an object

I am using Model-Glue/Coldspring for a new application and I thought I would throw CF9 ORM into the mix.
The only issue I am having right now is with populating an entity with an object. More or less the code below verifies that only one username can exist. There is some other logic that is not displayed.
My first thought was to using something like this:
var entity = entityload('UserAccount' ,{UserName=arguments.UserAccount.getUserName()},"true")
entity = arguments.UserAccount;
How ever this does not work the way that I expected. Is it even possible to populate an entity with an object or do I need to use the setters?
Not sure if this is what you're looking for. If you have...
component persistent="true" entityName="Foo"
{
property a;
property b;
}
You can pass a struct in the 2nd param to init the entity (added in CF9.0.1 I believe)
EntityNew("Foo", {a="1",b="2"});
To populate Foo with another object, you can use the Memento pattern, and implement a GetMemento() function to your object that returns a struct of all its properties.
EntityNew("Foo", bar.getMemento());
However, CF does NOT call your custom setters! If you want to set them using setters, you may add calls to the setters in your init() constructor, or use your MVC framework of choice to populate the bean. In Model-Glue, it is makeEventBean().
Update: Or... Here's hack...
EntityNew("Foo", DeserializeJSON(SerializeJSON(valueObject)));
Use this at your own risk. JSON might do weird things to your numbers and the 'yes','no','true','false' strings. :)
Is it even possible to populate an entity with an object or do I need to use the setters?
If you mean "Is it possible to create load an ORM Entity from an instance of that persistent CFC that already exists and has properties set?", then yes you can using EntityLoadByExample( object,[unique] )
entity = EntityLoadByExample( arguments.userAccount,true );
This assumes the userAccount CFC has been defined as persistent, and its username value has been set before being passed in (which seems to be the case in your situation).
Bear in mind that if any other properties have been set in the object you are passing, including empty strings, they will be used as filters to load the entity, so if they do not exactly match a record in your database, nothing will be loaded.

Reusing property editors for Blend 4

I have a custom silverlight control, which exposes a property with DataGridLength type. Now I want that property to have the same editor as a common DataGridColumn's Width property, with the combobox and everything, like this:
instead, I only get a simple TextBox, with "Auto" written in, with no way to set to SizeToCells and so on.
I assume I need a DesignTime attribute, but none of the ones I found in ComponentModel namespace even came close...
I guess you just have to create an Enum with the all the values autorized (Pixels, SizeToCells, etc ...), you that Enum as the type of your property DataGridLength and then, in the code of your control, take the corresponding action regarding the value sent.