Monthly Archives: December 2009

Observable trackable collection in WPF

For a WPF application I needed a two-way binding between a DataGrid and a collection of business entities. First I used a simple ObservableCollection, and although the binding itself works fine, there is no fully built-in functionality to keep tracking information of added, changed or removed entities. So I decided to write a generic ObservableTrackableCollection. In my project I used the MVVM pattern: the ViewModel is bound to the view and uses the model entities.

Important to know first is that I wanted the model to be responsible for its validation. Here’s a simplified version of the SupplierModel:

public class SupplierModel
{
public int Id { get; set; }
public string Name { get; set; }
public string Description{ get; set; }
public string Address { get; set; }
public string Number { get; set; }
public int PaymentTerms { get; set; }
public string ValidationErrors
{
get
{
StringBuilder message = new StringBuilder();
message.Append(this["Name"]);
message.Append(this["PaymentTerms"]);
return message.ToString();
}
}
public string this[string property]
{
get
{
string message = null;
switch (property)
{
case "Name":
if (string.IsNullOrEmpty(Name))
{
message = "Name is mandatory!";
}
break;
case "PaymentTerms":
if (PaymentTerms > 365)
{
message = "Payment terms shoud be smaller than
or equal to 365!"
;
}
break;
default:
break;
}
return message;
}
}
}

The view model is bound to the DataGrid, so it implements INotifyPropertyChanged so that property changes can be detected by the binding mechanism, and also IDataErrorInfo so that validation errors can be detected. The view model exposes the model by encapsulating it. Validation is done  by delegating the validation task to the SupplierModel. Here’s the SupplierViewModel:

public class SupplierViewModel : INotifyPropertyChanged, IDataErrorInfo
{
public event PropertyChangedEventHandler PropertyChanged;

private SupplierModel supplier = null;

public SupplierViewModel()
{
this.supplier = new SupplierModel();
}
public SupplierViewModel(SupplierModel supplier)
{
this.supplier = supplier;
}
protected void RaisePropertyChanged<T>(Expression<Func<T>> property)
{
PropertyChangedEventHandler handler = this.PropertyChanged;

if (handler != null)
handler(this, property.CreateChangeEventArgs());
}
public int Id
{
get { return supplier.Id; }
set { supplier.Id = value; RaisePropertyChanged(() => Id); }
}
public string Name
{
get { return supplier.Name; }
set { supplier.Name = value; RaisePropertyChanged(() => Name); }
}
public string Description
{
get { return supplier.Description; }
set { supplier.Description= value; RaisePropertyChanged(() =>
Description); }
}
public string Address
{
get { return supplier.Address; }
set { supplier.Address = value; RaisePropertyChanged(() => Address); }
}
public string Number
{
get { return supplier.Number; }
set { supplier.Number = value; RaisePropertyChanged(() => Number); }
}
public int PaymentTerms
{
get { return supplier.PaymentTerms; }
set { supplier.PaymentTerms = value; RaisePropertyChanged(()
=> PaymentTerms); }
}
#region IDataErrorInfo Members
/// <summary>
/// A summary of the errors for the supplier object. The supplier model takes
/// care of the actual validation, and its results is used by the view model to
/// update the validation state.
/// </summary>
public string Error
{
get
{
return supplier.ValidationErrors;
}
}
public string this[string property]
{
get
{
return supplier[property];
}
}
#endregion
}

As you see, every time a property is set, RaisePropertyChanged is used to indicate a change. It uses the following extension method CreateChangeEventArgs:

public static class PropertyExtensions
{
public static PropertyChangedEventArgs CreateChangeEventArgs<T>(
this Expression<Func<T>> property)
{
var expression = property.Body as MemberExpression;
var member = expression.Member;
return new PropertyChangedEventArgs(member.Name);
}
}

This way we don’t have to use strings when notifying a property has changed and we avoid runtime errors if we mistype something.

I also have a MainViewModel, to which the DataCOntext of the view is set. This view model contains a ObservableTrackableCollection<SupplierViewModel> property, which is bound to the DataGrid on the view.

public class MainViewModel
{
private ObservableTrackableCollection<SupplierViewModel>
suppliers = new ObservableTrackableCollection<SupplierViewModel>();
public MainViewModel()
{
Suppliers.ItemsAdded +=
new ObservableTrackableCollection<SupplierViewModel>
.CollectionItemHandler(Suppliers_ItemsAdded);
Suppliers.ItemsRemoved +=
new ObservableTrackableCollection<SupplierViewModel>
.CollectionItemHandler(Suppliers_ItemsRemoved);
Suppliers.ItemPropertyChanged +=
new ObservableTrackableCollection<SupplierViewModel>
.ItemPropertyHandler(Suppliers_ItemPropertyChanged);
}
public ObservableTrackableCollection<SupplierViewModel> Suppliers
{
get { return suppliers; }
}
void Suppliers_ItemPropertyChanged(object sender, ItemPropertyEventArgs args)
{
System.Diagnostics.Debug.WriteLine("changed: " + args.PropertyName);
}
void Suppliers_ItemsRemoved(object sender,
CollectionItemEventArgs<SupplierViewModel> args)
{
foreach (var item in args.CollectionItems)
{
System.Diagnostics.Debug.WriteLine("removed: " + item.Name);
}
}
void Suppliers_ItemsAdded(object sender,
CollectionItemEventArgs<SupplierViewModel> args)
{
foreach (var item in args.CollectionItems)
{
System.Diagnostics.Debug.WriteLine("added: " + item.Name);
}
}

public void LoadSuppliers()
{
IList<SupplierModel> supplierList = SupplierRepository.GetSuppliers();
foreach (SupplierModel supplier in supplierList)
{
suppliers.Add(new SupplierViewModel(supplier));
}
suppliers.ResetTrackInfo();
}
private DelegateCommand saveSuppliersCommand;
public ICommand SaveSuppliersCommand
{
get
{
if (saveSuppliersCommand == null)
{
saveSuppliersCommand = new DelegateCommand(SaveSuppliers,
CanSaveSuppliers);
}
return saveSuppliersCommand;
}
}

As you can see, thanks to the ObservableTrackableCollection we have a number of built-in features:

  • events like ItemsAdded, ItemsRemoved, ItemPropertyChanged allow the view model to be informed of changes
  • the property IsCollectionValid is true when every item in the collection validates, or false when there is at least one error
  • the GetChanges method allows us to get the added, modified, deleted and original entities in the collection
  • the ResetTrackInfo sets the track information of all collection items to ‘not modified’.

Note that I use the model’s SupplierRepository to fill the suppliers collection with SupplierModel items, using a service:

public class SupplierRepository
{
public static IList<Schepers.Client.SCSA.Models.SupplierModel> GetSuppliers()
{
StockServiceReference.StockServiceClient client
= new StockServiceClient();
GetSuppliersResponse response =
client.GetSuppliers(new GetSuppliersRequest() { });
client.Close();
IList<SupplierModel> suppliers =
response.Suppliers.TransformList<StockServiceReference.Supplier,
SupplierModel>();
return suppliers;
}
}

And here is the implementation of my ObservableTrackableCollection:

 
/// <summary>
/// An ObservableTrackableCollection is an observable collection of a
/// generic type with tracking capabilities. The generic type has to
/// implement INotifyPropertyChanged so that the ObservableTrackableCollection
/// is notified when a property of the type changed. It also has to
/// implement IDataErrorInfo, to that it is able to validate its collection
/// items.
/// </summary>
/// <typeparam name="T">Type that collection consists of.</typeparam>
public class ObservableTrackableCollection<T> : ObservableCollection<T>
where T : INotifyPropertyChanged, IDataErrorInfo
{
// Delegate for collection handler
public delegate void CollectionItemHandler(object sender,
CollectionItemEventArgs<T> args);
public delegate void ItemPropertyHandler(object sender,
ItemPropertyEventArgs args);
// Events
public event CollectionItemHandler ItemsAdded;
public event CollectionItemHandler ItemsRemoved;
public event ItemPropertyHandler ItemPropertyChanged;
/// <summary>
/// True if all collection items validate, false if there is a validation
/// error in one or more items. It uses the IDataErrorInfo implementation of
/// the entities to do this validation.
/// </summary>
public bool IsCollectionValid
{
get
{
bool areErrors = false;
foreach (IDataErrorInfo item in this)
{
if (item.Error != string.Empty)
{
areErrors = true;
break;
}
}
return !areErrors;
}
}
/// <summary>
/// Occurs when items have been added to the collection.
/// </summary>
/// <param name="sender"></param>
/// <param name="args"></param>
protected void OnItemsAdded(object sender, CollectionItemEventArgs<T> args)
{
if (ItemsAdded != null)
{
ItemsAdded(sender, args);
}
}
/// <summary>
/// Occurs when items have been removed from the collection.
/// </summary>
/// <param name="sender"></param>
/// <param name="args"></param>
protected void OnItemsRemoved(object sender, CollectionItemEventArgs<T> args)
{
if (ItemsRemoved != null)
{
ItemsRemoved(sender, args);
}
}
/// <summary>
/// Occurs when a property has been changed.
/// </summary>
/// <param name="sender"></param>
/// <param name="args"></param>
protected void OnItemPropertyChanged(object sender, ItemPropertyEventArgs args)
{
if (ItemPropertyChanged != null)
{
ItemPropertyChanged(sender, args);
}
}
// List of all trackable objects.
private List<Trackable<T>> items = new List<Trackable<T>>();
/// <summary>
/// Occurs when an item has been added to or removed from the observable
/// collection.
/// </summary>
/// <param name="e"></param>
protected override void OnCollectionChanged(
System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
base.OnCollectionChanged(e);
switch (e.Action)
{
case System.Collections.Specialized.NotifyCollectionChangedAction.Add:
// One or more items have been added.
IList<T> addedCollectionItems = new List<T>();
foreach (var item in e.NewItems)
{
// Add item to trackable collection.
Trackable<T> trackable = new Trackable<T>((T)item,
TrackingState.Added);
items.Add(trackable);
addedCollectionItems.Add((T)item);
// Every time a new item is inserted, attach a
// PropertyChangedEventHandler
// so that we can track property changes of the item.
((T)item).PropertyChanged += new PropertyChangedEventHandler(
ObservableTrackableCollection_PropertyChanged);
}
OnItemsAdded(this, new CollectionItemEventArgs<T>(
addedCollectionItems));
break;
case System.Collections.Specialized
.NotifyCollectionChangedAction.Remove:
// One or more items have been deleted.
IList<T> removedCollectionItems = new List<T>();
foreach (T item in e.OldItems)
{
Trackable<T> trackable = items.Find(t => t.Item.Equals(item));
switch (trackable.TrackingState)
{
case TrackingState.Unmodified:
case TrackingState.Changed:
// Item was original or modified, and then deleted.
trackable.TrackingState = TrackingState.Removed;
break;
case TrackingState.Added:
// Item was added and then deleted, no need to track
// it anymore.
items.Remove(trackable);
break;
default:
break;
}
removedCollectionItems.Add((T)item);
}
OnItemsRemoved(this, new CollectionItemEventArgs<T>(
removedCollectionItems));
break;
case System.Collections.Specialized
.NotifyCollectionChangedAction.Replace:
throw new NotImplementedException("Replace not yet handled.");
}
}
/// <summary>
/// Occurs when a property has changed.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
void ObservableTrackableCollection_PropertyChanged(object sender,
PropertyChangedEventArgs e)
{
// Mark as modified only if it was original item.
Trackable<T> trackable = items.Find(t => t.Item.Equals(sender));
switch (trackable.TrackingState)
{
case TrackingState.Unmodified:
trackable.TrackingState = TrackingState.Changed;
break;
default:
break;
}
OnItemPropertyChanged(this, new ItemPropertyEventArgs(e.PropertyName));
}
/// <summary>
/// Resets tracking information of all items in the collection.
/// </summary>
public void ResetTrackInfo()
{
foreach (Trackable<T> trackable in items)
{
trackable.TrackingState = TrackingState.Unmodified;
}
}
/// <summary>
/// Gets changes in the collection for specified track state.
/// </summary>
/// <param name="trackingState">Tracking state.</param>
/// <returns>List of changed items in collection.</returns>
public List<T> GetChanges(TrackingState trackingState)
{
var results = from i in items
where i.TrackingState == trackingState
select i.Item;
return results.ToList<T>();
}
/// <summary>
/// This class is used by ObservableTrackableCollection to enable
/// tracking of items of type U.
/// </summary>
/// <typeparam name="U">Type to track.</typeparam>
private class Trackable<U>
{
public Trackable(U item, TrackingState trackingState)
{
Item = item;
TrackingState = trackingState;
}
public U Item;
public TrackingState TrackingState;
}
}

/// <summary>
/// Tracking states.
/// </summary>
public enum TrackingState
{
/// <summary>
/// Indicates that an item is not changed, deleted or added.
/// </summary>
Unmodified,
/// <summary>
/// Indicates an item was changed.
/// </summary>
Changed,
/// <summary>
/// Indicates an item was removed.
/// </summary>
Removed,
/// <summary>
/// Indicates an item was added.
/// </summary>
Added
};

/// <summary>
/// EventArgs for collection items.
/// </summary>
/// <typeparam name="T"></typeparam>
public class CollectionItemEventArgs<T> : EventArgs
{
public IList<T> CollectionItems { get; set; }
public CollectionItemEventArgs(IList<T> collectionItems)
{
this.CollectionItems = collectionItems;
}
}
/// <summary>
/// EventArgs for item properties.
/// </summary>
public class ItemPropertyEventArgs : EventArgs
{
public string PropertyName { get; set; }
public ItemPropertyEventArgs(string propertyName)
{
this.PropertyName = propertyName;
}
}

This is my first version, so it may be improved, but the general idea is that this generic ObservableTrackableCollection gives you validation and tracking capabilities, but that each component in the MVVM pattern still has its own responsability: the model contains model data and validation logic, the view model encapsulates this model and contains everything that is needed by the view like for example property change notifications, properties the view can bound too, etc…

Advertisements

Creating a database table with zip codes

Today I needed a database table in Sql Server that contains all Belgian zip codes and city names. Obviously I was not going to create this by hand, so I wanted to share how I did it because I think it’s a common requirement.

The first step was to find a list of zip codes and city names. On the site http://www.post.be/site/nl/residential/customerservice/search/postal_codes.html you can download this list in excel format. From this file I extracted the zipcode and city name and saved this as a csv-file, which then looked like:

1000;Brussel
1000;Bruxelles
1005;Ass. Réun. Com. Communau. Commune
1005;Brusselse Hoofdstedelijke Raad
1005;Conseil Region Bruxelles-Capitale
1005;Ver.Verg.Gemeensch.Gemeensch.Comm.
1006;Raad Vlaamse Gemeenschapscommissie
etc...

The second step was to create the database table that will contain this information, so I created a table called City with the following columns:

ZipCode, int
Name, varchar(100)

The next step was the actual import from the csv file into the table. To do this, I used the bulk copy technique: just open a new query window and execute the following statement:

BULK
INSERT City
FROM 'd:postcodes.csv'
WITH
(
FIELDTERMINATOR = ';',
ROWTERMINATOR = 'n'
)

And now the table City is filled with 2903 records:

City table

As a last step I added another column as the primary key:

Id, int, primary key (identity)

And now each entry has a primary key:

City table

I think this is probably the easiest way to create a table with zip codes and city names. Except maybe… if someone would give you the script to create and fill the City table 🙂 Well here it is (it seems I can only upload doc files, sorry for that): script_generate_zip_codes_belgium