Click here to Skip to main content
15,896,453 members
Articles / Desktop Programming / WPF

How to create stock charts using the Silverlight Toolkit

Rate me:
Please Sign up or sign in to vote.
4.70/5 (15 votes)
16 Feb 2009CPOL2 min read 142.3K   2.7K   65  
An article on how to create a Candlestick stock chart using the Silverlight Toolkit.
<!--
// (c) Copyright Microsoft Corporation.
// This source is subject to the Microsoft Public License (Ms-PL).
// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details.
// All other rights reserved.
-->

<UserControl x:Class="Microsoft.Windows.Controls.Samples.UpDownBaseSample"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    xmlns:controls="clr-namespace:Microsoft.Windows.Controls;assembly=Microsoft.Windows.Controls"
    xmlns:samples="clr-namespace:Microsoft.Windows.Controls.Samples">
    <StackPanel>
        <!-- UpDownBase&lt;T&gt; -->
        <ContentControl Content="UpDownBase&lt;T&gt;" Style="{StaticResource Header}" />
        <Grid>
            <Grid.RowDefinitions>
                <RowDefinition Height="Auto" />
                <RowDefinition Height="50" />
            </Grid.RowDefinitions>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="200" />
                <ColumnDefinition Width="200" />
                <ColumnDefinition Width="200" />
            </Grid.ColumnDefinitions>

            <TextBlock Grid.Row="0" Grid.Column="0" Style="{StaticResource Description}" Text="NUD : UpDownBase&lt;int&gt;"/>
            <StackPanel x:Name="spNUD" Grid.Row="1" Grid.Column="0" Margin="5"/>
            <TextBlock Grid.Row="0" Grid.Column="1" Style="{StaticResource Description}" Text="TUD : UpDownBase&lt;DateTime&gt;"/>
            <StackPanel x:Name="spTUD" Grid.Row="1" Grid.Column="1" Margin="5"/>
            <TextBlock Grid.Row="0" Grid.Column="2" Style="{StaticResource Description}" Text="SUD : UpDownBase&lt;string&gt;"/>
            <StackPanel x:Name="spSUD" Grid.Row="1" Grid.Column="2" Margin="5"/>
        </Grid>

        <!-- Interactive Usage -->
        <ContentControl Content="Interactive Usage" Style="{StaticResource Header}" />
        <Grid>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="200" />
                <ColumnDefinition Width="10" />
                <ColumnDefinition Width="300" />
            </Grid.ColumnDefinitions>
            <Grid>
                <StackPanel x:Name="sp">
                    <!--
                    <TextBlock x:Name="output" />
                    -->
                </StackPanel>
            </Grid>
            <Grid Grid.Column="2">
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="80" />
                    <ColumnDefinition Width="5" />
                    <ColumnDefinition Width="*" />
                </Grid.ColumnDefinitions>
                <Grid.RowDefinitions>
                    <RowDefinition Height="Auto" />
                    <RowDefinition Height="Auto" />
                    <RowDefinition Height="Auto" />
                    <RowDefinition Height="Auto" />
                    <RowDefinition Height="Auto" />
                    <RowDefinition Height="Auto" />
                    <RowDefinition Height="Auto" />
                    <RowDefinition Height="Auto" />
                </Grid.RowDefinitions>

                <TextBlock Text="Type:" Grid.Row="0" Grid.Column="0" />
                <ListBox x:Name="lbType" Grid.Row="0" Grid.Column="2" HorizontalAlignment="Left">
                    <ListBoxItem Content="NUD : UpDownBase&lt;int&gt;" IsSelected="True"/>
                    <ListBoxItem Content="TUD : UpDownBase&lt;DateTime&gt;" />
                    <ListBoxItem Content="SUD : UpDownBase&lt;string&gt;" />
                </ListBox>

                <TextBlock Text="Value:" Grid.Row="1" Grid.Column="0" />
                <TextBox x:Name="tbValue" Grid.Row="1" Grid.Column="2" />
<!--
                <TextBlock Text="SpinnerStyle:" Grid.Row="2" Grid.Column="0" />
                <TextBox x:Name="tbSpinnerStyle" Grid.Row="2" Grid.Column="2" />

                <CheckBox x:Name="cbIsCyclic" Grid.Row="3" Grid.Column="2" 
                          IsChecked="False"
                          HorizontalAlignment="Left" Content="IsCyclic"/>
-->

                <CheckBox x:Name="cbIsEditable" Grid.Row="4" Grid.Column="2" 
                          IsChecked="True"
                          HorizontalAlignment="Left" Content="IsEditable"/>

                <CheckBox x:Name="cbIsEnabled" Grid.Row="5" Grid.Column="2"
                          IsChecked="True"
                          HorizontalAlignment="Left" Content="IsEnabled" />

                <Button x:Name="btnChange" Grid.Row="6" Grid.Column="2"
                        HorizontalAlignment="Left" Content="Change!"/>
            </Grid>
        </Grid>
        <src:SourceViewer xmlns:src="clr-namespace:Microsoft.Windows.Controls.Samples;assembly=Microsoft.Windows.Controls.Samples.Common" xmlns:sys="clr-namespace:System;assembly=mscorlib">
  <src:SourceFile Path="UpDownBaseSample.xaml">
    <src:SourceFile.Source>
      <sys:String>&lt;!--
// (c) Copyright Microsoft Corporation.
// This source is subject to the Microsoft Public License (Ms-PL).
// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details.
// All other rights reserved.
--&gt;

&lt;UserControl x:Class="Microsoft.Windows.Controls.Samples.UpDownBaseSample"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    xmlns:controls="clr-namespace:Microsoft.Windows.Controls;assembly=Microsoft.Windows.Controls"
    xmlns:samples="clr-namespace:Microsoft.Windows.Controls.Samples"&gt;
    &lt;StackPanel&gt;
        &lt;!-- UpDownBase&amp;lt;T&amp;gt; --&gt;
        &lt;ContentControl Content="UpDownBase&amp;lt;T&amp;gt;" Style="{StaticResource Header}" /&gt;
        &lt;Grid&gt;
            &lt;Grid.RowDefinitions&gt;
                &lt;RowDefinition Height="Auto" /&gt;
                &lt;RowDefinition Height="50" /&gt;
            &lt;/Grid.RowDefinitions&gt;
            &lt;Grid.ColumnDefinitions&gt;
                &lt;ColumnDefinition Width="200" /&gt;
                &lt;ColumnDefinition Width="200" /&gt;
                &lt;ColumnDefinition Width="200" /&gt;
            &lt;/Grid.ColumnDefinitions&gt;

            &lt;TextBlock Grid.Row="0" Grid.Column="0" Style="{StaticResource Description}" Text="NUD : UpDownBase&amp;lt;int&amp;gt;"/&gt;
            &lt;StackPanel x:Name="spNUD" Grid.Row="1" Grid.Column="0" Margin="5"/&gt;
            &lt;TextBlock Grid.Row="0" Grid.Column="1" Style="{StaticResource Description}" Text="TUD : UpDownBase&amp;lt;DateTime&amp;gt;"/&gt;
            &lt;StackPanel x:Name="spTUD" Grid.Row="1" Grid.Column="1" Margin="5"/&gt;
            &lt;TextBlock Grid.Row="0" Grid.Column="2" Style="{StaticResource Description}" Text="SUD : UpDownBase&amp;lt;string&amp;gt;"/&gt;
            &lt;StackPanel x:Name="spSUD" Grid.Row="1" Grid.Column="2" Margin="5"/&gt;
        &lt;/Grid&gt;

        &lt;!-- Interactive Usage --&gt;
        &lt;ContentControl Content="Interactive Usage" Style="{StaticResource Header}" /&gt;
        &lt;Grid&gt;
            &lt;Grid.ColumnDefinitions&gt;
                &lt;ColumnDefinition Width="200" /&gt;
                &lt;ColumnDefinition Width="10" /&gt;
                &lt;ColumnDefinition Width="300" /&gt;
            &lt;/Grid.ColumnDefinitions&gt;
            &lt;Grid&gt;
                &lt;StackPanel x:Name="sp"&gt;
                    &lt;!--
                    &lt;TextBlock x:Name="output" /&gt;
                    --&gt;
                &lt;/StackPanel&gt;
            &lt;/Grid&gt;
            &lt;Grid Grid.Column="2"&gt;
                &lt;Grid.ColumnDefinitions&gt;
                    &lt;ColumnDefinition Width="80" /&gt;
                    &lt;ColumnDefinition Width="5" /&gt;
                    &lt;ColumnDefinition Width="*" /&gt;
                &lt;/Grid.ColumnDefinitions&gt;
                &lt;Grid.RowDefinitions&gt;
                    &lt;RowDefinition Height="Auto" /&gt;
                    &lt;RowDefinition Height="Auto" /&gt;
                    &lt;RowDefinition Height="Auto" /&gt;
                    &lt;RowDefinition Height="Auto" /&gt;
                    &lt;RowDefinition Height="Auto" /&gt;
                    &lt;RowDefinition Height="Auto" /&gt;
                    &lt;RowDefinition Height="Auto" /&gt;
                    &lt;RowDefinition Height="Auto" /&gt;
                &lt;/Grid.RowDefinitions&gt;

                &lt;TextBlock Text="Type:" Grid.Row="0" Grid.Column="0" /&gt;
                &lt;ListBox x:Name="lbType" Grid.Row="0" Grid.Column="2" HorizontalAlignment="Left"&gt;
                    &lt;ListBoxItem Content="NUD : UpDownBase&amp;lt;int&amp;gt;" IsSelected="True"/&gt;
                    &lt;ListBoxItem Content="TUD : UpDownBase&amp;lt;DateTime&amp;gt;" /&gt;
                    &lt;ListBoxItem Content="SUD : UpDownBase&amp;lt;string&amp;gt;" /&gt;
                &lt;/ListBox&gt;

                &lt;TextBlock Text="Value:" Grid.Row="1" Grid.Column="0" /&gt;
                &lt;TextBox x:Name="tbValue" Grid.Row="1" Grid.Column="2" /&gt;
&lt;!--
                &lt;TextBlock Text="SpinnerStyle:" Grid.Row="2" Grid.Column="0" /&gt;
                &lt;TextBox x:Name="tbSpinnerStyle" Grid.Row="2" Grid.Column="2" /&gt;

                &lt;CheckBox x:Name="cbIsCyclic" Grid.Row="3" Grid.Column="2" 
                          IsChecked="False"
                          HorizontalAlignment="Left" Content="IsCyclic"/&gt;
--&gt;

                &lt;CheckBox x:Name="cbIsEditable" Grid.Row="4" Grid.Column="2" 
                          IsChecked="True"
                          HorizontalAlignment="Left" Content="IsEditable"/&gt;

                &lt;CheckBox x:Name="cbIsEnabled" Grid.Row="5" Grid.Column="2"
                          IsChecked="True"
                          HorizontalAlignment="Left" Content="IsEnabled" /&gt;

                &lt;Button x:Name="btnChange" Grid.Row="6" Grid.Column="2"
                        HorizontalAlignment="Left" Content="Change!"/&gt;
            &lt;/Grid&gt;
        &lt;/Grid&gt;
    &lt;/StackPanel&gt;
&lt;/UserControl&gt;
</sys:String>
    </src:SourceFile.Source>
  </src:SourceFile>
  <src:SourceFile Path="UpDownBaseSample.xaml.cs">
    <src:SourceFile.Source>
      <sys:String>// (c) Copyright Microsoft Corporation.
// This source is subject to the Microsoft Public License (Ms-PL).
// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details.
// All other rights reserved.

using System;
using System.Diagnostics;
using System.Globalization;
using System.Windows;
using System.Windows.Controls;
using System.ComponentModel;

namespace Microsoft.Windows.Controls.Samples
{
    /// &lt;summary&gt;
    /// UpDownBase sample page.
    /// &lt;/summary&gt;
    [Sample("UpDownBase", DifficultyLevel.Basic)]
    [Category("UpDownBase")]
    public partial class UpDownBaseSample : UserControl
    {
        /// &lt;summary&gt;
        /// Initializes a new instance of the UpDownBaseSample class.
        /// &lt;/summary&gt;
        public UpDownBaseSample()
        {
            InitializeComponent();

            // create instance of the test non generic sub classes
            spNUD.Children.Add(new NUD());
            spTUD.Children.Add(new TUD());
            spSUD.Children.Add(new SUD());

            btnChange.Click += (s, a) =&gt; CreateUDB();
        }

        /// &lt;summary&gt;
        /// This function does following three things:
        /// 1. Remove existing interactive UpDownBase control if it exists;
        /// 2. Create a new instance of selected UpDownBase subclass type;
        /// 3. Add the newly created UpDownBase control to visual tree.
        /// &lt;/summary&gt;
        private void CreateUDB()
        {
            if (udb != null)
            {
                Debug.Assert(udb == sp.Children[0], "udb should be sp's only child!");
                sp.Children.Remove(udb);
            }

            switch (lbType.SelectedIndex)
            {
                case 0:
                    udb = new NUD();
                    ConfigUDB((UpDownBase&lt;int&gt;)udb);
                    break;
                case 1:
                    udb = new TUD();
                    ConfigUDB((UpDownBase&lt;DateTime&gt;)udb);
                    break;
                case 2:
                    udb = new SUD();
                    ConfigUDB((UpDownBase&lt;string&gt;)udb);
                    break;
            }

            sp.Children.Add(udb);
        }

        /// &lt;summary&gt;
        /// Configure a UpDownBase&amp;lt;T&amp;gt; control.
        /// &lt;/summary&gt;
        /// &lt;typeparam name="T"&gt;Type parameter.&lt;/typeparam&gt;
        /// &lt;param name="udbt"&gt;UpDownBase&amp;lt;T&amp;gt; control.&lt;/param&gt;
        private void ConfigUDB&lt;T&gt;(UpDownBase&lt;T&gt; udbt)
        {
            Debug.Assert(udbt != null, "udb should not be null!");

            udbt.Value = ParseUtility.ReadValue(tbValue, udbt.Value);
            ////udbt.IsCyclic = (bool)cbIsCyclic.IsChecked;
            udbt.IsEditable = (bool)cbIsEditable.IsChecked;
            udbt.IsEnabled = (bool)cbIsEnabled.IsChecked;
        }

        /// &lt;summary&gt;
        /// The interactive UpDownBase control.
        /// &lt;/summary&gt;
        private UpDownBase udb;

        #region Non-generic test classes inheriting from UpDownBase&lt;T&gt;
        /// &lt;summary&gt;
        /// Non-generic test class inheriting from UpDownBase&amp;lt;int&amp;gt;.
        /// &lt;/summary&gt;
        /// &lt;remarks&gt;
        /// Value is of type int, in between 0 and 10.
        /// IsCyclic decides whether Value will wrap around 0 and 10.
        /// OnIncrement and OnDecrement change Value by 1.
        /// &lt;/remarks&gt;
        [TemplateVisualState(Name = "Normal", GroupName = "CommonStates")]
        [TemplateVisualState(Name = "MouseOver", GroupName = "CommonStates")]
        [TemplateVisualState(Name = "Pressed", GroupName = "CommonStates")]
        [TemplateVisualState(Name = "Disabled", GroupName = "CommonStates")]

        [TemplateVisualState(Name = "Focused", GroupName = "FocusStates")]
        [TemplateVisualState(Name = "Unfocused", GroupName = "FocusStates")]

        [TemplatePart(Name = "TextName", Type = typeof(TextBox))]
        [TemplatePart(Name = "SpinnerName", Type = typeof(Spinner))]
        [StyleTypedProperty(Property = "SpinnerStyle", StyleTargetType = typeof(Spinner))]
        internal class NUD : UpDownBase&lt;int&gt;
        {
            /// &lt;summary&gt;
            /// Private variable to replace IsCyclic property for now removed from UpDownBase&amp;lt;T&amp;gt;.
            /// &lt;/summary&gt;
            private bool IsCyclic = true;

            /// &lt;summary&gt;
            /// Internal constructor.
            /// &lt;/summary&gt;
            internal NUD()
                : base()
            {
                Value = 0;
            }

            /// &lt;summary&gt;
            /// Called by OnSpin when the spin direction is SpinDirection.Increase.
            /// &lt;/summary&gt;
            protected override void OnIncrement()
            {
                Value = IsCyclic ? (Value + 1) % 10 : Math.Min(10, Value + 1);
            }

            /// &lt;summary&gt;
            /// Called by OnSpin when the spin direction is SpinDirection.Increase.
            /// &lt;/summary&gt;
            protected override void OnDecrement()
            {
                Value = IsCyclic ? (Value - 1) % 10 : Math.Max(0, Value - 1);
            }

            /// &lt;summary&gt;
            /// Called by ApplyValue to parse user input.
            /// &lt;/summary&gt;
            /// &lt;param name="text"&gt;User input.&lt;/param&gt;
            /// &lt;returns&gt;Value parsed from user input.&lt;/returns&gt;
            protected override int ParseValue(string text)
            {
                return int.Parse(text, CultureInfo.CurrentCulture);
            }

            /// &lt;summary&gt;
            /// Called to render Value for Text template part to display.
            /// &lt;/summary&gt;
            /// &lt;returns&gt;Formatted Value.&lt;/returns&gt;
            protected override string FormatValue()
            {
                return Value.ToString(CultureInfo.CurrentCulture);
            }
        }

        /// &lt;summary&gt;
        /// Non-generic test class inheriting from UpDownBase&amp;lt;DateTime&amp;gt;.
        /// &lt;/summary&gt;
        /// &lt;remarks&gt;
        /// Value is of type DateTime. 
        /// IsCyclic decides whether Value will wrap around DateTime.MinValue and DateTime.MaxValue.
        /// OnIncrement and OnDecrement change Value by one hour.
        /// &lt;/remarks&gt;
        [TemplateVisualState(Name = "Normal", GroupName = "CommonStates")]
        [TemplateVisualState(Name = "MouseOver", GroupName = "CommonStates")]
        [TemplateVisualState(Name = "Pressed", GroupName = "CommonStates")]
        [TemplateVisualState(Name = "Disabled", GroupName = "CommonStates")]

        [TemplateVisualState(Name = "Focused", GroupName = "FocusStates")]
        [TemplateVisualState(Name = "Unfocused", GroupName = "FocusStates")]

        [TemplatePart(Name = "TextName", Type = typeof(TextBox))]
        [TemplatePart(Name = "SpinnerName", Type = typeof(Spinner))]
        [StyleTypedProperty(Property = "SpinnerStyle", StyleTargetType = typeof(Spinner))]
        internal class TUD : UpDownBase&lt;DateTime&gt;
        {
            /// &lt;summary&gt;
            /// Private variable to replace IsCyclic property for now removed from UpDownBase&amp;lt;T&amp;gt;.
            /// &lt;/summary&gt;
            private bool IsCyclic = true;

            /// &lt;summary&gt;
            /// Internal constructor.
            /// &lt;/summary&gt;
            internal TUD()
                : base()
            {
                Value = DateTime.Now;
            }

            /// &lt;summary&gt;
            /// Called by OnSpin when the spin direction is SpinDirection.Increase.
            /// &lt;/summary&gt;
            protected override void OnIncrement()
            {
                try
                {
                    Value = Value.AddHours(1);
                }
                catch (ArgumentOutOfRangeException)
                {
                    Value = IsCyclic ? DateTime.MinValue : DateTime.MaxValue;
                }
            }

            /// &lt;summary&gt;
            /// Called by OnSpin when the spin direction is SpinDirection.Increase.
            /// &lt;/summary&gt;
            protected override void OnDecrement()
            {
                try
                {
                    Value = Value.AddHours(-1);
                }
                catch (ArgumentOutOfRangeException)
                {
                    Value = IsCyclic ? DateTime.MaxValue : DateTime.MinValue;
                }
            }

            /// &lt;summary&gt;
            /// Called by ApplyValue to parse user input.
            /// &lt;/summary&gt;
            /// &lt;param name="text"&gt;User input.&lt;/param&gt;
            /// &lt;returns&gt;Value parsed from user input.&lt;/returns&gt;
            protected override DateTime ParseValue(string text)
            {
                return DateTime.Parse(text, CultureInfo.CurrentCulture);
            }

            /// &lt;summary&gt;
            /// Called to render Value for Text template part to display.
            /// &lt;/summary&gt;
            /// &lt;returns&gt;Formatted Value.&lt;/returns&gt;
            protected override string FormatValue()
            {
                return Value.ToShortTimeString();
            }
        }

        /// &lt;summary&gt;
        /// Non-generic class for UpDownBase&amp;lt;T&amp;gt; testing only.
        /// &lt;/summary&gt;
        /// &lt;remarks&gt;
        /// Value is of type string.
        /// OnIncrement and OnDecrement change string length by 1 character.
        /// IsCyclic decides whether Value length will cycle through 0 to 10.
        /// &lt;/remarks&gt;
        [TemplateVisualState(Name = "Normal", GroupName = "CommonStates")]
        [TemplateVisualState(Name = "MouseOver", GroupName = "CommonStates")]
        [TemplateVisualState(Name = "Pressed", GroupName = "CommonStates")]
        [TemplateVisualState(Name = "Disabled", GroupName = "CommonStates")]

        [TemplateVisualState(Name = "Focused", GroupName = "FocusStates")]
        [TemplateVisualState(Name = "Unfocused", GroupName = "FocusStates")]

        [TemplatePart(Name = "TextName", Type = typeof(TextBox))]
        [TemplatePart(Name = "SpinnerName", Type = typeof(Spinner))]
        [StyleTypedProperty(Property = "SpinnerStyle", StyleTargetType = typeof(Spinner))]
        internal class SUD : UpDownBase&lt;string&gt;
        {
            /// &lt;summary&gt;
            /// Private variable to replace IsCyclic property for now removed from UpDownBase&amp;lt;T&amp;gt;.
            /// &lt;/summary&gt;
            private bool IsCyclic = true;

            /// &lt;summary&gt;
            /// Internal constructor.
            /// &lt;/summary&gt;
            internal SUD()
                : base()
            {
                Value = "0";
            }

            /// &lt;summary&gt;
            /// Called by OnSpin when the spin direction is SpinDirection.Increase.
            /// &lt;/summary&gt;
            protected override void OnIncrement()
            {
                string value = string.IsNullOrEmpty(Value) ? "0" : Value;
                if (value.Length &gt;= 10)
                {
                    if (IsCyclic)
                    {
                        value = "0";
                    }
                }
                else
                {
                    value = value + value.Length.ToString(CultureInfo.CurrentCulture);
                }
                Value = value;
            }

            /// &lt;summary&gt;
            /// Called by OnSpin when the spin direction is SpinDirection.Increase.
            /// &lt;/summary&gt;
            protected override void OnDecrement()
            {
                string value = string.IsNullOrEmpty(Value) ? "0" : Value;
                if (value.Length &lt;= 1)
                {
                    if (IsCyclic)
                    {
                        value = "0123456789";
                    }
                }
                else
                {
                    value = value.Substring(0, value.Length - 1);
                }
                Value = value;
            }

            /// &lt;summary&gt;
            /// Called by ApplyValue to parse user input.
            /// &lt;/summary&gt;
            /// &lt;param name="text"&gt;User input.&lt;/param&gt;
            /// &lt;returns&gt;Value parsed from user input.&lt;/returns&gt;
            protected override string ParseValue(string text)
            {
                if (string.IsNullOrEmpty(text))
                {
                    text = "0";
                }

                if (text.Length &gt; 10)
                {
                    text = text.Substring(0, 10);
                }

                return text;
            }

            /// &lt;summary&gt;
            /// Called to render Value for Text template part to display.
            /// &lt;/summary&gt;
            /// &lt;returns&gt;Formatted Value.&lt;/returns&gt;
            protected override string FormatValue()
            {
                return string.IsNullOrEmpty(Value) ? "0" : Value;
            }
        }
        #endregion 
    }
}
</sys:String>
    </src:SourceFile.Source>
  </src:SourceFile>
</src:SourceViewer>
    </StackPanel>
</UserControl>

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

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


Written By
South Africa South Africa
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions