TickBar.cs source code in C# .NET

Source code for the .NET framework in C#

                        

Code:

/ 4.0 / 4.0 / untmp / DEVDIV_TFS / Dev10 / Releases / RTMRel / wpf / src / Framework / System / Windows / Controls / Primitives / TickBar.cs / 1305600 / TickBar.cs

                            //---------------------------------------------------------------------------- 
//
// Copyright (C) Microsoft Corporation.  All rights reserved.
//
//--------------------------------------------------------------------------- 

using System; 
using System.ComponentModel; 
using System.Diagnostics;
using System.Globalization; 
using System.Collections;
using MS.Internal;
using System.Windows.Threading;
 
using System.Windows;
using System.Windows.Data; 
 
using System.Windows.Input;
using System.Windows.Media; 
using System.Windows.Shapes;
using MS.Internal.KnownBoxes;

using MS.Win32; 

 
// For typeconverter 
using System.ComponentModel.Design.Serialization;
using System.Reflection; 

namespace System.Windows.Controls.Primitives
{
    ///  
    /// Enum which describes how to position the TickBar.
    ///  
    public enum TickBarPlacement 
    {
        ///  
        /// Position this tick at the left of target element.
        /// 
        Left,
 
        /// 
        /// Position this tick at the top of target element. 
        ///  
        Top,
 
        /// 
        /// Position this tick at the right of target element.
        /// 
        Right, 

        ///  
        /// Position this tick at the bottom of target element. 
        /// 
        Bottom, 

        // NOTE: if you add or remove any values in this enum, be sure to update TickBar.IsValidTickBarPlacement()
    };
 
    /// 
    /// TickBar is an element that use for drawing Slider's Ticks. 
    ///  
    [Localizability(LocalizationCategory.None, Readability = Readability.Unreadable)] // cannot be read & localized as string
    public class TickBar : FrameworkElement 
    {
        //-------------------------------------------------------------------
        //
        //  Constructors 
        //
        //------------------------------------------------------------------- 
        #region Constructors 

        static TickBar() 
        {
            SnapsToDevicePixelsProperty.OverrideMetadata(typeof(TickBar), new FrameworkPropertyMetadata(true));
        }
 
        /// 
        ///     Default constructor for TickBar class 
        ///  
        /// 
        ///     Automatic determination of current Dispatcher. Use alternative constructor 
        ///     that accepts a Dispatcher for best performance.
        /// 
        public TickBar() : base()
        { 
        }
 
        #endregion 

        ///  
        /// Fill property
        /// 
        public static readonly DependencyProperty FillProperty
            = DependencyProperty.Register( 
                "Fill",
                typeof(Brush), 
                typeof(TickBar), 
                new FrameworkPropertyMetadata(
                    (Brush)null, 
                    FrameworkPropertyMetadataOptions.AffectsRender,
                    null,
                    null)
                ); 

        ///  
        /// Fill property 
        /// 
        public Brush Fill 
        {
            get
            {
                return (Brush)GetValue(FillProperty); 
            }
            set 
            { 
                SetValue(FillProperty, value);
            } 
        }

        /// 
        ///     The DependencyProperty for the  property. 
        /// 
        public static readonly DependencyProperty MinimumProperty = 
                RangeBase.MinimumProperty.AddOwner( 
                        typeof(TickBar),
                        new FrameworkPropertyMetadata( 
                                0.0,
                                FrameworkPropertyMetadataOptions.AffectsRender));

        ///  
        ///     Logical position where the Minimum Tick will be drawn
        ///  
        [Bindable(true), Category("Appearance")] 
        public double Minimum
        { 
            get { return (double) GetValue(MinimumProperty); }
            set { SetValue(MinimumProperty, value); }
        }
 
        /// 
        ///     The DependencyProperty for the   property. 
        ///  
        public static readonly DependencyProperty MaximumProperty =
                RangeBase.MaximumProperty.AddOwner( 
                        typeof(TickBar),
                        new FrameworkPropertyMetadata(
                                100.0,
                                FrameworkPropertyMetadataOptions.AffectsRender)); 

        ///  
        ///     Logical position where the Maximum Tick will be drawn 
        /// 
        [Bindable(true), Category("Appearance")] 
        public double Maximum
        {
            get { return (double) GetValue(MaximumProperty); }
            set { SetValue(MaximumProperty, value); } 
        }
 
        ///  
        ///     The DependencyProperty for the   property.
        ///  
        public static readonly DependencyProperty SelectionStartProperty =
                Slider.SelectionStartProperty.AddOwner(
                        typeof(TickBar),
                        new FrameworkPropertyMetadata( 
                                -1.0d,
                                FrameworkPropertyMetadataOptions.AffectsRender)); 
 
        /// 
        ///     Logical position where the SelectionStart Tick will be drawn 
        /// 
        [Bindable(true), Category("Appearance")]
        public double SelectionStart
        { 
            get { return (double) GetValue(SelectionStartProperty); }
            set { SetValue(SelectionStartProperty, value); } 
        } 

        ///  
        ///     The DependencyProperty for the   property.
        /// 
        public static readonly DependencyProperty SelectionEndProperty =
                Slider.SelectionEndProperty.AddOwner( 
                        typeof(TickBar),
                        new FrameworkPropertyMetadata( 
                                -1.0d, 
                                FrameworkPropertyMetadataOptions.AffectsRender));
 
        /// 
        ///     Logical position where the SelectionEnd Tick will be drawn
        /// 
        [Bindable(true), Category("Appearance")] 
        public double SelectionEnd
        { 
            get { return (double) GetValue(SelectionEndProperty); } 
            set { SetValue(SelectionEndProperty, value); }
        } 

        /// 
        ///     The DependencyProperty for the   property.
        ///  
        public static readonly DependencyProperty IsSelectionRangeEnabledProperty =
                Slider.IsSelectionRangeEnabledProperty.AddOwner( 
                        typeof(TickBar), 
                        new FrameworkPropertyMetadata(
                                BooleanBoxes.FalseBox, 
                                FrameworkPropertyMetadataOptions.AffectsRender));

        /// 
        ///     IsSelectionRangeEnabled specifies whether to draw SelectionStart Tick and SelectionEnd Tick or not. 
        /// 
        [Bindable(true), Category("Appearance")] 
        public bool IsSelectionRangeEnabled 
        {
            get { return (bool) GetValue(IsSelectionRangeEnabledProperty); } 
            set { SetValue(IsSelectionRangeEnabledProperty, BooleanBoxes.Box(value)); }
        }

        ///  
        /// DependencyProperty for  property.
        ///  
        public static readonly DependencyProperty TickFrequencyProperty = 
                Slider.TickFrequencyProperty.AddOwner(
                        typeof(TickBar), 
                        new FrameworkPropertyMetadata(
                                1.0,
                                FrameworkPropertyMetadataOptions.AffectsRender));
 
        /// 
        /// TickFrequency property defines how the tick will be drawn. 
        ///  
        [Bindable(true), Category("Appearance")]
        public double TickFrequency 
        {
            get { return (double) GetValue(TickFrequencyProperty); }
            set { SetValue(TickFrequencyProperty, value); }
        } 

        ///  
        /// DependencyProperty for  property. 
        /// 
        public static readonly DependencyProperty TicksProperty = 
                Slider.TicksProperty.AddOwner(
                        typeof(TickBar),
                        new FrameworkPropertyMetadata(
                                new FreezableDefaultValueFactory(DoubleCollection.Empty), 
                                FrameworkPropertyMetadataOptions.AffectsRender));
 
        ///  
        /// The Ticks property contains collection of value of type Double which
        /// are the logical positions use to draw the ticks. 
        /// The property value is a .
        /// 
        [Bindable(true), Category("Appearance")]
        public DoubleCollection Ticks 
        {
            get { return (DoubleCollection) GetValue(TicksProperty); } 
            set { SetValue(TicksProperty, value); } 
        }
 
        /// 
        /// DependencyProperty for IsDirectionReversed property.
        /// 
        public static readonly DependencyProperty IsDirectionReversedProperty = 
                Slider.IsDirectionReversedProperty.AddOwner(
                        typeof(TickBar), 
                        new FrameworkPropertyMetadata( 
                                BooleanBoxes.FalseBox,
                                FrameworkPropertyMetadataOptions.AffectsRender)); 

        /// 
        /// The IsDirectionReversed property defines the direction of value incrementation.
        /// By default, if Tick's orientation is Horizontal, ticks will be drawn from left to right. 
        /// (And, bottom to top for Vertical orientation).
        /// If IsDirectionReversed is 'true' the direction of the drawing will be in opposite direction. 
        /// Ticks property contains collection of value of type Double which 
        /// 
        [Bindable(true), Category("Appearance")] 
        public bool IsDirectionReversed
        {
            get { return (bool) GetValue(IsDirectionReversedProperty); }
            set { SetValue(IsDirectionReversedProperty, BooleanBoxes.Box(value)); } 
        }
 
        ///  
        /// DependencyProperty for  property.
        ///  
        public static readonly DependencyProperty PlacementProperty =
                DependencyProperty.Register(
                        "Placement",
                        typeof(TickBarPlacement), 
                        typeof(TickBar),
                        new FrameworkPropertyMetadata( 
                                TickBarPlacement.Top, 
                                FrameworkPropertyMetadataOptions.AffectsRender),
                        new ValidateValueCallback(IsValidTickBarPlacement)); 

        /// 
        /// Placement property specified how the Tick will be placed.
        /// This property affects the way ticks are drawn. 
        /// This property has type of .
        ///  
        [Bindable(true), Category("Appearance")] 
        public TickBarPlacement Placement
        { 
            get { return (TickBarPlacement) GetValue(PlacementProperty); }
            set { SetValue(PlacementProperty, value); }
        }
 
        private static bool IsValidTickBarPlacement(object o)
        { 
            TickBarPlacement placement = (TickBarPlacement)o; 
            return placement == TickBarPlacement.Left ||
                   placement == TickBarPlacement.Top || 
                   placement == TickBarPlacement.Right ||
                   placement == TickBarPlacement.Bottom;
        }
 
        /// 
        /// DependencyProperty for ReservedSpace property. 
        ///  
        public static readonly DependencyProperty ReservedSpaceProperty =
                DependencyProperty.Register( 
                        "ReservedSpace",
                        typeof(double),
                        typeof(TickBar),
                        new FrameworkPropertyMetadata( 
                                0d,
                                FrameworkPropertyMetadataOptions.AffectsRender)); 
 
        /// 
        /// TickBar will use ReservedSpaceProperty for left and right spacing (for horizontal orientation) or 
        /// tob and bottom spacing (for vertical orienation).
        /// The space on both sides of TickBar is half of specified ReservedSpace.
        /// This property has type of .
        ///  
        [Bindable(true), Category("Appearance")]
        public double ReservedSpace 
        { 
            get { return (double) GetValue(ReservedSpaceProperty); }
            set { SetValue(ReservedSpaceProperty, value); } 
        }

        /// 
        /// Draw ticks. 
        /// Ticks can be draw in 8 diffrent ways depends on Placment property and IsDirectionReversed property.
        /// 
        /// This function also draw selection-tick(s) if IsSelectionRangeEnabled is 'true' and 
        /// SelectionStart and SelectionEnd are valid.
        /// 
        /// The primary ticks (for Mininum and Maximum value) height will be 100% of TickBar's render size (use Width or Height
        /// depends on Placement property).
        ///
        /// The secondary ticks (all other ticks, including selection-tics) height will be 75% of TickBar's render size. 
        ///
        /// Brush that use to fill ticks is specified by Shape.Fill property. 
        /// 
        /// Pen that use to draw ticks is specified by Shape.Pen property.
        ///  
        protected override void OnRender(DrawingContext dc)
        {
            Size size = new Size(ActualWidth,ActualHeight);
            double range = Maximum - Minimum; 
            double tickLen = 0.0d;  // Height for Primary Tick (for Mininum and Maximum value)
            double tickLen2;        // Height for Secondary Tick 
            double logicalToPhysical = 1.0; 
            double progression = 1.0d;
            Point startPoint = new Point(0d,0d); 
            Point endPoint = new Point(0d, 0d);

            // Take Thumb size in to account
            double halfReservedSpace = ReservedSpace * 0.5; 

            switch(Placement) 
            { 
                case TickBarPlacement.Top:
                    if (DoubleUtil.GreaterThanOrClose(ReservedSpace, size.Width)) 
                    {
                        return;
                    }
                    size.Width -= ReservedSpace; 
                    tickLen = - size.Height;
                    startPoint = new Point(halfReservedSpace, size.Height); 
                    endPoint = new Point(halfReservedSpace + size.Width, size.Height); 
                    logicalToPhysical = size.Width / range;
                    progression = 1; 
                    break;

                case TickBarPlacement.Bottom:
                    if (DoubleUtil.GreaterThanOrClose(ReservedSpace, size.Width)) 
                    {
                        return; 
                    } 
                    size.Width -= ReservedSpace;
                    tickLen = size.Height; 
                    startPoint = new Point(halfReservedSpace, 0d);
                    endPoint = new Point(halfReservedSpace + size.Width, 0d);
                    logicalToPhysical = size.Width / range;
                    progression = 1; 
                    break;
 
                case TickBarPlacement.Left: 
                    if (DoubleUtil.GreaterThanOrClose(ReservedSpace, size.Height))
                    { 
                        return;
                    }
                    size.Height -= ReservedSpace;
                    tickLen = -size.Width; 
                    startPoint = new Point(size.Width, size.Height + halfReservedSpace);
                    endPoint = new Point(size.Width, halfReservedSpace); 
                    logicalToPhysical = size.Height / range * -1; 
                    progression = -1;
                    break; 

                case TickBarPlacement.Right:
                    if (DoubleUtil.GreaterThanOrClose(ReservedSpace, size.Height))
                    { 
                        return;
                    } 
                    size.Height -= ReservedSpace; 
                    tickLen = size.Width;
                    startPoint = new Point(0d, size.Height + halfReservedSpace); 
                    endPoint = new Point(0d, halfReservedSpace);
                    logicalToPhysical = size.Height / range * -1;
                    progression = -1;
                    break; 
            };
 
            tickLen2 = tickLen * 0.75; 

            // Invert direciton of the ticks 
            if (IsDirectionReversed)
            {
                progression = -progression;
                logicalToPhysical *= -1; 

                // swap startPoint & endPoint 
                Point pt = startPoint; 
                startPoint = endPoint;
                endPoint = pt; 
            }

            Pen pen = new Pen(Fill, 1.0d);
 
            bool snapsToDevicePixels = SnapsToDevicePixels;
            DoubleCollection xLines = snapsToDevicePixels ? new DoubleCollection() : null; 
            DoubleCollection yLines = snapsToDevicePixels ? new DoubleCollection() : null; 

            // Is it Vertical? 
            if ((Placement == TickBarPlacement.Left) || (Placement == TickBarPlacement.Right))
            {
                // Reduce tick interval if it is more than would be visible on the screen
                double interval = TickFrequency; 
                if (interval > 0.0)
                { 
                    double minInterval = (Maximum - Minimum) / size.Height; 
                    if (interval < minInterval)
                    { 
                        interval = minInterval;
                    }
                }
 
                // Draw Min & Max tick
                dc.DrawLine(pen, startPoint, new Point(startPoint.X + tickLen, startPoint.Y)); 
                dc.DrawLine(pen, new Point(startPoint.X, endPoint.Y), 
                                 new Point(startPoint.X + tickLen, endPoint.Y));
 
                if (snapsToDevicePixels)
                {
                    xLines.Add(startPoint.X);
                    yLines.Add(startPoint.Y - 0.5); 
                    xLines.Add(startPoint.X + tickLen);
                    yLines.Add(endPoint.Y - 0.5); 
                    xLines.Add(startPoint.X + tickLen2); 
                }
 
                // This property is rarely set so let's try to avoid the GetValue
                // caching of the mutable default value
                DoubleCollection ticks = null;
                bool hasModifiers; 
                if (GetValueSource(TicksProperty, null, out hasModifiers)
                    != BaseValueSourceInternal.Default || hasModifiers) 
                { 
                    ticks = Ticks;
                } 

                // Draw ticks using specified Ticks collection
                if ((ticks != null) && (ticks.Count > 0))
                { 
                    for (int i = 0; i < ticks.Count; i++)
                    { 
                        if (DoubleUtil.LessThanOrClose(ticks[i],Minimum) || DoubleUtil.GreaterThanOrClose(ticks[i],Maximum)) 
                        {
                            continue; 
                        }

                        double adjustedTick = ticks[i] - Minimum;
 
                        double y = adjustedTick * logicalToPhysical + startPoint.Y;
                        dc.DrawLine(pen, 
                            new Point(startPoint.X, y), 
                            new Point(startPoint.X + tickLen2, y));
 
                        if (snapsToDevicePixels)
                        {
                            yLines.Add(y - 0.5);
                        } 
                    }
                } 
                // Draw ticks using specified TickFrequency 
                else if (interval > 0.0)
                { 
                    for (double i = interval; i < range; i += interval)
                    {
                        double y = i * logicalToPhysical + startPoint.Y;
 
                        dc.DrawLine(pen,
                            new Point(startPoint.X, y), 
                            new Point(startPoint.X + tickLen2, y)); 

                        if (snapsToDevicePixels) 
                        {
                            yLines.Add(y - 0.5);
                        }
                    } 
                }
 
                // Draw Selection Ticks 
                if (IsSelectionRangeEnabled)
                { 
                    double y0 = (SelectionStart - Minimum) * logicalToPhysical + startPoint.Y;
                    Point pt0 = new Point(startPoint.X, y0);
                    Point pt1 = new Point(startPoint.X + tickLen2, y0);
                    Point pt2 = new Point(startPoint.X + tickLen2, y0 + Math.Abs(tickLen2) * progression); 

                    PathSegment[] segments = new PathSegment[] { 
                        new LineSegment(pt2, true), 
                        new LineSegment(pt0, true),
                    }; 
                    PathGeometry geo = new PathGeometry(new PathFigure[] { new PathFigure(pt1, segments, true) });

                    dc.DrawGeometry(Fill, pen, geo);
 
                    y0 = (SelectionEnd - Minimum) * logicalToPhysical + startPoint.Y;
                    pt0 = new Point(startPoint.X, y0); 
                    pt1 = new Point(startPoint.X + tickLen2, y0); 
                    pt2 = new Point(startPoint.X + tickLen2, y0 - Math.Abs(tickLen2) * progression);
 
                    segments = new PathSegment[] {
                        new LineSegment(pt2, true),
                        new LineSegment(pt0, true),
                    }; 
                    geo = new PathGeometry(new PathFigure[] { new PathFigure(pt1, segments, true) });
                    dc.DrawGeometry(Fill, pen, geo); 
                } 
            }
            else  // Placement == Top || Placement == Bottom 
            {
                // Reduce tick interval if it is more than would be visible on the screen
                double interval = TickFrequency;
                if (interval > 0.0) 
                {
                    double minInterval = (Maximum - Minimum) / size.Width; 
                    if (interval < minInterval) 
                    {
                        interval = minInterval; 
                    }
                }

                // Draw Min & Max tick 
                dc.DrawLine(pen, startPoint, new Point(startPoint.X, startPoint.Y + tickLen));
                dc.DrawLine(pen, new Point(endPoint.X, startPoint.Y), 
                                 new Point(endPoint.X, startPoint.Y + tickLen)); 

                if (snapsToDevicePixels) 
                {
                    xLines.Add(startPoint.X - 0.5);
                    yLines.Add(startPoint.Y);
                    xLines.Add(startPoint.X - 0.5); 
                    yLines.Add(endPoint.Y + tickLen);
                    yLines.Add(endPoint.Y + tickLen2); 
                } 

                // This property is rarely set so let's try to avoid the GetValue 
                // caching of the mutable default value
                DoubleCollection ticks = null;
                bool hasModifiers;
                if (GetValueSource(TicksProperty, null, out hasModifiers) 
                    != BaseValueSourceInternal.Default || hasModifiers)
                { 
                    ticks = Ticks; 
                }
 
                // Draw ticks using specified Ticks collection
                if ((ticks != null) && (ticks.Count > 0))
                {
                    for (int i = 0; i < ticks.Count; i++) 
                    {
                        if (DoubleUtil.LessThanOrClose(ticks[i],Minimum) || DoubleUtil.GreaterThanOrClose(ticks[i],Maximum)) 
                        { 
                            continue;
                        } 
                        double adjustedTick = ticks[i] - Minimum;

                        double x = adjustedTick * logicalToPhysical + startPoint.X;
                        dc.DrawLine(pen, 
                            new Point(x, startPoint.Y),
                            new Point(x, startPoint.Y + tickLen2)); 
 
                        if (snapsToDevicePixels)
                        { 
                            xLines.Add(x - 0.5);
                        }
                    }
                } 
                // Draw ticks using specified TickFrequency
                else if (interval > 0.0) 
                { 
                    for (double i = interval; i < range; i += interval)
                    { 
                        double x = i * logicalToPhysical + startPoint.X;
                        dc.DrawLine(pen,
                            new Point(x, startPoint.Y),
                            new Point(x, startPoint.Y + tickLen2)); 

                        if (snapsToDevicePixels) 
                        { 
                            xLines.Add(x - 0.5);
                        } 
                    }
                }

                // Draw Selection Ticks 
                if (IsSelectionRangeEnabled)
                { 
                    double x0 = (SelectionStart - Minimum) * logicalToPhysical + startPoint.X; 
                    Point pt0 = new Point(x0, startPoint.Y);
                    Point pt1 = new Point(x0, startPoint.Y + tickLen2); 
                    Point pt2 = new Point(x0 + Math.Abs(tickLen2) * progression, startPoint.Y + tickLen2);

                    PathSegment[] segments = new PathSegment[] {
                        new LineSegment(pt2, true), 
                        new LineSegment(pt0, true),
                    }; 
                    PathGeometry geo = new PathGeometry(new PathFigure[] { new PathFigure(pt1, segments, true) }); 

                    dc.DrawGeometry(Fill, pen, geo); 

                    x0 = (SelectionEnd - Minimum) * logicalToPhysical + startPoint.X;
                    pt0 = new Point(x0, startPoint.Y);
                    pt1 = new Point(x0, startPoint.Y + tickLen2); 
                    pt2 = new Point(x0 - Math.Abs(tickLen2) * progression, startPoint.Y + tickLen2);
 
                    segments = new PathSegment[] { 
                        new LineSegment(pt2, true),
                        new LineSegment(pt0, true), 
                    };
                    geo = new PathGeometry(new PathFigure[] { new PathFigure(pt1, segments, true) });
                    dc.DrawGeometry(Fill, pen, geo);
                } 
            }
 
            if (snapsToDevicePixels) 
            {
                xLines.Add(ActualWidth); 
                yLines.Add(ActualHeight);
                VisualXSnappingGuidelines = xLines;
                VisualYSnappingGuidelines = yLines;
            } 
            return;
        } 
 
        private void BindToTemplatedParent(DependencyProperty target, DependencyProperty source)
        { 
            if (!HasNonDefaultValue(target))
            {
                Binding binding = new Binding();
                binding.RelativeSource = RelativeSource.TemplatedParent; 
                binding.Path = new PropertyPath(source);
                SetBinding(target, binding); 
            } 
        }
 
        /// 
        /// TickBar sets bindings on its properties to its TemplatedParent if the
        /// properties are not already set.
        ///  
        internal override void OnPreApplyTemplate()
        { 
            base.OnPreApplyTemplate(); 

            Slider parent = TemplatedParent as Slider; 
            if (parent != null)
            {
                BindToTemplatedParent(TicksProperty, Slider.TicksProperty);
                BindToTemplatedParent(TickFrequencyProperty, Slider.TickFrequencyProperty); 
                BindToTemplatedParent(IsSelectionRangeEnabledProperty, Slider.IsSelectionRangeEnabledProperty);
                BindToTemplatedParent(SelectionStartProperty, Slider.SelectionStartProperty); 
                BindToTemplatedParent(SelectionEndProperty, Slider.SelectionEndProperty); 
                BindToTemplatedParent(MinimumProperty, Slider.MinimumProperty);
                BindToTemplatedParent(MaximumProperty, Slider.MaximumProperty); 
                BindToTemplatedParent(IsDirectionReversedProperty, Slider.IsDirectionReversedProperty);

                if (!HasNonDefaultValue(ReservedSpaceProperty) && parent.Track != null)
                { 
                    Binding binding = new Binding();
                    binding.Source = parent.Track.Thumb; 
 
                    if (parent.Orientation == Orientation.Horizontal)
                    { 
                        binding.Path = new PropertyPath(Thumb.ActualWidthProperty);
                    }
                    else
                    { 
                        binding.Path = new PropertyPath(Thumb.ActualHeightProperty);
                    } 
 
                    SetBinding(ReservedSpaceProperty, binding);
                } 
            }
        }
    }
} 

// File provided for Reference Use Only by Microsoft Corporation (c) 2007.
// Copyright (c) Microsoft Corporation. All rights reserved.
                        

Link Menu

Network programming in C#, Network Programming in VB.NET, Network Programming in .NET
This book is available now!
Buy at Amazon US or
Buy at Amazon UK