TriggerBase.cs source code in C# .NET

Source code for the .NET framework in C#

                        

Code:

/ 4.0 / 4.0 / DEVDIV_TFS / Dev10 / Releases / RTMRel / wpf / src / Framework / System / Windows / TriggerBase.cs / 1305600 / TriggerBase.cs

                            using System.Collections.Specialized; 
using System.IO;
using System.Windows.Markup;
using System.Security.Permissions;
 
using MS.Utility;
using MS.Internal; 
 
using System;
using System.ComponentModel;            // DesignerSerializationVisibilityAttribute & DefaultValue 
using System.Diagnostics;

namespace System.Windows
{ 
    /// 
    ///     A visual trigger is the base class for specifying a conditional 
    /// value within a Style object. 
    /// 
    ///  
    ///     The TriggerBase class deals with the values, and a derived class
    /// is responsible for handling the conditions that determine whether a
    /// value is to be used.
    /// 
    ///     In other words, the TriggerBase class stores the "then" portion
    /// of an if/then.  The derived class handles the "if" part. 
    /// 
    ///     The most common derived type is the Trigger class.  A
    /// property trigger is conditional on property values, creating logic like: 
    ///
    ///     if( MouseOver == true ) then (Background = Red )
    ///
    ///     The various Set methods in this class handles the (Background = Red) portion 
    /// of this logic.
    ///  
    [Localizability(LocalizationCategory.None, Readability=Readability.Unreadable)] 
    public abstract class TriggerBase : DependencyObject
    { 
        internal TriggerBase()
        {
            ;
        } 

        ///  
        ///     A collection of trigger actions to perform when this trigger 
        /// object becomes active.
        ///  
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
        public TriggerActionCollection EnterActions
        {
            get 
            {
                // Verify Context Access 
                VerifyAccess(); 

                if( _enterActions == null ) 
                {
                    _enterActions = new TriggerActionCollection();
                    if( IsSealed )
                    { 
                        // This collection might receive its first query after
                        //  the containing trigger had already been sealed 
                        _enterActions.Seal(this); 
                    }
                } 
                return _enterActions;
            }
        }
 
        // Internal way to check without triggering a pointless allocation
        internal bool HasEnterActions { get { return _enterActions != null && _enterActions.Count > 0; } } 
 
        /// 
        ///     A collection of trigger actions to perform when this trigger 
        /// object becomes inactive.
        /// 
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
        public TriggerActionCollection ExitActions 
        {
            get 
            { 
                // Verify Context Access
                VerifyAccess(); 

                if( _exitActions == null )
                {
                    _exitActions = new TriggerActionCollection(); 
                    if( IsSealed )
                    { 
                        // This collection might receive its first query after 
                        //  the containing trigger had already been sealed
                        _exitActions.Seal(this); 
                    }
                }
                return _exitActions;
            } 
        }
 
        // Internal way to check without triggering a pointless allocation 
        internal bool HasExitActions { get { return _exitActions != null && _exitActions.Count > 0; } }
 
//  Here's the internal version that does what Robby thinks it should do.
        internal bool ExecuteEnterActionsOnApply
        {
            get 
            {
                return true; 
            } 
        }
 
        internal bool ExecuteExitActionsOnApply
        {
            get
            { 
                return false;
            } 
        } 

/*  Here's the version that looks like a public API.  (Needs Style/Template parser changes before these will actually parse.) 

        // These fields determine what we do about any existing Enter/ExitActions
        //  when the Trigger is first applied.
 
        /// 
        /// If set to 'true', EnterActions will be executed immediately if the 
        ///  conditions are met, before any "enter" change has occurred. 
        /// 
 
        // This is on the assumption that the "enter" change has occurred already.
        //  For example:  the IsChecked property
        //  change occurred before the CheckBox Style/Template is applied.
        [DefaultValue(false)] 
        public bool ExecuteEnterActionsOnApply
        { 
            get 
            {
                return _executeEnterActionsOnApply; 
            }
            set
            {
                if (IsSealed) 
                {
                    throw new InvalidOperationException(SR.Get(SRID.CannotChangeAfterSealed, "trigger")); 
                } 

                _executeEnterActionsOnApply = value; 
            }
        }

        ///  
        /// If set to 'true', ExitActions will be executed immediately if the
        ///  conditions are not met, before any "exit" change has occurred. 
        ///  
        [DefaultValue(false)]
        public bool ExecuteExitActionsOnApply 
        {
            get
            {
                return _executeExitActionsOnApply; 
            }
            set 
            { 
                if (IsSealed)
                { 
                    throw new InvalidOperationException(SR.Get(SRID.CannotChangeAfterSealed, "trigger"));
                }

                _executeExitActionsOnApply = value; 
            }
        } 
 
*/
        ///  
        ///     Parameter validation work common to the SetXXXX methods that deal
        /// with the container node of the Style/Template.
        /// 
        internal void ProcessParametersContainer(DependencyProperty dp) 
        {
            // Not allowed to use Style to affect the StyleProperty. 
            if (dp == FrameworkElement.StyleProperty) 
            {
                throw new ArgumentException(SR.Get(SRID.StylePropertyInStyleNotAllowed)); 
            }
        }

        ///  
        ///     Parameter validation work common to the SetXXXX methods that deal
        /// with visual tree child nodes. 
        ///  
        internal string ProcessParametersVisualTreeChild(DependencyProperty dp, string target)
        { 
            if (target == null)
            {
                throw new ArgumentNullException("target");
            } 

            if (target.Length == 0) 
            { 
                throw new ArgumentException(SR.Get(SRID.ChildNameMustBeNonEmpty));
            } 

            return String.Intern(target);
        }
 
        /// 
        ///     After the parameters have been validated, store it in the 
        /// PropertyValues collection. 
        /// 
        ///  
        ///     All these will be looked at again (and processed into runtime
        /// data structures) by Style.Seal(). We keep them around even after
        /// that point should we need to serialize this data back out.
        ///  
        internal void AddToPropertyValues(string childName, DependencyProperty dp, object value, PropertyValueType valueType)
        { 
            // Store original data 
            PropertyValue propertyValue = new PropertyValue();
            propertyValue.ValueType = valueType; 
            propertyValue.Conditions = null;  // Delayed - derived class is responsible for this item.
            propertyValue.ChildName = childName;
            propertyValue.Property = dp;
            propertyValue.ValueInternal = value; 

            PropertyValues.Add(propertyValue); 
        } 

        //CASRemoval:[StrongNameIdentityPermission(SecurityAction.InheritanceDemand, PublicKey = Microsoft.Internal.BuildInfo.WCP_PUBLIC_KEY_STRING)] 
        internal override void Seal()
        {
            // Verify Context Access
            VerifyAccess(); 

            base.Seal(); 
 
            // Super classes have added all delayed conditions
 
            // Track Dependent/Source relationship for prediction
            for (int i = 0; i < PropertyValues.Count; i++)
            {
                PropertyValue propertyValue = PropertyValues[i]; 

                DependencyProperty dependent = propertyValue.Property; 
 
                for (int j = 0; j < propertyValue.Conditions.Length; j++)
                { 
                    DependencyProperty source = propertyValue.Conditions[j].Property;

                    // Check for obvious cycles.  Don't test for cycles if we have
                    // something other than self as the target, since this means that 
                    // the templatedParent is presumably not the target.  See windows bug
                    // 984916 for details. 
                    if (source == dependent && propertyValue.ChildName == StyleHelper.SelfName) 
                    {
                        throw new InvalidOperationException(SR.Get(SRID.PropertyTriggerCycleDetected, source.Name)); 
                    }
                }
            }
 
            if( _enterActions != null )
            { 
                _enterActions.Seal(this); 
            }
            if( _exitActions != null ) 
            {
                _exitActions.Seal(this);
            }
 
            // Remove thread affinity so it can be accessed across threads
            DetachFromDispatcher(); 
        } 

        // This will transfer information in the _setters collection to PropertyValues array. 
        internal void ProcessSettersCollection(SetterBaseCollection setters)
        {
            // Add information in Setters collection to PropertyValues array.
            if( setters != null ) 
            {
                // Seal Setters 
                setters.Seal(); 

                for (int i = 0; i < setters.Count; i++ ) 
                {
                    Setter setter = setters[i] as Setter;
                    if( setter != null )
                    { 
                        DependencyProperty dp = setter.Property;
                        object value          = setter.ValueInternal; 
                        string target         = setter.TargetName; 

                        if( target == null ) 
                        {
                            ProcessParametersContainer(dp);

                            target = StyleHelper.SelfName; 
                        }
                        else 
                        { 
                            target = ProcessParametersVisualTreeChild(dp, target); // name string will get interned
                        } 

                        DynamicResourceExtension dynamicResource = value as DynamicResourceExtension;
                        if (dynamicResource == null)
                        { 
                            AddToPropertyValues(target, dp, value, PropertyValueType.Trigger);
                        } 
                        else 
                        {
                            AddToPropertyValues(target, dp, dynamicResource.ResourceKey, PropertyValueType.PropertyTriggerResource); 
                        }
                    }
                    else
                    { 
                        throw new InvalidOperationException(SR.Get(SRID.VisualTriggerSettersIncludeUnsupportedSetterType, setter.GetType().Name));
                    } 
                } 
            }
        } 

        // Define the DO's inheritance context

        internal override DependencyObject InheritanceContext 
        {
            get { return _inheritanceContext; } 
        } 

        // Receive a new inheritance context (this will be a FE/FCE) 
        internal override void AddInheritanceContext(DependencyObject context, DependencyProperty property)
        {
            InheritanceContextHelper.AddInheritanceContext(context,
                                                              this, 
                                                              ref _hasMultipleInheritanceContexts,
                                                              ref _inheritanceContext); 
        } 

        // Remove an inheritance context (this will be a FE/FCE) 
        internal override void RemoveInheritanceContext(DependencyObject context, DependencyProperty property)
        {
            InheritanceContextHelper.RemoveInheritanceContext(context,
                                                                  this, 
                                                                  ref _hasMultipleInheritanceContexts,
                                                                  ref _inheritanceContext); 
        } 

        // Says if the current instance has multiple InheritanceContexts 

        internal override bool HasMultipleInheritanceContexts
        {
            get { return _hasMultipleInheritanceContexts; } 
        }
 
        // This ranking is used when trigger needs to be sorted relative to 
        //  the ordering, as when determining precedence for enter/exit
        //  animation composition.  Otherwise, it stays at default value of zero. 
        internal Int64 Layer
        {
            get { return _globalLayerRank; }
        } 

        // Set self rank to current number, increment global static. 
        internal void EstablishLayer() 
        {
            if( _globalLayerRank == 0 ) 
            {
                lock(Synchronized)
                {
                    _globalLayerRank = _nextGlobalLayerRank++; 
                }
 
                if( _nextGlobalLayerRank == Int64.MaxValue ) 
                {
                    throw new InvalidOperationException(SR.Get(SRID.PropertyTriggerLayerLimitExceeded)); 
                }
            }
        }
 
        // evaluate the current state of the trigger
        internal virtual bool GetCurrentState(DependencyObject container, UncommonField dataField) 
        { 
            Debug.Assert( false,
                "This method was written to handle Trigger, MultiTrigger, DataTrigger, and MultiDataTrigger.  It looks like a new trigger type was added - please add support as appropriate."); 

            return false;
        }
 
        // Collection of TriggerConditions
        internal TriggerCondition[] TriggerConditions 
        { 
            get { return _triggerConditions; }
            set { _triggerConditions = value; } 
        }

        // Synchronized (write locks, lock-free reads): Covered by the TriggerBase instance
        /* property */ internal FrugalStructList PropertyValues = new FrugalStructList(); 

        // Global, cross-object synchronization 
        private static object Synchronized = new object(); 

        // Conditions 
        TriggerCondition[] _triggerConditions;

        // Fields to implement DO's inheritance context
        private DependencyObject _inheritanceContext = null; 
        private bool _hasMultipleInheritanceContexts = false;
 
        // Fields to handle enter/exit actions. 
        private TriggerActionCollection _enterActions = null;
        private TriggerActionCollection _exitActions = null; 

//      On hold - is this a new public API we want to do?
//        private bool _executeEnterActionsOnApply = false;
//        private bool _executeExitActionsOnApply = false; 

        private Int64 _globalLayerRank = 0; 
        private static Int64 _nextGlobalLayerRank = System.Windows.Media.Animation.Storyboard.Layers.PropertyTriggerStartLayer; 
    }
} 

// File provided for Reference Use Only by Microsoft Corporation (c) 2007.
// Copyright (c) Microsoft Corporation. All rights reserved.
using System.Collections.Specialized; 
using System.IO;
using System.Windows.Markup;
using System.Security.Permissions;
 
using MS.Utility;
using MS.Internal; 
 
using System;
using System.ComponentModel;            // DesignerSerializationVisibilityAttribute & DefaultValue 
using System.Diagnostics;

namespace System.Windows
{ 
    /// 
    ///     A visual trigger is the base class for specifying a conditional 
    /// value within a Style object. 
    /// 
    ///  
    ///     The TriggerBase class deals with the values, and a derived class
    /// is responsible for handling the conditions that determine whether a
    /// value is to be used.
    /// 
    ///     In other words, the TriggerBase class stores the "then" portion
    /// of an if/then.  The derived class handles the "if" part. 
    /// 
    ///     The most common derived type is the Trigger class.  A
    /// property trigger is conditional on property values, creating logic like: 
    ///
    ///     if( MouseOver == true ) then (Background = Red )
    ///
    ///     The various Set methods in this class handles the (Background = Red) portion 
    /// of this logic.
    ///  
    [Localizability(LocalizationCategory.None, Readability=Readability.Unreadable)] 
    public abstract class TriggerBase : DependencyObject
    { 
        internal TriggerBase()
        {
            ;
        } 

        ///  
        ///     A collection of trigger actions to perform when this trigger 
        /// object becomes active.
        ///  
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
        public TriggerActionCollection EnterActions
        {
            get 
            {
                // Verify Context Access 
                VerifyAccess(); 

                if( _enterActions == null ) 
                {
                    _enterActions = new TriggerActionCollection();
                    if( IsSealed )
                    { 
                        // This collection might receive its first query after
                        //  the containing trigger had already been sealed 
                        _enterActions.Seal(this); 
                    }
                } 
                return _enterActions;
            }
        }
 
        // Internal way to check without triggering a pointless allocation
        internal bool HasEnterActions { get { return _enterActions != null && _enterActions.Count > 0; } } 
 
        /// 
        ///     A collection of trigger actions to perform when this trigger 
        /// object becomes inactive.
        /// 
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
        public TriggerActionCollection ExitActions 
        {
            get 
            { 
                // Verify Context Access
                VerifyAccess(); 

                if( _exitActions == null )
                {
                    _exitActions = new TriggerActionCollection(); 
                    if( IsSealed )
                    { 
                        // This collection might receive its first query after 
                        //  the containing trigger had already been sealed
                        _exitActions.Seal(this); 
                    }
                }
                return _exitActions;
            } 
        }
 
        // Internal way to check without triggering a pointless allocation 
        internal bool HasExitActions { get { return _exitActions != null && _exitActions.Count > 0; } }
 
//  Here's the internal version that does what Robby thinks it should do.
        internal bool ExecuteEnterActionsOnApply
        {
            get 
            {
                return true; 
            } 
        }
 
        internal bool ExecuteExitActionsOnApply
        {
            get
            { 
                return false;
            } 
        } 

/*  Here's the version that looks like a public API.  (Needs Style/Template parser changes before these will actually parse.) 

        // These fields determine what we do about any existing Enter/ExitActions
        //  when the Trigger is first applied.
 
        /// 
        /// If set to 'true', EnterActions will be executed immediately if the 
        ///  conditions are met, before any "enter" change has occurred. 
        /// 
 
        // This is on the assumption that the "enter" change has occurred already.
        //  For example:  the IsChecked property
        //  change occurred before the CheckBox Style/Template is applied.
        [DefaultValue(false)] 
        public bool ExecuteEnterActionsOnApply
        { 
            get 
            {
                return _executeEnterActionsOnApply; 
            }
            set
            {
                if (IsSealed) 
                {
                    throw new InvalidOperationException(SR.Get(SRID.CannotChangeAfterSealed, "trigger")); 
                } 

                _executeEnterActionsOnApply = value; 
            }
        }

        ///  
        /// If set to 'true', ExitActions will be executed immediately if the
        ///  conditions are not met, before any "exit" change has occurred. 
        ///  
        [DefaultValue(false)]
        public bool ExecuteExitActionsOnApply 
        {
            get
            {
                return _executeExitActionsOnApply; 
            }
            set 
            { 
                if (IsSealed)
                { 
                    throw new InvalidOperationException(SR.Get(SRID.CannotChangeAfterSealed, "trigger"));
                }

                _executeExitActionsOnApply = value; 
            }
        } 
 
*/
        ///  
        ///     Parameter validation work common to the SetXXXX methods that deal
        /// with the container node of the Style/Template.
        /// 
        internal void ProcessParametersContainer(DependencyProperty dp) 
        {
            // Not allowed to use Style to affect the StyleProperty. 
            if (dp == FrameworkElement.StyleProperty) 
            {
                throw new ArgumentException(SR.Get(SRID.StylePropertyInStyleNotAllowed)); 
            }
        }

        ///  
        ///     Parameter validation work common to the SetXXXX methods that deal
        /// with visual tree child nodes. 
        ///  
        internal string ProcessParametersVisualTreeChild(DependencyProperty dp, string target)
        { 
            if (target == null)
            {
                throw new ArgumentNullException("target");
            } 

            if (target.Length == 0) 
            { 
                throw new ArgumentException(SR.Get(SRID.ChildNameMustBeNonEmpty));
            } 

            return String.Intern(target);
        }
 
        /// 
        ///     After the parameters have been validated, store it in the 
        /// PropertyValues collection. 
        /// 
        ///  
        ///     All these will be looked at again (and processed into runtime
        /// data structures) by Style.Seal(). We keep them around even after
        /// that point should we need to serialize this data back out.
        ///  
        internal void AddToPropertyValues(string childName, DependencyProperty dp, object value, PropertyValueType valueType)
        { 
            // Store original data 
            PropertyValue propertyValue = new PropertyValue();
            propertyValue.ValueType = valueType; 
            propertyValue.Conditions = null;  // Delayed - derived class is responsible for this item.
            propertyValue.ChildName = childName;
            propertyValue.Property = dp;
            propertyValue.ValueInternal = value; 

            PropertyValues.Add(propertyValue); 
        } 

        //CASRemoval:[StrongNameIdentityPermission(SecurityAction.InheritanceDemand, PublicKey = Microsoft.Internal.BuildInfo.WCP_PUBLIC_KEY_STRING)] 
        internal override void Seal()
        {
            // Verify Context Access
            VerifyAccess(); 

            base.Seal(); 
 
            // Super classes have added all delayed conditions
 
            // Track Dependent/Source relationship for prediction
            for (int i = 0; i < PropertyValues.Count; i++)
            {
                PropertyValue propertyValue = PropertyValues[i]; 

                DependencyProperty dependent = propertyValue.Property; 
 
                for (int j = 0; j < propertyValue.Conditions.Length; j++)
                { 
                    DependencyProperty source = propertyValue.Conditions[j].Property;

                    // Check for obvious cycles.  Don't test for cycles if we have
                    // something other than self as the target, since this means that 
                    // the templatedParent is presumably not the target.  See windows bug
                    // 984916 for details. 
                    if (source == dependent && propertyValue.ChildName == StyleHelper.SelfName) 
                    {
                        throw new InvalidOperationException(SR.Get(SRID.PropertyTriggerCycleDetected, source.Name)); 
                    }
                }
            }
 
            if( _enterActions != null )
            { 
                _enterActions.Seal(this); 
            }
            if( _exitActions != null ) 
            {
                _exitActions.Seal(this);
            }
 
            // Remove thread affinity so it can be accessed across threads
            DetachFromDispatcher(); 
        } 

        // This will transfer information in the _setters collection to PropertyValues array. 
        internal void ProcessSettersCollection(SetterBaseCollection setters)
        {
            // Add information in Setters collection to PropertyValues array.
            if( setters != null ) 
            {
                // Seal Setters 
                setters.Seal(); 

                for (int i = 0; i < setters.Count; i++ ) 
                {
                    Setter setter = setters[i] as Setter;
                    if( setter != null )
                    { 
                        DependencyProperty dp = setter.Property;
                        object value          = setter.ValueInternal; 
                        string target         = setter.TargetName; 

                        if( target == null ) 
                        {
                            ProcessParametersContainer(dp);

                            target = StyleHelper.SelfName; 
                        }
                        else 
                        { 
                            target = ProcessParametersVisualTreeChild(dp, target); // name string will get interned
                        } 

                        DynamicResourceExtension dynamicResource = value as DynamicResourceExtension;
                        if (dynamicResource == null)
                        { 
                            AddToPropertyValues(target, dp, value, PropertyValueType.Trigger);
                        } 
                        else 
                        {
                            AddToPropertyValues(target, dp, dynamicResource.ResourceKey, PropertyValueType.PropertyTriggerResource); 
                        }
                    }
                    else
                    { 
                        throw new InvalidOperationException(SR.Get(SRID.VisualTriggerSettersIncludeUnsupportedSetterType, setter.GetType().Name));
                    } 
                } 
            }
        } 

        // Define the DO's inheritance context

        internal override DependencyObject InheritanceContext 
        {
            get { return _inheritanceContext; } 
        } 

        // Receive a new inheritance context (this will be a FE/FCE) 
        internal override void AddInheritanceContext(DependencyObject context, DependencyProperty property)
        {
            InheritanceContextHelper.AddInheritanceContext(context,
                                                              this, 
                                                              ref _hasMultipleInheritanceContexts,
                                                              ref _inheritanceContext); 
        } 

        // Remove an inheritance context (this will be a FE/FCE) 
        internal override void RemoveInheritanceContext(DependencyObject context, DependencyProperty property)
        {
            InheritanceContextHelper.RemoveInheritanceContext(context,
                                                                  this, 
                                                                  ref _hasMultipleInheritanceContexts,
                                                                  ref _inheritanceContext); 
        } 

        // Says if the current instance has multiple InheritanceContexts 

        internal override bool HasMultipleInheritanceContexts
        {
            get { return _hasMultipleInheritanceContexts; } 
        }
 
        // This ranking is used when trigger needs to be sorted relative to 
        //  the ordering, as when determining precedence for enter/exit
        //  animation composition.  Otherwise, it stays at default value of zero. 
        internal Int64 Layer
        {
            get { return _globalLayerRank; }
        } 

        // Set self rank to current number, increment global static. 
        internal void EstablishLayer() 
        {
            if( _globalLayerRank == 0 ) 
            {
                lock(Synchronized)
                {
                    _globalLayerRank = _nextGlobalLayerRank++; 
                }
 
                if( _nextGlobalLayerRank == Int64.MaxValue ) 
                {
                    throw new InvalidOperationException(SR.Get(SRID.PropertyTriggerLayerLimitExceeded)); 
                }
            }
        }
 
        // evaluate the current state of the trigger
        internal virtual bool GetCurrentState(DependencyObject container, UncommonField dataField) 
        { 
            Debug.Assert( false,
                "This method was written to handle Trigger, MultiTrigger, DataTrigger, and MultiDataTrigger.  It looks like a new trigger type was added - please add support as appropriate."); 

            return false;
        }
 
        // Collection of TriggerConditions
        internal TriggerCondition[] TriggerConditions 
        { 
            get { return _triggerConditions; }
            set { _triggerConditions = value; } 
        }

        // Synchronized (write locks, lock-free reads): Covered by the TriggerBase instance
        /* property */ internal FrugalStructList PropertyValues = new FrugalStructList(); 

        // Global, cross-object synchronization 
        private static object Synchronized = new object(); 

        // Conditions 
        TriggerCondition[] _triggerConditions;

        // Fields to implement DO's inheritance context
        private DependencyObject _inheritanceContext = null; 
        private bool _hasMultipleInheritanceContexts = false;
 
        // Fields to handle enter/exit actions. 
        private TriggerActionCollection _enterActions = null;
        private TriggerActionCollection _exitActions = null; 

//      On hold - is this a new public API we want to do?
//        private bool _executeEnterActionsOnApply = false;
//        private bool _executeExitActionsOnApply = false; 

        private Int64 _globalLayerRank = 0; 
        private static Int64 _nextGlobalLayerRank = System.Windows.Media.Animation.Storyboard.Layers.PropertyTriggerStartLayer; 
    }
} 

// 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