aboutsummaryrefslogtreecommitdiffstats
path: root/Software/Graphics/True Color Capture
ModeNameSize
-rw-r--r--hand_fabric.png231422logstatsplain
-rw-r--r--hand_phone.png268124logstatsplain
-rw-r--r--loading_background.png347710logstatsplain
-rw-r--r--take_picture.png232959logstatsplain
using RealTimeGraphEx.Components;
using RealTimeGraphEx.Enums;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Runtime.ExceptionServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Input;
using System.Windows.Markup;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using System.Windows.Threading;

namespace RealTimeGraphEx
{
    #region Delegates

    public delegate void ZoomCompleteDelegate(Point transformOrigin, double scaleX, double scaleY);
    public delegate void PanningCompleteDelegate(Point translate);

    #endregion

    /// <summary>
    /// Represents an abstract base class for the RealTimeGraphEx.
    /// </summary>
    [ContentProperty("Components")]
    public abstract class RealTimeGraphExBase : UserControl, IDisposable
    {
        private Rect lastBounds;
        private Point lastCenter;
        private bool _loaded;

        #region Events

        /// <summary>
        /// Occurs just before rendering the actual visuals on the writeable bitmap.
        /// </summary>
        public event Action<WriteableBitmap, double> BeforeRenderVisuals;

        /// <summary>
        /// Occurs after rendering the actual visuals on the writeable bitmap.
        /// </summary>
        public event Action<WriteableBitmap, double> AfterRenderVisuals;

        /// <summary>
        /// Occurs when [zooming to].
        /// </summary>
        public event ZoomCompleteDelegate ZoomComplete;

        /// <summary>
        /// Occurs when [panning to].
        /// </summary>
        public event PanningCompleteDelegate PanningComplete;

        /// <summary>
        /// Occurs when the mouse pointer moves while over this element.
        /// </summary>
        public new event MouseEventHandler MouseMove;

        /// <summary>
        /// Occurs when the mouse pointer leaves the bounds of this element.
        /// </summary>
        public new event MouseEventHandler MouseLeave;

        /// <summary>
        /// Occurs when [minimum maximum changed].
        /// </summary>
        public event EventHandler MinMaxChanged;

        #endregion

        #region Protected Fields

        //Visuals
        internal Grid gridMain;
        internal Grid gridLinesAndImageWrapperGrid;
        internal Grid gridBack;
        internal StackPanel stackLeft;
        internal StackPanel stackRight;
        internal double virtualMinimum;
        internal double virtualMaximum;
        internal double virtualStart;
        internal double virtualEnd;
        protected Image img;
        internal Grid gridInnerContentWrapper;
        //protected ScaleTransform scaleTransform;
        //protected TranslateTransform translateTransform;
        protected Canvas selectionCanvas;
        protected Rectangle selectionRectangle;
        protected Thumb moveThumb;
        protected bool isSelectionMouseDown;
        protected bool isScaled;

        //Multi threading
        protected Thread pushThread;
        protected ManualResetEvent _requestTermination;
        protected ManualResetEvent _terminated;

        //Plotting
        protected double xValueCounter;

        //Resizing
        protected DispatcherTimer resizeTimer;
        protected bool isAfterResize;

        #endregion

        #region Properties

        /// <summary>
        /// Gets or sets whether this graph is controlled by a synchronization manager.
        /// </summary>
        internal bool IsSynced { get; set; }

        /// <summary>
        /// Gets or sets an extra content which will be placed on top of the graph control.
        /// </summary>
        public FrameworkElement InnerContent
        {
            get { return (FrameworkElement)GetValue(InnerContentProperty); }
            set { SetValue(InnerContentProperty, value); }
        }
        public static readonly DependencyProperty InnerContentProperty =
            DependencyProperty.Register("InnerContent", typeof(FrameworkElement), typeof(RealTimeGraphExBase), new PropertyMetadata(null, new PropertyChangedCallback(InnerContentChanged)));
        private static void InnerContentChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            //Add the content control to the inner wrapper grid.
            (d as RealTimeGraphExBase).gridInnerContentWrapper.Children.Add((d as RealTimeGraphExBase).InnerContent);
        }

        /// <summary>
        /// Gets or sets the maximum expected value to be plotted on the graph (default 255).
        /// </summary>
        public double Maximum
        {
            get { return (double)GetValue(MaximumProperty); }
            set { SetValue(MaximumProperty, value); }
        }
        public static readonly DependencyProperty MaximumProperty =
            DependencyProperty.Register("Maximum", typeof(double), typeof(RealTimeGraphExBase), new PropertyMetadata(255.0, (d, e) =>
             {
                 var control = d as RealTimeGraphExBase;
                 CrossModelChanged(d, e);
                 control.MinMaxChanged?.Invoke(control, new EventArgs());
             }));

        /// <summary>
        /// Gets or sets the minimum expected value to be plotted on the graph (default 0).
        /// </summary>
        public double Minimum
        {
            get { return (double)GetValue(MinimumProperty); }
            set { SetValue(MinimumProperty, value); }
        }
        public static readonly DependencyProperty MinimumProperty =
            DependencyProperty.Register("Minimum", typeof(double), typeof(RealTimeGraphExBase), new PropertyMetadata(0.0, (d, e) =>
            {
                var control = d as RealTimeGraphExBase;
                CrossModelChanged(d, e);
                control.MinMaxChanged?.Invoke(control, new EventArgs());
            }));



        /// <summary>
        /// Gets or sets the graph refresh rate in milliseconds (default 30, affects performance).
        /// </summary>
        public int RefreshRate
        {
            get { return (int)GetValue(RefreshRateProperty); }
            set { SetValue(RefreshRateProperty, value); }
        }
        public static readonly DependencyProperty RefreshRateProperty =
            DependencyProperty.Register("RefreshRate", typeof(int), typeof(RealTimeGraphExBase), new PropertyMetadata(30, new PropertyChangedCallback(CrossModelChanged)));

        /// <summary>
        /// Gets or sets whether the graph will be rendered antialiased (affects performance).
        /// </summary>
        public bool Antialiased
        {
            get { return (bool)GetValue(AntialiasedProperty); }
            set { SetValue(AntialiasedProperty, value); }
        }
        public static readonly DependencyProperty AntialiasedProperty =
            DependencyProperty.Register("Antialiased", typeof(bool), typeof(RealTimeGraphExBase), new PropertyMetadata(true, new PropertyChangedCallback(CrossModelChanged)));

        /// <summary>
        /// Gets or sets the collection of graph add-on components.
        /// </summary>
        public ObservableCollection<ComponentBase> Components
        {
            get { return (ObservableCollection<ComponentBase>)GetValue(ComponentsProperty); }
            set { SetValue(ComponentsProperty, value); }
        }
        public static readonly DependencyProperty ComponentsProperty =
            DependencyProperty.Register("Components", typeof(ObservableCollection<ComponentBase>), typeof(RealTimeGraphExBase), new PropertyMetadata(null, new PropertyChangedCallback(ComponentsChanged)));
        private static void ComponentsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            (d as RealTimeGraphExBase).OnRenderComponents();
        }

        /// <summary>
        /// Gets or sets a value indicating whether this graph is paused.
        /// </summary>
        /// <value>
        ///   <c>true</c> if this instance is paused; otherwise, <c>false</c>.
        /// </value>
        public bool IsPaused
        {
            get { return (bool)GetValue(IsPausedProperty); }
            set { SetValue(IsPausedProperty, value); }
        }
        public static readonly DependencyProperty IsPausedProperty =
            DependencyProperty.Register("IsPaused", typeof(bool), typeof(RealTimeGraphExBase), new PropertyMetadata(false, new PropertyChangedCallback(CrossModelChanged)));

        ///// <summary>
        ///// Gets or sets the zoom level for the graph.
        ///// </summary>
        public double Zoom
        {
            get { return (double)GetValue(ZoomProperty); }
            set { SetValue(ZoomProperty, value); }
        }
        public static readonly DependencyProperty ZoomProperty =
            DependencyProperty.Register("Zoom", typeof(double), typeof(RealTimeGraphExBase), new PropertyMetadata(1.0, new PropertyChangedCallback(ZoomChanged)));
        private static void ZoomChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var control = d as RealTimeGraphExBase;

            if (control.ZoomMode != ZoomModeEnum.Manual && control.ZoomMode != ZoomModeEnum.MouseWheel) return;

            if (control.Zoom < 1)
            {
                control.Zoom = 1;
                return;
            }

            if (control.Zoom > 1)
            {
                control.gridLinesAndImageWrapperGrid.Cursor = Cursors.SizeAll;
            }
            else
            {
                control.gridLinesAndImageWrapperGrid.Cursor = Cursors.Arrow;
            }

            control.gridLinesAndImageWrapperGrid.RenderTransformOrigin = new Point(0.5, 0.5);
            //control.scaleTransform.ScaleX = control.Zoom;
            //control.scaleTransform.ScaleY = control.Zoom;

            double desiredWidth = control.gridMain.ActualWidth * control.Zoom;
            double desiredHeight = control.gridMain.ActualHeight * control.Zoom;
            double desiredLeft = ((desiredWidth - control.gridMain.ActualWidth) / 2) * -1;
            double desiredTop = ((desiredHeight - control.gridMain.ActualHeight) / 2) * -1;

            control.gridLinesAndImageWrapperGrid.Width = desiredWidth;
            control.gridLinesAndImageWrapperGrid.Height = desiredHeight;
            control.gridLinesAndImageWrapperGrid.Margin = new Thickness(desiredLeft, desiredTop, 0, 0);

            Point relativePoint = control.gridLinesAndImageWrapperGrid.TransformToAncestor(control.gridMain).Transform(new Point(0, 0));
            Rect bounds = control.BoundsRelativeTo(control.gridLinesAndImageWrapperGrid, control.gridMain);

            if (desiredLeft > 0)
            {
                control.gridLinesAndImageWrapperGrid.Margin = new Thickness(0, control.gridLinesAndImageWrapperGrid.Margin.Top, 0, 0);
            }

            if (desiredTop > 0)
            {
                control.gridLinesAndImageWrapperGrid.Margin = new Thickness(control.gridLinesAndImageWrapperGrid.Margin.Left, 0, 0, 0);
            }

            if (desiredWidth < control.gridMain.ActualWidth)
            {
                control.gridLinesAndImageWrapperGrid.Margin = new Thickness(0, control.gridLinesAndImageWrapperGrid.Margin.Top, 0, 0);
            }

            if (desiredHeight < control.gridMain.ActualHeight)
            {
                control.gridLinesAndImageWrapperGrid.Margin = new Thickness(control.gridLinesAndImageWrapperGrid.Margin.Left, 0, 0, 0);
            }

            control.OnZoomingComplete(new Point(0, 0), control.Zoom, control.Zoom);
        }

        /// <summary>
        /// Gets or sets the zoom mode.
        /// </summary>
        /// <value>
        /// The zoom mode.
        /// </value>
        public ZoomModeEnum ZoomMode
        {
            get { return (ZoomModeEnum)GetValue(ZoomModeProperty); }
            set { SetValue(ZoomModeProperty, value); }
        }
        public static readonly DependencyProperty ZoomModeProperty =
            DependencyProperty.Register("ZoomMode", typeof(ZoomModeEnum), typeof(RealTimeGraphExBase), new PropertyMetadata(ZoomModeEnum.Selection));

        /// <summary>
        /// Gets or sets the mouse value.
        /// </summary>
        /// <value>
        /// The mouse value.
        /// </value>
        public double MouseValue
        {
            get { return (double)GetValue(MouseValueProperty); }
            set { SetValue(MouseValueProperty, value); }
        }
        public static readonly DependencyProperty MouseValueProperty =
            DependencyProperty.Register("MouseValue", typeof(double), typeof(RealTimeGraphExBase), new PropertyMetadata(0.0));

        /// <summary>
        /// Gets or sets the selection fill.
        /// </summary>
        /// <value>
        /// The selection fill.
        /// </value>
        public Brush SelectionFill
        {
            get { return (Brush)GetValue(SelectionFillProperty); }
            set { SetValue(SelectionFillProperty, value); }
        }
        public static readonly DependencyProperty SelectionFillProperty =
            DependencyProperty.Register("SelectionFill", typeof(Brush), typeof(RealTimeGraphExBase), new PropertyMetadata(new SolidColorBrush() { Color = Colors.Black, Opacity = 0.2 }, new PropertyChangedCallback(SelectionFillChanged)));
        private static void SelectionFillChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var control = d as RealTimeGraphExBase;
            control.selectionRectangle.Fill = control.SelectionFill;
        }

        /// <summary>
        /// Gets or sets the selection fill.
        /// </summary>
        /// <value>
        /// The selection fill.
        /// </value>
        public Brush SelectionStroke
        {
            get { return (Brush)GetValue(SelectionStrokeProperty); }
            set { SetValue(SelectionStrokeProperty, value); }
        }
        public static readonly DependencyProperty SelectionStrokeProperty =
            DependencyProperty.Register("SelectionStroke", typeof(Brush), typeof(RealTimeGraphExBase), new PropertyMetadata(Brushes.White, new PropertyChangedCallback(SelectionStrokeChanged)));
        private static void SelectionStrokeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var control = d as RealTimeGraphExBase;
            control.selectionRectangle.Stroke = control.SelectionStroke;
        }

        public ZoomDirectionEnum ZoomDirection
        {
            get { return (ZoomDirectionEnum)GetValue(ZoomDirectionProperty); }
            set { SetValue(ZoomDirectionProperty, value); }
        }
        public static readonly DependencyProperty ZoomDirectionProperty =
            DependencyProperty.Register("ZoomDirection", typeof(ZoomDirectionEnum), typeof(RealTimeGraphExBase), new PropertyMetadata(ZoomDirectionEnum.Both));

        /// <summary>
        /// Gets or sets the maximum points to display on the graph (default 1000).
        /// </summary>
        public int MaxPoints
        {
            get { return (int)GetValue(MaxPointsProperty); }
            set { SetValue(MaxPointsProperty, value); }
        }
        public static readonly DependencyProperty MaxPointsProperty =
            DependencyProperty.Register("MaxPoints", typeof(int), typeof(RealTimeGraphExBase), new PropertyMetadata(1000, new PropertyChangedCallback(CrossModelChanged)));


        /// <summary>
        /// Gets or sets a value indicating whether [use automatic range].
        /// </summary>
        public bool UseAutoRange
        {
            get { return (bool)GetValue(UseAutoRangeProperty); }
            set { SetValue(UseAutoRangeProperty, value); }
        }
        public static readonly DependencyProperty UseAutoRangeProperty =
            DependencyProperty.Register("UseAutoRange", typeof(bool), typeof(RealTimeGraphExBase), new PropertyMetadata(false, new PropertyChangedCallback(CrossModelChanged)));

        #endregion

        #region Cross Thread Fields

        protected double _height;
        protected double _width;
        protected double _mainHeight;
        protected double _mainWidth;
        protected double _offSetX;
        protected double _offSetY;
        protected int _refreshRate;
        protected double _maximum;
        protected double _minimum;
        protected double _originalMinimum;
        protected bool _antialiased;
        protected bool _isPaused;
        protected bool _disableRendering;
        protected bool _useAutoRange;
        #endregion

        #region Constructors

        /// <summary>
        /// Initializes a new instance of RealTimeGraphExBase
        /// </summary>
        public RealTimeGraphExBase()
        {
            this.Loaded += OnGraphLoaded;
            this.SizeChanged += OnSizeChanged;
            resizeTimer = new DispatcherTimer();
            resizeTimer.Interval = TimeSpan.FromMilliseconds(150);
            resizeTimer.IsEnabled = false;
            resizeTimer.Tick += OnResizeEnd;

            Components = new ObservableCollection<ComponentBase>();

            //Uri NewTheme = new Uri(@"/RealTimeGraphEx;component/Resources/Resources.xaml", UriKind.Relative);
            //ResourceDictionary dictionary = (ResourceDictionary)Application.LoadComponent(NewTheme);
            //Application.Current.Resources.MergedDictionaries.Add(dictionary);

            Initialize();
        }

        #endregion

        #region Virtual Methods

        /// <summary>
        /// Enabled/Disabled the rendering of the graph's image.
        /// </summary>
        protected virtual void ChangeRenderMode(bool enabled)
        {
            _disableRendering = !enabled;
        }

        /// <summary>
        /// Raises the BeforeRenderVisuals event.
        /// </summary>
        /// <param name="bmp"></param>
        protected virtual void OnBeforeRenderingVisuals(WriteableBitmap bmp, double scaleFactor)
        {
            if (BeforeRenderVisuals != null) BeforeRenderVisuals(bmp, scaleFactor);
        }

        /// <summary>
        /// Raises the AfterRenderVisuals event.
        /// </summary>
        /// <param name="bmp"></param>
        protected virtual void OnAfterRenderingVisuals(WriteableBitmap bmp, double scaleFactor)
        {
            if (AfterRenderVisuals != null) AfterRenderVisuals(bmp, scaleFactor);
        }

        /// <summary>
        /// Override this method to apply logic when graph resize is complete.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        protected virtual void OnResizeEnd(object sender, EventArgs e)
        {
            resizeTimer.IsEnabled = false;
            resizeTimer.Stop();
            OnSetCrossThreadFields();
            isAfterResize = true;
        }

        /// <summary>
        /// Override this method to apply logic while graph is resizing.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        protected virtual void OnSizeChanged(object sender, SizeChangedEventArgs e)
        {
            resizeTimer.IsEnabled = true;
            resizeTimer.Stop();
            resizeTimer.Start();
        }

        /// <summary>
        /// Initializes the control.
        /// </summary>
        protected virtual void Initialize()
        {
            //Initialize the main grid
            gridMain = new Grid() { ClipToBounds = true };

            gridMain.ColumnDefinitions.Add(new ColumnDefinition() { Width = GridLength.Auto });
            gridMain.ColumnDefinitions.Add(new ColumnDefinition() { Width = new GridLength(1, GridUnitType.Star) });
            gridMain.ColumnDefinitions.Add(new ColumnDefinition() { Width = GridLength.Auto });

            //Initialize the grid lines and image wrapper grid.
            gridLinesAndImageWrapperGrid = new Grid() { Background = Brushes.Transparent };

            //Initialize the grid lines grid.
            gridBack = new Grid();

            //Initialize the graph image.
            img = new Image() { Stretch = Stretch.None, HorizontalAlignment = HorizontalAlignment.Left, VerticalAlignment = VerticalAlignment.Stretch };

            //Initialize the Y Axis grid.
            stackLeft = new StackPanel() { HorizontalAlignment = HorizontalAlignment.Left, Orientation = Orientation.Horizontal, FlowDirection = System.Windows.FlowDirection.LeftToRight };

            //Initializing the X Axis grid.
            stackRight = new StackPanel() { HorizontalAlignment = HorizontalAlignment.Right, Orientation = Orientation.Horizontal, FlowDirection = System.Windows.FlowDirection.RightToLeft };

            //Initialize the inner content wrapper.
            gridInnerContentWrapper = new Grid();

            //Add the main grid to the control.
            this.Content = gridMain;

            //Add the wrapper grid to the main grid.
            gridMain.Children.Add(gridLinesAndImageWrapperGrid);

            //Add the grid lines grid to the wrapper.
            gridLinesAndImageWrapperGrid.Children.Add(gridBack);

            //Add the graph image to the wrapper.
            gridMain.Children.Add(img);
            img.IsHitTestVisible = false;
            Grid.SetColumnSpan(img, 3);

            //Add the left grid to the main grid.
            gridMain.Children.Add(stackLeft);

            //Add the right grid to the main grid.
            gridMain.Children.Add(stackRight);

            gridMain.Children.Add(gridInnerContentWrapper);

            Grid.SetColumn(stackLeft, 0);
            Grid.SetColumnSpan(stackLeft, 1);
            Grid.SetColumn(stackRight, 2);
            Grid.SetColumnSpan(stackRight, 1);
            Grid.SetColumn(gridLinesAndImageWrapperGrid, 1);
            Grid.SetColumnSpan(gridLinesAndImageWrapperGrid, 1);
            Grid.SetColumn(gridInnerContentWrapper, 0);
            Grid.SetColumnSpan(gridInnerContentWrapper, 3);

            //Set size changed event to get the exact size of image grid!
            gridLinesAndImageWrapperGrid.SizeChanged += OnImageGridSizeChanged;

            //Zooming
            //scaleTransform = new ScaleTransform(1, 1);
            //translateTransform = new TranslateTransform(0, 0);
            //TransformGroup group = new TransformGroup();
            //group.Children.Add(scaleTransform);
            //group.Children.Add(translateTransform);
            //gridLinesAndImageWrapperGrid.RenderTransform = group;
            gridLinesAndImageWrapperGrid.PreviewMouseWheel += gridLinesAndImageWrapperGrid_PreviewMouseWheel;
            moveThumb = new Thumb() { HorizontalAlignment = System.Windows.HorizontalAlignment.Stretch, VerticalAlignment = System.Windows.VerticalAlignment.Stretch, Opacity = 0 };
            if (!gridLinesAndImageWrapperGrid.Children.Contains(moveThumb))
            {
                gridLinesAndImageWrapperGrid.Children.Add(moveThumb);
                moveThumb.DragDelta += moveThumb_DragDelta;
            }

            selectionCanvas = new Canvas() { Background = null, HorizontalAlignment = System.Windows.HorizontalAlignment.Stretch, VerticalAlignment = System.Windows.VerticalAlignment.Stretch };
            Panel.SetZIndex(moveThumb, 9999);
            Grid.SetColumnSpan(selectionCanvas, 3);
            gridMain.Children.Add(selectionCanvas);
            selectionRectangle = new Rectangle() { Fill = SelectionFill, Stroke = SelectionStroke };

            gridLinesAndImageWrapperGrid.PreviewMouseDown += gridLinesAndImageWrapperGrid_PreviewMouseDown;
            selectionCanvas.MouseMove += selectionCanvas_MouseMove;
            selectionCanvas.PreviewMouseUp += selectionCanvas_PreviewMouseUp;
            gridLinesAndImageWrapperGrid.PreviewMouseLeftButtonDown += gridLinesAndImageWrapperGrid_PreviewMouseLeftButtonDown;
            gridLinesAndImageWrapperGrid.MouseMove += OnMouseMove;
            gridLinesAndImageWrapperGrid.MouseLeave += OnMouseLeave;
            this.SizeChanged += (x, y) => { if (isScaled) { ResetZoom(); }; };
        }

        /// <summary>
        /// Resets the graph zooming.
        /// </summary>
        public virtual void ResetZoom()
        {
            if (!isScaled) return;

            isScaled = false;

            gridLinesAndImageWrapperGrid.RenderTransformOrigin = new Point(0.5, 0.5);

            DoubleAnimation aniX = new DoubleAnimation();
            aniX.Duration = new Duration(TimeSpan.FromSeconds(0.1));
            aniX.To = gridMain.ActualWidth;

            DoubleAnimation aniY = new DoubleAnimation();
            aniY.Duration = new Duration(TimeSpan.FromSeconds(0.1));
            aniY.To = gridMain.ActualHeight;

            ThicknessAnimation marginAni = new ThicknessAnimation();
            marginAni.Duration = new Duration(TimeSpan.FromSeconds(0.1));
            marginAni.To = new Thickness(0, 0, 0, 0);

            if (!double.IsNaN(gridLinesAndImageWrapperGrid.Width))
            {
                gridLinesAndImageWrapperGrid.BeginAnimation(Grid.WidthProperty, aniX);
            }

            if (!double.IsNaN(gridLinesAndImageWrapperGrid.Height))
            {
                gridLinesAndImageWrapperGrid.BeginAnimation(Grid.HeightProperty, aniY);
            }

            aniY.CurrentTimeInvalidated += (x, y) =>
            {
                ApplyVirtualMaximumMinimum();
            };

            marginAni.Completed += (x, y) =>
            {
                if (!double.IsNaN(gridLinesAndImageWrapperGrid.Width))
                {
                    gridLinesAndImageWrapperGrid.BeginAnimation(Grid.WidthProperty, null);
                }

                if (!double.IsNaN(gridLinesAndImageWrapperGrid.Height))
                {
                    gridLinesAndImageWrapperGrid.BeginAnimation(Grid.HeightProperty, null);
                }

                gridLinesAndImageWrapperGrid.Width = double.NaN;
                gridLinesAndImageWrapperGrid.Height = double.NaN;

                gridLinesAndImageWrapperGrid.BeginAnimation(Grid.MarginProperty, null);
                gridLinesAndImageWrapperGrid.Margin = new Thickness(0, 0, 0, 0);

                ApplyVirtualMaximumMinimum();

                Thread t = new Thread(() =>
                {
                    Thread.Sleep(100);

                    this.Dispatcher.BeginInvoke(new Action(() =>
                    {
                        OnZoomingComplete(new Point(0.5, 0.5), gridLinesAndImageWrapperGrid.ActualWidth, gridLinesAndImageWrapperGrid.ActualHeight);
                    }), DispatcherPriority.Background);
                });
                t.Start();
            };

            gridLinesAndImageWrapperGrid.BeginAnimation(Grid.MarginProperty, marginAni);
            gridLinesAndImageWrapperGrid.Cursor = Cursors.Arrow;

            gridLinesAndImageWrapperGrid.PreviewMouseDown += gridLinesAndImageWrapperGrid_PreviewMouseDown;
        }

        /// <summary>
        /// Override this method to apply extra logic when the grid containing the graph and grid lines changes its size.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        protected virtual void OnImageGridSizeChanged(object sender, SizeChangedEventArgs e)
        {
            OnSetCrossThreadFields();
        }

        /// <summary>
        /// Occurs after the loaded graph event.
        /// </summary>
        /// <param name="sender">Graph instance.</param>
        /// <param name="e">Arguments</param>
        protected virtual void OnGraphLoaded(object sender, RoutedEventArgs e)
        {
            if (!_loaded)
            {
                _loaded = true;

                if (InnerContent != null)
                {
                    gridInnerContentWrapper.Children.Clear();
                    gridInnerContentWrapper.Children.Add(InnerContent);
                }

                OnRenderComponents();

                OnSetCrossThreadFields();
            }
        }

        /// <summary>
        /// Set the RealTimeGraphCrossThreadModel properties used by the graph thread.
        /// </summary>
        protected virtual void OnSetCrossThreadFields()
        {
            this.Dispatcher.Invoke(() =>
            {
                _antialiased = Antialiased;
                _height = gridLinesAndImageWrapperGrid.ActualHeight;
                _width = gridLinesAndImageWrapperGrid.ActualWidth;
                _mainWidth = gridMain.ActualWidth;
                _mainHeight = gridMain.ActualHeight;
                _maximum = Maximum;
                _minimum = Minimum;
                _originalMinimum = Minimum;
                _refreshRate = RefreshRate;
                _isPaused = IsPaused;
                virtualMinimum = Minimum;
                virtualMaximum = Maximum;
                virtualStart = 0;
                virtualEnd = gridMain.ActualWidth;
                _useAutoRange = UseAutoRange;
                RenderOptions.SetEdgeMode(this, _antialiased ? EdgeMode.Unspecified : EdgeMode.Aliased);

            }, DispatcherPriority.Send);
        }

        /// <summary>
        /// Override this method to apply extra logic for clearing the graph.
        /// </summary>
        protected virtual void OnClearGraph()
        {

        }

        /// <summary>
        /// Override this method to render the graph image.
        /// </summary>
        protected internal virtual void OnRenderGraph()
        {

        }

        /// <summary>
        /// Override this method to set the cross thread model properties.
        /// </summary>
        /// <param name="e"></param>
        protected virtual void OnGraphPropertiesChanged(DependencyPropertyChangedEventArgs e)
        {
            OnSetCrossThreadFields();
        }

        /// <summary>
        /// Convert the specified integer to graph Y position.
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
        protected virtual double ConvertYToImageY(double value)
        {
            double valuePrecentage = ((((value - _minimum) * 100) / (_maximum - _minimum)) * _height) / 100;
            return valuePrecentage;
        }

        /// <summary>
        /// Convert the specified integer to graph X position.
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
        protected virtual double ConvertYToImageX(double value)
        {
            double valuePrecentage = ((((value - _minimum) * 100) / (_maximum - _minimum)) * _width) / 100;
            return valuePrecentage;
        }

        /// <summary>
        /// Convert the specified integer to graph flipped Y position.
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
        protected virtual double ConvertYToImageYFliped(double value)
        {
            double valuePrecentage = ConvertYToImageY(value);
            valuePrecentage = _height - valuePrecentage; //Flip
            return valuePrecentage;
        }

        /// <summary>
        /// Renders the collection of graph components.
        /// </summary>
        protected virtual void OnRenderComponents()
        {
            if (Components != null)
            {
                foreach (var component in Components)
                {
                    component.Graph = this;
                    component.Render(this);

                    if (component.Location == ComponentLocationEnum.Left)
                    {
                        if (!stackLeft.Children.Contains(component))
                        {
                            stackLeft.Children.Add(component);
                        }
                    }
                    else if (component.Location == ComponentLocationEnum.Right)
                    {
                        if (!stackRight.Children.Contains(component))
                        {
                            stackRight.Children.Add(component);
                        }
                    }
                    else if (component.Location == ComponentLocationEnum.Back)
                    {
                        if (!gridBack.Children.Contains(component))
                        {
                            gridBack.Children.Add(component);
                        }
                    }
                    else if (component.Location == ComponentLocationEnum.Front)
                    {
                        if (!gridInnerContentWrapper.Children.Contains(component))
                        {
                            gridInnerContentWrapper.Children.Add(component);
                        }
                    }

                }
            }
        }

        /// <summary>
        /// Sets the current state of the graph.
        /// </summary>
        /// <param name="paused">if set to <c>true</c> [paused].</param>
        protected virtual void SetPaused(bool paused)
        {
            this.Dispatcher.Invoke(() =>
            {

                IsPaused = paused;

            });
        }

        /// <summary>
        /// Called when [zooming to].
        /// </summary>
        /// <param name="transformOrigin">The transform origin.</param>
        /// <param name="scaleX">The scale markerPosition.</param>
        /// <param name="scaleY">The scale y.</param>
        protected virtual void OnZoomingComplete(Point transformOrigin, double scaleX, double scaleY)
        {
            if (ZoomComplete != null) ZoomComplete(transformOrigin, scaleX, scaleY);
        }

        /// <summary>
        /// Called when [panning to].
        /// </summary>
        /// <param name="translate">The translate.</param>
        protected virtual void OnPanningComplete(Point translate)
        {
            if (PanningComplete != null) PanningComplete(translate);
        }

        /// <summary>
        /// Converts the y position to value.
        /// </summary>
        /// <param name="y">The Y Position.</param>
        /// <returns>The Y Value</returns>
        protected virtual double ConvertYPositionToValue(double y)
        {
            double valuePrecentage = (y * 100) / gridLinesAndImageWrapperGrid.ActualHeight;
            valuePrecentage = (valuePrecentage * (_maximum - _minimum)) / 100;
            valuePrecentage = _minimum + (_maximum - _minimum) - valuePrecentage; //Flip
            return valuePrecentage;
        }

        /// <summary>
        /// Add an IRealTimeGraphComponent instance to the components collection.
        /// </summary>
        /// <param name="component"></param>
        internal virtual void ApplyComponent(FrameworkElement component)
        {
            if (!stackLeft.Children.Contains(component) && !stackLeft.Children.Contains(component))
            {
                if (component.HorizontalAlignment == System.Windows.HorizontalAlignment.Right)
                {
                    stackRight.Children.Add(component);
                }
                else
                {
                    stackLeft.Children.Add(component);
                }
            }
        }

        /// <summary>
        /// Called when [mouse move].
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="MouseEventArgs"/> instance containing the event data.</param>
        protected virtual void OnMouseMove(object sender, MouseEventArgs e)
        {
            MouseValue = ConvertYPositionToValue(e.GetPosition(gridLinesAndImageWrapperGrid).Y);
            if (MouseMove != null) MouseMove(this, e);
        }

        /// <summary>
        /// Called when [mouse leave].
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="MouseEventArgs"/> instance containing the event data.</param>
        protected virtual void OnMouseLeave(object sender, MouseEventArgs e)
        {
            if (MouseLeave != null) MouseLeave(this, e);
        }

        /// <summary>
        /// Normalizes the value.
        /// </summary>
        /// <param name="value">The value.</param>
        protected virtual void NormalizeValue(ref double value)
        {
            if (value > _maximum)
            {
                value = _maximum;
            }
            else if (value < _originalMinimum)
            {
                value = _originalMinimum;
            }
        }

        /// <summary>
        /// Override to apply extra logic when dragging on zoom mode.
        /// </summary>
        /// <param name="e">The <see cref="DragDeltaEventArgs"/> instance containing the event data.</param>
        protected virtual void OnDragging(DragDeltaEventArgs e)
        {

        }
        #endregion

        #region Protected Methods

        /// <summary>
        /// Clears the graph.
        /// </summary>
        protected void ClearGraph()
        {
            StopPushThread();
            xValueCounter = 0;
            OnSetCrossThreadFields();

            OnClearGraph();

            if (img != null)
            {
                WriteableBitmap bmp = BitmapFactory.New((int)_width, (int)_height);
                bmp.Clear(Colors.Transparent);

                bmp.Freeze();

                img.Dispatcher.BeginInvoke(new Action(() =>
                {
                    img.Source = bmp;
                }), System.Windows.Threading.DispatcherPriority.Send);
            }
        }

        /// <summary>
        /// Starts the "push" thread.
        /// </summary>
        protected void StartPushThread()
        {
            if (pushThread == null && !IsSynced)
            {
                _requestTermination = new ManualResetEvent(false);
                _terminated = new ManualResetEvent(false);
                pushThread = new Thread(PushDataThreadMethod);
                pushThread.IsBackground = true;
                pushThread.Start();
            }
        }

        /// <summary>
        /// Stops the "push" thread.
        /// </summary>
        protected void StopPushThread()
        {
            if (pushThread != null)
            {
                _requestTermination.Set();
                _terminated.WaitOne(50);
                pushThread = null;
            }
        }

        /// <summary>
        /// Draw the specified polygon vectorsCollection on the graph. The drawing will be anti-aliased if specified in the cross model fields.
        /// </summary>
        [DebuggerStepThrough]
        [DebuggerHidden]
        protected void DrawPolyline(WriteableBitmap bmp, int[] points, Color stroke)
        {
            try
            {
                if (points.Length > 0)
                {
                    if (_antialiased)
                    {
                        bmp.DrawPolylineAa(points, stroke);
                    }
                    else
                    {
                        bmp.DrawPolyline(points, stroke);
                    }
                }
            }
            catch
            {
                Debug.WriteLine("[RealTimeGraphEx] [DrawPolyline] Error encountered while trying to Draw on bitmap.");
            }
        }

        /// <summary>
        /// Forces events update.
        /// </summary>
        public static void DoEvents()
        {
            Application.Current.Dispatcher.Invoke(DispatcherPriority.Background,
                                                  new Action(delegate { }));
        }

        #endregion

        #region Private Methods

        /// <summary>
        /// The push thread method.
        /// </summary>
        [HandleProcessCorruptedStateExceptions]
        private void PushDataThreadMethod()
        {
            while (!_requestTermination.WaitOne(0))
            {
                try
                {
                    OnRenderGraph();
                }
                catch { }
                Thread.Sleep(_refreshRate);
            }

            _terminated.Set();
        }

        private Rect BoundsRelativeTo(FrameworkElement element, Visual relativeTo)
        {
            return
              element.TransformToVisual(relativeTo)
                     .TransformBounds(LayoutInformation.GetLayoutSlot(element));
        }

        private void ApplyVirtualMaximumMinimum()
        {
            var b = new Rect(gridLinesAndImageWrapperGrid.Margin.Left, gridLinesAndImageWrapperGrid.Margin.Top, gridLinesAndImageWrapperGrid.Width, gridLinesAndImageWrapperGrid.Height);

            double maxDecreasePrecentage = (Math.Abs(b.Top) * 100) / gridMain.ActualHeight;
            double minIncreasePrecentage = ((b.Bottom - gridMain.ActualHeight) * 100) / gridMain.ActualHeight;


            virtualMaximum = ConvertYPositionToValue(Math.Abs(b.Top));
            virtualMinimum = ConvertYPositionToValue(b.Height - Math.Abs(b.Height - (gridMain.ActualHeight - b.Top)));
            virtualStart = Math.Abs(b.Left);
            virtualEnd = virtualStart + gridMain.ActualWidth;

            _offSetX = gridLinesAndImageWrapperGrid.Margin.Left;
            _offSetY = gridLinesAndImageWrapperGrid.Margin.Top;
        }

        #endregion

        #region Public Methods

        /// <summary>
        /// Gets the size of the graph render.
        /// </summary>
        /// <returns></returns>
        public Rect GetGraphRenderBounds()
        {
            var b = BoundsRelativeTo(gridLinesAndImageWrapperGrid, gridMain);
            return b;
        }

        public void Clear()
        {
            ClearGraph();
        }

        public void RenderComponents()
        {
            OnRenderComponents();
        }

        #endregion

        #region Static Methods

        protected static void CrossModelChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            (d as RealTimeGraphExBase).OnGraphPropertiesChanged(e);
        }

        #endregion

        #region Event Handlers

        /// <summary>
        /// Handles the PreviewMouseWheel event of the gridLinesAndImageWrapperGrid control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="MouseWheelEventArgs"/> instance containing the event data.</param>
        protected void gridLinesAndImageWrapperGrid_PreviewMouseWheel(object sender, MouseWheelEventArgs e)
        {
            if (ZoomMode == ZoomModeEnum.MouseWheel)
            {
                gridLinesAndImageWrapperGrid.PreviewMouseDown -= gridLinesAndImageWrapperGrid_PreviewMouseDown;
                Zoom += (e.Delta < 0 ? -0.1 : 0.1);
            }
            else if (ZoomMode == ZoomModeEnum.Selection)
            {

                var center = new Point(e.GetPosition(gridLinesAndImageWrapperGrid).X, e.GetPosition(gridLinesAndImageWrapperGrid).Y);

                int zoomFactor = 20;

                double width = gridLinesAndImageWrapperGrid.ActualWidth + (e.Delta < 0 ? zoomFactor * -1 : zoomFactor);
                double height = gridLinesAndImageWrapperGrid.ActualHeight + (e.Delta < 0 ? zoomFactor * -1 : zoomFactor);
                double left = gridLinesAndImageWrapperGrid.Margin.Left;
                double top = gridLinesAndImageWrapperGrid.Margin.Top;

                bool reset = true;

                if (ZoomDirection != ZoomDirectionEnum.Y)
                {
                    if (width > gridMain.ActualWidth)
                    {
                        gridLinesAndImageWrapperGrid.Width = width;
                        reset = false;
                    }
                }

                if (ZoomDirection != ZoomDirectionEnum.X)
                {
                    if (height > gridMain.ActualHeight)
                    {
                        gridLinesAndImageWrapperGrid.Height = height;
                        reset = false;
                    }
                }

                double factorX = zoomFactor / (gridLinesAndImageWrapperGrid.ActualWidth / center.X);
                double factorY = zoomFactor / (gridLinesAndImageWrapperGrid.ActualHeight / center.Y);

                if (ZoomDirection != ZoomDirectionEnum.Y)
                {
                    if (gridLinesAndImageWrapperGrid.Margin.Left + (e.Delta < 0 ? factorX : -factorX) < 0)
                    {
                        left = gridLinesAndImageWrapperGrid.Margin.Left + (e.Delta < 0 ? factorX : -factorX);
                        reset = false;
                    }
                }

                if (ZoomDirection != ZoomDirectionEnum.X)
                {
                    if (gridLinesAndImageWrapperGrid.Margin.Top + (e.Delta < 0 ? factorY : -factorY) < 0)
                    {
                        top = gridLinesAndImageWrapperGrid.Margin.Top + (e.Delta < 0 ? factorY : -factorY);
                        reset = false;
                    }
                }

                gridLinesAndImageWrapperGrid.Margin =
                    new Thickness(
                        left,
                        top,
                        gridLinesAndImageWrapperGrid.Margin.Right,
                        gridLinesAndImageWrapperGrid.Margin.Bottom);

                OnZoomingComplete(lastCenter, gridLinesAndImageWrapperGrid.Width, gridLinesAndImageWrapperGrid.Height);
                ApplyVirtualMaximumMinimum();

                if (reset)
                {
                    ResetZoom();
                    gridLinesAndImageWrapperGrid.PreviewMouseDown += gridLinesAndImageWrapperGrid_PreviewMouseDown;
                    isScaled = false;
                }
                else
                {
                    gridLinesAndImageWrapperGrid.PreviewMouseDown -= gridLinesAndImageWrapperGrid_PreviewMouseDown;
                    gridLinesAndImageWrapperGrid.Cursor = Cursors.SizeAll;
                    isScaled = true;
                }
            }
        }

        /// <summary>
        /// Handles the DragDelta event of the moveThumb control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="DragDeltaEventArgs"/> instance containing the event data.</param>
        protected void moveThumb_DragDelta(object sender, DragDeltaEventArgs e)
        {
            if (ZoomMode == ZoomModeEnum.None) return;

            Point relativePoint = gridLinesAndImageWrapperGrid.TransformToAncestor(gridMain).Transform(new Point(0, 0));
            Rect bounds = BoundsRelativeTo(gridLinesAndImageWrapperGrid, gridMain);

            double right = (gridLinesAndImageWrapperGrid.ActualWidth + gridLinesAndImageWrapperGrid.Margin.Left);
            double bottom = (gridLinesAndImageWrapperGrid.ActualHeight + gridLinesAndImageWrapperGrid.Margin.Top);

            if (gridLinesAndImageWrapperGrid.Margin.Left + e.HorizontalChange <= 0 && right + e.HorizontalChange >= gridMain.ActualWidth)
            {
                gridLinesAndImageWrapperGrid.Margin = new Thickness(gridLinesAndImageWrapperGrid.Margin.Left + e.HorizontalChange, gridLinesAndImageWrapperGrid.Margin.Top, 0, 0);
                //translateTransform.X += e.HorizontalChange;
                OnPanningComplete(new Point(gridLinesAndImageWrapperGrid.Margin.Left, gridLinesAndImageWrapperGrid.Margin.Top));
                ApplyVirtualMaximumMinimum();
            }

            if (gridLinesAndImageWrapperGrid.Margin.Top + e.VerticalChange <= 0 && bottom + e.VerticalChange >= gridMain.ActualHeight)
            {
                gridLinesAndImageWrapperGrid.Margin = new Thickness(gridLinesAndImageWrapperGrid.Margin.Left, gridLinesAndImageWrapperGrid.Margin.Top + e.VerticalChange, 0, 0);
                //translateTransform.Y += e.VerticalChange;
                OnPanningComplete(new Point(gridLinesAndImageWrapperGrid.Margin.Left, gridLinesAndImageWrapperGrid.Margin.Top));
                ApplyVirtualMaximumMinimum();
            }

            OnDragging(e);
        }

        /// <summary>
        /// Handles the PreviewMouseLeftButtonDown event of the gridLinesAndImageWrapperGrid control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="MouseButtonEventArgs"/> instance containing the event data.</param>
        protected void gridLinesAndImageWrapperGrid_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            if (ZoomMode != ZoomModeEnum.Selection) return;

            if (e.ClickCount == 2)
            {
                ResetZoom();
            }
        }

        /// <summary>
        /// </summary>
        /// <returns></returns>
        protected void selectionCanvas_PreviewMouseUp(object sender, MouseButtonEventArgs e)
        {
            if (ZoomMode != ZoomModeEnum.Selection) return;

            var bounds = new Rect(Canvas.GetLeft(selectionRectangle), Canvas.GetTop(selectionRectangle), selectionRectangle.Width, selectionRectangle.Height);
            selectionCanvas.Children.Remove(selectionRectangle);
            isSelectionMouseDown = false;
            selectionCanvas.Background = null;

            if (selectionRectangle.Width <= 5 || selectionRectangle.Height <= 5) return;

            Point center = new Point(bounds.Left * (gridLinesAndImageWrapperGrid.ActualWidth / bounds.Width), bounds.Top * (gridLinesAndImageWrapperGrid.ActualHeight / bounds.Height));
            gridLinesAndImageWrapperGrid.RenderTransformOrigin = center;

            lastBounds = bounds;
            lastCenter = center;

            double scaleX = gridLinesAndImageWrapperGrid.ActualWidth;
            double scaleY = gridLinesAndImageWrapperGrid.ActualHeight;

            if (ZoomDirection != ZoomDirectionEnum.Y)
            {
                scaleX = gridLinesAndImageWrapperGrid.ActualWidth * (gridLinesAndImageWrapperGrid.ActualWidth / bounds.Width);
            }

            if (ZoomDirection != ZoomDirectionEnum.X)
            {
                scaleY = gridLinesAndImageWrapperGrid.ActualHeight * (gridLinesAndImageWrapperGrid.ActualHeight / bounds.Height);
            }

            isScaled = true;

            DoubleAnimation aniX = new DoubleAnimation();
            aniX.Duration = new Duration(TimeSpan.FromSeconds(0.1));
            aniX.To = scaleX;

            DoubleAnimation aniY = new DoubleAnimation();
            aniY.Duration = new Duration(TimeSpan.FromSeconds(0.1));
            aniY.To = scaleY;

            ThicknessAnimation marginAni = new ThicknessAnimation();
            marginAni.Duration = new Duration(TimeSpan.FromSeconds(0.1));
            marginAni.To = new Thickness(center.X * -1, center.Y * -1, 0, 0);

            aniY.CurrentTimeInvalidated += (x, y) =>
            {
                ApplyVirtualMaximumMinimum();
            };

            aniY.Completed += (x, y) =>
            {
                gridLinesAndImageWrapperGrid.BeginAnimation(Grid.WidthProperty, null);
                gridLinesAndImageWrapperGrid.BeginAnimation(Grid.HeightProperty, null);
                gridLinesAndImageWrapperGrid.Width = scaleX;
                gridLinesAndImageWrapperGrid.Height = scaleY;

                gridLinesAndImageWrapperGrid.BeginAnimation(Grid.MarginProperty, null);

                double marginLeft = 0;
                double marginTop = 0;

                if (ZoomDirection != ZoomDirectionEnum.Y)
                {
                    marginLeft = center.X * -1;
                }

                if (ZoomDirection != ZoomDirectionEnum.X)
                {
                    marginTop = center.Y * -1;
                }

                gridLinesAndImageWrapperGrid.Margin = new Thickness(marginLeft, marginTop, 0, 0);

                OnZoomingComplete(center, scaleX, scaleY);
                ApplyVirtualMaximumMinimum();
            };

            gridLinesAndImageWrapperGrid.Width = gridLinesAndImageWrapperGrid.ActualWidth;
            gridLinesAndImageWrapperGrid.Height = gridLinesAndImageWrapperGrid.ActualHeight;

            gridLinesAndImageWrapperGrid.BeginAnimation(Grid.MarginProperty, marginAni);
            gridLinesAndImageWrapperGrid.BeginAnimation(Grid.WidthProperty, aniX);
            gridLinesAndImageWrapperGrid.BeginAnimation(Grid.HeightProperty, aniY);
            gridLinesAndImageWrapperGrid.Cursor = Cursors.SizeAll;

            gridLinesAndImageWrapperGrid.PreviewMouseDown -= gridLinesAndImageWrapperGrid_PreviewMouseDown;
        }

        /// <summary>
        /// Handles the MouseMove event of the selectionCanvas control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.Windows.Input.MouseEventArgs"/> instance containing the event data.</param>
        protected void selectionCanvas_MouseMove(object sender, MouseEventArgs e)
        {
            if (isSelectionMouseDown)
            {
                double width = e.GetPosition(selectionCanvas).X - Canvas.GetLeft(selectionRectangle);
                double height = e.GetPosition(selectionCanvas).Y - Canvas.GetTop(selectionRectangle);

                if (width > 0)
                {
                    selectionRectangle.Width = width;
                }

                if (height > 0)
                {
                    selectionRectangle.Height = height;
                }
            }
        }

        /// <summary>
        /// Handles the PreviewMouseDown event of the gridLinesAndImageWrapperGrid control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.Windows.Input.MouseButtonEventArgs"/> instance containing the event data.</param>
        protected void gridLinesAndImageWrapperGrid_PreviewMouseDown(object sender, MouseButtonEventArgs e)
        {
            if (ZoomMode != ZoomModeEnum.Selection) return;
            selectionCanvas.Children.Remove(selectionRectangle);
            selectionCanvas.Background = Brushes.Transparent;
            selectionRectangle.Width = 0;
            selectionRectangle.Height = 0;
            selectionCanvas.Children.Add(selectionRectangle);
            Canvas.SetLeft(selectionRectangle, e.GetPosition(selectionCanvas).X);
            Canvas.SetTop(selectionRectangle, e.GetPosition(selectionCanvas).Y);
            isSelectionMouseDown = true;
            DoEvents();
        }

        #endregion

        #region IDisposable Members

        /// <summary>
        /// Disposes the current instance.
        /// </summary>
        public void Dispose()
        {
            if (pushThread != null)
            {
                StopPushThread();
            }
        }

        #endregion
    }
}