Silverlight DataBinding, avoid BindingExpression Path error on missing properties, hide controls instead
imagine the following simple Models (example for simplicity reasons; in fact, we have MVVM here but it doesn't matter):
public class User {
public string Username { get; set; }
}
public class StackOverflowUser : User {
public int Reputation { get; set; }
}
Now we have a Silverlight UserControl
which contains the following Controls
(again, this is just an example, stripped do开发者_如何转开发wn to the core):
<Grid>
<TextBlock Text="Username:" />
<TextBlock Text="{Binding Path=Username}" />
<TextBlock Text="Reputation:" />
<TextBlock Text="{Binding Path=Reputation}" />
</Grid>
Now I'd like this UserControl
to be compatible with both Models, User
and StackOverflowUser
. I might set the UserControl's DataContext
to either a User
or StackOverflowUser
Type:
this.DataContext = new User { Username = "john.doe" };
If set to StackOverflowUser
, everything works fine. If set to User
, I'm getting a "BindingExpression Path error", because the Property Reputation
is missing in the User
Model. Which I understand completely.
Is there any way to 1) avoid this exception and 2) control the visibility of the controls, collapse when bound property is not available?
Of course, we prefer an elegant solution, where the problem is solved by tuning the Binding Expression and/or using Converters etc. and avoid tons of code behind if possible.
Thanks in advance for your help and suggestions,
best regards,Thomas
Unfortunately Silverlight is limited in its polymorphic behavior regarding DataTemplates, I can only think of a workaround:
Give the User class the property Reputation too, but make it meaningless, for example -1. Then apply a style to the reputation TextBlocks:
<Page.Resources>
<Style Key="Reputation">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=Reputation} Value="-1">
<Setter Property="Visibility" Value="Invisible" />
</DataTrigger>
</Style.Triggers>
</Style>
</Page.Resources>
...
<TextBlock Text="Reputation:" Style="{StaticResource Reputation}">
<TextBlock Text="{Binding Path=Reputation}" Style="{StaticResource Reputation}">
You could also try (I can not test this):
- giving the User class a new property that identifies its type,
- make a second Style for the second TextBlock
bind its DataTrigger to the type identifying property and move the {Binding Path=Reputation} declaration into a Setter:
<Style Key="ReputationContent"> <Style.Triggers> <DataTrigger Binding="{Binding Path=Type} Value="StackOverflow"> <Setter Property="Visibility" Value="Invisible" /> <Setter Property="Text" Value="{Binding Path=Reputation}" /> </DataTrigger> </Style.Triggers> </Style>
But you see, there is no elegant way, it's a shame that DataTemplate do not have a DataType property in Silverlight.
You mentioned you're using MVVM. This is the value of your viewmodel - to shape model data in preparation for the view. The viewmodel could have accessible properties for both username and reputation (and maybe even another bool for binding the visibility). The viewmodel would include all logic on how to fill those properties from either model (User or StackOverflowUser). The view would have no knowledge of a User or StackOverflowUser object, just the viewmodel.
I finally got my problem solved. A co-worker finally implemented a solution including a workaround for WPFs DataTemplates DataType attribute (or generally, a DataTemplateSelector). It's not very pretty (i guess, no workaround is) but it works. Unfortunately, i cannot post any code snippets because its closed-source. But i found some links afterwards, providing a pretty similar solution, like this one: Silverlight: a port of the DataTemplateSelector. If you have a similar problem, this will help you as well. Here or there are more thoughts on this subject.
The actual solution is following Ozan's hints. Unfortunately, his solution is not working so I don't want to mark his comment as the accepted answer but I give at least an upvote.
Thanks!
best regards,
Thomas
I know this has already been answered, but I still think its worth this post. Using reflection you can have a property in your ViewModel that will easily handle Dto objects which only sometimes have the property. Reflection can be expensive though, so weigh that with your decision.
public int? Reputation
{
get
{
var prop = Dto.GetType().GetProperty("Reputation");
return (prop != null)? (int)prop.GetValue(Dto, null) : null;
}
set
{
var prop = Dto.GetType().GetProperty("Reputation");
if(prop !=null) prop.SetValue(Dto,value, null);
}
}
精彩评论