views:

98

answers:

3

Hi Stackoverflowers!

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 down 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

+1  A: 

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

  1. giving the User class a new property that identifies its type,
  2. make a second Style for the second TextBlock
  3. 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.

Ozan
Thanks for your suggestions! Adding (Moving) Properties from inherited to base class would kinda destroy the overall business object model... Also tried to get your second example working, and found "DataTriggers" in the "Microsoft.Expression.Interactions" library (from Blend), wasn't able to get the "Style.Triggers" working, though...
moonground.de
Ahh, again I used a WPF feature that is not available in Silverlight.. I'm sorry. I guess you need to do some code-behind, ugly as it is.Have you tried setting the FallbackValue property of the Binding? Maybe then it does not throw an exception on class User.
Ozan
When setting FallbackValue, it will work for displaying the fallback value but still produce this binding error. Things become even harder if you want to control the visibility: You could set the Fallback Value to "Collapsed" for example, but this would also happen if the property value is null. But in fact, there is a little difference between a property which is "null" or a property which is not existing, and it's quite impossible to find it out this way... btw. I appreciate your suggestions and effort Ozan *thumbs up*
moonground.de
+1  A: 

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.

Brandon Copeland
Hi, this is what I actually started to evaluate this morning... For the ViewModel, it still means some ugly code, because you have to check all supported types (if I find an elegant solution, I will post it), but at least you can transfer the problem from the XAML into code where you gain more control over it.
moonground.de
A: 

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

moonground.de