// --------------------------------------------------------------------------------------------------------------------
//
// Copyright (c) 2014 OxyPlot contributors
//
//
// Represents a control that displays a .
//
// --------------------------------------------------------------------------------------------------------------------
namespace OxyPlot.WindowsForms
{
using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Windows.Forms;
///
/// Represents a control that displays a .
///
[Serializable]
public class PlotView : Control, IPlotView
{
///
/// The category for the properties of this control.
///
private const string OxyPlotCategory = "OxyPlot";
///
/// The invalidate lock.
///
private readonly object invalidateLock = new object();
///
/// The model lock.
///
private readonly object modelLock = new object();
///
/// The rendering lock.
///
private readonly object renderingLock = new object();
///
/// The render context.
///
private readonly GraphicsRenderContext renderContext;
///
/// The tracker label.
///
[NonSerialized]
private Label trackerLabel;
///
/// The current model (holding a reference to this plot view).
///
[NonSerialized]
private PlotModel currentModel;
///
/// The is model invalidated.
///
private bool isModelInvalidated;
///
/// The model.
///
private PlotModel model;
///
/// The default controller.
///
private IPlotController defaultController;
///
/// The update data flag.
///
private bool updateDataFlag = true;
///
/// The zoom rectangle.
///
private Rectangle zoomRectangle;
///
/// Initializes a new instance of the class.
///
public PlotView()
{
this.renderContext = new GraphicsRenderContext();
// ReSharper disable DoNotCallOverridableMethodsInConstructor
this.DoubleBuffered = true;
// ReSharper restore DoNotCallOverridableMethodsInConstructor
this.PanCursor = Cursors.Hand;
this.ZoomRectangleCursor = Cursors.SizeNWSE;
this.ZoomHorizontalCursor = Cursors.SizeWE;
this.ZoomVerticalCursor = Cursors.SizeNS;
}
///
/// Gets the actual model in the view.
///
///
/// The actual model.
///
Model IView.ActualModel
{
get
{
return this.Model;
}
}
///
/// Gets the actual model.
///
/// The actual model.
public PlotModel ActualModel
{
get
{
return this.Model;
}
}
///
/// Gets the actual controller.
///
///
/// The actual .
///
IController IView.ActualController
{
get
{
return this.ActualController;
}
}
///
/// Gets the coordinates of the client area of the view.
///
public OxyRect ClientArea
{
get
{
return new OxyRect(this.ClientRectangle.Left, this.ClientRectangle.Top, this.ClientRectangle.Width, this.ClientRectangle.Height);
}
}
///
/// Gets the actual plot controller.
///
/// The actual plot controller.
public IPlotController ActualController
{
get
{
return this.Controller ?? (this.defaultController ?? (this.defaultController = new PlotController()));
}
}
///
/// Gets or sets the model.
///
[Browsable(false)]
[DefaultValue(null)]
[Category(OxyPlotCategory)]
public PlotModel Model
{
get
{
return this.model;
}
set
{
if (this.model != value)
{
this.model = value;
this.OnModelChanged();
}
}
}
///
/// Gets or sets the plot controller.
///
/// The controller.
[Browsable(false)]
[DefaultValue(null)]
[Category(OxyPlotCategory)]
public IPlotController Controller { get; set; }
///
/// Gets or sets the pan cursor.
///
[Category(OxyPlotCategory)]
public Cursor PanCursor { get; set; }
///
/// Gets or sets the horizontal zoom cursor.
///
[Category(OxyPlotCategory)]
public Cursor ZoomHorizontalCursor { get; set; }
///
/// Gets or sets the rectangle zoom cursor.
///
[Category(OxyPlotCategory)]
public Cursor ZoomRectangleCursor { get; set; }
///
/// Gets or sets the vertical zoom cursor.
///
[Category(OxyPlotCategory)]
public Cursor ZoomVerticalCursor { get; set; }
///
/// Hides the tracker.
///
public void HideTracker()
{
if (this.trackerLabel != null)
{
this.trackerLabel.Visible = false;
}
}
///
/// Hides the zoom rectangle.
///
public void HideZoomRectangle()
{
this.zoomRectangle = Rectangle.Empty;
this.Invalidate();
}
///
/// Invalidates the plot (not blocking the UI thread)
///
/// if set to true, all data collections will be updated.
public void InvalidatePlot(bool updateData)
{
lock (this.invalidateLock)
{
this.isModelInvalidated = true;
this.updateDataFlag = this.updateDataFlag || updateData;
}
this.Invalidate();
}
///
/// Called when the Model property has been changed.
///
public void OnModelChanged()
{
lock (this.modelLock)
{
if (this.currentModel != null)
{
((IPlotModel)this.currentModel).AttachPlotView(null);
this.currentModel = null;
}
if (this.Model != null)
{
((IPlotModel)this.Model).AttachPlotView(this);
this.currentModel = this.Model;
}
}
this.InvalidatePlot(true);
}
///
/// Sets the cursor type.
///
/// The cursor type.
public void SetCursorType(CursorType cursorType)
{
switch (cursorType)
{
case CursorType.Pan:
this.Cursor = this.PanCursor;
break;
case CursorType.ZoomRectangle:
this.Cursor = this.ZoomRectangleCursor;
break;
case CursorType.ZoomHorizontal:
this.Cursor = this.ZoomHorizontalCursor;
break;
case CursorType.ZoomVertical:
this.Cursor = this.ZoomVerticalCursor;
break;
default:
this.Cursor = Cursors.Arrow;
break;
}
}
///
/// Shows the tracker.
///
/// The data.
public void ShowTracker(TrackerHitResult data)
{
if (this.trackerLabel == null)
{
this.trackerLabel = new Label { Parent = this, BackColor = Color.LightSkyBlue, AutoSize = true, Padding = new Padding(5) };
}
this.trackerLabel.Text = data.ToString();
this.trackerLabel.Top = (int)data.Position.Y - this.trackerLabel.Height;
this.trackerLabel.Left = (int)data.Position.X - this.trackerLabel.Width / 2;
this.trackerLabel.Visible = true;
}
///
/// Shows the zoom rectangle.
///
/// The rectangle.
public void ShowZoomRectangle(OxyRect rectangle)
{
this.zoomRectangle = new Rectangle((int)rectangle.Left, (int)rectangle.Top, (int)rectangle.Width, (int)rectangle.Height);
this.Invalidate();
}
///
/// Sets the clipboard text.
///
/// The text.
public void SetClipboardText(string text)
{
try
{
// todo: can't get the following solution to work
// http://stackoverflow.com/questions/5707990/requested-clipboard-operation-did-not-succeed
Clipboard.SetText(text);
}
catch (ExternalException ee)
{
// Requested Clipboard operation did not succeed.
MessageBox.Show(this, ee.Message, "OxyPlot");
}
}
///
/// Raises the event.
///
/// A that contains the event data.
protected override void OnMouseDown(MouseEventArgs e)
{
base.OnMouseDown(e);
this.Focus();
this.Capture = true;
this.ActualController.HandleMouseDown(this, e.ToMouseDownEventArgs(GetModifiers()));
}
///
/// Raises the event.
///
/// A that contains the event data.
protected override void OnMouseMove(MouseEventArgs e)
{
base.OnMouseMove(e);
this.ActualController.HandleMouseMove(this, e.ToMouseEventArgs(GetModifiers()));
}
///
/// Raises the event.
///
/// A that contains the event data.
protected override void OnMouseUp(MouseEventArgs e)
{
base.OnMouseUp(e);
this.Capture = false;
this.ActualController.HandleMouseUp(this, e.ToMouseUpEventArgs(GetModifiers()));
}
///
/// Raises the event.
///
/// An that contains the event data.
protected override void OnMouseEnter(EventArgs e)
{
base.OnMouseEnter(e);
this.ActualController.HandleMouseEnter(this, e.ToMouseEventArgs(GetModifiers()));
}
///
/// Raises the event.
///
/// An that contains the event data.
protected override void OnMouseLeave(EventArgs e)
{
base.OnMouseLeave(e);
this.ActualController.HandleMouseLeave(this, e.ToMouseEventArgs(GetModifiers()));
}
///
/// Raises the event.
///
/// A that contains the event data.
protected override void OnMouseWheel(MouseEventArgs e)
{
base.OnMouseWheel(e);
this.ActualController.HandleMouseWheel(this, e.ToMouseWheelEventArgs(GetModifiers()));
}
///
/// Raises the event.
///
/// A that contains the event data.
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
try
{
lock (this.invalidateLock)
{
if (this.isModelInvalidated)
{
if (this.model != null)
{
((IPlotModel)this.model).Update(this.updateDataFlag);
this.updateDataFlag = false;
}
this.isModelInvalidated = false;
}
}
lock (this.renderingLock)
{
this.renderContext.SetGraphicsTarget(e.Graphics);
if (this.model != null)
{
if (!this.model.Background.IsUndefined())
{
using (var brush = new SolidBrush(this.model.Background.ToColor()))
{
e.Graphics.FillRectangle(brush, e.ClipRectangle);
}
}
((IPlotModel)this.model).Render(this.renderContext, this.Width, this.Height);
}
if (this.zoomRectangle != Rectangle.Empty)
{
using (var zoomBrush = new SolidBrush(Color.FromArgb(0x40, 0xFF, 0xFF, 0x00)))
using (var zoomPen = new Pen(Color.Black))
{
zoomPen.DashPattern = new float[] { 3, 1 };
e.Graphics.FillRectangle(zoomBrush, this.zoomRectangle);
e.Graphics.DrawRectangle(zoomPen, this.zoomRectangle);
}
}
}
}
catch (Exception paintException)
{
var trace = new StackTrace(paintException);
Debug.WriteLine(paintException);
Debug.WriteLine(trace);
using (var font = new Font("Arial", 10))
{
e.Graphics.ResetTransform();
e.Graphics.DrawString(
"OxyPlot paint exception: " + paintException.Message, font, Brushes.Red, this.Width * 0.5f, this.Height * 0.5f, new StringFormat { Alignment = StringAlignment.Center, LineAlignment = StringAlignment.Center });
}
}
}
///
/// Raises the event.
///
/// A that contains the event data.
protected override void OnPreviewKeyDown(PreviewKeyDownEventArgs e)
{
base.OnPreviewKeyDown(e);
var args = new OxyKeyEventArgs { ModifierKeys = GetModifiers(), Key = e.KeyCode.Convert() };
this.ActualController.HandleKeyDown(this, args);
}
///
/// Raises the event.
///
/// An that contains the event data.
protected override void OnResize(EventArgs e)
{
base.OnResize(e);
this.InvalidatePlot(false);
}
///
/// Gets the current modifier keys.
///
/// A value.
private static OxyModifierKeys GetModifiers()
{
var modifiers = OxyModifierKeys.None;
// ReSharper disable once RedundantNameQualifier
if ((Control.ModifierKeys & Keys.Shift) == Keys.Shift)
{
modifiers |= OxyModifierKeys.Shift;
}
// ReSharper disable once RedundantNameQualifier
if ((Control.ModifierKeys & Keys.Control) == Keys.Control)
{
modifiers |= OxyModifierKeys.Control;
}
// ReSharper disable once RedundantNameQualifier
if ((Control.ModifierKeys & Keys.Alt) == Keys.Alt)
{
modifiers |= OxyModifierKeys.Alt;
}
return modifiers;
}
}
}