aboutsummaryrefslogtreecommitdiffstats
path: root/Software/Visual_Studio/PPC/Tango.PPC.Common/PPCViewModel.cs
blob: 98eef6883058fdc508021692553a601b296ff596 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Tango.BL;
using Tango.Core.DI;
using Tango.PPC.Common.Application;
using Tango.PPC.Common.Authentication;
using Tango.PPC.Common.Connection;
using Tango.PPC.Common.Connectivity;
using Tango.PPC.Common.EventLogging;
using Tango.PPC.Common.ExternalBridge;
using Tango.PPC.Common.HotSpot;
using Tango.PPC.Common.Navigation;
using Tango.PPC.Common.Notifications;
using Tango.PPC.Common.Printing;
using Tango.PPC.Common.RemoteAssistance;
using Tango.PPC.Common.RemoteDesktop;
using Tango.PPC.Common.Storage;
using Tango.PPC.Common.Synchronization;
using Tango.PPC.Common.ThreadLoading;
using Tango.Settings;
using Tango.SharedUI;
using static Tango.SharedUI.Controls.NavigationControl;

namespace Tango.PPC.Common
{
    /// <summary>
    /// Represents a PPC view model base class.
    /// </summary>
    /// <seealso cref="Tango.SharedUI.ViewModel" />
    public abstract class PPCViewModel : ViewModel, INavigationBlocker
    {
        /// <summary>
        /// Gets the static observable entities adapter.
        /// </summary>
        public ObservablesStaticCollections Adapter
        {
            get { return ObservablesStaticCollections.Instance; }
        }

        /// <summary>
        /// Gets or sets the application manager.
        /// </summary>
        [TangoInject]
        public IPPCApplicationManager ApplicationManager { get; set; }

        /// <summary>
        /// Gets or sets the authentication provider.
        /// </summary>
        [TangoInject]
        public IAuthenticationProvider AuthenticationProvider { get; set; }

        /// <summary>
        /// Gets or sets the navigation manager.
        /// </summary>
        [TangoInject]
        public INavigationManager NavigationManager { get; set; }

        /// <summary>
        /// Gets or sets the notification provider.
        /// </summary>
        [TangoInject]
        public INotificationProvider NotificationProvider { get; set; }

        /// <summary>
        /// Gets or sets the machine provider.
        /// </summary>
        [TangoInject]
        public IMachineProvider MachineProvider { get; set; }

        /// <summary>
        /// Gets or sets the external bridge service.
        /// </summary>
        [TangoInject]
        public IPPCExternalBridgeService ExternalBridgeService { get; set; }

        /// <summary>
        /// Gets or sets the printing manager.
        /// </summary>
        [TangoInject]
        public IPrintingManager PrintingManager { get; set; }

        /// <summary>
        /// Gets or sets the connectivity provider.
        /// </summary>
        [TangoInject]
        public IConnectivityProvider ConnectivityProvider { get; set; }

        /// <summary>
        /// Gets or sets the hot spot provider.
        /// </summary>
        [TangoInject]
        public IHotSpotProvider HotSpotProvider { get; set; }

        /// <summary>
        /// Gets or sets the remote assistance provider.
        /// </summary>
        [TangoInject]
        public IRemoteAssistanceProvider RemoteAssistanceProvider { get; set; }

        /// <summary>
        /// Gets or sets the storage provider.
        /// </summary>
        [TangoInject]
        public IStorageProvider StorageProvider { get; set; }

        /// <summary>
        /// Gets or sets the event logger.
        /// </summary>
        [TangoInject]
        public IEventLogger EventLogger { get; set; }

        /// <summary>
        /// Gets or sets the machine data synchronizer.
        /// </summary>
        [TangoInject]
        public IMachineDataSynchronizer MachineDataSynchronizer { get; set; }

        /// <summary>
        /// Gets or sets the remote desktop service.
        /// </summary>
        [TangoInject]
        public IRemoteDesktopService RemoteDesktopService { get; set; }

        /// <summary>
        /// Gets or sets the thread loading service.
        /// </summary>
        [TangoInject]
        public IThreadLoadingService ThreadLoadingService { get; set; }

        private PPCSettings _settings;
        /// <summary>
        /// Gets the main PPC settings.
        /// </summary>
        public PPCSettings Settings
        {
            get
            {
                if (_settings == null)
                {
                    _settings = SettingsManager.Default.GetOrCreate<PPCSettings>();
                }

                return _settings;
            }
            private set { _settings = value; }
        }

        private bool _isVisible;
        /// <summary>
        /// Gets or sets a value indicating whether this <see cref="PPCViewModel"/> view is visible.
        /// </summary>
        public bool IsVisible
        {
            get { return _isVisible; }
            private set { _isVisible = value; RaisePropertyChangedAuto(); }
        }

        /// <summary>
        /// Called when the application has been started.
        /// </summary>
        public abstract void OnApplicationStarted();

        /// <summary>
        /// Called when the application is shutting down.
        /// </summary>
        public virtual void OnApplicationShuttingDown()
        {

        }

        /// <summary>
        /// Called when the navigation system has navigated to this VM view.
        /// </summary>
        public virtual void OnNavigatedTo()
        {
            IsVisible = true;
        }

        /// <summary>
        /// Called when the navigation system has navigated to this VM view.
        /// </summary>
        /// <param name="fromVM">The view model instance of the previous view model</param>
        public virtual void OnNavigatedTo(PPCViewModel fromVM)
        {
            
        }

        /// <summary>
        /// Called when the navigation system has navigated from this VM view.
        /// </summary>
        public virtual void OnNavigatedFrom()
        {
            IsVisible = false;
        }

        /// <summary>
        /// Called before the navigation system has navigated to this VM view.
        /// </summary>
        public virtual void OnBeforeNavigatedTo()
        {

        }

        /// <summary>
        /// Called before the navigation system has navigated from this VM view.
        /// </summary>
        public virtual void OnBeforeNavigatedFrom()
        {
            IsVisible = false;
        }

        /// <summary>
        /// Raises the specified message using the default <see cref="TangoMessenger"/>.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="message">The message.</param>
        protected void RaiseMessage<T>(T message) where T : class
        {
            TangoMessenger.Default.Send<T>(message);
        }

        /// <summary>
        /// Raises the specified message using the default <see cref="TangoMessenger"/>.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        protected void RaiseMessage<T>() where T : class
        {
            TangoMessenger.Default.Send<T>(Activator.CreateInstance<T>());
        }

        /// <summary>
        /// Registers a message handle for the specified message type T.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="handler">The handler.</param>
        protected void RegisterForMessage<T>(Action<T> handler)
        {
            TangoMessenger.Default.Register<T>(handler);
        }

        /// <summary>
        /// Called before the navigation system navigates from this object.
        /// Return false to abort the navigation.
        /// </summary>
        /// <returns></returns>
        public virtual Task<bool> OnNavigateOutRequest()
        {
            return Task.FromResult(true);
        }

        /// <summary>
        /// Called before the navigation system navigates back from this object.
        /// Return false to abort the navigation.
        /// </summary>
        /// <returns></returns>
        public virtual Task<bool> OnNavigateBackRequest()
        {
            return Task.FromResult(true);
        }

        /// <summary>
        /// Called when the application is ready and all modules views are loaded.
        /// </summary>
        public virtual void OnApplicationReady()
        {

        }
    }

    /// <summary>
    /// Represents a PPC view model base class a View property as an instance of a <see cref="IPPCView"/> contract.
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <seealso cref="Tango.SharedUI.ViewModel" />
    public abstract class PPCViewModel<T> : PPCViewModel where T : IPPCView
    {
        private T _view;
        /// <summary>
        /// Gets the IPPCView instance.
        /// </summary>
        [TangoInject(TangoInjectMode.WhenAvailable)]
        public T View
        {
            get { return _view; }
            set
            {
                _view = value;
                ViewAttached = true;
                OnViewAttached();
            }
        }


        /// <summary>
        /// Gets a value indicating whether the instance of IPPCView is available.
        /// </summary>
        public bool ViewAttached { get; private set; }

        /// <summary>
        /// Called when the application has been started.
        /// </summary>
        public override void OnApplicationStarted()
        {

        }

        /// <summary>
        /// Called when the instance of IPPCView is available.
        /// </summary>
        public virtual void OnViewAttached()
        {

        }
    }
}
ng 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; #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, new PropertyChangedCallback(CrossModelChanged))); /// <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, new PropertyChangedCallback(CrossModelChanged))); /// <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))); #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; #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; 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> private void PushDataThreadMethod() { while (!_requestTermination.WaitOne(0)) { OnRenderGraph(); 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 } }