Sunday, August 14, 2011

Silverlight 5 Beta - Relative Source Binding

In this post we will be discussing another new feature of Silverlight 5 Beta. It is Relative Source binding. RelativeSource has long been missing from Silverlight. It has always been there in WPF. Silverlight 5 is currently available in Beta and can be downloaded from here:

http://www.microsoft.com/download/en/details.aspx?id=23887

Let's create a MVVM Light Silverlight application AppSilverlightRelativeSourceBinding. It would have a list of students on display. The user should be allowed to remove any student on the list. In order to see the application of Relative Source binding, we would see how we can bind the Command on the child element to a parent element. Actually, the Remove button on child view should be bound to a Command in the DataContext of a parent element in the hierarchy [UserControl].


Just make sure that you have Silverlight 5 version selected in the project's properties.


This view model is the Main view model of the application. It has a collection of students (StudentViewModel). Since the collection is an ObservableCollection, all the addition / removal of elements in the collection would directly reflected in the view. It also has a Command which removes the student from the collection. This is the Command which would be bound by child elements.
namespace AppSilverlightRelativeSourceBinding.ViewModel
{
    using GalaSoft.MvvmLight;
    using System.Collections.ObjectModel;
    using GalaSoft.MvvmLight.Command;

    public class MainViewModel : ViewModelBase
    {
        #region Properties

        public string Welcome
        {
            get
            {
                return "Silverlight 5 Beta - Relative Source Binding";
            }
        }

        ObservableCollection<StudentViewModel> _students;
        public ObservableCollection<StudentViewModel> Students
        {
            get
            {
                if (_students == null)
                {
                    _students = new ObservableCollection<StudentViewModel>();

                    _students.Add(new StudentViewModel() { StudentFirstName = "Kamran", StudentLastName = "Khan" });
                    _students.Add(new StudentViewModel() { StudentFirstName = "Asad", StudentLastName = "Hussain" });
                    _students.Add(new StudentViewModel() { StudentFirstName = "Faisal", StudentLastName = "Lashari" });
                    _students.Add(new StudentViewModel() { StudentFirstName = "Hyder", StudentLastName = "Baloch" });
                    _students.Add(new StudentViewModel() { StudentFirstName = "Baber", StudentLastName = "Chaudhari" });
                }
                return _students;
            }
        }

        #endregion

        #region Commands

        RelayCommand<StudentViewModel> _removeStudentCommand;
        public RelayCommand<StudentViewModel> RemoveStudentCommand
        {
            get
            {
                return _removeStudentCommand ??
                        new RelayCommand<StudentViewModel>((student) =>
                        {
                            if (this.Students.Contains(student))
                            {
                                this.Students.Remove(student);
                            }
                        });
            }
        }

        #endregion        
    }
}

The definition of StudentViewModel used above is as follows:
namespace AppSilverlightRelativeSourceBinding.ViewModel
{   
    using GalaSoft.MvvmLight;

    public class StudentViewModel : ViewModelBase
    {
        #region Notifiable Properties

        string _studentLastName;
        public string StudentLastName
        {
            get { return _studentLastName; }
            set
            {
                _studentLastName = value;
                RaisePropertyChanged("StudentLastName");
            }
        }

        string _studentFirstName;
        public string StudentFirstName
        {
            get { return _studentFirstName; }
            set
            {
                _studentFirstName = value;
                RaisePropertyChanged("StudentFirstName");
            }
        }

        #endregion

    }
}
It simply has two properties for first and last names of a student. The view model inherits from ViewModelBase which is provided by MVVM Light toolkit. Now let's see how we can display this view model. It is an implicit DataTemplate to show the student. The first and last names are displayed in a TextBlock. It also has a Remove button. Clicking the button would delete the student from the collection. Since StudentViewModel has no idea about which collection it has been part of and it has to be handled by something which holds this collection. So the button's Command needs to be bound to MainViewModel. This is why Relative Source binding makes most sense to be used here. Let's define this DataTemplate in MainSkin.xaml resource dictionary provided by MVVM Light.
<DataTemplate DataType="vm:StudentViewModel" >
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="auto" />
            <ColumnDefinition Width="30" />
            <ColumnDefinition Width="*" />
        </Grid.ColumnDefinitions>
        <Button Content="Remove" Grid.Column="0"                       
                CommandParameter="{Binding}" >                
            <Button.Command>
                <Binding RelativeSource="{RelativeSource AncestorType=UserControl}" 
                         Path="DataContext.RemoveStudentCommand" />
            </Button.Command>
        </Button>
        <Border Grid.Column="1" />
        <Grid Grid.Column="2" >
            <TextBlock>  
                <Run Text="{Binding StudentLastName}" FontWeight="Bold" />
                <Run>
                    <Run.Text>
                        <Binding Path="StudentFirstName" StringFormat=",{0}" />
                    </Run.Text>
                    </Run>
            </TextBlock>
        </Grid>
    </Grid>
</DataTemplate>
We need to include this namespace in the above resource dictionary.
xmlns:vm="clr-namespace:AppSilverlightRelativeSourceBinding.ViewModel"
Now the design of MainPage is simple enough. Mainly it has a ListBox to display the collection of StudentViewModel. Since MainSkin.xaml resource dictionary is merged here, the implicit DataTemplate is automatically applied.
<UserControl x:Class="AppSilverlightRelativeSourceBinding.MainPage"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
             mc:Ignorable="d"            
             DataContext="{Binding Main, Source={StaticResource Locator}}">
    <UserControl.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary Source="Skins/MainSkin.xaml" />
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
    </UserControl.Resources>
    <Grid x:Name="LayoutRoot">
        <Grid.RowDefinitions>
            <RowDefinition Height="auto" />
            <RowDefinition Height="*" />
        </Grid.RowDefinitions>
        <TextBlock FontSize="36" Grid.Row="0"
                   FontWeight="Bold"
                   Foreground="Purple"
                   Text="{Binding Welcome}"
                   VerticalAlignment="Center"
                   HorizontalAlignment="Center"
                   TextWrapping="Wrap" />
        <ListBox ItemsSource="{Binding Students}"
                 Grid.Row="1" />
    </Grid>
</UserControl>
When we run the application, the application is loaded as follows:


Download Code:

1 comment:

Anonymous said...

Great post, been spending hours looking for the right syntax for this having jsut moved from the solution posted at DAn Wahlin's blog form a while back.