Click here to Skip to main content
13,701,113 members
Click here to Skip to main content
Add your own
alternative version

Tagged as

Stats

47.6K views
556 downloads
125 bookmarked
Posted 7 Jun 2015
Licenced CPOL

Plain C# implementation of WPF Concepts - Part 1 AProps and Introduction to Bindings

, 16 Jun 2015
Rate this:
Please Sign up or sign in to vote.
Implementating Attached Properties and Bindings outside of WPF, in plain C#

Introduction

WPF (Windows Presentation Foundation) introduced many new programming paradigms which, to the best of my knowledge, were not utilized by any other framework or language. These paradigms make WPF programming very powerful, concise and perfect for separations of concerns. Once you understand how to use these paradigms you can produce a lot of functionality with a few lines of code while maintaining separation of concerns, thus making your software easy to extend, modify and debug.

The interesting thing about WPF paradigms is that many of them do not have anything to do with WPF or Visual Programming or even C# language and can be implemented and applied for non-visual programming and in other languages, e.g. Java and JavaScript, producing the same advantages for building completely different applications.

The way Microsoft team implemented the WPF paradigms couples them strongly to WPF. As a result, in order to use them outside of WPF, even in C#, another implementation is required. In fact, my feeling is that WPF's architects and developers themselves did not quite understand the terrific conceptual breakthroughs that they made and therefore did not push for more wide-spread acceptance of those paradigms.

In these series of articles I am presenting my own implementation of most of the WPF paradigms in plain (non-WPF) C#. Many of these paradigms are implemented a bit differently - usually in a more generic way then their WPF counterparts. I also dropped some features that I do not use often in WPF. Originally these paradigms were briefly described in a series of my blog article - you can see them at the code project.

In the future I plan to create similar packages for Java and JavaScript (or, perhaps, TypeScript).

Here is a list of WPF-less paradigm implementations that will be described in these article series:

  • Attached properties
  • Bindings
  • DataTemplates
  • Generic Trees (functional trees) - instead of Visual and Logical trees.
  • RoutedEvents
  • Behaviors

 

In this installment of the series I discuss re-implementing Attached Properties and Bindings outside of WPF. I also discuss greater-then-WPF, but promoted-by-WPF concepts of Non-Invasive Object Modification (NIOM) and Data and Functionality Mimicking (DAFM).

At this point I did not enable multithreading or choosing threads for the functionality - so all of the samples are single threaded and a change handling happens in the same thread as the change invocation. I plan to address threading sometime in the future.

Some knowledge of WPF is desirable but not required for reading this article, as I provide non-WPF samples illustrating every one of the features described here.

Code Structure

The test solution files can be found within folders under 'Tests' directory.

Also 'Tests' directory contains a NP.Tests.GenericTestObjects library used for creating (very simple) test objects if they are used in multiple tests.

Folder 'NP.Paradigms' contains the generic WPF-less code implementing the WPF paradigms described in this article.

Non-WPF Implementation of Usage of Attached Properties (AProps) and the Concept of Non-Invasiveness

Attached Properties in WPF

Important Note for WPF developers reading this article: when talking about Attached Properties below, I also include Dependency Properties - I consider them to be a version of Attached Properties that are required to be defined in a class that uses them.

Attached properties in WPF were introduced in part in order for objects with potentially hundreds of properties (but most of those properties having default values) to take less space than in a straight forward implementation - so called sparse implementation.

At the same time, they address a number of other, very important issues:

  1. They allow attaching any property to any object without that property being defined on that object in advance.
  2. A property change handler can be registered so that when an attached property changes on an object, it is being invoked. This allows some really interesting processing to be performed after an Attached Property change and makes the foundation of attaching and detaching behaviors to an object.
  3. Attached Properties can be used as WPF Binding target. In our binding implementation we are going to relax this condition - so that not only our Attached Properties (or AProps as I call them), but also usual properties can be used as binding targets.
  4. WPF Attached Properties can be made to propagate down the visual tree. I have not implemented this part outside of WPF yet.

 

Attached Properties, Behaviors and the Concept of Non-Invasive Object Modification (NIOM)

There is a software development concept of non-invasive object modification (NIOM) that has been widely used, but not formalized yet; this is my attempt at formalization.

Plain C# allows to 'Add' methods to a class without class modification - the so called extension methods can be defined in a static class outside of the class on whose objects they are called. The non-invasiveness in this case is purely nominal of course - they are not 'real' methods on the modified class; e.g. they do not have access to the private or protected class members, but it is still important.

WPF's attached properties are defined in a static class and can be attached to any object derived from DependencyObject class. This allows associating some data with an object without modifying the object's class. This is very important for separation of concerns. E.g. assume that you have a generic object Widget. Widget which represents a window with some information in it. Some Widgets have headers and some don't. One way of implementing a Widget with a header would be to create a class WidgetWithHeader that inherits from class Widget. This, however, will prevent using any other super-class for the WidgetWithHeader (because of the lack of Multiple Implementation Inheritance in C#). Instead, we can create a header for the Widget as a completely separate control and attach it to the Widgets that require them using an Attached Property. In that case we can even use the same Attached Property to attach the header to non-Widget objects that might still need headers.

Of course, the same approach of attaching properties can be also applied to non-Visual objects.

Behaviors are the most powerful way of modifying the objects non-invasively. In WPF the behaviors are usually attached to objects using Attached Properties. The behaviors themselves provide event handlers for the object's events. Using behaviors, you can create very complex functionality for an object outside of the object's class.

Again there is nothing in Behaviors that prevents them from being used outside of WPF and I gave examples of non-visual Behaviors in View-View Model based WPF and XAML Implementational Patterns. (WPF and XAML Patterns of Code Reuse in Easy Samples. Part 2)

AProps Usage Samples

I call my outside-of-WPF implementation of Attached Properties AProps in order to differentiate it from the WPF implementation.

I'll start by showing how to use AProps, and later talk about how they were implemented - as it is my firm conviction that first, the users need to understand what a concept is needed for, and only later and less importantly - how it was implemented.

The samples presented in this sub-section are located under 'Tests/APropTests' and 'Tests/APropsIndividualHandlersTests' folders. Both are referencing a very simple Person class defined within NP.Tests.GenericTestObjects project. Here is the code for class Person:

public class Person
{
    public string FirstName { get; set; }
    public string LastName { get; set; }

    public override string ToString()
    {
        return FirstName + " " + LastName;
    }
}

In NP.Tests.APropsTests project, we show how to

  • Create an AProp for specific object and property types, specifying default property values and generic pre and post change handlers (by generic I mean that they are going to fire on every object if its corresponding AProp changes).
  • Set the property on any object of the corresponding type and observe the handlers firing.

 

Here is the code for the Program.Main() method of the tester project:

public static void Main()
{
    // create the AProp
    AProp<Person, bool> isStudentAProp = new AProp<Person, bool>
    (
        false, // by default, the person is not a student
        (obj, oldVal, newVal) =>// to be called before the property is set
        {
            Console.WriteLine
            (
                "\tIsStudent AProp is about to change on person " + obj + " from " + oldVal + " to " + newVal
            );
            return true; // returning false would cancel the update.
        },
        (obj, oldVal, newVal) =>// to be called after the property is set
        {
            Console.WriteLine("\tIsStudent AProp changed on the person " + obj);
        }
    );

    Person nick = new Person { FirstName = "Nick", LastName = "Polyak" };

    // before property change, isNickStudent is set to default value 'false'
    bool isNickStudent = isStudentAProp.GetProperty(nick); 

    Console.WriteLine("Default IsStudent AProp on object nick is " + isNickStudent);

    // if the value is the same as before (false) - no callbacks are fired
    isStudentAProp.SetProperty(nick, false);

    // if the value change to 'true' - the callbacks are fired.
    isStudentAProp.SetProperty(nick, true);

    isNickStudent = isStudentAProp.GetProperty(nick);

    // will show that isNickStudent is now set to 'true'
    Console.WriteLine("Default IsStudent AProp on object nick is " + isNickStudent);
}  

Line AProp<Person, bool> isStudentAProp = new AProp<Person, bool>(...) creates the AProp.

Just like in case of a WPF Attached Property, AProp is a separate object. Unlike a WPF Attached Property - AProp object does not have to be defined as static within a static class as long as it is visible everywhere you want to use it for getting or setting a value on an object. In our case we are using it only within Program.Main() method and so, we can define it within the same function.

Also, note that AProp is more type safe than WPF's Attached Property - its two type arguments define the type of an object to which we want to attach and the type of the attached property correspondingly - in our case we want to be able to attach property IsStudent of type bool to objects of type Person, so we have generic types defined as <Person, bool>.

AProps can be attached to any Reference Type objects - not Value Types are allowed. This is again, much more generic than WPF - which allows to use only classes derived from DependencyObject class for that purpose.

Now, let us look at the whole constructor

  // create the AProp
AProp<Person, bool> isStudentAProp = new AProp<Person, bool>
(
    false, // by default, the person is not a student
    (obj, oldVal, newVal) =>// to be called before the property is set
    {
        Console.WriteLine
        (
            "\tIsStudent AProp is about to change on person " + obj + " from " + oldVal + " to " + newVal
        );
        return true; // returning false would cancel the update.
    },  // is called before the property is set.
    (obj, oldVal, newVal) =>// to be called after the property is set
    {
        Console.WriteLine("\tIsStudent AProp changed on the person " + obj);
    }
);

The first argument to the constructor is the default value (property value returned if no value has been ever set on the object in question).

The second argument to the constructor is a delegate of type BeforePropertyChangedDelegate<ObjectType, PropertyType>:

public delegate bool BeforePropertyChangedDelegate<ObjectType, PropertyType>
(
    ObjectType obj,
    PropertyType oldPropertyValue,
    PropertyType newPropertyValue
);  

It allows operating the object whose AProp is being set, the old property value and the new property value. It also gives you last chance to cancel the property change by returning boolean value false.

We implement it as a Lambda:

(obj, oldVal, newVal) =>// to be called before the property is set
{
    Console.WriteLine
    (
        "\tIsStudent AProp is about to change on person " + obj + " from " + oldVal + " to " + newVal
    );
    return true; // returning false would cancel the update.
}  

As you see - our handler is printing a message that the property is about to change.

Next argument is the post change handler of type OnPropertyChangedDelegate<ObjectType, PropertyType>:

public delegate void OnPropertyChangedDelegate<ObjectType, PropertyType>
(
    ObjectType obj, 
    PropertyType oldPropertyValue,
    PropertyType newPropertyValue
);  

In our example it is also a Lambda:

(obj, oldVal, newVal) =>// to be called after the property is set
{
    Console.WriteLine("\tIsStudent AProp changed on the person " + obj);
}  

that prints a message stating that the property has changed.

We shift the messages printed within pre and post change handlers by a tab "\t" in order not to confuse them with the message printed within the Main method itself.

Note that both pre and post change handlers can be passed as nulls (or not passed at all), in that case no generic handler will be fired on the property change.

The rest of the code is pretty much explained by the in-line comments

// create Person object nick
Person nick = new Person { FirstName = "Nick", LastName = "Polyak" };

// before property change, isNickStudent is set to default value 'false'
bool isNickStudent = isStudentAProp.GetProperty(nick); 

Console.WriteLine("Default IsStudent AProp on object nick is " + isNickStudent);

// if the value is the same as before (false) - no callbacks are fired
isStudentAProp.SetProperty(nick, false);

// if the value change to 'true' - the callbacks are fired.
isStudentAProp.SetProperty(nick, true);

isNickStudent = isStudentAProp.GetProperty(nick);

// will show that isNickStudent is now set to 'true'
Console.WriteLine("Default IsStudent AProp on object nick is " + isNickStudent);  

We create a Person object nick and use the AProp's setters and getter to set and get the properties on that object. Here is an example of getting isStudentAProp value from object nick:

bool isNickStudent = isStudentAProp.GetProperty(nick);   

and here is an example of setting isStudentAProp value on object nick to true:

isStudentAProp.SetProperty(nick, true);  

Note, that if the property is being set to the same value as before (default or not), nothing will change and no handlers will be fired as we demonstrate in the code.

Default IsStudent AProp on object nick is False
        IsStudent AProp is about to change on person Nick Polyak from False to True
        IsStudent AProp changed on the person Nick Polyak
Default IsStudent AProp on object nick is True

Using AProp Change Handlers on Individual Objects

The second AProp sample is located under NP.Tests.APropsIndividualHandlersTests solution. It shows how to add a Post change AProp handler to an individual object - so that it will only be fired when AProp changes on that object but not on any other object.

This is important functionality that Attached Properties do not have - the only way I could imitate it - is by creating a binding with the Attached Property whose change we want to detect being the source of the binding and using the Target's Attached Propertie's change handler for change detection.

The test's code located within Program.Main() method is simpler than that of the previous example:

public static void Main()
{
    // do not set generic pre and post change handlers for the AProp
    AProp<Person, bool> isStudentAProp = new AProp<Person, bool>
    (
        false // by default, the person is not a student
    );

    // create a person object
    Person nick = new Person { FirstName = "Nick", LastName = "Polyak" };

    // create another Person object
    Person joe = new Person { FirstName = "Joe", LastName = "Doe" };

    // attach the inidividual isStudentAProp change handler to object 'nick'
    isStudentAProp.AddOnPropertyChangedHandler(nick, IndividualPropChangeHandler);

    // handler is set for object 'nick' so it will be triggered here
    isStudentAProp.SetProperty(nick, true);

    // handler is not set for object 'joe', so it won't be triggered here
    isStudentAProp.SetProperty(joe, true);
}

private static void IndividualPropChangeHandler(Person obj, bool oldPropertyValue, bool newPropertyValue)
{
    Console.WriteLine("This is INDIVIDUAL AProp value change handler fired on object " + obj.ToString());
}  

We create isStudentProp as an AProp without any generic change handlers.

Two Person objects nick and joe are created - in order to show that when we attach an individual change handler to nick, changing the AProp on joe won't trigger it.

Here is how we attach the isStudentAProp individual change handler to object nick:

// attach the inidividual isStudentAProp change handler to object 'nick'
isStudentAProp.AddOnPropertyChangedHandler(nick, IndividualPropChangeHandler);

When running this test you see that the handler is only fired for object nick:

This is INDIVIDUAL AProp value change handler fired on object Nick Polyak

Clearing AProps

In order to reset an AProp on an object to the default, one can use APropClearAProperty(Object obj) method. E.g. in the example abover, one can clear the isStudentAProp on object nick by calling isStudentAProp.ClearAProperty(nick). All the individual change handlers will also be removed from nick object.

AProps vs Attached Properties

The examples above present complete capabilities of the AProps. They are considerably less complex than those of the Attached Properties, but they cover everything I ever needed from the Attached Properties except for the ability to inherit property values down the visual tree. Unlike Attached Properties, AProps can be defined on any refernce type object, not only on those descendent from DependencyObject. On top of this, AProps are more type safe and allow registering change handlers for individual objects.

AProp Implementation

There are two ways to associate a (property) value with an object. One way is to add this value to the object's class - in this case each object of this class will contain a reference to this value.

The other, less usual way of creating such association is to create a collection of Values accessible by the object. This can be done by using Dictionary<ObjectType, ValueType> with objects being the keys. Then (providing you have a reference to the dictionary), you can get the property value for each object. This is how Microsoft implemented Attached Properties and this is how I implemented AProps.

AProp implementation code is located in file AProp.cs within NP.Paradigms project.

The object-to-value dictionary is defined by class member:

ConditionalWeakTable<ObjectType, APropertyValueWrapper<ObjectType, PropertyType>> _objectToPropValueMap;  

I used ConditionalWeakTable and not the usual Dictionary class, because it creates a weak reference to its keys and values, so that the object destruction will not be hindered by it being it being one of the keys. Moreover, the destroyed object will be automatically removed from the ConditionalWeakTable collection. Also, ConditionalWeakTable us multi-thread safe, so one does not need to worry about accessing and setting AProps on multiple threads.

Keys of this 'Dictionary' are the plain objects of ObjectType, while the values are plain property values, but objects of class APropertyValueWrapper<ObjectType, PropertyType>.

APropertyValueWrapper<ObjectType, PropertyType> is a private class, defined in the same file. It contains the AProp value as

internal PropertyType APropertyValue { get; set; }  

It also contains a weak reference to the object that has that value:

internal WeakReference<ObjectType> ObjReference { get; private set; }  

and an event serving for attaching the individual AProp change event handlers:

// allows to set event handlers to be fired one the AProperty is changed on the object
internal event OnPropertyChangedDelegate<ObjectType, PropertyType> OnPropertyChangedEvent = null;  

The event can be fired via the following code:

internal void FireOnPropertyChangedEvent(PropertyType oldPropertyValue)
{
    if (OnPropertyChangedEvent != null)
    {
        ObjectType obj;

        if (ObjReference.TryGetTarget(out obj))
        {
            OnPropertyChangedEvent(obj, oldPropertyValue, APropertyValue);
        }
    }
}  

The above also shows why we need a reference to the object 'containing' the AProp value - we need it in order to pass it as the first argument to the event handler.

Class AProp<ObjectType, PropertyType> provides the methods we showed above for setting and getting the AProp values on objects. Also it provides PropertyType _defaultValue class member that defines a value to be returned by AProp.GetProperty(ObjectType obj) method in case the AProp is not set on the passed object (there is no corresponding entry within ConditionalWeakTable table).

There is also a non-strongly typed version of AProp class:

public class AProp : AProp<object, object>   

It is more generic, but less type safe.

Registering AProps

Class AProp<ObjectType, PropertyType> implements a very simple interface

public interface IAPropValueGetter
{
    object GetObjectAPropValue(object obj);
}  

This interface allows to get the untyped AProp value of the passed object.

There is also a static class

public static class AllAProps
{
    static public List<IAPropValueGetter> TheAProps
    {
        get
        {
          ...
        }
    }
    ...
}  

It contains a collection TheAProps of all AProps ever defined in your application. Each AProp object is added to AllAProps.TheAProps collection within its constructor. There is also a built-in mechanism for removing the references to the stale (garbage collected) AProps.

This collection is not being used at this point, but this is the only way all of AProps within your application and potentially can be used by the tools e.g. Snoop improved to handle AProps. (Currently there is no such capability in Snoop, of course).

Non-WPF Implementation of the Bindings and the Concept of Data and Functionality Mimicking (DAFM)

Introduction

This article is already getting large, so I am going to put only a Binding teaser here with the main material following in the second installment of this series.

WPF is not the first framework to start using binding, but WPF (to the best of my knowledge) is the first framework that is binding centric. The famouse MVVM pattern is just one manifestation of what the binding can achieve.

Binding is the core of the concept that I call Data and Functionality Mimicking (DAFM).

MVVM is an example of DAFM - you create a pure non visual skeleton called View Model or VM. This skeleton can be responsible for creating some data and data collections, communication with the other parts of the application and the back end and so on.

View (or the visual part of the application) is the meat that grows around the skeleton and mimics its data and functionality. Changes to the View (e.g. user input into an editable area) can also result in changes to the View Model (Bindings can work in both directions).

Important Note: The picture above might make people think that the View Model has some knowledge of the View. THIS IS NOT TRUE! The only way the View Model can control the View is via the bindings. View, however, is aware of the View Model and can modify it through the binding and by some other means.

Two Types of Data and Two Types of Data Binding

At the high level there are two types of data structures:

  1. Data that can be represented by names mapped into values. A C# object of class Person considered in the previous sub-section is an example of such data - its FirstName property is mapped say to value 'joe' and its LastName property is mapped to value 'doe';
  2. Data that can be represented by a collection of objects of similar shape. Any collection, say of integers, will be an example of such data.

By combining the two types of data, we can create a data hierarchy - e.g. a Property of a class can contain a collection of some objects of complex types that contain multiple properties etc.

 

JSON is very well suited for representing the data structure hierarchy - both the name-values and the collections.

The better known WPF Binding is the one that binds two properties on two objects, i.e. it binds the name-value types of data hierarchy.

The other type of binding - is a binding between two collections - it makes the target collection mimic the structure of the source collection - when items are inserted into or deleted from the source collection, the corresponding items are also inserted into or deleted from the target collection. This type of binding is used in WPF only implicitly - when an ItemsControl's ItemsSource is bound to an ObservableCollection<T>

Below I present classes that implement both types of data binding.

Data Binding Samples

As promised above, I'll show some capabilities of non-WPF bindings here while a larger discussion and implementation explanations will be followed in the next article.

Property Binding Sample

This test is located under PropertyBindingTests.sln solution.

In order to demonstrate a binding with composite paths I created a hierarchy of classes.

The source object is of class Contact. It contains HomeAddress and WorkAddress properties of type Address. Address contains City and Street string properties.

public class Address : INotifyPropertyChanged
{
    ...

    public string City
    {
       ...
    }


    public string Street
    {
       ...
    }
} 

and

public class Contact : Person, INotifyPropertyChanged
{
    ...

    public Address HomeAddress
    {
       ...
    }

    public Address WorkAddress
    {
       ...
    }
}  

Both Contact and Address classes implement INotifiablePropertyChanged interface and all the properties fire PropertyChanged event when the property changes.

The target object is of class PrintModel. It contains HomeCityPrintObj property of class PrintProp. It also contains a method Print() that calls HomeCityPrintObj.Print(). PrintProp class contains _propertyName string field, PropValueToPrint string property and method Print() that prints property name and property value:

public class PrintProp
{
    public PrintProp(string propName)
    {
        _propName = propName;
    }

    readonly string _propName;
    public object PropValueToPrint { get; set; } 

    public void Print()
    {
        string strToPrint = "null";

        if (PropValueToPrint != null)
            strToPrint = PropValueToPrint.ToString();

        Console.WriteLine(_propName + ": " + strToPrint);
    }
}

and

public class PrintModel : INotifyPropertyChanged
{
    ...

    PrintProp _homeCityPrintObj = null;
    public PrintProp HomeCityPrintObj
    {
        private get
        {
            return _homeCityPrintObj;
        }
        set
        {
            if (_homeCityPrintObj == value)
                return;

            _homeCityPrintObj = value;

            OnPropertyChanged("HomeCityPrintObj");

        }
    }

    public PrintModel()
    {
        HomeCityPrintObj = new PrintProp("Home City");
    }

    public void Print()
    {
        HomeCityPrintObj.Print();
    }
}  

In Program.Main() method we create a Contact and a PrintModel and bind the HomeAddress/City property of the Contact to HomeCityPrintObj/PropValueToPrint property of the target:

public static void Main()
{
    // create the Contact object (binding's source object)
    Contact joeContact = new Contact
    {
        FirstName = "Joe",
        LastName = "Doe",

        HomeAddress = new Address { City = "Boston" },
    };

    // Create the PrintModel (binding's target object)
    PrintModel printModel = new PrintModel();
    Console.WriteLine("Before binding the printModel's Home City is null");
    printModel.Print();

    //create the binding
    OneWayPropertyBinding<object, object> homeCityBinding = new OneWayPropertyBinding<object, object>();

    // set the binding's links from the source object to the source property are "HomeAddress" and "City"
    CompositePathGetter<object> homeCitySourcePathGetter =
        new CompositePathGetter<object>
        (
            new BindingPathLink<object>[]
            {
                new BindingPathLink<object>("HomeAddress"),
                new BindingPathLink<object>("City"),
            },
            null
        );

    homeCitySourcePathGetter.TheObj = joeContact;
    homeCityBinding.SourcePropertyGetter = homeCitySourcePathGetter;

    // set the binding's links from the target object to the target property are "HomeCityPrintObj" and "PropValueToPrint"
    CompositePathSetter<object> homeCityTargetPathSetter = new CompositePathSetter<object>
    (
        new BindingPathLink<object>[]
        {
            new BindingPathLink<object>("HomeCityPrintObj"),
            new BindingPathLink<object>("PropValueToPrint")
        }
    );

    homeCityTargetPathSetter.TheObj = printModel;
    homeCityBinding.TargetPropertySetter = homeCityTargetPathSetter;

    // do the actual binding between the source and the target
    // by calling Bind() method on the binding.
    homeCityBinding.Bind();


    Console.WriteLine("\nAfter binding the printModel's Home City is Boston");
    printModel.Print();

    joeContact.HomeAddress.City = "Brookline";

    Console.WriteLine("\nHome City change is detected - now Home City is Brookline");
    printModel.Print();

    joeContact.HomeAddress = new Address { City = "Allston" };

    Console.WriteLine("\nHome Address change is detected - now Home City is Allston");
    printModel.Print();


    printModel.HomeCityPrintObj = new PrintProp("Home City");
    Console.WriteLine("\nWe change the whole target link, but the binding keeps the target up to date:");
    printModel.Print();
}  

Here is the result of running this sample:

Before binding the printModel's Home City is null
Home City: null

After binding the printModel's Home City is Boston
Home City: Boston

Home City change is detected - now Home City is Brookline
Home City: Brookline

Home Address change is detected - now Home City is Allston
Home City: Allston

We change the whole target link, but the binding keeps the target up to date:
Home City: Allston  

Notice that our property binding can bind via a compound path on the target (in our case it is HomeCityPrintObj/PropValueToPrint), while WPF property binding can only bind the immediate properties on the target. In this sense our binding is more generic than that of WPF.

Notice also, that as long as the corresponding property on the source path fires INotifyPropertyChanged.PropertyChanged event on change, the target property will be changed also: first we change the city name and then we change the whole HomeAddress and in both cases, the change is picked up by the PrintModel.

Another interesting result, is that even if we change a link on the target path e.g.:

printModel.HomeCityPrintObj = new PrintProp("Home City");  

and this link change also triggers a PropertyChanged event, the active binding will force the new printModel.HomeCityPrintObj to have correct PropValueToPrint.

In the subsequent article we'll show that our Binding can have AProp and WPF Attached Property links both in the source and target paths.

Collection Binding Sample

Collection binding sample is located under CollectionBindingTests.sln solution.

We are using an ObservableCollection<Person> as the Binding source and ObservableCollection<PersonVM> as the Binding target collections.

Person is a very simple class that we already used above. It contains FirstName and LastName string properties. PersonVM inherits from Person and adds IsEnabled and IsVisible properties:

public class PersonVM : Person
{
    public bool IsVisible { get; set; }

    public bool IsEnabled { get; set; }

    public PersonVM()
    {
        IsEnabled = true;
        IsVisible = true;
    }

    public PersonVM(Person p) : this()
    {
        this.FirstName = p.FirstName;
        this.LastName = p.LastName;
    }
}  

In a sense, collection of Person objects can be considered a Model and collection of PersonVM objects can be considered a View Model.

Here is the code of the Program.Main() method:

public static void Main()
{
    // source collection
    ObservableCollection<Person> personCollection = new ObservableCollection<Person>();

    personCollection.Add(new Person { FirstName = "Nick", LastName = "Polyak" });
    personCollection.Add(new Person { FirstName = "Joe", LastName = "Doe" });

    // target collection
    Collection<PersonVM> personVMCollection = new Collection<PersonVM>();

    Console.WriteLine("Before binding personVMCollection is Empty:");
    personVMCollection.PrintCollection();

    OneWayCollectionBinding<Person, PersonVM> collectionBinding =
        new OneWayCollectionBinding<Person, PersonVM>
        {
            SourceCollection = personCollection,
            TargetCollection = personVMCollection,

            SourceToTargetItemDelegate = (person) => new PersonVM(person) //source to target converter
        };

    collectionBinding.Bind();
    Console.WriteLine("After binding personVMCollection is populated with the same items as the input collection:");
    personVMCollection.PrintCollection();

    Console.WriteLine("When 'John Smith' person is added to the source collection, he is also added to the target collection:");
    personCollection.Add(new Person { FirstName = "John", LastName = "Smith" });
    personVMCollection.PrintCollection();


    Console.WriteLine("When 'Nick Polyak' person is removed from the source collection, he is also removed from the target collection:");
    personCollection.RemoveAt(0);
    personVMCollection.PrintCollection();
}  

After Bind() method is called on the Binding, the target collection mimics the source collection, whatever changes are made to the source collection:

Before binding personVMCollection is Empty:
EMPTY
After binding personVMCollection is populated with the same items as the input collection:
Nick Polyak, Joe Doe
When 'John Smith' person is added to the source collection, he is also added to the target collection:
Nick Polyak, Joe Doe, John Smith
When 'Nick Polyak' person is removed from the source collection, he is also removed from the target collection:
Joe Doe, John Smith

In order for the mimicking to take place, the source collection should implement INotifyCollectionChanged interface, while the target collection should implement ICollection<T> interface, i.e. allow adding and removing items.

Summary

In this article I showed how to implement and use Attached Properties and Bindings outside of WPF, providing also some usage examples. The AProps and Bindings discussed here, are in many ways more generic and less restricting than those of WPF and can be used on any objects, not only on DependencyObjects.

In the next article I plan to concentrate more on the bindings, provide binding implementation details, introduce the notion of an event binding and give more usage examples.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Nick Polyak
Architect AWebPros
United States United States
I am a software architect and a developer with great passion for new engineering solutions and finding and applying design patterns.

I am passionate about learning new ways of building software and sharing my knowledge with others.

I worked with many various languages including C#, Java and C++.

I have my Ph.D. from RPI.

here is my linkedin profile - I'll be happy to connect!

You may also be interested in...

Comments and Discussions

 
Generalvery nice Pin
BillW3320-Jul-15 11:21
professionalBillW3320-Jul-15 11:21 
GeneralRe: very nice Pin
Nick Polyak20-Jul-15 11:41
professionalNick Polyak20-Jul-15 11:41 
GeneralGreat stuff! Pin
Sorcerdon9-Jul-15 3:42
memberSorcerdon9-Jul-15 3:42 
GeneralRe: Great stuff! Pin
Nick Polyak9-Jul-15 3:45
professionalNick Polyak9-Jul-15 3:45 
GeneralGood Work ! Pin
Wonde Tadesse24-Jun-15 5:26
professionalWonde Tadesse24-Jun-15 5:26 
GeneralRe: Good Work ! Pin
Nick Polyak24-Jun-15 13:52
professionalNick Polyak24-Jun-15 13:52 
GeneralRe: Good Work ! Pin
Wonde Tadesse25-Jun-15 1:59
professionalWonde Tadesse25-Jun-15 1:59 
GeneralRe: Good Work ! Pin
Nick Polyak25-Jun-15 13:40
professionalNick Polyak25-Jun-15 13:40 
GeneralRe: Good Work ! Pin
Wonde Tadesse26-Jun-15 3:18
professionalWonde Tadesse26-Jun-15 3:18 
GeneralRe: Good Work ! Pin
Nick Polyak27-Jun-15 15:50
professionalNick Polyak27-Jun-15 15:50 
GeneralRe: Good Work ! Pin
Wonde Tadesse28-Jun-15 8:49
professionalWonde Tadesse28-Jun-15 8:49 
GeneralRe: Good Work ! Pin
Nick Polyak28-Jun-15 8:58
professionalNick Polyak28-Jun-15 8:58 
GeneralRe: Good Work ! Pin
Wonde Tadesse28-Jun-15 18:06
professionalWonde Tadesse28-Jun-15 18:06 
GeneralMy vote of 5 Pin
L Hills16-Jun-15 4:33
memberL Hills16-Jun-15 4:33 
GeneralRe: My vote of 5 Pin
Nick Polyak16-Jun-15 4:45
professionalNick Polyak16-Jun-15 4:45 
GeneralRe: My vote of 5 Pin
L Hills16-Jun-15 23:27
memberL Hills16-Jun-15 23:27 
GeneralRe: My vote of 5 Pin
Nick Polyak17-Jun-15 3:14
professionalNick Polyak17-Jun-15 3:14 
GeneralMy vote of 5 Pin
Paulo Zemek11-Jun-15 12:56
mvpPaulo Zemek11-Jun-15 12:56 
GeneralRe: My vote of 5 Pin
Nick Polyak11-Jun-15 13:23
professionalNick Polyak11-Jun-15 13:23 
QuestionA small mistake Pin
Paulo Zemek11-Jun-15 11:56
mvpPaulo Zemek11-Jun-15 11:56 
AnswerRe: A small mistake Pin
Nick Polyak11-Jun-15 13:23
professionalNick Polyak11-Jun-15 13:23 
GeneralBeautiful Pin
Erick Mattew10-Jun-15 2:47
memberErick Mattew10-Jun-15 2:47 
GeneralRe: Beautiful Pin
Nick Polyak10-Jun-15 14:57
professionalNick Polyak10-Jun-15 14:57 
GeneralMy vote of 5 Pin
Evgeny Bestfator9-Jun-15 20:59
professionalEvgeny Bestfator9-Jun-15 20:59 
GeneralRe: My vote of 5 Pin
Nick Polyak10-Jun-15 14:58
professionalNick Polyak10-Jun-15 14:58 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

Permalink | Advertise | Privacy | Cookies | Terms of Use | Mobile
Web01-2016 | 2.8.180910.1 | Last Updated 16 Jun 2015
Article Copyright 2015 by Nick Polyak
Everything else Copyright © CodeProject, 1999-2018
Layout: fixed | fluid