using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Documents; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Imaging; using System.Windows.Navigation; using System.Windows.Shapes; using RealTimeGraphX.EventArguments; namespace RealTimeGraphX.WPF { /// /// Represents a WPF graph surface. /// /// /// public class WpfGraphSurface : Control, IGraphSurface { private WriteableBitmap _writeable_bitmap; private System.Drawing.Bitmap _gdi_bitmap; private System.Drawing.Graphics _g; private bool _size_changed; private System.Drawing.SizeF _size; private System.Drawing.RectangleF _zoom_rect; private Rectangle _selection_rectangle; private Canvas _selection_canvas; private bool _is_selection_mouse_down_zoom; private bool _is_selection_mouse_down_pan; private Point _selection_start_point; private bool _is_scaled; private Point _current_mouse_position; private Point _last_mouse_position; private Grid _grid; #region Events /// /// Occurs when the surface size has changed. /// public event EventHandler SurfaceSizeChanged; /// /// Occurs when the surface zoom rectangle has changed. /// public event EventHandler ZoomRectChanged; #endregion #region Properties /// /// Gets or sets current graph rendered image. /// public BitmapSource Image { get { return (BitmapSource)GetValue(ImageProperty); } private set { SetValue(ImageProperty, value); } } public static readonly DependencyProperty ImageProperty = DependencyProperty.Register("Image", typeof(BitmapSource), typeof(WpfGraphSurface), new PropertyMetadata(null)); /// /// Gets or sets the graph controller. /// public IGraphController Controller { get { return (IGraphController)GetValue(ControllerProperty); } set { SetValue(ControllerProperty, value); } } public static readonly DependencyProperty ControllerProperty = DependencyProperty.Register("Controller", typeof(IGraphController), typeof(WpfGraphSurface), new PropertyMetadata(null, (d, e) => (d as WpfGraphSurface).OnControllerChanged(e.OldValue as IGraphController, e.NewValue as IGraphController))); #endregion #region Constructors /// /// Initializes the class. /// static WpfGraphSurface() { DefaultStyleKeyProperty.OverrideMetadata(typeof(WpfGraphSurface), new FrameworkPropertyMetadata(typeof(WpfGraphSurface))); } /// /// Initializes a new instance of the class. /// public WpfGraphSurface() { SizeChanged += WpfGraphSurface_SizeChanged; } #endregion #region Apply Template /// /// When overridden in a derived class, is invoked whenever application code or internal processes call . /// public override void OnApplyTemplate() { base.OnApplyTemplate(); _selection_rectangle = GetTemplateChild("PART_SelectionRectangle") as Rectangle; _selection_canvas = GetTemplateChild("PART_SelectionCanvas") as Canvas; _grid = GetTemplateChild("PART_Grid") as Grid; _selection_canvas.MouseDown += OnSelectionCanvasMouseDown; _selection_canvas.MouseUp += OnSelectionCanvasMouseUp; _selection_canvas.MouseMove += OnSelectionCanvasMouseMove; } #endregion #region Protected Methods /// /// Called when the mouse moves over the zoom/pan selection canvas. /// /// The sender. /// The instance containing the event data. protected virtual void OnSelectionCanvasMouseMove(object sender, MouseEventArgs e) { _current_mouse_position = e.GetPosition(_selection_canvas); if (_is_selection_mouse_down_zoom && Keyboard.IsKeyDown(Key.LeftCtrl)) { Canvas.SetLeft(_selection_rectangle, _selection_start_point.X); Canvas.SetTop(_selection_rectangle, _selection_start_point.Y); Point _selection_current_point = e.GetPosition(_selection_canvas); if (_selection_current_point.X - _selection_start_point.X > 1) { _selection_rectangle.Width = _selection_current_point.X - _selection_start_point.X; } if (_selection_current_point.Y - _selection_start_point.Y > 1) { _selection_rectangle.Height = _selection_current_point.Y - _selection_start_point.Y; } if (_selection_current_point.X < _selection_start_point.X) { Canvas.SetLeft(_selection_rectangle, _selection_current_point.X); _selection_rectangle.Width = _selection_start_point.X - _selection_current_point.X; } if (_selection_current_point.Y < _selection_start_point.Y) { Canvas.SetTop(_selection_rectangle, _selection_current_point.Y); _selection_rectangle.Height = _selection_start_point.Y - _selection_current_point.Y; } } else if (_is_selection_mouse_down_pan && _is_scaled) { Point _selection_current_point = e.GetPosition(_selection_canvas); double delta_x = _current_mouse_position.X - _last_mouse_position.X; double delta_y = _current_mouse_position.Y - _last_mouse_position.Y; double x = _zoom_rect.Left - delta_x; double y = _zoom_rect.Top - delta_y; if (x < 0) { x = 0; } if (y < 0) { y = 0; } if (x + _zoom_rect.Width > _size.Width) { x = x - (x + _zoom_rect.Width - _size.Width); } if (y + _zoom_rect.Height > _size.Height) { y = y - (y + _zoom_rect.Height - _size.Height); } _zoom_rect = new System.Drawing.RectangleF((float)x, (float)y, _zoom_rect.Width, _zoom_rect.Height); ZoomRectChanged?.Invoke(this, new EventArgs()); } _last_mouse_position = _current_mouse_position; } /// /// Called when the mouse released from the zoom/pan selection canvas. /// /// The sender. /// The instance containing the event data. protected virtual void OnSelectionCanvasMouseUp(object sender, MouseButtonEventArgs e) { _selection_canvas.ReleaseMouseCapture(); if (_is_selection_mouse_down_pan) { _is_selection_mouse_down_pan = false; } else if (_is_selection_mouse_down_zoom) { _is_selection_mouse_down_zoom = false; _zoom_rect = new System.Drawing.RectangleF((float)Canvas.GetLeft(_selection_rectangle), (float)Canvas.GetTop(_selection_rectangle), (float)_selection_rectangle.Width, (float)_selection_rectangle.Height); _selection_rectangle.Visibility = Visibility.Hidden; _is_scaled = true; ZoomRectChanged?.Invoke(this, new EventArgs()); } } /// /// Called when the mouse pressed on the zoom/pan selection canvas. /// /// The sender. /// The instance containing the event data. protected virtual void OnSelectionCanvasMouseDown(object sender, MouseButtonEventArgs e) { Mouse.Capture(_selection_canvas); _selection_start_point = e.GetPosition(_selection_canvas); _current_mouse_position = _selection_start_point; _last_mouse_position = _current_mouse_position; if (e.ClickCount == 2) { _zoom_rect = new System.Drawing.RectangleF(); _is_scaled = false; ZoomRectChanged?.Invoke(this, new EventArgs()); } else if (Keyboard.IsKeyDown(Key.LeftCtrl)) { _selection_rectangle.Width = 0; _selection_rectangle.Height = 0; _is_selection_mouse_down_zoom = true; _is_selection_mouse_down_pan = false; _selection_rectangle.Visibility = Visibility.Visible; } else { _is_selection_mouse_down_pan = true; } } /// /// Called when the property has changed. /// /// The old controller. /// The new controller. protected virtual void OnControllerChanged(IGraphController oldController, IGraphController newController) { if (oldController != null) { oldController.Surface = null; } if (newController != null) { newController.Surface = this; } } #endregion #region IGraphSurface /// /// Called before drawing has started. /// public void BeginDraw() { if (_size_changed) { _writeable_bitmap = new WriteableBitmap((int)Math.Max(_size.Width, 1), (int)Math.Max(_size.Height, 1), 96.0, 96.0, PixelFormats.Pbgra32, null); _gdi_bitmap = new System.Drawing.Bitmap(_writeable_bitmap.PixelWidth, _writeable_bitmap.PixelHeight, _writeable_bitmap.BackBufferStride, System.Drawing.Imaging.PixelFormat.Format32bppPArgb, _writeable_bitmap.BackBuffer); _size_changed = false; } _writeable_bitmap.Lock(); _g = System.Drawing.Graphics.FromImage(_gdi_bitmap); _g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias; _g.Clear(System.Drawing.Color.Transparent); } /// /// Applies transformation on the current pass. /// /// The transform. public void SetTransform(GraphTransform transform) { _g.TranslateTransform((float)transform.TranslateX, (float)transform.TranslateY); _g.ScaleTransform((float)transform.ScaleX, (float)transform.ScaleY); } /// /// Draws the stroke of the specified data series points. /// /// The data series. /// The points. public void DrawSeries(WpfGraphDataSeries dataSeries, IEnumerable points) { _g.DrawPolygon(dataSeries.GdiPen, points.ToArray()); } /// /// Fills the specified data series points. /// /// The data series. /// The points. public void FillSeries(WpfGraphDataSeries dataSeries, IEnumerable points) { var brush = dataSeries.GdiFill; if (dataSeries.GdiFill is System.Drawing.Drawing2D.LinearGradientBrush) { var gradient = dataSeries.GdiFill as System.Drawing.Drawing2D.LinearGradientBrush; gradient.ResetTransform(); gradient.ScaleTransform((_size.Width / gradient.Rectangle.Width), (_size.Height / gradient.Rectangle.Height)); } _g.FillPolygon(dataSeries.GdiFill, points.ToArray()); } /// /// Called when drawing has completed. /// public void EndDraw() { _writeable_bitmap.AddDirtyRect(new Int32Rect(0, 0, _writeable_bitmap.PixelWidth, _writeable_bitmap.PixelHeight)); _writeable_bitmap.Unlock(); var cloned = _writeable_bitmap.Clone(); cloned.Freeze(); Dispatcher.BeginInvoke(new Action((() => { Image = cloned; }))); _g.Dispose(); } /// /// Returns the actual size of the surface. /// /// public System.Drawing.SizeF GetSize() { return _size; } /// /// Returns the current bounds of the zooming rectangle. /// /// public System.Drawing.RectangleF GetZoomRect() { return _zoom_rect; } #endregion #region Event Handlers /// /// Handles the WPF Graph Surface Size Changed event. /// /// The source of the event. /// The event arguments. private void WpfGraphSurface_SizeChanged(object sender, SizeChangedEventArgs e) { _size = new System.Drawing.SizeF((float)e.NewSize.Width, (float)e.NewSize.Height); _size_changed = true; SurfaceSizeChanged?.Invoke(this, new EventArgs()); } #endregion } }