Showing posts with label PropertyChanged. Show all posts
Showing posts with label PropertyChanged. Show all posts

Friday, August 5, 2011

Binding ObservableCollection to Text Properties

In this post we will be discussing the issue when we bind a collection based (ObservableCollection) property to some scalar DependencyProperty e.g. Text property of a TextBlock.

Let's create a sample MVVM Light based WPF application.


Note:
Yes, you would need an installation of MVVM Light in order to follow this example.

Let's update MainWindow's definition as follows:
<Window x:Class="AppBindingScalarPropertiesCollections.MainWindow"
        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"
        xmlns:converters="clr-namespace:AppBindingScalarPropertiesCollections.Converters"
        mc:Ignorable="d"
        Height="386"
        Width="514"
        Title="MVVM Light Application"
        DataContext="{Binding Main, Source={StaticResource Locator}}">
    
    <Window.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary Source="Skins/MainSkin.xaml" />
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
    </Window.Resources>

    <Grid x:Name="LayoutRoot">
        <Grid.Resources>
            <converters:StudentsListToStringConverter x:Key="studentConverter" />
        </Grid.Resources>
        <Grid.RowDefinitions>
            <RowDefinition Height="42*" />
            <RowDefinition Height="37*" />
            <RowDefinition Height="189*" />
            <RowDefinition Height="79*" />
        </Grid.RowDefinitions>
        <TextBlock FontSize="36"
                   FontWeight="Bold"
                   Foreground="Purple"
                   Text="{Binding Welcome}"
                   VerticalAlignment="Center"
                   HorizontalAlignment="Center"
                   TextWrapping="Wrap" Margin="246,104,246,71" Grid.Row="2" />
        <Label Content="New Student" Height="23" HorizontalAlignment="Left"
               Margin="6,15,0,0" Name="label1" VerticalAlignment="Top" Width="97" />
        <TextBox Height="25" HorizontalAlignment="Right" Margin="0,13,12,0"  
                 VerticalAlignment="Top" Width="370"
                 Text="{Binding NewStudentName, UpdateSourceTrigger=PropertyChanged}" />
        <GroupBox Header="Students List" Height="179" HorizontalAlignment="Left" Margin="6,2,0,0" 
                  Name="groupBox1" VerticalAlignment="Top" Width="476" Grid.Row="2">
            <Grid>
                <ListBox ItemsSource="{Binding Students}"  />
            </Grid>
        </GroupBox>
        <GroupBox Header="Comma Separated Students List" Height="57" HorizontalAlignment="Left"
                   VerticalAlignment="Top" Width="476" Grid.Row="3" Margin="0,8,0,0">
            <TextBlock 
                Text="{Binding Students, Converter={StaticResource studentConverter}}" 
                Height="21" />
        </GroupBox>
        <Button Content="Add" Grid.Row="1" Height="26" HorizontalAlignment="Left" 
                Margin="382,7,0,0" VerticalAlignment="Top" Width="99"
                Command="{Binding AddNewStudentCommand}" />
    </Grid>
</Window>
The above view has some expectations from view model. Let's update the view model provided by MVVM Light's view model locator as follows:
namespace AppBindingScalarPropertiesCollections.ViewModel
{
    using GalaSoft.MvvmLight;
    using System.Collections.ObjectModel;
    using System.Windows.Input;
    using GalaSoft.MvvmLight.Command;
  
    public class MainViewModel : ViewModelBase
    {
        public string Welcome
        {
            get
            {
                return "Welcome to MVVM Light";
            }
        }

        string _newStudentName;
        public string NewStudentName
        {
            get { return _newStudentName; }
            set 
            {
                _newStudentName = value;
                RaisePropertyChanged("NewStudentName");
            }
        }

        ObservableCollection<string> _students;
        public ObservableCollection<string> Students
        {
            get
            {
                if (_students == null)
                {
                    _students = new ObservableCollection<string>();
                    _students.Add("Muhammad");
                    _students.Add("Ryan");
                    _students.Add("Jim");
                    _students.Add("Brian");
                    _students.Add("Josh");                   
                    _students.Add("Jeremy");
                }
                return _students;
            }
        }

        ICommand _addNewStudentCommand;
        public ICommand AddNewStudentCommand
        {
            get 
            {
                if (_addNewStudentCommand == null)
                {
                    _addNewStudentCommand = new RelayCommand(
                        () =>
                        {
                            if (!Students.Contains(NewStudentName))
                            {
                                Students.Add(NewStudentName);
                            }
                        });
                }
                
                return _addNewStudentCommand;
            }
        }
    }
}
Mainly it has three things. NewStudentName property to be bound to the TextBox to enter the name of a new student. AddNewStudentCommand
for the button's Command property. Clicking this button should add the student's name as entered in the TextBox to Student's collection if it already doesn't exist. The ListBox and TextBlock showing this are automatically expected be updated as a new student is added to the collection. Students collection to be bound to ListBox's ItemSource and TextBlock's Text properties.

We need the Student's list in the TextBlock to be comma separated names of all students. The view is using a converter for this purpose. It is a simple IValueConverter. Let's see the definition of this converter.
namespace AppBindingScalarPropertiesCollections.Converters
{
    using System.Windows.Data;
    using System.Collections.ObjectModel;
    using System.Linq;

    class StudentsListToStringConverter : IValueConverter
    {
        public object Convert(object value, System.Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            string studentCommaSeparatedList = string.Empty;
            ObservableCollection<string> studentList = value as ObservableCollection<string>;

            if (studentList != null)
            {
                studentCommaSeparatedList = string.Join(", ",
                                            (from string studentName
                                                 in studentList
                                             select studentName).ToArray<string>());
            }           

            return studentCommaSeparatedList;
        }

        public object ConvertBack(object value, System.Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            throw new System.NotImplementedException();
        }
    }
}
In the Convert method of this IValueConverter, we are simple joining the elements of the collection with comma separation and returning it.

Let's run the application now. It appears as follows...


As we enter new student's name and hit Add button, the new student is added to Students collection. Since this collection is bound to the ItemsSource of the ListBox, it appears in the list box. But the same does not appear in the TextBlock's comma separated list. This is weird!!!

Basically, the reason is very simple. When we bind to the Text property of TextBlock, it is just interested in the PropertyChanged events. It handles this event to update its contents. But it seems that it doesn't handle CollectionChanged event of ObservableCollection. That is why it is not able to update itself. Now since we know the problem how we can resolve this. Basically there might be two different solutions to this problem.

Solution # 1: [Raise PropertyChanged Event when collection is updated for items]

This is very simple. Since we are just adding items in the Execute method of the ICommand bound to the Add button, we can simply do it there. Let's update the ICommand definition in the view model as follows:
public ICommand AddNewStudentCommand
{
    get 
    {
        if (_addNewStudentCommand == null)
        {
            _addNewStudentCommand = new RelayCommand(
                () =>
                {
                    if (!Students.Contains(NewStudentName))
                    {
                        Students.Add(NewStudentName);
                        RaisePropertyChanged("Students");
                    }
                });
        }
        
        return _addNewStudentCommand;
    }
}
Here RaisePropertyChanged method raises PropertyChanged event for the name of property provided as argument. This is available due to inheritence of this view model by ViewModelBase from MVVM Light. Let's run the application again.


As we entered Sekhar and hit the button, it appears both in the ListBox and TextBlock. This is exactly what we desired. Zindabad!!!

2nd Solution: [Use MultiBinding, adding binding for ObservableCollection.Count]
In this solution, we can simply add binding for Count property from the same collection. We can change the binding of TextBlock as follows:
<Window x:Class="AppBindingScalarPropertiesCollections.MainWindow"
        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"
        xmlns:converters="clr-namespace:AppBindingScalarPropertiesCollections.Converters"
        mc:Ignorable="d"
        Height="386"
        Width="514"
        Title="MVVM Light Application"
        DataContext="{Binding Main, Source={StaticResource Locator}}">
    
    <Window.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary Source="Skins/MainSkin.xaml" />
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
    </Window.Resources>

    <Grid x:Name="LayoutRoot">
        <Grid.Resources>
            <converters:StudentListToStringMultiConverter x:Key="studentMultiConverter" />
        </Grid.Resources>
        <Grid.RowDefinitions>
            <RowDefinition Height="42*" />
            <RowDefinition Height="37*" />
            <RowDefinition Height="189*" />
            <RowDefinition Height="79*" />
        </Grid.RowDefinitions>
        <TextBlock FontSize="36"
                   FontWeight="Bold"
                   Foreground="Purple"
                   Text="{Binding Welcome}"
                   VerticalAlignment="Center"
                   HorizontalAlignment="Center"
                   TextWrapping="Wrap" Margin="246,104,246,71" Grid.Row="2" />
        <Label Content="New Student" Height="23" HorizontalAlignment="Left"
               Margin="6,15,0,0" Name="label1" VerticalAlignment="Top" Width="97" />
        <TextBox Height="25" HorizontalAlignment="Right" Margin="0,13,12,0"  
                 VerticalAlignment="Top" Width="370"
                 Text="{Binding NewStudentName, UpdateSourceTrigger=PropertyChanged}" />
        <GroupBox Header="Students List" Height="179" HorizontalAlignment="Left" Margin="6,2,0,0" 
                  Name="groupBox1" VerticalAlignment="Top" Width="476" Grid.Row="2">
            <Grid>
                <ListBox ItemsSource="{Binding Students}"  />
            </Grid>
        </GroupBox>
        <GroupBox Header="Comma Separated Students List" Height="57" HorizontalAlignment="Left"
                   VerticalAlignment="Top" Width="476" Grid.Row="3" Margin="0,8,0,0">

            <TextBlock                 
                Height="21" >
                <TextBlock.Text>
                    <MultiBinding  Converter="{StaticResource studentMultiConverter}" >
                        <Binding Path ="Students"/>
                        <Binding Path ="Students.Count" />
                    </MultiBinding>
                </TextBlock.Text>
            </TextBlock>
        </GroupBox>
        <Button Content="Add" Grid.Row="1" Height="26" HorizontalAlignment="Left" 
                Margin="382,7,0,0" VerticalAlignment="Top" Width="99"
                Command="{Binding AddNewStudentCommand}" />
    </Grid>
</Window>
In addition of Binding update, we also need to update the converter to IMultiValueConverter. Let's see the definition of StudentListToStringMultiConverter used above.
class StudentListToStringMultiConverter : IMultiValueConverter
{

    public object Convert(object[] values, System.Type targetType, object parameter, 
        System.Globalization.CultureInfo culture)
    {
        string studentCommaSeparatedList = string.Empty;
        ObservableCollection<string> studentList = values[0] as ObservableCollection<string>;

        if (studentList != null)
        {
            studentCommaSeparatedList = string.Join(", ",
                                        (from string studentName
                                             in studentList
                                         select studentName).ToArray<string>());
        }

        return studentCommaSeparatedList;
    }

    public object[] ConvertBack(object value, System.Type[] targetTypes, object parameter,
        System.Globalization.CultureInfo culture)
    {
        throw new System.NotImplementedException();
    }
}
Running the application should have the same result as the first option.

Any Questions??? Comments...

Download:
The sameple project can be downloaded from SkyDrive here:

Thursday, February 10, 2011

MVVM - Observable INotifyPropertyChanged.PropertyChanged Event Stream Using Reactive Extension

In this post we are going to discuss how we can use observable event streams using Reactive Extension. Since we have been trying to apply this knowledge to MVVM and seeking what benefit we can obtain from Rx, we will be mostly interested in those events which we deal with the most frequently. One such event is PropertyChanged event of INotifyPropertyChanged.

Let’s start with a sample WPF application. As is general in this blog, We will start with a very simple example. Then we will keep adding complexity to it and discuss why we actually need them. Below is a typical WPF window. It just has a button. We need to show another window to enter the details about student.

<Window x:Class="WpfApp_MVVM_ReactiveModelSynch.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Button Content="Show Student Form" Height="47" HorizontalAlignment="Left"
Margin="104,244,0,0" Name="button1" VerticalAlignment="Top" Width="318" Click="button1_Click" />
</Grid>
</Window>

Since these details need to be shared between different StudentView windows, we are defining the Student object as instance variable. When user clicks the window, we are constructing the view model on the fly but we keep passing the same Student instance. This would enable the information to be shared between different windows. We are calling Show() on this new instance of StudentView. As you know this would show this window as a Modeless window. This is to demonstrate the scenarios in which we might not instantiate view models inside the view but we need to hand it over, somehow, to the view.

public partial class MainWindow : Window
{
private Student _student;

public MainWindow()
{
InitializeComponent();

_student = new Student();
}

private void button1_Click(object sender, RoutedEventArgs e)
{
new StudentView(new StudentViewModel(_student)).Show();
}
}



Now we have a look at the definition of StudentView. It just has two text boxes for user entry. The information regarding Id and Name of Student can be entered in those text boxes. These text boxes are bound to StudentId and StudentName properties from the DataContext.

<Window x:Class="WpfApp_MVVM_ReactiveModelSynch.StudentView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="StudentView" Height="300" Width="484">
<Grid>
<Label Content="Id" Height="26" HorizontalAlignment="Left" Margin="11,30,0,0"
Name="labelId" VerticalAlignment="Top" Width="73" />
<TextBox Height="26" HorizontalAlignment="Left" Margin="90,30,0,0"
Name="textBoxId" VerticalAlignment="Top" Width="362"
Text="{Binding Path=StudentId, UpdateSourceTrigger=PropertyChanged}"/>
<Label Content="Name" Height="25" HorizontalAlignment="Left" Margin="11,76,0,0"
Name="labelName" VerticalAlignment="Top" Width="73" />
<TextBox Height="26" HorizontalAlignment="Left" Margin="90,75,0,0" Name="textBoxName"
VerticalAlignment="Top" Width="362"
Text="{Binding Path=StudentName, UpdateSourceTrigger=PropertyChanged}"/>
</Grid>
</Window>

In the code behind, we assign the view model passed as constructor argument to the DataContext property of the view.

public partial class StudentView : Window
{
public StudentView(StudentViewModel studentViewModel)
{
InitializeComponent();

this.DataContext = studentViewModel;
}
}



As is expected by the view, StudentViewModel has two properties StudentId and StudentName. Both support change notification using INotifyPropertyChanged’s PropertyChanged event. As is specified in the MainWindow.xaml.cs, we are using Student object as constructor parameter. We are assigning it to the instance variable _model. We are using _model as Model for this view. WPF Binding system would update the values of these properties as the user enters data in the text boxes bound to these properties. In the setters of these properties, we are just assigning these values to the corresponding properties in _model. This should keep model being updated. Since this is shared by the view models used by each StudentView object, we expect the other views to be updated as these changes are applied.

public class StudentViewModel : INotifyPropertyChanged
{
private int _studentId;
private string _studentName;

private Student _model;

public StudentViewModel(Student student)
{
_model = student;
}

public int StudentId
{
get { return _studentId; }
set
{
if (_studentId != value)
{
_studentId = value;
_model.StudentId = value;
OnPropertyChanged("StudentId");
}
}
}

public string StudentName
{
get { return _studentName; }
set
{
if(_studentName != value)
{
_studentName = value;
_model.StudentName = value;
OnPropertyChanged("StudentName");
}
}
}

public event PropertyChangedEventHandler PropertyChanged = delegate { };
private void OnPropertyChanged(string propertyName)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}

As you might have guessed from the above code, Student has two properties StudentId and StudentName. This also implements INotifyPropertyChanged so needs to define PropertyChanged event. Whenever the values of these properties are changed, PropertyChanged event is raised to notify all observers about this change. Though it does not provide the actual change but it does tell about the property updated. We can handle this event in the code using StudentModel and act accordingly.

public class Student : INotifyPropertyChanged
{
private int _studentId;
private string _studentName;

public int StudentId
{
get { return _studentId; }
set
{
if (_studentId != value)
{
_studentId = value;
OnPropertyChanged("StudentId");
}
}
}

public string StudentName
{
get { return _studentName; }
set
{
if (_studentName != value)
{
_studentName = value;
OnPropertyChanged("StudentName");
}
}
}

public event PropertyChangedEventHandler PropertyChanged = delegate { };
private void OnPropertyChanged(string propertyName)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}

Now let’s run this! We open two StudentView windows. We enter data in one window. The data is not being updated in the second window. Why is this?? As is apparent in the code of view model, as we receive updates in View Model’s properties through WPF Binding System, we are passing the same updates to the corresponding properties in the model.


If you put break points in the model properties’ setter, you would notice that the setter code is being executed. Even the PropertyChanged event is being raised. We just need to handle this event in the view model so that the view model could update its properties. Since these properties are supporting change notifications using INotifyPropertyChanged, the bound element in the view should also be updated accordingly. As discussed, let’s subscribe PropertyChanged event in the view model for model object.
_model.PropertyChanged += new PropertyChangedEventHandler(_model_PropertyChanged);

The definition of _model_PropertyChanged is as follows:

void _model_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
object value = _model.GetType().GetProperty(e.PropertyName).GetValue(_model, null);
this.GetType().GetProperty(e.PropertyName).SetValue(this, value, null);
}

This is based on reflection and based on the assumption that View Model has properties with same names. Their types are also assumed to be supportive. Since we only have limited properties, we can also avoid reflection and update each property. Let’s do that.

void _model_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
this.StudentId = _model.StudentId;
this.StudentName = _model.StudentName;
}

Now let’s run this application. We will again open two windows and enter data in one window and observe if the same updates are made in the second window.


As expected, the updates are applied in the second window.

Now since we have fulfilled whatever the requirements have stated, we should be the happiest person in the world. But we can certainly improve on this implementation. We needed to handle model’s PropertyChanged event in StudentViewModel. We can avoid these by using observable event stream support available in Reactive Extension (Rx). Not only it allows to subscribe to the observable event stream, it also allows a filtered subscription. So We will be subscribing to a subset of events using Where clause even if there is an active event stream. It is also possible that we are receiving frequent updates from the model, like millions in a second. We don’t want to update our view so frequently. Similarly, since we have set UpdateSourceTrigger as PropertyChanged for both binding in the view. All the changes in the Text property of the text box will cause the property setter to execute. It is possible that the user of this system types very frequently, we might just want to update the model when it settles and not after each key entry. In short we might need throttling support for these property changes.

Using Observable Event Stream for model’s PropertyChanged event:
Before adding the code described in this section remove model’s PropertyChanged handling logic from StudentViewModel. This includes event subscription in the constructor and definition of event handler. Before making the updates suggested above, we need to reference assemblies provided by Rx installer. We need to add references for following assemblies:

1. System.Reactive.dll
2. System.CoreEx


These assemblies are installed as part of installation of Reactive Extension (Rx). It can be downloaded as follows:

http://msdn.microsoft.com/en-us/devlabs/ee794896

Update the code of the constructor of StudentViewModel as follows:

public StudentViewModel(Student student)
{
_model = student;

Observable.FromEvent<PropertyChangedEventArgs>(_model, "PropertyChanged")
.Subscribe((arg) =>
{
this.StudentId = _model.StudentId;
this.StudentName = _model.StudentName;
});
}

System.Linq.Observable from System.Reactive assembly allows to subscribe with any event stream. System.Collections.Generic.IEvent<T> is defined in System.CoreEx assembly. We are subscribing to PropertyChanged event stream from _model instance. Observable.FromEvent<T>() returns IObservable<IEvent<T>>. Since it is an IObservable, we can subscribe to it. The code that we want to execute when event is triggered can be specified in the onNext delegate. Let's run the application now.



Like before, we are opening two instances of StudentView window. When we enter information in the text boxes, information is updated in other window. The contents are updated because of the event handling logic in the observable event stream handler code as presented above.

Filtering events from Event Stream:
Since it is an IObservable<T> we can filter it using Where operator. Let's filter it to just handle the case when event is received because of update in StudentName property. The changes in StudentId are ignored. Let's update the code as follows:

public StudentViewModel(Student student)
{
_model = student;

Observable.FromEvent<PropertyChangedEventArgs>(_model, "PropertyChanged")
.Where(et => et.EventArgs.PropertyName == "StudentName")
.Subscribe((arg) =>
{
this.StudentId = _model.StudentId;
this.StudentName = _model.StudentName;
});
}

Now run the application and again open two instances. The update in text box for StudentName shows the updates in the other window. If we update StudentId, it doesn't update the other window as the event stream is filtered.



Throttling events from Event Stream:
Since are binding using UpdateSourceTrigger as PropertyChanged, as the user types in the text boxes in StudentView, WPF Binding System automatically updates the values in the View model's properties. This updates corresponding properties of the model using the view model's property setters. We just don't want to keep sending updates to the model but we should wait until the user is done typing and then update the model's properties. This can be supported by throttling support. This can by achieved introducing PropertyChanged event in the view models for itself. We can remove the code to update corresponding model's properties in the setters. Let's update the definition of StudentViewModel as follows:

public class StudentViewModel : INotifyPropertyChanged
{
private int _studentId;
private string _studentName;

private Student _model;

public StudentViewModel(Student student)
{
_model = student;

Observable.FromEvent<PropertyChangedEventArgs>(_model, "PropertyChanged")
.Where(et => et.EventArgs.PropertyName == "StudentName")
.Subscribe((arg) =>
{
this.StudentId = _model.StudentId;
this.StudentName = _model.StudentName;
});



Observable.FromEvent<PropertyChangedEventArgs>(this, "PropertyChanged")
.Throttle(TimeSpan.FromMilliseconds(500))
.Where(et => et.EventArgs.PropertyName == "StudentName")
.Subscribe((ar) =>
{
_model.StudentId = this.StudentId;
_model.StudentName = this.StudentName;
});

}

//void _model_PropertyChanged(object sender, PropertyChangedEventArgs e)
//{
// //object value = _model.GetType().GetProperty(e.PropertyName).GetValue(_model, null);
// //this.GetType().GetProperty(e.PropertyName).SetValue(this, value, null);
// //this.StudentId = _model.StudentId;
// //this.StudentName = _model.StudentName;
//}

public int StudentId
{
get { return _studentId; }
set
{
if (_studentId != value)
{
_studentId = value;
//_model.StudentId = value;
OnPropertyChanged("StudentId");
}
}
}

public string StudentName
{
get { return _studentName; }
set
{
if(_studentName != value)
{
_studentName = value;
//_model.StudentName = value;
OnPropertyChanged("StudentName");
}
}
}

public event PropertyChangedEventHandler PropertyChanged = delegate { };
private void OnPropertyChanged(string propertyName)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}

We needed to remove the code resulting in the updates in the setter of properties in the view model. Let' throttle these updates. When user types in StudentName, the IObservable doesn't update the other window immediately. It is rather waiting for property to settle down. After which it is causing the PropertyChanged events to be handled.



Download: