In the previous episode, I discussed how an enum
can be used to encapsulate char
values in a database, in this case using a Gender enum
as an example. As cool as that was, it does pose a problem with databinding in Silverlight that I'd like to share with you. Part of that explanation leads us into the realm of reflection (that would make for a cool blog title :) ) and then into some cool trick in binding, all to end with a bit of a disappointment. So if you already feel depressed, stop reading now :-P.
The Story
First there was a BussinessObjectBase
. This class handles a lot of stuff around transporting data between the service and the client and back. It also helps in keeping track of property changes and notifies about them. It's a very generic class and it serves as a base class for all our 'business' objects.
In this case, let's say there is a PersonBase
class, that is derived from BusinessObjectBase
. PersonBase
is generated based on some metadata, so we don't want to touch this source. The PersonBase
class has a property called Gender
that is of type string
.
Then there is the Person
class, which was generated but is meant to be used for custom code, so this is where we will write some code to make our Gender
available as a Gender
instead of a string
.
We could simply write something like this in the Person
class:
private char GetGender()
{
string gender = base.Gender;
if (gender.Length == 0)
{
return DefaultGender;
}
return gender[0];
}
public Gender GenderValue
{
get
{
return (Gender)GetGender();
}
set
{
base.Gender = ((char)value).ToString();
}
}
Our intention here is to hide the base implementation of the Gender
property and have our own implementation that returns an actual Gender enum
. Nothing special so far.
The next step is to use this new property in a databinding scenario. Here is what the binding statement in XAML would look like:
{Binding Path=Gender, Mode=TwoWay}
Now if you'd try to use a binding statement like this and run the code, what would happen is that you would get an AmbiguousMatchException
, the reason being that the binding engine can't distinguish between PersonBase.Gender
and Person.Gender
.
What? But we wanted to hide PersonBase.Gender
, right? Absolutely, but both properties are public
, so both are actually available.
Reflecting on the 'new' Property
To get a better understanding of why this was happening, I decided to write some reflection code:
object person = newPerson();
object personBase = newPersonBase();
Type personType = person.GetType();
PropertyInfo personGenderProperty = personType.GetProperty("Gender");
MessageBox.Show(personGenderProperty.GetValue(person, null).ToString());
Actually trying to get the value of the Gender
property through reflection threw the AmbiguousMatchException
, just like it did when databinding to it. Actually calling personType.GetProperties
from the Immediate Window in Visual Studio returned both properties. Then the exception all of a sudden makes sense.
What might seem a bit awkward is the fact that both properties are there. If I'd try to write string gender = somePerson.Gender
where somePerson
is of type Person
then it would not compile because I can't implicitly cast this, proving that the base property is actually hidden. Still, having the property available is needed, because the derived class needs to be able to call the property in the base class.
Trying to Bind to it Anyway
Still, I needed to find a way to bind to this property. I came up with some simple solutions:
- Make the base property protected
- Rename one of the properties to remove the problem all together
- Find a way to distinguish between the two properties in databinding
The first solution doesn't work for me, because I can't touch this code (it was generated, remember?).
The second solution would mean that I'd have to rename the property in the derived class, which doesn't seem very nice.
The third solution was a long shot, but I had to give it a try. I did actually find this syntax to use for a case like this, however it wasn't very well documented, so I did some experimentation to get a better understanding of it. I started writing this in XAML:
{Binding Path=(local:Person.Gender)}
This failed with an exception telling me that this was an invalid value for the attribute. I was surprised and puzzled, because I read about other people using it and it is actually in the documentation like this. Some further investigation taught me that this only works on dependency properties. I've built a small example of this and it actually works very well, however...
...having a dependency property with all the plumbing involved is best done by deriving your class from DependencyObject
(which contains stuff like GetValue
and SetValue
). I obviously can't derive Person
or PersonBase
from DependencyObject
, because I have already derived them from another class. This means I now have to rename the property in the derived class :(.
You can find the example of binding to a new property here. Hopefully it is helpful to you. It was a great learning experience overall. Just too bad it didn't lead to a better solution for me.