开发者

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

  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.


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);
        }
    }
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜