ManipulationLogic.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 / Core / CSharp / System / Windows / Input / ManipulationLogic.cs / 1305600 / ManipulationLogic.cs

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

using System; 
using System.Collections.Generic; 
using System.Diagnostics;
using System.Security; 
using System.Windows;
using System.Windows.Interop;
using System.Windows.Input.Manipulations;
using System.Windows.Media; 
using System.Windows.Threading;
using MS.Internal; 
using MS.Internal.PresentationCore; 

namespace System.Windows.Input 
{
    /// 
    ///     Handles detection of manipulations.
    ///  
    internal sealed class ManipulationLogic
    { 
        ///  
        ///     Instantiates an instance of this class.
        ///  
        internal ManipulationLogic(ManipulationDevice manipulationDevice)
        {
            _manipulationDevice = manipulationDevice;
        } 

        ///  
        ///     Hooked up to the manipulation processor and inertia processor's started event. 
        /// 
        ///  
        ///     SecurityCrticial: Calls PushEvent.
        ///     TreatAsSafe: Pushes a ManipulationStarted event, which does not need to be protected.
        /// 
        [SecurityCritical, SecurityTreatAsSafe] 
        private void OnManipulationStarted(object sender, Manipulation2DStartedEventArgs e)
        { 
            PushEvent(new ManipulationStartedEventArgs( 
                _manipulationDevice,
                LastTimestamp, 
                _currentContainer,
                new Point(e.OriginX, e.OriginY)));
        }
 
        /// 
        ///     Hooked up to the manipulation processor and inertia processor's delta event. 
        ///  
        /// 
        ///     SecurityCrticial: Calls PushEvent. 
        ///     TreatAsSafe: Pushes a ManipulationDelta event, which does not need to be protected.
        /// 
        [SecurityCritical, SecurityTreatAsSafe]
        private void OnManipulationDelta(object sender, Manipulation2DDeltaEventArgs e) 
        {
            var deltaArguments = new ManipulationDeltaEventArgs( 
                _manipulationDevice, 
                LastTimestamp,
                _currentContainer, 
                new Point(e.OriginX, e.OriginY),
                ConvertDelta(e.Delta, null),
                ConvertDelta(e.Cumulative, _lastManipulationBeforeInertia),
                ConvertVelocities(e.Velocities), 
                IsInertiaActive);
 
            PushEvent(deltaArguments); 
        }
 
        /// 
        ///     Hooked up to the manipulation processor's completed event.
        /// 
        ///  
        ///     SecurityCritical: Calls PushEvent.
        ///     TreatAsSafe: Pushes a ManipulationInertiaStartingEventArgs event, which doesn't need to be protected. 
        ///  
        [SecurityCritical, SecurityTreatAsSafe]
        private void OnManipulationCompleted(object sender, Manipulation2DCompletedEventArgs e) 
        {
            // Manipulation portion completed.

            if (_manualComplete && !_manualCompleteWithInertia) 
            {
                // This is the last event in the sequence. 
 
                ManipulationCompletedEventArgs completedArguments = ConvertCompletedArguments(e);
                RaiseManipulationCompleted(completedArguments); 
            }
            else
            {
                // This event will configure inertia, which will start after this event. 

                _lastManipulationBeforeInertia = ConvertDelta(e.Total, null); 
 
                ManipulationInertiaStartingEventArgs inertiaArguments = new ManipulationInertiaStartingEventArgs(
                    _manipulationDevice, 
                    LastTimestamp,
                    _currentContainer,
                    new Point(e.OriginX, e.OriginY),
                    ConvertVelocities(e.Velocities), 
                    false);
 
                PushEvent(inertiaArguments); 
            }
 
            _manipulationProcessor = null;
        }

        ///  
        ///     Hooked up to the inertia processor's completed event.
        ///  
        ///  
        ///     SecurityCritical: Calls PushEvent.
        ///     TreatAsSafe: Pushes a ManipulationInertiaStarting event, which doesn't need to be protected. 
        /// 
        [SecurityCritical, SecurityTreatAsSafe]
        private void OnInertiaCompleted(object sender, Manipulation2DCompletedEventArgs e)
        { 
            // Inertia portion completed.
 
            ClearTimer(); 

            if (_manualComplete && _manualCompleteWithInertia) 
            {
                // Another inertia portion was requested

                _lastManipulationBeforeInertia = ConvertDelta(e.Total, _lastManipulationBeforeInertia); 

                ManipulationInertiaStartingEventArgs inertiaArguments = new ManipulationInertiaStartingEventArgs( 
                    _manipulationDevice, 
                    LastTimestamp,
                    _currentContainer, 
                    new Point(e.OriginX, e.OriginY),
                    ConvertVelocities(e.Velocities),
                    true);
 
                PushEvent(inertiaArguments);
            } 
            else 
            {
                // This is the last event in the sequence. 

                ManipulationCompletedEventArgs completedArguments = ConvertCompletedArguments(e);

                RaiseManipulationCompleted(completedArguments); 
            }
 
            _inertiaProcessor = null; 
        }
 
        /// 
        ///     SecurityCritical: Calls PushEvent.
        ///     TreatAsSafe: Pushes a ManipulationCompleted event, which doesn't need to be protected.
        ///  
        [SecurityCritical, SecurityTreatAsSafe]
        private void RaiseManipulationCompleted(ManipulationCompletedEventArgs e) 
        { 
            PushEvent(e);
        } 

        /// 
        ///     Called after a Completed event has been processed.
        ///  
        internal void OnCompleted()
        { 
            _lastManipulationBeforeInertia = null; 
            SetContainer(null);
        } 

        /// 
        ///     Converts an Affine2DOperationCompletedEventArgs object into a ManipulationCompletedEventArgs object.
        ///  
        private ManipulationCompletedEventArgs ConvertCompletedArguments(Manipulation2DCompletedEventArgs e)
        { 
            return new ManipulationCompletedEventArgs( 
                _manipulationDevice,
                LastTimestamp, 
                _currentContainer,
                new Point(e.OriginX, e.OriginY),
                ConvertDelta(e.Total, _lastManipulationBeforeInertia),
                ConvertVelocities(e.Velocities), 
                IsInertiaActive);
        } 
 
        private static ManipulationDelta ConvertDelta(ManipulationDelta2D delta, ManipulationDelta add)
        { 
            if (add != null)
            {
                return new ManipulationDelta(
                    new Vector(delta.TranslationX + add.Translation.X, delta.TranslationY + add.Translation.Y), 
                    AngleUtil.RadiansToDegrees(delta.Rotation) + add.Rotation,
                    new Vector(delta.ScaleX * add.Scale.X, delta.ScaleY * add.Scale.Y), 
                    new Vector(delta.ExpansionX + add.Expansion.X, delta.ExpansionY + add.Expansion.Y)); 
            }
            else 
            {
                return new ManipulationDelta(
                    new Vector(delta.TranslationX, delta.TranslationY),
                    AngleUtil.RadiansToDegrees(delta.Rotation), 
                    new Vector(delta.ScaleX, delta.ScaleY),
                    new Vector(delta.ExpansionX, delta.ExpansionY)); 
            } 
        }
 
        private static ManipulationVelocities ConvertVelocities(ManipulationVelocities2D velocities)
        {
            return new ManipulationVelocities(
                new Vector(velocities.LinearVelocityX, velocities.LinearVelocityY), 
                AngleUtil.RadiansToDegrees(velocities.AngularVelocity),
                new Vector(velocities.ExpansionVelocityX, velocities.ExpansionVelocityY)); 
        } 

        ///  
        ///     Completes any pending manipulation or inerita processing.
        /// 
        /// 
        ///     If a manipulation is active, specifies whether to continue 
        ///     to an inertia phase (true) or simply end the sequence (true).
        ///  
        internal void Complete(bool withInertia) 
        {
            try 
            {
                _manualComplete = true;
                _manualCompleteWithInertia = withInertia;
 
                if (IsManipulationActive)
                { 
                    _manipulationProcessor.CompleteManipulation(GetCurrentTimestamp()); 
                }
                else if (IsInertiaActive) 
                {
                    _inertiaProcessor.Complete(GetCurrentTimestamp());
                }
            } 
            finally
            { 
                _manualComplete = false; 
                _manualCompleteWithInertia = false;
            } 
        }

        /// 
        ///     Gets ManipulationCompletedEventArgs object out of ManipulationInertiaStartingEventArgs 
        /// 
        private ManipulationCompletedEventArgs GetManipulationCompletedArguments(ManipulationInertiaStartingEventArgs e) 
        { 
            Debug.Assert(_lastManipulationBeforeInertia != null);
            return new ManipulationCompletedEventArgs( 
                _manipulationDevice,
                LastTimestamp,
                _currentContainer,
                new Point(e.ManipulationOrigin.X, e.ManipulationOrigin.Y), 
                _lastManipulationBeforeInertia,
                e.InitialVelocities, 
                IsInertiaActive); 
        }
 
        /// 
        ///     Starts the inertia phase based on the results of a ManipulationInertiaStarting event.
        /// 
        internal void BeginInertia(ManipulationInertiaStartingEventArgs e) 
        {
            if (e.CanBeginInertia()) 
            { 
                _inertiaProcessor = new InertiaProcessor2D();
                _inertiaProcessor.Delta += OnManipulationDelta; 
                _inertiaProcessor.Completed += OnInertiaCompleted;

                e.ApplyParameters(_inertiaProcessor);
 
                // Setup a timer to tick the inertia to completion
                _inertiaTimer = new DispatcherTimer(); 
                _inertiaTimer.Interval = TimeSpan.FromMilliseconds(15); 
                _inertiaTimer.Tick += new EventHandler(OnInertiaTick);
                _inertiaTimer.Start(); 
            }
            else
            {
                // This is the last event in the sequence. 
                ManipulationCompletedEventArgs completedArguments = GetManipulationCompletedArguments(e);
                RaiseManipulationCompleted(completedArguments); 
                PushEventsToDevice(); 
            }
        } 

        internal static Int64 GetCurrentTimestamp()
        {
            // Does QueryPerformanceCounter to get the current time in 100ns units 
            return MediaContext.CurrentTicks;
        } 
 
        private void OnInertiaTick(object sender, EventArgs e)
        { 
            // Tick the inertia
            if (IsInertiaActive)
            {
                if (!_inertiaProcessor.Process(GetCurrentTimestamp())) 
                {
                    ClearTimer(); 
                } 

                PushEventsToDevice(); 
            }
            else
            {
                ClearTimer(); 
            }
        } 
 
        private void ClearTimer()
        { 
            if (_inertiaTimer != null)
            {
                _inertiaTimer.Stop();
                _inertiaTimer = null; 
            }
        } 
 
        /// 
        ///     Prepares and raises a manipulation event. 
        /// 
        /// 
        ///     Critical: Adds an input event to a list that will eventually be added to the InputManager queue.
        ///               Accesses _generatedEvent. 
        /// 
        [SecurityCritical] 
        private void PushEvent(InputEventArgs e) 
        {
            // We only expect to generate one event at a time and should never need a queue. 
            Debug.Assert(_generatedEvent == null, "There is already a generated event waiting to be pushed.");
            _generatedEvent = e;
        }
 
        /// 
        ///     Pushes generated events to the inertia input provider. 
        ///  
        /// 
        ///     SecurityCritical: ProcessManipulationInput. Accesses _generatedEvent. 
        ///     TreatAsSafe: OK to send manipulation and inertia events.
        /// 
        [SecurityCritical, SecurityTreatAsSafe]
        internal void PushEventsToDevice() 
        {
            if (_generatedEvent != null) 
            { 
                InputEventArgs generatedEvent = _generatedEvent;
                _generatedEvent = null; 
                _manipulationDevice.ProcessManipulationInput(generatedEvent);
            }
        }
 
        /// 
        ///     Raises ManipulationBoundaryFeedback to allow handlers to provide feedback that manipulation has hit an edge. 
        ///  
        /// The total unused manipulation.
        ///  
        ///     SecurityCrticial: Calls PushEvent.
        ///     TreatAsSafe: Pushes a ManipulationBoundaryFeedbackEventArgs event, which does not need to be protected.
        /// 
        [SecurityCritical, SecurityTreatAsSafe] 
        internal void RaiseBoundaryFeedback(ManipulationDelta unusedManipulation, bool requestedComplete)
        { 
            bool hasUnusedManipulation = (unusedManipulation != null); 
            if ((!hasUnusedManipulation || requestedComplete) && HasPendingBoundaryFeedback)
            { 
                // Create a "zero" message to end currently pending feedback
                unusedManipulation = new ManipulationDelta(new Vector(), 0.0, new Vector(1.0, 1.0), new Vector());
                HasPendingBoundaryFeedback = false;
            } 
            else if (hasUnusedManipulation)
            { 
                HasPendingBoundaryFeedback = true; 
            }
 
            if (unusedManipulation != null)
            {
                PushEvent(new ManipulationBoundaryFeedbackEventArgs(_manipulationDevice, LastTimestamp, _currentContainer, unusedManipulation));
            } 
        }
 
        private bool HasPendingBoundaryFeedback 
        {
            get; 
            set;
        }

        private int LastTimestamp 
        {
            get; 
            set; 
        }
 
        internal void ReportFrame(ICollection manipulators)
        {
            Int64 timestamp = GetCurrentTimestamp();
            LastTimestamp = (int)timestamp; // InputEventArgs timestamps are Int32 while the processors take Int64 

            int numManipulators = manipulators.Count; 
            if (IsInertiaActive && (numManipulators > 0)) 
            {
                // Inertia is active but now there are fingers, stop inertia 
                _inertiaProcessor.Complete(timestamp);
                PushEventsToDevice();
            }
 
            if (!IsManipulationActive && (numManipulators > 0))
            { 
                // Time to start a new manipulation 

                ManipulationStartingEventArgs startingArgs = RaiseStarting(); 
                if (!startingArgs.RequestedCancel && (startingArgs.Mode != ManipulationModes.None))
                {
                    // Determine if we allow single-finger manipulation
                    if (startingArgs.IsSingleTouchEnabled || (numManipulators >= 2)) 
                    {
                        SetContainer(startingArgs.ManipulationContainer); 
                        _mode = startingArgs.Mode; 
                        _pivot = startingArgs.Pivot;
                        IList parameters = startingArgs.Parameters; 

                        _manipulationProcessor = new ManipulationProcessor2D(ConvertMode(_mode), ConvertPivot(_pivot));

                        if (parameters != null) 
                        {
                            int count = parameters.Count; 
                            for (int i = 0; i < parameters.Count; i++) 
                            {
                                _manipulationProcessor.SetParameters(parameters[i]); 
                            }
                        }

                        _manipulationProcessor.Started += OnManipulationStarted; 
                        _manipulationProcessor.Delta += OnManipulationDelta;
                        _manipulationProcessor.Completed += OnManipulationCompleted; 
 
                        _currentManipulators.Clear();
                    } 
                }
            }

            if (IsManipulationActive) 
            {
                // A manipulation process is available to process this frame of manipulators 
                UpdateManipulators(manipulators); 
                _manipulationProcessor.ProcessManipulators(timestamp, CurrentManipulators);
                PushEventsToDevice(); 
            }
        }

        ///  
        ///     Critical - Calls ProcessManipulationInput.
        ///     TreatAsSafe - Creates the event being raised itself, an event that is not considered critical. 
        ///  
        [SecurityCritical, SecurityTreatAsSafe]
        private ManipulationStartingEventArgs RaiseStarting() 
        {
            ManipulationStartingEventArgs starting = new ManipulationStartingEventArgs(_manipulationDevice, Environment.TickCount);
            starting.ManipulationContainer = _manipulationDevice.Target;
 
            _manipulationDevice.ProcessManipulationInput(starting);
 
            return starting; 
        }
 
        internal IInputElement ManipulationContainer
        {
            get { return _currentContainer; }
            set 
            {
                // 
 

 
                SetContainer(value);
            }
        }
 
        internal ManipulationModes ManipulationMode
        { 
            get { return _mode; } 
            set
            { 
                _mode = value;
                if (_manipulationProcessor != null)
                {
                    _manipulationProcessor.SupportedManipulations = ConvertMode(_mode); 
                }
            } 
        } 

        private static Manipulations2D ConvertMode(ManipulationModes mode) 
        {
            Manipulations2D manipulations = Manipulations2D.None;

            if ((mode & ManipulationModes.TranslateX) != 0) 
            {
                manipulations |= Manipulations2D.TranslateX; 
            } 

            if ((mode & ManipulationModes.TranslateY) != 0) 
            {
                manipulations |= Manipulations2D.TranslateY;
            }
 
            if ((mode & ManipulationModes.Scale) != 0)
            { 
                manipulations |= Manipulations2D.Scale; 
            }
 
            if ((mode & ManipulationModes.Rotate) != 0)
            {
                manipulations |= Manipulations2D.Rotate;
            } 

            return manipulations; 
        } 

        internal ManipulationPivot ManipulationPivot 
        {
            get { return _pivot; }
            set
            { 
                _pivot = value;
                if (_manipulationProcessor != null) 
                { 
                    _manipulationProcessor.Pivot = ConvertPivot(value);
                } 
            }
        }

        private static ManipulationPivot2D ConvertPivot(ManipulationPivot pivot) 
        {
            if (pivot != null) 
            { 
                Point center = pivot.Center;
                return new ManipulationPivot2D() 
                {
                    X = (float)center.X,
                    Y = (float)center.Y,
                    Radius = (float)Math.Max(1.0, pivot.Radius) 
                };
            } 
 
            return null;
        } 

        internal void SetManipulationParameters(ManipulationParameters2D parameter)
        {
            if (_manipulationProcessor != null) 
            {
                _manipulationProcessor.SetParameters(parameter); 
            } 
        }
 
        private void UpdateManipulators(ICollection updatedManipulators)
        {
            // Clear out the old removed collection and use it to store
            // the new current collection. The old current collection 
            // will be used to generate the new removed collection.
            _removedManipulators.Clear(); 
            var temp = _removedManipulators; 
            _removedManipulators = _currentManipulators;
            _currentManipulators = temp; 

            // End the manipulation if the element is not
            // visible anymore
            UIElement uie = _currentContainer as UIElement; 
            if (uie != null)
            { 
                if (!uie.IsVisible) 
                {
                    return; 
                }
            }
            else
            { 
                UIElement3D uie3D = _currentContainer as UIElement3D;
                if (uie3D != null && 
                    !uie3D.IsVisible) 
                {
                    return; 
                }
            }

            // For each updated manipulator, convert it to the correct format in the 
            // current collection and remove it from the removed collection. What is left
            // in the removed collection will be the manipulators that were removed. 
            foreach (IManipulator updatedManipulator in updatedManipulators) 
            {
                // 
                int id = updatedManipulator.Id;
                _removedManipulators.Remove(id); // This manipulator was not removed
                Point position = updatedManipulator.GetPosition(_currentContainer);
                position = _manipulationDevice.GetTransformedManipulatorPosition(position); 
                _currentManipulators[id] = new Manipulator2D(id, (float)position.X, (float)position.Y);
            } 
        } 

        ///  
        ///     Critical - Calls PresentationSource.CriticalFromVisual.
        ///     TreatAsSafe - Does not expose PresentationSource itself.
        /// 
        [SecurityCritical, SecurityTreatAsSafe] 
        private void SetContainer(IInputElement newContainer)
        { 
            // unsubscribe from LayoutUpdated 
            UnsubscribeFromLayoutUpdated();
 
            // clear cached values
            _containerPivotPoint = new Point();
            _containerSize = new Size();
            _root = null; 

            // remember the new container 
            _currentContainer = newContainer; 

            if (newContainer != null) 
            {
                // get the new root
                PresentationSource presentationSource = PresentationSource.CriticalFromVisual((Visual)newContainer);
                if (presentationSource != null) 
                {
                    _root = presentationSource.RootVisual as UIElement; 
                } 

                // subscribe to LayoutUpdated 
                if (_containerLayoutUpdated != null)
                {
                    SubscribeToLayoutUpdated();
                } 
            }
        } 
 
        internal event EventHandler ContainerLayoutUpdated
        { 
            add
            {
                bool wasNull = _containerLayoutUpdated == null;
                _containerLayoutUpdated += value; 

                // if this is the first handler, try to subscribe to LayoutUpdated event 
                if (wasNull && _containerLayoutUpdated != null) 
                {
                    SubscribeToLayoutUpdated(); 
                }
            }
            remove
            { 
                bool wasNull = _containerLayoutUpdated == null;
                _containerLayoutUpdated -= value; 
 
                // if this is the last handler, unsubscribe from LayoutUpdated event
                if (!wasNull && _containerLayoutUpdated == null) 
                {
                    UnsubscribeFromLayoutUpdated();
                }
            } 
        }
 
        private void SubscribeToLayoutUpdated() 
        {
            UIElement container = _currentContainer as UIElement; 
            if (container != null)
            {
                container.LayoutUpdated += OnLayoutUpdated;
            } 
        }
 
        private void UnsubscribeFromLayoutUpdated() 
        {
            UIElement container = _currentContainer as UIElement; 
            if (container != null)
            {
                container.LayoutUpdated -= OnLayoutUpdated;
            } 
        }
 
        ///  
        /// OnLayoutUpdated handler, raises ContainerLayoutUpdated event if container's position or size have been changed
        /// since the last LayoutUpdate. 
        /// 
        /// 
        /// 
        private void OnLayoutUpdated(object sender, EventArgs e) 
        {
            Debug.Assert(_containerLayoutUpdated != null); 
 
            //check position and size and update the cached values
            if (UpdateCachedPositionAndSize()) 
            {
                _containerLayoutUpdated(this, EventArgs.Empty);
            }
        } 

        private bool UpdateCachedPositionAndSize() 
        { 
            // Determine if the manipulation needs to be updated because of position or size change.
            // * Size change is detected by comparing RenderSize 
            // * Position change is detected by translating PivotPoint to the element coordinate, in general
            // this is not accurate because rotation over PivotPoint won't be detected but the PivotPoint is selected far outside
            // of the Window bounds, so practically that should be a very rare case.
            // The more accurate solution would require 2 or 3 points which is more expensive. 
            if (_root == null)
            { 
                return false; 
            }
 
            UIElement container = _currentContainer as UIElement;
            if (container == null)
            {
                return false; 
            }
 
            Size renderSize = container.RenderSize; 
            Point translatedPivotPoint = _root.TranslatePoint(LayoutUpdateDetectionPivotPoint, container);
 
            bool changed = (!DoubleUtil.AreClose(renderSize, _containerSize) ||
                            !DoubleUtil.AreClose(translatedPivotPoint, _containerPivotPoint));
            if (changed)
            { 
                // update cached values
                _containerSize = renderSize; 
                _containerPivotPoint = translatedPivotPoint; 
            }
 
            return changed;
        }

        private IEnumerable CurrentManipulators 
        {
            get { return (_currentManipulators.Count > 0) ? _currentManipulators.Values : null; } 
        } 

        internal bool IsManipulationActive 
        {
            get { return _manipulationProcessor != null; }
        }
 
        private bool IsInertiaActive
        { 
            get { return _inertiaProcessor != null; } 
        }
 
        private ManipulationDevice _manipulationDevice;

        private IInputElement _currentContainer;
        private ManipulationPivot _pivot; 
        private ManipulationModes _mode;
 
        private ManipulationProcessor2D _manipulationProcessor; 
        private InertiaProcessor2D _inertiaProcessor;
 
        // A list of manipulators that are currently active (i.e. fingers touching the screen)
        private Dictionary _currentManipulators = new Dictionary(2);

        // A list of manipulators that have been removed (stored to avoid allocating each frame) 
        private Dictionary _removedManipulators = new Dictionary(2);
 
        // When inertia starts, its values are relative to the end point specified in 
        // this event. WPF's API wants to expose inertia deltas relative to the first
        // Started event. This Completed event provides enough information to convert 
        // the delta values so that they are relative to the Started event.
        private ManipulationDelta _lastManipulationBeforeInertia;

        ///  
        ///     Critical: This event is sent to the input manager queue -- possible spoofing vector.
        ///  
        [SecurityCritical] 
        private InputEventArgs _generatedEvent;
 
        private DispatcherTimer _inertiaTimer;

        private bool _manualComplete;
        private bool _manualCompleteWithInertia; 

        private EventHandler _containerLayoutUpdated; 
 
        // pivot point to detect position and size change, see UpdateCachedPositionAndSize for more details
        // The odd magic number is to make it more rare. 
        private static readonly Point LayoutUpdateDetectionPivotPoint = new Point(-10234.1234, -10234.1234);

        // cached values to detect position and size change
        private Point _containerPivotPoint; 
        private Size _containerSize;
        private UIElement _root; 
    } 
}

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

using System; 
using System.Collections.Generic; 
using System.Diagnostics;
using System.Security; 
using System.Windows;
using System.Windows.Interop;
using System.Windows.Input.Manipulations;
using System.Windows.Media; 
using System.Windows.Threading;
using MS.Internal; 
using MS.Internal.PresentationCore; 

namespace System.Windows.Input 
{
    /// 
    ///     Handles detection of manipulations.
    ///  
    internal sealed class ManipulationLogic
    { 
        ///  
        ///     Instantiates an instance of this class.
        ///  
        internal ManipulationLogic(ManipulationDevice manipulationDevice)
        {
            _manipulationDevice = manipulationDevice;
        } 

        ///  
        ///     Hooked up to the manipulation processor and inertia processor's started event. 
        /// 
        ///  
        ///     SecurityCrticial: Calls PushEvent.
        ///     TreatAsSafe: Pushes a ManipulationStarted event, which does not need to be protected.
        /// 
        [SecurityCritical, SecurityTreatAsSafe] 
        private void OnManipulationStarted(object sender, Manipulation2DStartedEventArgs e)
        { 
            PushEvent(new ManipulationStartedEventArgs( 
                _manipulationDevice,
                LastTimestamp, 
                _currentContainer,
                new Point(e.OriginX, e.OriginY)));
        }
 
        /// 
        ///     Hooked up to the manipulation processor and inertia processor's delta event. 
        ///  
        /// 
        ///     SecurityCrticial: Calls PushEvent. 
        ///     TreatAsSafe: Pushes a ManipulationDelta event, which does not need to be protected.
        /// 
        [SecurityCritical, SecurityTreatAsSafe]
        private void OnManipulationDelta(object sender, Manipulation2DDeltaEventArgs e) 
        {
            var deltaArguments = new ManipulationDeltaEventArgs( 
                _manipulationDevice, 
                LastTimestamp,
                _currentContainer, 
                new Point(e.OriginX, e.OriginY),
                ConvertDelta(e.Delta, null),
                ConvertDelta(e.Cumulative, _lastManipulationBeforeInertia),
                ConvertVelocities(e.Velocities), 
                IsInertiaActive);
 
            PushEvent(deltaArguments); 
        }
 
        /// 
        ///     Hooked up to the manipulation processor's completed event.
        /// 
        ///  
        ///     SecurityCritical: Calls PushEvent.
        ///     TreatAsSafe: Pushes a ManipulationInertiaStartingEventArgs event, which doesn't need to be protected. 
        ///  
        [SecurityCritical, SecurityTreatAsSafe]
        private void OnManipulationCompleted(object sender, Manipulation2DCompletedEventArgs e) 
        {
            // Manipulation portion completed.

            if (_manualComplete && !_manualCompleteWithInertia) 
            {
                // This is the last event in the sequence. 
 
                ManipulationCompletedEventArgs completedArguments = ConvertCompletedArguments(e);
                RaiseManipulationCompleted(completedArguments); 
            }
            else
            {
                // This event will configure inertia, which will start after this event. 

                _lastManipulationBeforeInertia = ConvertDelta(e.Total, null); 
 
                ManipulationInertiaStartingEventArgs inertiaArguments = new ManipulationInertiaStartingEventArgs(
                    _manipulationDevice, 
                    LastTimestamp,
                    _currentContainer,
                    new Point(e.OriginX, e.OriginY),
                    ConvertVelocities(e.Velocities), 
                    false);
 
                PushEvent(inertiaArguments); 
            }
 
            _manipulationProcessor = null;
        }

        ///  
        ///     Hooked up to the inertia processor's completed event.
        ///  
        ///  
        ///     SecurityCritical: Calls PushEvent.
        ///     TreatAsSafe: Pushes a ManipulationInertiaStarting event, which doesn't need to be protected. 
        /// 
        [SecurityCritical, SecurityTreatAsSafe]
        private void OnInertiaCompleted(object sender, Manipulation2DCompletedEventArgs e)
        { 
            // Inertia portion completed.
 
            ClearTimer(); 

            if (_manualComplete && _manualCompleteWithInertia) 
            {
                // Another inertia portion was requested

                _lastManipulationBeforeInertia = ConvertDelta(e.Total, _lastManipulationBeforeInertia); 

                ManipulationInertiaStartingEventArgs inertiaArguments = new ManipulationInertiaStartingEventArgs( 
                    _manipulationDevice, 
                    LastTimestamp,
                    _currentContainer, 
                    new Point(e.OriginX, e.OriginY),
                    ConvertVelocities(e.Velocities),
                    true);
 
                PushEvent(inertiaArguments);
            } 
            else 
            {
                // This is the last event in the sequence. 

                ManipulationCompletedEventArgs completedArguments = ConvertCompletedArguments(e);

                RaiseManipulationCompleted(completedArguments); 
            }
 
            _inertiaProcessor = null; 
        }
 
        /// 
        ///     SecurityCritical: Calls PushEvent.
        ///     TreatAsSafe: Pushes a ManipulationCompleted event, which doesn't need to be protected.
        ///  
        [SecurityCritical, SecurityTreatAsSafe]
        private void RaiseManipulationCompleted(ManipulationCompletedEventArgs e) 
        { 
            PushEvent(e);
        } 

        /// 
        ///     Called after a Completed event has been processed.
        ///  
        internal void OnCompleted()
        { 
            _lastManipulationBeforeInertia = null; 
            SetContainer(null);
        } 

        /// 
        ///     Converts an Affine2DOperationCompletedEventArgs object into a ManipulationCompletedEventArgs object.
        ///  
        private ManipulationCompletedEventArgs ConvertCompletedArguments(Manipulation2DCompletedEventArgs e)
        { 
            return new ManipulationCompletedEventArgs( 
                _manipulationDevice,
                LastTimestamp, 
                _currentContainer,
                new Point(e.OriginX, e.OriginY),
                ConvertDelta(e.Total, _lastManipulationBeforeInertia),
                ConvertVelocities(e.Velocities), 
                IsInertiaActive);
        } 
 
        private static ManipulationDelta ConvertDelta(ManipulationDelta2D delta, ManipulationDelta add)
        { 
            if (add != null)
            {
                return new ManipulationDelta(
                    new Vector(delta.TranslationX + add.Translation.X, delta.TranslationY + add.Translation.Y), 
                    AngleUtil.RadiansToDegrees(delta.Rotation) + add.Rotation,
                    new Vector(delta.ScaleX * add.Scale.X, delta.ScaleY * add.Scale.Y), 
                    new Vector(delta.ExpansionX + add.Expansion.X, delta.ExpansionY + add.Expansion.Y)); 
            }
            else 
            {
                return new ManipulationDelta(
                    new Vector(delta.TranslationX, delta.TranslationY),
                    AngleUtil.RadiansToDegrees(delta.Rotation), 
                    new Vector(delta.ScaleX, delta.ScaleY),
                    new Vector(delta.ExpansionX, delta.ExpansionY)); 
            } 
        }
 
        private static ManipulationVelocities ConvertVelocities(ManipulationVelocities2D velocities)
        {
            return new ManipulationVelocities(
                new Vector(velocities.LinearVelocityX, velocities.LinearVelocityY), 
                AngleUtil.RadiansToDegrees(velocities.AngularVelocity),
                new Vector(velocities.ExpansionVelocityX, velocities.ExpansionVelocityY)); 
        } 

        ///  
        ///     Completes any pending manipulation or inerita processing.
        /// 
        /// 
        ///     If a manipulation is active, specifies whether to continue 
        ///     to an inertia phase (true) or simply end the sequence (true).
        ///  
        internal void Complete(bool withInertia) 
        {
            try 
            {
                _manualComplete = true;
                _manualCompleteWithInertia = withInertia;
 
                if (IsManipulationActive)
                { 
                    _manipulationProcessor.CompleteManipulation(GetCurrentTimestamp()); 
                }
                else if (IsInertiaActive) 
                {
                    _inertiaProcessor.Complete(GetCurrentTimestamp());
                }
            } 
            finally
            { 
                _manualComplete = false; 
                _manualCompleteWithInertia = false;
            } 
        }

        /// 
        ///     Gets ManipulationCompletedEventArgs object out of ManipulationInertiaStartingEventArgs 
        /// 
        private ManipulationCompletedEventArgs GetManipulationCompletedArguments(ManipulationInertiaStartingEventArgs e) 
        { 
            Debug.Assert(_lastManipulationBeforeInertia != null);
            return new ManipulationCompletedEventArgs( 
                _manipulationDevice,
                LastTimestamp,
                _currentContainer,
                new Point(e.ManipulationOrigin.X, e.ManipulationOrigin.Y), 
                _lastManipulationBeforeInertia,
                e.InitialVelocities, 
                IsInertiaActive); 
        }
 
        /// 
        ///     Starts the inertia phase based on the results of a ManipulationInertiaStarting event.
        /// 
        internal void BeginInertia(ManipulationInertiaStartingEventArgs e) 
        {
            if (e.CanBeginInertia()) 
            { 
                _inertiaProcessor = new InertiaProcessor2D();
                _inertiaProcessor.Delta += OnManipulationDelta; 
                _inertiaProcessor.Completed += OnInertiaCompleted;

                e.ApplyParameters(_inertiaProcessor);
 
                // Setup a timer to tick the inertia to completion
                _inertiaTimer = new DispatcherTimer(); 
                _inertiaTimer.Interval = TimeSpan.FromMilliseconds(15); 
                _inertiaTimer.Tick += new EventHandler(OnInertiaTick);
                _inertiaTimer.Start(); 
            }
            else
            {
                // This is the last event in the sequence. 
                ManipulationCompletedEventArgs completedArguments = GetManipulationCompletedArguments(e);
                RaiseManipulationCompleted(completedArguments); 
                PushEventsToDevice(); 
            }
        } 

        internal static Int64 GetCurrentTimestamp()
        {
            // Does QueryPerformanceCounter to get the current time in 100ns units 
            return MediaContext.CurrentTicks;
        } 
 
        private void OnInertiaTick(object sender, EventArgs e)
        { 
            // Tick the inertia
            if (IsInertiaActive)
            {
                if (!_inertiaProcessor.Process(GetCurrentTimestamp())) 
                {
                    ClearTimer(); 
                } 

                PushEventsToDevice(); 
            }
            else
            {
                ClearTimer(); 
            }
        } 
 
        private void ClearTimer()
        { 
            if (_inertiaTimer != null)
            {
                _inertiaTimer.Stop();
                _inertiaTimer = null; 
            }
        } 
 
        /// 
        ///     Prepares and raises a manipulation event. 
        /// 
        /// 
        ///     Critical: Adds an input event to a list that will eventually be added to the InputManager queue.
        ///               Accesses _generatedEvent. 
        /// 
        [SecurityCritical] 
        private void PushEvent(InputEventArgs e) 
        {
            // We only expect to generate one event at a time and should never need a queue. 
            Debug.Assert(_generatedEvent == null, "There is already a generated event waiting to be pushed.");
            _generatedEvent = e;
        }
 
        /// 
        ///     Pushes generated events to the inertia input provider. 
        ///  
        /// 
        ///     SecurityCritical: ProcessManipulationInput. Accesses _generatedEvent. 
        ///     TreatAsSafe: OK to send manipulation and inertia events.
        /// 
        [SecurityCritical, SecurityTreatAsSafe]
        internal void PushEventsToDevice() 
        {
            if (_generatedEvent != null) 
            { 
                InputEventArgs generatedEvent = _generatedEvent;
                _generatedEvent = null; 
                _manipulationDevice.ProcessManipulationInput(generatedEvent);
            }
        }
 
        /// 
        ///     Raises ManipulationBoundaryFeedback to allow handlers to provide feedback that manipulation has hit an edge. 
        ///  
        /// The total unused manipulation.
        ///  
        ///     SecurityCrticial: Calls PushEvent.
        ///     TreatAsSafe: Pushes a ManipulationBoundaryFeedbackEventArgs event, which does not need to be protected.
        /// 
        [SecurityCritical, SecurityTreatAsSafe] 
        internal void RaiseBoundaryFeedback(ManipulationDelta unusedManipulation, bool requestedComplete)
        { 
            bool hasUnusedManipulation = (unusedManipulation != null); 
            if ((!hasUnusedManipulation || requestedComplete) && HasPendingBoundaryFeedback)
            { 
                // Create a "zero" message to end currently pending feedback
                unusedManipulation = new ManipulationDelta(new Vector(), 0.0, new Vector(1.0, 1.0), new Vector());
                HasPendingBoundaryFeedback = false;
            } 
            else if (hasUnusedManipulation)
            { 
                HasPendingBoundaryFeedback = true; 
            }
 
            if (unusedManipulation != null)
            {
                PushEvent(new ManipulationBoundaryFeedbackEventArgs(_manipulationDevice, LastTimestamp, _currentContainer, unusedManipulation));
            } 
        }
 
        private bool HasPendingBoundaryFeedback 
        {
            get; 
            set;
        }

        private int LastTimestamp 
        {
            get; 
            set; 
        }
 
        internal void ReportFrame(ICollection manipulators)
        {
            Int64 timestamp = GetCurrentTimestamp();
            LastTimestamp = (int)timestamp; // InputEventArgs timestamps are Int32 while the processors take Int64 

            int numManipulators = manipulators.Count; 
            if (IsInertiaActive && (numManipulators > 0)) 
            {
                // Inertia is active but now there are fingers, stop inertia 
                _inertiaProcessor.Complete(timestamp);
                PushEventsToDevice();
            }
 
            if (!IsManipulationActive && (numManipulators > 0))
            { 
                // Time to start a new manipulation 

                ManipulationStartingEventArgs startingArgs = RaiseStarting(); 
                if (!startingArgs.RequestedCancel && (startingArgs.Mode != ManipulationModes.None))
                {
                    // Determine if we allow single-finger manipulation
                    if (startingArgs.IsSingleTouchEnabled || (numManipulators >= 2)) 
                    {
                        SetContainer(startingArgs.ManipulationContainer); 
                        _mode = startingArgs.Mode; 
                        _pivot = startingArgs.Pivot;
                        IList parameters = startingArgs.Parameters; 

                        _manipulationProcessor = new ManipulationProcessor2D(ConvertMode(_mode), ConvertPivot(_pivot));

                        if (parameters != null) 
                        {
                            int count = parameters.Count; 
                            for (int i = 0; i < parameters.Count; i++) 
                            {
                                _manipulationProcessor.SetParameters(parameters[i]); 
                            }
                        }

                        _manipulationProcessor.Started += OnManipulationStarted; 
                        _manipulationProcessor.Delta += OnManipulationDelta;
                        _manipulationProcessor.Completed += OnManipulationCompleted; 
 
                        _currentManipulators.Clear();
                    } 
                }
            }

            if (IsManipulationActive) 
            {
                // A manipulation process is available to process this frame of manipulators 
                UpdateManipulators(manipulators); 
                _manipulationProcessor.ProcessManipulators(timestamp, CurrentManipulators);
                PushEventsToDevice(); 
            }
        }

        ///  
        ///     Critical - Calls ProcessManipulationInput.
        ///     TreatAsSafe - Creates the event being raised itself, an event that is not considered critical. 
        ///  
        [SecurityCritical, SecurityTreatAsSafe]
        private ManipulationStartingEventArgs RaiseStarting() 
        {
            ManipulationStartingEventArgs starting = new ManipulationStartingEventArgs(_manipulationDevice, Environment.TickCount);
            starting.ManipulationContainer = _manipulationDevice.Target;
 
            _manipulationDevice.ProcessManipulationInput(starting);
 
            return starting; 
        }
 
        internal IInputElement ManipulationContainer
        {
            get { return _currentContainer; }
            set 
            {
                // 
 

 
                SetContainer(value);
            }
        }
 
        internal ManipulationModes ManipulationMode
        { 
            get { return _mode; } 
            set
            { 
                _mode = value;
                if (_manipulationProcessor != null)
                {
                    _manipulationProcessor.SupportedManipulations = ConvertMode(_mode); 
                }
            } 
        } 

        private static Manipulations2D ConvertMode(ManipulationModes mode) 
        {
            Manipulations2D manipulations = Manipulations2D.None;

            if ((mode & ManipulationModes.TranslateX) != 0) 
            {
                manipulations |= Manipulations2D.TranslateX; 
            } 

            if ((mode & ManipulationModes.TranslateY) != 0) 
            {
                manipulations |= Manipulations2D.TranslateY;
            }
 
            if ((mode & ManipulationModes.Scale) != 0)
            { 
                manipulations |= Manipulations2D.Scale; 
            }
 
            if ((mode & ManipulationModes.Rotate) != 0)
            {
                manipulations |= Manipulations2D.Rotate;
            } 

            return manipulations; 
        } 

        internal ManipulationPivot ManipulationPivot 
        {
            get { return _pivot; }
            set
            { 
                _pivot = value;
                if (_manipulationProcessor != null) 
                { 
                    _manipulationProcessor.Pivot = ConvertPivot(value);
                } 
            }
        }

        private static ManipulationPivot2D ConvertPivot(ManipulationPivot pivot) 
        {
            if (pivot != null) 
            { 
                Point center = pivot.Center;
                return new ManipulationPivot2D() 
                {
                    X = (float)center.X,
                    Y = (float)center.Y,
                    Radius = (float)Math.Max(1.0, pivot.Radius) 
                };
            } 
 
            return null;
        } 

        internal void SetManipulationParameters(ManipulationParameters2D parameter)
        {
            if (_manipulationProcessor != null) 
            {
                _manipulationProcessor.SetParameters(parameter); 
            } 
        }
 
        private void UpdateManipulators(ICollection updatedManipulators)
        {
            // Clear out the old removed collection and use it to store
            // the new current collection. The old current collection 
            // will be used to generate the new removed collection.
            _removedManipulators.Clear(); 
            var temp = _removedManipulators; 
            _removedManipulators = _currentManipulators;
            _currentManipulators = temp; 

            // End the manipulation if the element is not
            // visible anymore
            UIElement uie = _currentContainer as UIElement; 
            if (uie != null)
            { 
                if (!uie.IsVisible) 
                {
                    return; 
                }
            }
            else
            { 
                UIElement3D uie3D = _currentContainer as UIElement3D;
                if (uie3D != null && 
                    !uie3D.IsVisible) 
                {
                    return; 
                }
            }

            // For each updated manipulator, convert it to the correct format in the 
            // current collection and remove it from the removed collection. What is left
            // in the removed collection will be the manipulators that were removed. 
            foreach (IManipulator updatedManipulator in updatedManipulators) 
            {
                // 
                int id = updatedManipulator.Id;
                _removedManipulators.Remove(id); // This manipulator was not removed
                Point position = updatedManipulator.GetPosition(_currentContainer);
                position = _manipulationDevice.GetTransformedManipulatorPosition(position); 
                _currentManipulators[id] = new Manipulator2D(id, (float)position.X, (float)position.Y);
            } 
        } 

        ///  
        ///     Critical - Calls PresentationSource.CriticalFromVisual.
        ///     TreatAsSafe - Does not expose PresentationSource itself.
        /// 
        [SecurityCritical, SecurityTreatAsSafe] 
        private void SetContainer(IInputElement newContainer)
        { 
            // unsubscribe from LayoutUpdated 
            UnsubscribeFromLayoutUpdated();
 
            // clear cached values
            _containerPivotPoint = new Point();
            _containerSize = new Size();
            _root = null; 

            // remember the new container 
            _currentContainer = newContainer; 

            if (newContainer != null) 
            {
                // get the new root
                PresentationSource presentationSource = PresentationSource.CriticalFromVisual((Visual)newContainer);
                if (presentationSource != null) 
                {
                    _root = presentationSource.RootVisual as UIElement; 
                } 

                // subscribe to LayoutUpdated 
                if (_containerLayoutUpdated != null)
                {
                    SubscribeToLayoutUpdated();
                } 
            }
        } 
 
        internal event EventHandler ContainerLayoutUpdated
        { 
            add
            {
                bool wasNull = _containerLayoutUpdated == null;
                _containerLayoutUpdated += value; 

                // if this is the first handler, try to subscribe to LayoutUpdated event 
                if (wasNull && _containerLayoutUpdated != null) 
                {
                    SubscribeToLayoutUpdated(); 
                }
            }
            remove
            { 
                bool wasNull = _containerLayoutUpdated == null;
                _containerLayoutUpdated -= value; 
 
                // if this is the last handler, unsubscribe from LayoutUpdated event
                if (!wasNull && _containerLayoutUpdated == null) 
                {
                    UnsubscribeFromLayoutUpdated();
                }
            } 
        }
 
        private void SubscribeToLayoutUpdated() 
        {
            UIElement container = _currentContainer as UIElement; 
            if (container != null)
            {
                container.LayoutUpdated += OnLayoutUpdated;
            } 
        }
 
        private void UnsubscribeFromLayoutUpdated() 
        {
            UIElement container = _currentContainer as UIElement; 
            if (container != null)
            {
                container.LayoutUpdated -= OnLayoutUpdated;
            } 
        }
 
        ///  
        /// OnLayoutUpdated handler, raises ContainerLayoutUpdated event if container's position or size have been changed
        /// since the last LayoutUpdate. 
        /// 
        /// 
        /// 
        private void OnLayoutUpdated(object sender, EventArgs e) 
        {
            Debug.Assert(_containerLayoutUpdated != null); 
 
            //check position and size and update the cached values
            if (UpdateCachedPositionAndSize()) 
            {
                _containerLayoutUpdated(this, EventArgs.Empty);
            }
        } 

        private bool UpdateCachedPositionAndSize() 
        { 
            // Determine if the manipulation needs to be updated because of position or size change.
            // * Size change is detected by comparing RenderSize 
            // * Position change is detected by translating PivotPoint to the element coordinate, in general
            // this is not accurate because rotation over PivotPoint won't be detected but the PivotPoint is selected far outside
            // of the Window bounds, so practically that should be a very rare case.
            // The more accurate solution would require 2 or 3 points which is more expensive. 
            if (_root == null)
            { 
                return false; 
            }
 
            UIElement container = _currentContainer as UIElement;
            if (container == null)
            {
                return false; 
            }
 
            Size renderSize = container.RenderSize; 
            Point translatedPivotPoint = _root.TranslatePoint(LayoutUpdateDetectionPivotPoint, container);
 
            bool changed = (!DoubleUtil.AreClose(renderSize, _containerSize) ||
                            !DoubleUtil.AreClose(translatedPivotPoint, _containerPivotPoint));
            if (changed)
            { 
                // update cached values
                _containerSize = renderSize; 
                _containerPivotPoint = translatedPivotPoint; 
            }
 
            return changed;
        }

        private IEnumerable CurrentManipulators 
        {
            get { return (_currentManipulators.Count > 0) ? _currentManipulators.Values : null; } 
        } 

        internal bool IsManipulationActive 
        {
            get { return _manipulationProcessor != null; }
        }
 
        private bool IsInertiaActive
        { 
            get { return _inertiaProcessor != null; } 
        }
 
        private ManipulationDevice _manipulationDevice;

        private IInputElement _currentContainer;
        private ManipulationPivot _pivot; 
        private ManipulationModes _mode;
 
        private ManipulationProcessor2D _manipulationProcessor; 
        private InertiaProcessor2D _inertiaProcessor;
 
        // A list of manipulators that are currently active (i.e. fingers touching the screen)
        private Dictionary _currentManipulators = new Dictionary(2);

        // A list of manipulators that have been removed (stored to avoid allocating each frame) 
        private Dictionary _removedManipulators = new Dictionary(2);
 
        // When inertia starts, its values are relative to the end point specified in 
        // this event. WPF's API wants to expose inertia deltas relative to the first
        // Started event. This Completed event provides enough information to convert 
        // the delta values so that they are relative to the Started event.
        private ManipulationDelta _lastManipulationBeforeInertia;

        ///  
        ///     Critical: This event is sent to the input manager queue -- possible spoofing vector.
        ///  
        [SecurityCritical] 
        private InputEventArgs _generatedEvent;
 
        private DispatcherTimer _inertiaTimer;

        private bool _manualComplete;
        private bool _manualCompleteWithInertia; 

        private EventHandler _containerLayoutUpdated; 
 
        // pivot point to detect position and size change, see UpdateCachedPositionAndSize for more details
        // The odd magic number is to make it more rare. 
        private static readonly Point LayoutUpdateDetectionPivotPoint = new Point(-10234.1234, -10234.1234);

        // cached values to detect position and size change
        private Point _containerPivotPoint; 
        private Size _containerSize;
        private UIElement _root; 
    } 
}

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