Files
DynamicDataDisplay/Common/Plotter.cs
2024-02-23 00:46:06 -05:00

953 lines
25 KiB
C#

using System;
using System.IO;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using Microsoft.Research.DynamicDataDisplay.Common;
using Microsoft.Research.DynamicDataDisplay.Common.Auxiliary;
using System.Windows.Markup;
using System.ComponentModel;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Windows.Input;
using Microsoft.Research.DynamicDataDisplay.Common.UndoSystem;
using Microsoft.Research.DynamicDataDisplay.Navigation;
using System.Windows.Data;
using Microsoft.Research.DynamicDataDisplay.Charts.Navigation;
using System.Windows.Automation.Peers;
using System.Diagnostics;
namespace Microsoft.Research.DynamicDataDisplay
{
/// <summary>Plotter is a base control for displaying various graphs. It provides
/// means to draw chart itself and side space for axes, annotations, etc</summary>
[ContentProperty("Children")]
[TemplatePart(Name = "PART_HeaderPanel", Type = typeof(StackPanel))]
[TemplatePart(Name = "PART_FooterPanel", Type = typeof(StackPanel))]
[TemplatePart(Name = "PART_BottomPanel", Type = typeof(StackPanel))]
[TemplatePart(Name = "PART_LeftPanel", Type = typeof(StackPanel))]
[TemplatePart(Name = "PART_RightPanel", Type = typeof(StackPanel))]
[TemplatePart(Name = "PART_TopPanel", Type = typeof(StackPanel))]
[TemplatePart(Name = "PART_MainCanvas", Type = typeof(Canvas))]
[TemplatePart(Name = "PART_CentralGrid", Type = typeof(Grid))]
[TemplatePart(Name = "PART_MainGrid", Type = typeof(Grid))]
[TemplatePart(Name = "PART_ContentsGrid", Type = typeof(Grid))]
[TemplatePart(Name = "PART_ParallelCanvas", Type = typeof(Canvas))]
public abstract class Plotter : ContentControl
{
private PlotterLoadMode loadMode = PlotterLoadMode.Normal;
protected PlotterLoadMode LoadMode
{
get { return loadMode; }
}
protected Plotter() : this(PlotterLoadMode.Normal) { }
/// <summary>
/// Initializes a new instance of the <see cref="Plotter"/> class.
/// </summary>
protected Plotter(PlotterLoadMode loadMode)
{
this.loadMode = loadMode;
SetPlotter(this, this);
if (loadMode == PlotterLoadMode.Normal)
{
UpdateUIParts();
}
children = new PlotterChildrenCollection(this);
children.CollectionChanged += OnChildrenCollectionChanged;
Loaded += Plotter_Loaded;
Unloaded += Plotter_Unloaded;
genericResources = (ResourceDictionary)Application.LoadComponent(new Uri("/DynamicDataDisplay;component/Themes/Generic.xaml", UriKind.Relative));
ContextMenu = null;
}
void Plotter_Unloaded(object sender, RoutedEventArgs e)
{
OnUnloaded();
}
protected virtual void OnUnloaded() { }
protected override AutomationPeer OnCreateAutomationPeer()
{
return new PlotterAutomationPeer(this);
}
[EditorBrowsable(EditorBrowsableState.Never)]
public override bool ShouldSerializeContent()
{
return false;
}
protected override bool ShouldSerializeProperty(DependencyProperty dp)
{
// do not serialize context menu if it was created by DefaultContextMenu, because that context menu items contains references of plotter
if (dp == ContextMenuProperty && children.Any(el => el is DefaultContextMenu)) return false;
if (dp == TemplateProperty) return false;
if (dp == ContentProperty) return false;
return base.ShouldSerializeProperty(dp);
}
private const string templateKey = "defaultPlotterTemplate";
private const string styleKey = "defaultPlotterStyle";
private void UpdateUIParts()
{
ResourceDictionary dict = new ResourceDictionary
{
Source = new Uri("/DynamicDataDisplay;component/Common/PlotterStyle.xaml", UriKind.Relative)
};
Resources.MergedDictionaries.Add(dict);
Style = (Style)dict[styleKey];
ControlTemplate template = (ControlTemplate)dict[templateKey];
Template = template;
ApplyTemplate();
}
private ResourceDictionary genericResources;
protected ResourceDictionary GenericResources
{
get { return genericResources; }
}
/// <summary>
/// Forces plotter to load.
/// </summary>
public void PerformLoad()
{
isLoadedIntensionally = true;
ApplyTemplate();
Plotter_Loaded(null, null);
}
private bool isLoadedIntensionally = false;
protected virtual bool IsLoadedInternal
{
get { return isLoadedIntensionally || IsLoaded; }
}
private void Plotter_Loaded(object sender, RoutedEventArgs e)
{
ExecuteWaitingChildrenAdditions();
OnLoaded();
}
protected internal void ExecuteWaitingChildrenAdditions()
{
foreach (var action in waitingForExecute)
{
action();
}
waitingForExecute.Clear();
}
protected virtual void OnLoaded()
{
// this is done to enable keyboard shortcuts
Focus();
//foreach (var plotterElement in elementsWaitingForBeingAttached)
//{
// if (Children.Contains(plotterElement))
// {
// plotterElement.OnPlotterAttached(this);
// }
//}
//elementsWaitingForBeingAttached.Clear();
}
protected override void OnTemplateChanged(ControlTemplate oldTemplate, ControlTemplate newTemplate)
{
base.OnTemplateChanged(oldTemplate, newTemplate);
}
private Grid contentsGrid;
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
addedVisualElements.Clear();
foreach (var item in GetAllPanels())
{
INotifyingPanel panel = item as INotifyingPanel;
if (panel != null)
{
panel.ChildrenCreated -= notifyingItem_ChildrenCreated;
if (panel.NotifyingChildren != null)
{
panel.NotifyingChildren.CollectionChanged -= OnVisualCollectionChanged;
}
}
}
var headerPanel = GetPart<StackPanel>("PART_HeaderPanel");
MigrateChildren(this.headerPanel, headerPanel);
this.headerPanel = headerPanel;
var footerPanel = GetPart<StackPanel>("PART_FooterPanel");
MigrateChildren(this.footerPanel, footerPanel);
this.footerPanel = footerPanel;
var leftPanel = GetPart<StackPanel>("PART_LeftPanel");
MigrateChildren(this.leftPanel, leftPanel);
this.leftPanel = leftPanel;
var bottomPanel = GetPart<StackPanel>("PART_BottomPanel");
MigrateChildren(this.bottomPanel, bottomPanel);
this.bottomPanel = bottomPanel;
var rightPanel = GetPart<StackPanel>("PART_RightPanel");
MigrateChildren(this.rightPanel, rightPanel);
this.rightPanel = rightPanel;
var topPanel = GetPart<StackPanel>("PART_TopPanel");
MigrateChildren(this.topPanel, topPanel);
this.topPanel = topPanel;
var mainCanvas = GetPart<Canvas>("PART_MainCanvas");
MigrateChildren(this.mainCanvas, mainCanvas);
this.mainCanvas = mainCanvas;
var centralGrid = GetPart<Grid>("PART_CentralGrid");
MigrateChildren(this.centralGrid, centralGrid);
this.centralGrid = centralGrid;
var mainGrid = GetPart<Grid>("PART_MainGrid");
MigrateChildren(this.mainGrid, mainGrid);
this.mainGrid = mainGrid;
var parallelCanvas = GetPart<Canvas>("PART_ParallelCanvas");
MigrateChildren(this.parallelCanvas, parallelCanvas);
this.parallelCanvas = parallelCanvas;
var contentsGrid = GetPart<Grid>("PART_ContentsGrid");
MigrateChildren(this.contentsGrid, contentsGrid);
this.contentsGrid = contentsGrid;
Content = contentsGrid;
AddLogicalChild(contentsGrid);
foreach (var notifyingItem in GetAllPanels())
{
INotifyingPanel panel = notifyingItem as INotifyingPanel;
if (panel != null)
{
if (panel.NotifyingChildren == null)
panel.ChildrenCreated += notifyingItem_ChildrenCreated;
else
panel.NotifyingChildren.CollectionChanged += OnVisualCollectionChanged;
}
}
}
private void MigrateChildren(Panel previousParent, Panel currentParent)
{
if (previousParent != null && currentParent != null)
{
UIElement[] children = new UIElement[previousParent.Children.Count];
previousParent.Children.CopyTo(children, 0);
previousParent.Children.Clear();
foreach (var child in children)
{
if (!currentParent.Children.Contains(child))
{
currentParent.Children.Add(child);
}
}
}
else if (previousParent != null)
{
previousParent.Children.Clear();
}
}
private void notifyingItem_ChildrenCreated(object sender, EventArgs e)
{
INotifyingPanel panel = (INotifyingPanel)sender;
SubscribePanelEvents(panel);
}
private void SubscribePanelEvents(INotifyingPanel panel)
{
panel.ChildrenCreated -= notifyingItem_ChildrenCreated;
panel.NotifyingChildren.CollectionChanged -= OnVisualCollectionChanged;
panel.NotifyingChildren.CollectionChanged += OnVisualCollectionChanged;
}
private void OnVisualCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.NewItems != null)
{
foreach (var item in e.NewItems)
{
INotifyingPanel notifyingPanel = item as INotifyingPanel;
if (notifyingPanel != null)
{
if (notifyingPanel.NotifyingChildren != null)
{
notifyingPanel.NotifyingChildren.CollectionChanged -= OnVisualCollectionChanged;
notifyingPanel.NotifyingChildren.CollectionChanged += OnVisualCollectionChanged;
}
else
{
notifyingPanel.ChildrenCreated += notifyingItem_ChildrenCreated;
}
}
OnVisualChildAdded((UIElement)item, (UIElementCollection)sender);
}
}
if (e.OldItems != null)
{
foreach (var item in e.OldItems)
{
INotifyingPanel notifyingPanel = item as INotifyingPanel;
if (notifyingPanel != null)
{
notifyingPanel.ChildrenCreated -= notifyingItem_ChildrenCreated;
if (notifyingPanel.NotifyingChildren != null)
{
notifyingPanel.NotifyingChildren.CollectionChanged -= OnVisualCollectionChanged;
}
}
OnVisualChildRemoved((UIElement)item, (UIElementCollection)sender);
}
}
}
private readonly VisualBindingCollection visualBindingCollection = new VisualBindingCollection();
public VisualBindingCollection VisualBindings
{
get { return visualBindingCollection; }
}
protected virtual void OnVisualChildAdded(UIElement target, UIElementCollection uIElementCollection)
{
IPlotterElement element = null;
if (addingElements.Count > 0)
{
element = addingElements.Peek();
var dict = visualBindingCollection.Cache;
var proxy = dict[element];
List<UIElement> visualElements;
if (!addedVisualElements.ContainsKey(element))
{
visualElements = new List<UIElement>();
addedVisualElements.Add(element, visualElements);
}
else
{
visualElements = addedVisualElements[element];
}
visualElements.Add(target);
SetBindings(proxy, target);
}
}
private void SetBindings(UIElement proxy, UIElement target)
{
if (proxy != target)
{
foreach (var property in GetPropertiesToSetBindingOn())
{
BindingOperations.SetBinding(target, property, new Binding { Path = new PropertyPath(property.Name), Source = proxy, Mode = BindingMode.TwoWay });
}
}
}
private void RemoveBindings(UIElement proxy, UIElement target)
{
if (proxy != target)
{
foreach (var property in GetPropertiesToSetBindingOn())
{
BindingOperations.ClearBinding(target, property);
}
}
}
private IEnumerable<DependencyProperty> GetPropertiesToSetBindingOn()
{
yield return UIElement.OpacityProperty;
yield return UIElement.VisibilityProperty;
yield return UIElement.IsHitTestVisibleProperty;
//yield return FrameworkElement.DataContextProperty;
}
protected virtual void OnVisualChildRemoved(UIElement target, UIElementCollection uiElementCollection)
{
IPlotterElement element = null;
if (removingElements.Count > 0)
{
element = removingElements.Peek();
var dict = visualBindingCollection.Cache;
var proxy = dict[element];
if (addedVisualElements.ContainsKey(element))
{
var list = addedVisualElements[element];
list.Remove(target);
if (list.Count == 0)
{
dict.Remove(element);
}
addedVisualElements.Remove(element);
}
RemoveBindings(proxy, target);
}
}
internal virtual IEnumerable<Panel> GetAllPanels()
{
yield return headerPanel;
yield return footerPanel;
yield return leftPanel;
yield return bottomPanel;
yield return rightPanel;
yield return topPanel;
yield return mainCanvas;
yield return centralGrid;
yield return mainGrid;
yield return parallelCanvas;
yield return contentsGrid;
}
private T GetPart<T>(string name)
{
return (T)Template.FindName(name, this);
}
#region Children and add/removed events handling
private readonly PlotterChildrenCollection children;
/// <summary>
/// Provides access to Plotter's children charts.
/// </summary>
/// <value>The children.</value>
[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
public PlotterChildrenCollection Children
{
[DebuggerStepThrough]
get { return children; }
}
private List<Action> waitingForExecute = new List<Action>();
bool executedWaitingChildrenAdding = false;
private void OnChildrenCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (IsLoadedInternal && !executedWaitingChildrenAdding)
{
executedWaitingChildrenAdding = true;
ExecuteWaitingChildrenAdditions();
}
if (e.NewItems != null)
{
foreach (IPlotterElement item in e.NewItems)
{
if (IsLoadedInternal)
{
OnChildAdded(item);
}
else
{
waitingForExecute.Add(() => OnChildAdded(item));
}
}
}
if (e.OldItems != null)
{
foreach (IPlotterElement item in e.OldItems)
{
if (IsLoadedInternal)
{
OnChildRemoving(item);
}
else
{
waitingForExecute.Add(() => OnChildRemoving(item));
}
}
}
}
private readonly Stack<IPlotterElement> addingElements = new Stack<IPlotterElement>();
bool performChildChecks = true;
internal bool PerformChildChecks
{
get { return performChildChecks; }
set { performChildChecks = value; }
}
private IPlotterElement currentChild = null;
protected IPlotterElement CurrentChild
{
get { return currentChild; }
}
protected virtual void OnChildAdded(IPlotterElement child)
{
if (child != null)
{
addingElements.Push(child);
currentChild = child;
try
{
UIElement visualProxy = CreateVisualProxy(child);
visualBindingCollection.Cache.Add(child, visualProxy);
if (performChildChecks && child.Plotter != null)
{
throw new InvalidOperationException(Strings.Exceptions.PlotterElementAddedToAnotherPlotter);
}
FrameworkElement styleableElement = child as FrameworkElement;
if (styleableElement != null)
{
Type key = styleableElement.GetType();
if (genericResources.Contains(key))
{
Style elementStyle = (Style)genericResources[key];
styleableElement.Style = elementStyle;
}
}
if (performChildChecks)
{
child.OnPlotterAttached(this);
if (child.Plotter != this)
{
throw new InvalidOperationException(Strings.Exceptions.InvalidParentPlotterValue);
}
}
DependencyObject dependencyObject = child as DependencyObject;
if (dependencyObject != null)
{
SetPlotter(dependencyObject, this);
}
}
finally
{
addingElements.Pop();
currentChild = null;
}
}
}
private UIElement CreateVisualProxy(IPlotterElement child)
{
if (visualBindingCollection.Cache.ContainsKey(child))
throw new InvalidOperationException(Strings.Exceptions.VisualBindingsWrongState);
UIElement result = child as UIElement;
if (result == null)
{
result = new UIElement();
}
return result;
}
private readonly Stack<IPlotterElement> removingElements = new Stack<IPlotterElement>();
protected virtual void OnChildRemoving(IPlotterElement child)
{
if (child != null)
{
currentChild = child;
removingElements.Push(child);
try
{
// todo probably here child.Plotter can be null.
if (performChildChecks && child.Plotter != this && child.Plotter != null)
{
throw new InvalidOperationException(Strings.Exceptions.InvalidParentPlotterValueRemoving);
}
if (performChildChecks)
{
if (child.Plotter != null)
child.OnPlotterDetaching(this);
if (child.Plotter != null)
{
throw new InvalidOperationException(Strings.Exceptions.ParentPlotterNotNull);
}
}
DependencyObject dependencyObject = child as DependencyObject;
if (dependencyObject != null)
{
SetPlotter(dependencyObject, null);
}
visualBindingCollection.Cache.Remove(child);
if (addedVisualElements.ContainsKey(child) && addedVisualElements[child].Count > 0)
{
throw new InvalidOperationException(String.Format(Strings.Exceptions.PlotterElementDidnotCleanedAfterItself, child.ToString()));
}
}
finally
{
currentChild = null;
removingElements.Pop();
}
}
}
private readonly Dictionary<IPlotterElement, List<UIElement>> addedVisualElements = new Dictionary<IPlotterElement, List<UIElement>>();
#endregion
#region Layout zones
private Panel parallelCanvas;
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public Panel ParallelCanvas
{
get { return parallelCanvas; }
protected set { parallelCanvas = value; }
}
private Panel headerPanel;
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public Panel HeaderPanel
{
get { return headerPanel; }
protected set { headerPanel = value; }
}
private Panel footerPanel;
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public Panel FooterPanel
{
get { return footerPanel; }
protected set { footerPanel = value; }
}
private Panel leftPanel;
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public Panel LeftPanel
{
get { return leftPanel; }
protected set { leftPanel = value; }
}
private Panel rightPanel;
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public Panel RightPanel
{
get { return rightPanel; }
protected set { rightPanel = value; }
}
private Panel topPanel;
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public Panel TopPanel
{
get { return topPanel; }
protected set { topPanel = value; }
}
private Panel bottomPanel;
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public Panel BottomPanel
{
get { return bottomPanel; }
protected set { bottomPanel = value; }
}
private Panel mainCanvas;
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public Panel MainCanvas
{
get { return mainCanvas; }
protected set { mainCanvas = value; }
}
private Panel centralGrid;
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public Panel CentralGrid
{
get { return centralGrid; }
protected set { centralGrid = value; }
}
private Panel mainGrid;
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public Panel MainGrid
{
get { return mainGrid; }
protected set { mainGrid = value; }
}
#endregion
#region Screenshots & copy to clipboard
public BitmapSource CreateScreenshot()
{
UIElement parent = (UIElement)Parent;
Rect renderBounds = new Rect(RenderSize);
Point p1 = renderBounds.TopLeft;
Point p2 = renderBounds.BottomRight;
if (parent != null)
{
p1 = TranslatePoint(p1, parent);
p2 = TranslatePoint(p2, parent);
}
Int32Rect rect = new Rect(p1, p2).ToInt32Rect();
return ScreenshotHelper.CreateScreenshot(this, rect);
}
/// <summary>Saves screenshot to file.</summary>
/// <param name="filePath">File path.</param>
public void SaveScreenshot(string filePath)
{
ScreenshotHelper.SaveBitmapToFile(CreateScreenshot(), filePath);
}
/// <summary>
/// Saves screenshot to stream.
/// </summary>
/// <param name="stream">The stream.</param>
/// <param name="fileExtension">The file type extension.</param>
public void SaveScreenshotToStream(Stream stream, string fileExtension)
{
ScreenshotHelper.SaveBitmapToStream(CreateScreenshot(), stream, fileExtension);
}
/// <summary>Copies the screenshot to clipboard.</summary>
public void CopyScreenshotToClipboard()
{
Clipboard.Clear();
Clipboard.SetImage(CreateScreenshot());
}
#endregion
#region IsDefaultElement attached property
protected void SetAllChildrenAsDefault()
{
foreach (var child in Children.OfType<DependencyObject>())
{
child.SetValue(IsDefaultElementProperty, true);
}
}
/// <summary>Gets a value whether specified graphics object is default to this plotter or not</summary>
/// <param name="obj">Graphics object to check</param>
/// <returns>True if it is default or false otherwise</returns>
public static bool GetIsDefaultElement(DependencyObject obj)
{
return (bool)obj.GetValue(IsDefaultElementProperty);
}
public static void SetIsDefaultElement(DependencyObject obj, bool value)
{
obj.SetValue(IsDefaultElementProperty, value);
}
public static readonly DependencyProperty IsDefaultElementProperty = DependencyProperty.RegisterAttached(
"IsDefaultElement",
typeof(bool),
typeof(Plotter),
new UIPropertyMetadata(false));
/// <summary>Removes all user graphs from given UIElementCollection,
/// leaving only default graphs</summary>
protected static void RemoveUserElements(IList<IPlotterElement> elements)
{
int index = 0;
while (index < elements.Count)
{
DependencyObject d = elements[index] as DependencyObject;
if (d != null && !GetIsDefaultElement(d))
{
elements.RemoveAt(index);
}
else
{
index++;
}
}
}
public void RemoveUserElements()
{
RemoveUserElements(Children);
}
#endregion
#region IsDefaultAxis
public static bool GetIsDefaultAxis(DependencyObject obj)
{
return (bool)obj.GetValue(IsDefaultAxisProperty);
}
public static void SetIsDefaultAxis(DependencyObject obj, bool value)
{
obj.SetValue(IsDefaultAxisProperty, value);
}
public static readonly DependencyProperty IsDefaultAxisProperty = DependencyProperty.RegisterAttached(
"IsDefaultAxis",
typeof(bool),
typeof(Plotter),
new UIPropertyMetadata(false, OnIsDefaultAxisChanged));
private static void OnIsDefaultAxisChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
Plotter parentPlotter = null;
IPlotterElement plotterElement = d as IPlotterElement;
if (plotterElement != null)
{
parentPlotter = plotterElement.Plotter;
if (parentPlotter != null)
{
parentPlotter.OnIsDefaultAxisChangedCore(d, e);
}
}
}
protected virtual void OnIsDefaultAxisChangedCore(DependencyObject d, DependencyPropertyChangedEventArgs e) { }
#endregion
#region Undo
private readonly UndoProvider undoProvider = new UndoProvider();
public UndoProvider UndoProvider
{
get { return undoProvider; }
}
#endregion
#region Plotter attached property
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public static Plotter GetPlotter(DependencyObject obj)
{
return (Plotter)obj.GetValue(PlotterProperty);
}
public static void SetPlotter(DependencyObject obj, Plotter value)
{
obj.SetValue(PlotterProperty, value);
}
public static readonly DependencyProperty PlotterProperty = DependencyProperty.RegisterAttached(
"Plotter",
typeof(Plotter),
typeof(Plotter),
new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.Inherits, OnPlotterChanged));
private static void OnPlotterChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
FrameworkElement element = d as FrameworkElement;
Plotter prevPlotter = (Plotter)e.OldValue;
Plotter currPlotter = (Plotter)e.NewValue;
// raise Plotter[*] events, where * is Attached, Detaching, Changed.
if (element != null)
{
PlotterChangedEventArgs args = new PlotterChangedEventArgs(prevPlotter, currPlotter, PlotterDetachingEvent);
if (currPlotter == null && prevPlotter != null)
{
RaisePlotterEvent(element, args);
}
else if (currPlotter != null)
{
args.RoutedEvent = PlotterAttachedEvent;
RaisePlotterEvent(element, args);
}
args.RoutedEvent = PlotterChangedEvent;
RaisePlotterEvent(element, args);
}
}
private static void RaisePlotterEvent(FrameworkElement element, PlotterChangedEventArgs args)
{
element.RaiseEvent(args);
PlotterEvents.Notify(element, args);
}
#endregion
#region Plotter routed events
public static readonly RoutedEvent PlotterAttachedEvent = EventManager.RegisterRoutedEvent(
"PlotterAttached",
RoutingStrategy.Direct,
typeof(PlotterChangedEventHandler),
typeof(Plotter));
public static readonly RoutedEvent PlotterDetachingEvent = EventManager.RegisterRoutedEvent(
"PlotterDetaching",
RoutingStrategy.Direct,
typeof(PlotterChangedEventHandler),
typeof(Plotter));
public static readonly RoutedEvent PlotterChangedEvent = EventManager.RegisterRoutedEvent(
"PlotterChanged",
RoutingStrategy.Direct,
typeof(PlotterChangedEventHandler),
typeof(Plotter));
#endregion
}
public delegate void PlotterChangedEventHandler(object sender, PlotterChangedEventArgs e);
}