views:

4906

answers:

4

I have a scenario where I don't really know how to bind data to controls hosted in a UserControl to multiple datacontexts.

The data i want to bind comes from 2 classes

UserInfo, UserExtendedInfo

The datacontext of the UserControl is set to UserInfo so i can bind most controls easily doing the following

<Label Name="LblEmail" Text="{Binding Email}" />

However I don't know how to bind properties from the UserExtendedInfo class easily. My initial thought was to set the datacontext of each control that want's to use the data from UserExtendedInfo so i could do the same. But this seems cumbersome as i would have to manually assign each one indivdually. The data for UserExtendedInfo must be fetched from the database each time the UserControl becomes visible so that it doesn't get out of sync.

XAML:

<Label Name="LblTest" Text="{Binding Locale}" />

Code Behind:

Private Sub UserManager_IsVisibleChanged(ByVal sender As System.Object, ByVal e As System.Windows.DependencyPropertyChangedEventArgs)  
        If DirectCast(e.NewValue, Boolean) Then
            Dim user As UserInfo = DirectCast(DataContext, UserInfo)

            If user IsNot Nothing Then
                Dim usrExt As UserExtenedInfo = UserController.GetUserExtended(user.userID)

                LblTest.DataContext = usrExt
            Else
                Throw New ArgumentException("UserId doesn't exist or is less than 1")
            End If
        End If
    End Sub
+3  A: 

I would maybe think about wrapping your user object in a seperate class then setting the DataContext properties of sub-panels that contain the data.

For example:

public class UserDataContext
{
  public UserInfo UserInfo { get; set; }
  public UserExtendedInfo UserExtendedInfo { get; set; }
}

Then in your UserControl.xaml:

<!-- Binding for the UserControl should be set in its parent, but for clarity -->
<UserControl DataContext="{Binding UserDataContext}">
  <StackPanel>
    <Grid DataContext="{Binding UserInfo}">
       <TextBlock Text="{Binding Email}" />
    </Grid>
    <Grid DataContext="{Binding UserExtendedInfo}">
       <TextBlock Text="{Binding Locale}" />
       <TextBlock Text="{Binding AboutMe}" />
    </Grid>
  </StackPanel>
</UserControl>

This assumes that your UserInfo class has a property of Email

and

That your UserExtendedInfo class has a property of Locale and AboutMe

bendewey
Ok that makes a lot of sense. So much new stuff to learn in WPF :)
Alex
Instead of creating a new Model whose only purpose is to support this particular View, you should just add UserInfo and UserExtendedInfo as public properties of a ViewModel that you can bind to the View. (See Rich's [answer](http://stackoverflow.com/questions/679933/wpf-binding-multiple-controls-to-different-datacontexts/680052#680052)).
totorocat
+1  A: 

This is where M-V-VM comes in very handy. The idea (as I understand it at least...still very new to me) is that the Window itself is bound to a "ViewModel" class. The ViewModel class is just a class that represents all the data in a way that your entire page has access to everything it needs...it simply brings together all the different objects you'll need to bind to in one class...and you set the DataContext of the Window (or Page) to an instance of this class. Your UserInfo and UserInfoExtended instances are public properties of the ViewModel object, and you just use the Path of your binding element to get you through the appropriate properties of the appropriate objects you wish to bind each control to.

There's a great (but quite lengthy) video explaining this pattern, and it goes through a full example that illustrates many ways to accomplish this and many different reasons why this is a convenient and scalable model to use in a WPF app. It also covers many features of WPF as well as an introduction to dependency injection, which are very relevant topics as well, given the example.

Here's a link to the blog post which contains a link to the video I'm speaking of:

http://blog.lab49.com/archives/2650

Rich
just a note, the ViewModel does not need to have dependency properties. It just need to implement INotifyPropertyChanged if you want the controls to reflect change in one of its properties.
Denis Troller
A: 

Both Rich and bendewey had good answers. Exploring this same topic today in Silverlight instead of WPF, I found that it's not necessary to establish multiple DataContexts. Revising bendewey's example:

<UserControl DataContext="{Binding UserDataContext}">
  <StackPanel>
       <TextBlock Text="{Binding Path=UserInfo.Email}" />
       <TextBlock Text="{Binding Path=UserExtendedInfo.Locale}" />
       <TextBlock Text="{Binding Path=UserExtendedInfo.AboutMe}" />
  </StackPanel>
</UserControl>

Using the Binding Path you gain the flexibility to mix and match bindings to properties of different classes without concern for the DataContext of the controls' containers.

You can also extend the capabilities of bendewey's UserDataContext class by adding properties that manipulate properties of the UserInfo and UserExtendedInfo classes. You might, for example, combine first name and last name.

You may wish to implement INotifyPropertyChanged so that your controls update when you reset UserInfo and UserExtendedInfo.

It may be architecturally preferable to entirely isolate the underlying UserInfo and UserExtendedInfo classes from the XAML by exposing the required properties directly in UserDataContext, thereby eliminating the need for Binding Path.

BillVo
This is an improvement on bendeway's answer (+1), but I would improve a few more things: You don't need "Path=", and in 95% of cases I would use C# anonymous types instead of an explicit class.
Ray Burns
A: 

Here is the simplest method of all, and it works very well.

In the code-behind where you set the context, simply use an anonymous type containing all the desired values:

DataContext = new
{
  info = FetchUserInfoFromDatabase(),
  extendedInfo = FetchExtendedUserInfoFromDatabase(),
};

In the XAML you can bind to anything:

<UserControl>
  <StackPanel>
    <TextBlock Text="{Binding info.Email}" />
    <TextBlock Text="{Binding extendedInfo.Locale} />
  ...

Alternatively you can bind in two levels as other answers have described:

<UserControl>
  <StackPanel>
    <Grid DataContext="{Binding info}">
      <TextBlock Text={Binding Email}">
      ...
Ray Burns