Click here to Skip to main content
Click here to Skip to main content

WPF Tutorial - TypeConverter & Markup Extension

, 28 Dec 2010 CPOL
Rate this:
Please Sign up or sign in to vote.
XAML as an Extensible Markup Language has great flexibilities to create objects in XAML itself and do functions like automatic binding, Conversion of Data, etc. Markup Extension allows you to truly extend your markup upto a certain extent to elevate you to write less code and design your application

Table of Contents

Introduction

In my previous article I have discussed about basic architecture of WPF, and then gradually started learning with Layout panels, Transformation, introduced different Controls, containers, UI Transformation etc. In this article I will discuss the most important part of XAML which every developer must learn before starting with any XAML applications.

Markup Extensions are extensions to XAML which you might use to assign custom rules on your XAML based Applications. Thus any custom behavior which you would like to impose on your application in your designer, you should always use Markup Extensions. Here we will discuss how you can use Markup Extensions to generate your custom behaviors to XAML.

XAML or Extensible Application Markup Language is actually an XML format which has special schema defined. Now you might always wonder, how extensible the Markup is. What type of capabilities that are there within XAML which widely differs the XAML with XML. Yes, it is because of XAML parser which has huge number of capabilities to make the normal XML to a very rich UI designs.

You all know XAML is actually a text format. The tags are very same as with any XML where the attributes takes everything as String. Even though you want to assign a object to a string, you cannot do so because the object can only take a string. Markup Extension allows you to handle these kind of situations. So you can say a Markup extension is actually the way to extend a normal XML to a complete Extensible Markup as XAML.  

As XAML takes everything as string, sometimes we need to convert those data into valid values. For instance, when we use Margin, we need to specify each values of margin element. In such situation where the conversion is very simple and straight forward, we can use Type Converters to do this job rather than going for Markup extensions. Lets discuss about Type Converters first before we move to Markup Extensions.

Type Converter

As I already told you, markup as an extension of XML cannot impose restriction over a data element. That means we can only specify string data for attributes of any object in XAML. But XAML provides a flexibility to create your Type converter which allows you to impose restriction on the data. Thus even primitives like Single or Double could not have restrictions while you describe in your XAML. Type Converters plays a vital role to put this restriction to XAML parser.

TYPECONVERTERMAIN.JPG

XAML parser while parsing any value of an attribute needs two pieces of information.

  1. Value Type : This determines the Type to which the string data should be converted to.
  2. Actual Value

Well, when parser finds a data within an attribute, it first looks at the type. If the type is primitive, the parser tries a direct conversion. On the other hand, if it is an Enumerable, it tries to convert it to a particular value of an Enumerable. If neither of them satisfies the data, it finally tries to find appropriate Type Converters class, and converts it to an appropriate type. There are lots of typeconverters already defined in XAML, like Margin. Margin = 10,20,0,30 means margin :left,top,right,bottom is defined in sequence. Therefore system defines a typeconverter that converts this data into Thickness object.

Custom TypeConverter

To create a TypeConverter we need to decorate the Type with TypeConverterAttribute and define a custom class which converts the data into the actual type. And the actual converter class, a class which implements from TypeConverter.

Let us make it clear using an Example :

TypeConverter to convert a GeoPoint:

To create a TypeConverter as I have already told you, you need to create a class for which the TypeConverter to be applied. In my example, I have created a class which has two properties called Latitude and Longitude and creates an implementation of a Geographic Point. Lets see how the class looks like :

[global::System.ComponentModel.TypeConverter(typeof(GeoPointConverter))]
    public class GeoPointItem
    {
        public double Latitude { get; set; }
        public double Longitude { get; set; }

        public GeoPointItem()
        {
        }

        public GeoPointItem(double lat, double lon)
        {
            this.Latitude = lat;
            this.Longitude = lon;
        }

        public static GeoPointItem Parse(string data)
        {
            if (string.IsNullOrEmpty(data)) return new GeoPointItem();

            string[] items = data.Split(',');
            if (items.Count() != 2)
                throw new FormatException("GeoPoint should have both latitude 
                and longitude");

            double lat, lon;
            try
            {
                lat = Convert.ToDouble(items[0]);
            }
            catch (Exception ex) { 
                throw new FormatException("Latitude value cannot be converted", ex); 
            }

            try
            {
                lon = Convert.ToDouble(items[1]);
            }
            catch (Exception ex) { 
                throw new FormatException("Longitude value cannot be converted", ex); 
            }

            return new GeoPointItem(lat, lon);
        }

        public override string ToString()
        {
            return string.Format("{0},{1}", this.Latitude, this.Longitude);
        }
    }

In the above code you can see that I have created a quite normal class, which defines a Geographic point on Earth. The type has two parameters, Latitude and Longitude and both of which are Double values. I have also overridden the ToString() method, which is actually very important in this situation to get the string resultant of the object. The Parse method is used to parse a string format to GeoPointItem.

After implementing this, the first thing that you need to do is to decorate the class with TypeConverter Attribute. This attribute makes sure that the item is Converted easily using the TypeConverter GeoPointConverter passed as argument to the attribute. Thus while XAML parser parses the string, it will call the GeoPointConverter automatically to convert back the value appropriately.

After we are done with this, we need to create the actual converter. Lets look into it :

public class GeoPointConverter : global::System.ComponentModel.TypeConverter
    {

        //should return true if sourcetype is string
        public override bool CanConvertFrom(
         System.ComponentModel.ITypeDescriptorContext context, Type sourceType)
        {
            if (sourceType is string)
                return true;
            return base.CanConvertFrom(context, sourceType);
        }
        //should return true when destinationtype if GeopointItem
        public override bool CanConvertTo(
             System.ComponentModel.ITypeDescriptorContext context, Type destinationType)
        {
            if (destinationType is string)
                return true;

            return base.CanConvertTo(context, destinationType);
        }
        //Actual convertion from string to GeoPointItem
        public override object ConvertFrom(
     System.ComponentModel.ITypeDescriptorContext context, 
         System.Globalization.CultureInfo culture, object value)
        {
            if (value is string)
            {
                try
                {
                    return GeoPointItem.Parse(value as string);
                }
                catch (Exception ex)
                {
                    throw new Exception(string.Format(
      "Cannot convert '{0}' ({1}) because {2}", value, value.GetType(), ex.Message), ex);
                }
            }

            return base.ConvertFrom(context, culture, value);
        }

        //Actual convertion from GeoPointItem to string
        public override object ConvertTo(
         System.ComponentModel.ITypeDescriptorContext context, 
          System.Globalization.CultureInfo culture, object value, Type destinationType)
        {
            if(destinationType == null)
                throw new ArgumentNullException("destinationType");
    
             GeoPointItem gpoint = value as GeoPointItem;

            if(gpoint != null)
            if (this.CanConvertTo(context, destinationType))
                return gpoint.ToString();
            
            return base.ConvertTo(context, culture, value, destinationType);
        }
    }

In the above code we have implemented the converter class by deriving it from TypeConverter. After implementing it from TypeConverter class we need to override few methods which XAML parser calls and make appropriate modifications so that the XAML parser gets the actual value whenever required.

  1. CanConvertFrom : This will be called when XAML parser tries to parse a string into a GeopointItem value. When it returns true, it calls ConvertFrom to do actual Conversion.
  2. CanConvertTo : This will be called when XAML parser tries to parse a GeoPointItem variable to a string equivalent.When it returns true, it calls ConvertTo to do actual Conversion.
  3. ConvertFrom :Does actual conversion and returns the GeoPointItem after successful conversion.
  4. ConvertTo : Does actual conversion and returns the string equivalent of GeoPointItem passed in.

In the above example, you can see I have actually converter the string value to GeoPointItem and vice-versa using the TypeConverter class.

typeconverter.JPG

Now its time to use it. To do this I build a custom UserControl and put the property of that to have a GeoPoint. The XAML looks very simple :

<Grid>
        <Grid.RowDefinitions>
            <RowDefinition/>
            <RowDefinition/>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition/>
            <ColumnDefinition/>
        </Grid.ColumnDefinitions>
        <TextBlock Text="Latitude" Grid.Row="0" Grid.Column="0"></TextBlock>
        <TextBox x:Name="txtlat" MinWidth="40" Grid.Row="0" Grid.Column="1" 
              TextChanged="txtlat_TextChanged"/>
        <TextBlock Text="Longitude" Grid.Row="1" Grid.Column="0"></TextBlock>
        <TextBox x:Name="txtlon" MinWidth="40" Grid.Row="1" Grid.Column="1" 
             TextChanged="txtlon_TextChanged"/>
    </Grid>

It has 2 Textboxes which displays value of Latutude and Longitude individually. And when the value of these textboxes are modified, the actual value of the GeopointItem is modified.

public partial class GeoPoint : UserControl
{
        public static readonly DependencyProperty GeoPointValueProperty = 
       DependencyProperty.Register("GeoPointValue", typeof(GeoPointItem), 
             typeof(GeoPoint), new PropertyMetadata(new GeoPointItem(0.0, 0.0)));
        public GeoPoint()
        {
            InitializeComponent();
        }
       
        public GeoPointItem GeoPointValue
        {
            get
            {
                return this.GetValue(GeoPointValueProperty) as GeoPointItem;
            }
            set
            {
                this.SetValue(GeoPointValueProperty, value);
            }
        }

        private void txtlat_TextChanged(object sender, TextChangedEventArgs e)
        {
            GeoPointItem item = this.GeoPointValue;

            item.Latitude = Convert.ToDouble(txtlat.Text);
            this.GeoPointValue = item;
        }

        private void txtlon_TextChanged(object sender, TextChangedEventArgs e)
        {
            GeoPointItem item = this.GeoPointValue;

            item.Longitude = Convert.ToDouble(txtlon.Text);
            this.GeoPointValue = item;
        }

        private void UserControl_Loaded(object sender, RoutedEventArgs e)
        {
            GeoPointItem item = this.GeoPointValue;
            this.txtlat.Text = item.Latitude.ToString();
            this.txtlon.Text = item.Longitude.ToString();

        }
    }

Here when the UserControl Loads, it first loads values passed in to the TextBoxes. The TextChanged operation is handled to ensure the actual object is modified whenever the value of the textbox is modified.

From the window, we need to create an object of the UserControl and pass the value as under:

<converter:GeoPoint x:Name="cGeoPoint" GeoPointValue="60.5,20.5" />

The converter points to the namespace. Hence you can see the value is shown correctly in the TextBoxes.

Markup Extension

Now as you know about TypeConverter, let us take MarkupExtension into account. Markup Extensions gives the flexibility to create custom objects into your XAML attributes. Every markup extension is enclosed within a {} braces. Thus anything written within the Curl braces will be taken as Markup Extension. Thus XAML parser treats anything within the curl braces not as literal string and rather it tries to find the actual MarkupExtension corresponding to the name specified within the Curl braces.

Exception

If you want to put braces within a string you need to put {} at the beginning to make it an exception. You can read "How to Escape {} in XAML" [^] for more information.

Example of Markup Extension :

<TextBox Text={x:Null} />

Probably the simplest of all, it actually a MarkupExtension which returns Null to the Text string.

markupExtension.gif

There are already few Markup Extensions defined with XAML defined within System.Windows.Markup namespace which helps every time to produce functional features within your XAML. Let us discuss few of them :

NullExtension

This is the most simple MarkupExtension we ever have. It actually returns Null when placed against a value.

Content = "{x:Null}"

ArrayExtension

This is used to create an ArrayList of items. The x:Array actually returns an object of Array of type specified.

Values = {x:Array Type=sys:String}

StaticExtension

Another simple Extension which returns Static field or Property references.

Text="{x:Static Member=local:MyClass.StaticProperty}"

Thus when you define a static property StaticProperty for MyClass , you will automatically set the value to Text property using this.

TypeExtension

Type extension is used to get Types from objects. When the attribute of a control takes the Type object you can use it.

TargetType="{x:Type Button}" 

So TargetType receives a Type object of Button.

Reference

It is used to Refer an object declared somewhere in XAML by its name. It is introduced with .NET 4.0

Text="{x:Reference Name=Myobject}"

StaticResourceExtension

Resources are objects that are defined within the XAML. StaticResource substitutes the key assigned to the object and replaces its reference to the Resource element declared.

<Grid.Resources>

<Color x:Key="rKeyBlack">Black</Color>
<SolidColorBrush Color="{StaticResource rKeyBlack}" x:Key="rKeyBlackBrush"/>


</Grid.Resources>

<TextBlock Background="{StaticResource ResourceKey=rKeyBlackBrush}" />

StaticResource errors out if the key is not there during compile time.

DynamicResourceExtension

This is same as StaticResource, but it defers the value to be a runtime reference. Thus you can declare any key for a DynamicResource and it should be present during runtime when the actual object is created.

<TextBlock Background="{DynamicResource ResourceKey=rKeyBlackBrush}" />

Now the rKeyBlackBrush if not declared as Grid.Resources will not error out. You can add that during Runtime when Window Loads.

What are Resources ?

Resources are objects that are created the object is rendered by XAML parser. Every FrameworkElement object has a ResourceDictionary object placed within it. You can add Resources within this ResourceDictionary and can reuse those component within the scope.

Resources are reusable component which one can use for several times to produce its output. So if you need an object that you want to reuse more than once in your application, and you don't want the object to be changed within the scope, Resources are then the best option for you.

ResourceSample.JPG
<Grid.Resources>
            <Color x:Key="rKeyRed">Red</Color>
            <Color x:Key="rKeyCyan">Cyan</Color>
            <LinearGradientBrush x:Key="rKeyforegroundGradient">
                <GradientStop Color="{StaticResource rKeyRed}" Offset="0"></GradientStop>
                <GradientStop Color="{StaticResource rKeyCyan}" Offset="1"></GradientStop>
            </LinearGradientBrush>
        </Grid.Resources>
<TextBlock Text="{Binding ElementName=cGeoPoint, Path=GeoPointValue}" 
      FontSize="30" Margin="50" Grid.Row="1" Grid.ColumnSpan="2" 
      Foreground="{StaticResource rKeyforegroundGradient}" />

So you can see we have defined a LinearGradientBrush and used as Foreground of the TextBlock. This object can be reused any time when required.

Every FrameworkElement has a property called Resource which takes an object of ResourceDictionary. You can assign various resources to this Collection which you would use in different object created within the scope of the object. In XAML, resources are declared using x:Key attribute, and later this key can be used to refer to the resource in ResourceDictionary using StaticResource or DynamicResource.

Difference between StaticResource and DynamicResource

StaticResource finds the key defined within the ResourceDictionary under its scope during the Loading of the application. Hence the Compiler will throw error during compilation if not found in resources. On the other hand, DynamicResource Markup Extension defers the resource assignment to the actual runtime of the application. So the expression remains unevaluated until the object being created.

Note : If the resource is derived from Freezable (immutable), any change to the object will change the UI regardless of whether it is a StaticResource or DynamicResource. Example : Brush, Animation, Geometry objects are Freezable.

Choice between StaticResource and DynamicResource

  • StaticResource requires less CPU during runtime, so it is faster.
  • StaticResource are created when the application loads. So making everything as StaticResource means slowing down the Application Load process.
  • When the resources are unknown during compilation, you can use DynamicResource. DynamicResource are used when user interaction changes the look and feel of an object.
  • DynamicResource is best when you want your resource to be pluggable. You can read how to create pluggable resources from : Pluggable Styles and Resources in WPF with Language Converter Tool [^]

Binding

Binding is the most important and complex Markup extension which might be used to bind one object. It provides Databound object when the data object is assigned to the DataContext of the object.

Thus say you have an object Obj which has several properties like Name, Age etc. Then you might write

Text = "{Binding Name}"

which means the DataContext object will automatically be evaluated during runtime and the actual value of Name property of the object will be shown on the Text property.

Binding has lots of flexibilities, thus you can specify expressions on the databound objects and hence made it one of the most complex Markup extension.

binding.JPG
<StackPanel Orientation="Vertical">
        <TextBox x:Name="txtObj"></TextBox>
        <TextBox x:Name="txtCustom" Text="{Binding FallbackValue=10, 
        ElementName=txtObj, Path=Text,StringFormat='Text Entered : {0:N2}'}, Mode=TwoWay">
        </TextBox>
    </StackPanel>

As both of them are bound any change to the property automatically reflects the changes to the other textbox. There are few modes of these binding, OneTime, OneWayToSource, OneWay and TwoWay. StringFormat creates formatting of the string. We can have Converters associated with a binding to enable us to return appropriate value based on the value we feed in.

You can read how to create Converter for Binding from :

How to deal with Converter in Binding [^]

In case of binding, you must also make sure that the object which is bound to have implemented INotifyPropertyChanged. Otherwise every binding will work as OneTime.

You can read more on how you can implement Binding with INotifyPropertyChanged from : Change Notification for objects and Collection [^]

We will discuss Binding in greater detail in next article.

RelativeSource

RelativeSource is a MarkupExtension which you have to use within Binding. Binding has a property called RelativeSource, where you can specify a RelativeSource MarkupExtension. This Markup Extension allows you to give Relative reference to the element rather than absolutely specifying the value. RelativeSource comes handy when absolute Reference is unavailable.

RelativeSource has few properties which one can use :

  1. AncestorType : It defines the Type of the Ancestor element where the property to be found. So if you defined a button within a Grid, and you want to refer to that Grid, you might go for RelativeSource.
  2. Mode : Determines how the RelativeSource to be found. The enumeration has few values :
    • Self : This means the Binding will take place within the object itself. So if you want the Background and Foreground of the same object, RelativeSource Mode =Self will come handy.
    • FindAncestor : Finds elements parent to it. Thus it will look through all the visual element into the Visual tree and try to find the control for which the AncestorType is provided and it goes on until the object is found in the Ancestor elements to it.
    • TemplatedParent : TemplatedParent allows you to find the value from the object for which the Template is defined. Templates are definition of the control itself which helps to redefine the Data and Control Elements altogether. TemplatedParent allows you to find the Templated object directly.
    • PreviousData : This allows you to track the Previous Data element when the object is bound to a CollectionView. Thus this is actually RelativeSource to previous DataElement.
  3. AncestorLevel : Numeric value that determines how many levels that the RelativeSource should search before determining the result. If you specify 1 as AncestorLevel, it will go through only one level.

You can use RelativeSource for any binding.

<Grid Name="grd"> 

       <TextBox x:Name="txtCustom" Text="{Binding Name,
RelativeSource={RelativeSource AncestorType={x:Type Grid},
Mode=FindAncestor,AncestorLevel=2}}" />

</Grid>

This allows you to find the Grid. Practically speaking, this example doesnt gives you a sense when to use RelativeSource as in this case you can easily get the actual reference to the Grid. RelativeSource comes very useful when the Grid is somewhat inaccessible from it, say the TextBox is within the DataTemplate of a Grid.

TemplateBinding

TemplateBinding allows you to bind the value of a property of an object within the Template of a control to the object TemplatedParent to it.

<RadioButton Foreground="Red">
            <RadioButton.Template>
                <ControlTemplate>
                    <ToggleButton Content="{TemplateBinding Foreground}" />
                </ControlTemplate>
            </RadioButton.Template>
        </RadioButton>

In this case the ToggleButton Caption will be shown as #FFFF0000 which is the equivalent color for RadioButton.

MultiBinding

MultiBinding allows you to create Binding based on more than one Binding. You can create a class from IMultiValueConverter which will allow you to covert multipleBinding statements into one single output. The only difference between a normal converter and MultiValueConverter is a normal IValueConverter takes one value as argument whereas a MultiValueConverter takes an Array of values from all the Binding elements. We will discuss more on MultiBinding later in the series.

<Button x:Name="NextImageButton" >

    <Button.IsEnabled>

        <MultiBinding Converter="{StaticResource SelectedItemIndexIsNotLastToBoolean}">

            <Binding Mode="OneWay" ElementName="ImageListBox" Path="SelectedIndex" />

            <Binding Mode="OneWay" ElementName="ImageListBox" Path="Items.Count" />

        </MultiBinding>

    </Button.IsEnabled>

    <Image Source="Icons/navigate_right.png"/>

</Button>

Custom Markup Extension

In the final section, I will build my custom Markup Extension and use it. For simplicity we use Reflection to Get fields, Methods, and Properties and bind them into a ListBox. To create a custom Markup Extension, you need to create a class and inherit it from MarkupExtension. This class has an abstract method called ProvideValue which you need to override to make the MarkupExtension work. So actually the XAML parser calls this ProvideValue to get the output from MarkupExtension. So the actual implementation looks like :

public class ReflectionExtension : global::System.Windows.Markup.MarkupExtension
    {
        public Type CurrentType { get; set; }
        public bool IncludeMethods { get; set; }
        public bool IncludeFields { get; set; }
        public bool IncludeEvents { get; set; }

        public ReflectionExtension(Type currentType)
        {
            this.CurrentType = currentType;
        }
        
        public override object ProvideValue(IServiceProvider serviceProvider)
        {
            if (this.CurrentType == null)
                throw new ArgumentException("Type argument is not specified");

            ObservableCollection<string> collection = new ObservableCollection<string>();
            foreach(PropertyInfo p in this.CurrentType.GetProperties())
                collection.Add(string.Format("Property : {0}", p.Name));
 
            if(this.IncludeMethods)
                foreach(MethodInfo m in this.CurrentType.GetMethods())
                    collection.Add(string.Format("Method : {0} with {1} 
                      argument(s)", m.Name, m.GetParameters().Count()));
            if(this.IncludeFields)
                foreach(FieldInfo f in this.CurrentType.GetFields())
                    collection.Add(string.Format("Field : {0}", f.Name));
            if(this.IncludeEvents)
                foreach(EventInfo e in this.CurrentType.GetEvents())
                    collection.Add(string.Format("Events : {0}", e.Name));

            return collection;
        }

    }

You can see the constructor for this class takes one argument of Type. Now to use it we refer it in XAML and use it in similar fashion as we do with other MarkupExtensions.

<ListBox ItemsSource="{local:Reflection {x:Type Grid}, 
       IncludeMethods=true, IncludeFields=true, IncludeEvents=true}" 
         MaxHeight="200" Grid.Row="3" Grid.ColumnSpan="2" />

So here the Constructor gets argument from {x:Type Grid}. The first argument for any MarkupExtension is treated as Constructor argument. The other properties are defined using comma separated strings.

Conclusion

So this article deals with the basics of Markup Extension and Type Converters of basic XAML applications. We have left the Binding markup extension behind, and we will discuss on it on our next topic. I hope this article comes helpful to all of you. Thanks for reading.

License

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

Share

About the Author

Abhishek Sur
Architect
India India
Did you like his post?
 
Oh, lets go a bit further to know him better.
Visit his Website : www.abhisheksur.com to know more about Abhishek.
 
Abhishek also authored a book on .NET 4.5 Features and recommends you to read it, you will learn a lot from it.
http://bit.ly/EXPERTCookBook
 
Basically he is from India, who loves to explore the .NET world. He loves to code and in his leisure you always find him talking about technical stuffs.
 
Presently he is working in WPF, a new foundation to UI development, but mostly he likes to work on architecture and business classes. ASP.NET is one of his strength as well.
Have any problem? Write to him in his Forum.
 
You can also mail him directly to abhi2434@yahoo.com
 
Want a Coder like him for your project?
Drop him a mail to contact@abhisheksur.com
 
Visit His Blog

Dotnet Tricks and Tips



Dont forget to vote or share your comments about his Writing
Follow on   Twitter   Google+

Comments and Discussions

 
QuestionTypo? Pinmemberkmf26-Apr-13 1:13 
GeneralMy vote of 5 PinmemberUldis V.17-Jan-13 22:59 
QuestionUserControl_Loaded PinmemberMember 969022216-Dec-12 15:32 
GeneralMy vote of 5 PinmemberRenju Vinod24-Oct-12 21:22 
QuestionAbout Type Converter PinmemberShailesh vora3-Jul-12 20:47 
AnswerRe: About Type Converter PinmemberAbhishek Sur4-Jul-12 10:44 
Questionredundant checking of being string Pinmemberc.nicos13-Mar-12 17:41 
BugBug PinmemberJan Vratislav16-Dec-11 4:49 
Hallo,
 
I am just at the beginning of the article and I spotted a bug in the following code.
  public override bool CanConvertFrom(
         System.ComponentModel.ITypeDescriptorContext context, Type sourceType)
        {
            if (sourceType is string)
                return true;
            return base.CanConvertFrom(context, sourceType);
        }
 
The condition in the if-clause is always false because the sourceType is of Type not string.
 
Was an intension? Then just return false. Was it not? Then do following
 if (sourceType == typeof(String))
       return true;

GeneralRe: Bug PinmemberAbhishek Sur26-Aug-12 11:30 
GeneralMy vote of 5 PinmemberAvi Farah17-Oct-11 19:57 
GeneralMy vote of 5 Pinmembersweet_dog24-Aug-11 17:03 
GeneralMy vote of 5 PinmemberPhantom1329-Dec-10 11:53 
GeneralMy vote of 5 Pinmemberprasad0228-Dec-10 23:10 

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

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

| Advertise | Privacy | Terms of Use | Mobile
Web01 | 2.8.1411023.1 | Last Updated 28 Dec 2010
Article Copyright 2010 by Abhishek Sur
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid