In this tutorial, you create a user-defined ending style, which is in a cross-ending x-shape format. Then integrate it with the annotations framework rendering. And finally, you save and load it in XML format.
Start with the project created in Draw and Edit Annotations on Images.
Add the following declaration for the code of main form:
using System.Xml;
Add a new C# class to your project and name it AnnCrossLineEnding. Set this new class to inherit from AnnLineEnding. Then, override some members and implement new functionality for our custom ending style by adding the following code to the new class:
// The custom ending style we want to implement
public class AnnCrossLineEnding : AnnLineEnding
{
public AnnCrossLineEnding()
{
}
protected override AnnLineEnding Create()
{
return new AnnCrossLineEnding();
}
public override AnnLineEnding Clone()
{
AnnCrossLineEnding arrowLineEnding = base.Clone() as AnnCrossLineEnding;
arrowLineEnding.Closed = _closed;
return arrowLineEnding;
}
public override int Id
{
// Note when creating custom ending style you add unique Id that is not used before
// for existing ending style the ids all are negative so we will set id = 1 for our custom ending style
get { return 1; }
}
// This is the property that controls if we want to close the ending style cross shape
private bool _closed = false;
public bool Closed
{
get { return _closed; }
set { _closed = value; }
}
// Returns the array of points that composes style shape , this will be used when rendering the style and also when hit testing it
public override LeadPointD[] GetStylePoints(LeadPointD lineStart, LeadPointD lineEnd)
{
double length = this.Length.Value / 2;
// First cross on line start
double lineStartX = lineStart.X;
double lineStartY = lineStart.Y;
LeadPointD pt0 = LeadPointD.Create(lineStartX - length, lineStartY - length);
LeadPointD pt1 = LeadPointD.Create(lineStartX + length, lineStartY + length);
LeadPointD pt2 = LeadPointD.Create(lineStartX - length, lineStartY + length);
LeadPointD pt3 = LeadPointD.Create(lineStartX + length, lineStartY - length);
return new LeadPointD[] { pt0, pt1, pt2, pt3 };
}
// Here you can specify if you want the style to be hit testable and you can move the object by dragging it
public override bool HitTest(LeadPointD point, double hitTestBuffer, LeadPointD lineStart, LeadPointD lineEnd)
{
return false;
}
// Saving the custom style to the xml document
public override XmlNode Serialize(AnnSerializeOptions options, XmlNode parentNode, XmlDocument document, string elementName)
{
XmlNode styleNode = base.Serialize(options, parentNode, document, elementName);
XmlNode element = document.CreateElement("StyleClosed");
element.InnerText = _closed.ToString();
styleNode.AppendChild(element);
return styleNode;
}
// Loading the custom style from xml document
public override void Deserialize(AnnDeserializeOptions options, XmlNode element, XmlDocument document)
{
base.Deserialize(options, element, document);
XmlNode childNode = element.SelectSingleNode("StyleClosed");
if (childNode != null)
{
_closed = bool.Parse(childNode.FirstChild.Value);
}
}
In AnnPolyLineObjectRenderer override method AnnPolyLineObjectRenderer.RenderEndingStyles by adding the below class to the main form code:
// The custom polyline renderer that will take care of rendering custom ending style
public class AnnCustomPolylineObjectRenderer : AnnPolylineObjectRenderer
{
public override void Render(AnnContainerMapper mapper, AnnObject annObject)
{
base.Render(mapper, annObject);
}
public override void RenderEndingStyles(AnnContainerMapper mapper, AnnPolylineObject annPolyLineObject)
{
// Call the base to draw the original ending styles
base.RenderEndingStyles(mapper, annPolyLineObject);
// Now we will draw our ending style
AnnCrossLineEnding startStyle = annPolyLineObject.StartStyle as AnnCrossLineEnding;
AnnCrossLineEnding endStyle = annPolyLineObject.EndStyle as AnnCrossLineEnding;
AnnWinFormsRenderingEngine engine = this.RenderingEngine as AnnWinFormsRenderingEngine;
LeadPointCollection objectPoints = annPolyLineObject.Points;
int count = objectPoints.Count;
if (count < 2)
return;
if (annPolyLineObject.SupportsLineEndings)
{
LeadPointD firstPoint = objectPoints[0];
if (startStyle != null)
RenderCrossLineEnding(startStyle, engine, mapper, annPolyLineObject.FixedStateOperations, firstPoint, objectPoints[1]);
if (endStyle != null)
RenderCrossLineEnding(endStyle, engine, mapper, annPolyLineObject.FixedStateOperations, objectPoints[count - 1], objectPoints[count - 2]);
}
}
private void RenderCrossLineEnding(AnnCrossLineEnding crossLineEnding, AnnWinFormsRenderingEngine engine, AnnContainerMapper mapper, AnnFixedStateOperations operations, LeadPointD lineStart, LeadPointD lineEnd)
{
AnnStroke stroke = mapper.StrokeFromContainerCoordinates(crossLineEnding.Stroke, operations);
if (stroke != null)
{
LeadPointD[] endingStylePoints = mapper.PointsFromContainerCoordinates(crossLineEnding.GetStylePoints(lineStart, lineEnd), operations);
if (endingStylePoints != null && endingStylePoints.Length == 4)
{
using (Pen pen = AnnWinFormsRenderingEngine.ToPen(stroke))
{
engine.Context.DrawLine(pen, AnnWinFormsRenderingEngine.ToPoint(endingStylePoints[0]), AnnWinFormsRenderingEngine.ToPoint(endingStylePoints[1]));
engine.Context.DrawLine(pen, AnnWinFormsRenderingEngine.ToPoint(endingStylePoints[2]), AnnWinFormsRenderingEngine.ToPoint(endingStylePoints[3]));
if (crossLineEnding.Closed)
{
engine.Context.DrawLine(pen, AnnWinFormsRenderingEngine.ToPoint(endingStylePoints[0]), AnnWinFormsRenderingEngine.ToPoint(endingStylePoints[3]));
engine.Context.DrawLine(pen, AnnWinFormsRenderingEngine.ToPoint(endingStylePoints[2]), AnnWinFormsRenderingEngine.ToPoint(endingStylePoints[1]));
}
}
}
}
}
}
Integrate the new custom polyline object renderer to the annotations framework. Add the code below to the protected override void OnLoad(EventArgs e)
method after line
_annotations = new AutomationManagerHelper(automationManager);
:
// Hook the custom polyline renderer to the existing renderers
AnnPolylineObjectRenderer polyLineRenderer = automationManager.RenderingEngine.Renderers[AnnObject.LineObjectId] as AnnPolylineObjectRenderer;
AnnCustomPolylineObjectRenderer cutomerRenderer = new AnnCustomPolylineObjectRenderer();
cutomerRenderer.LocationsThumbStyle = polyLineRenderer.LocationsThumbStyle;
cutomerRenderer.RotateCenterThumbStyle = polyLineRenderer.RotateCenterThumbStyle;
cutomerRenderer.RotateGripperThumbStyle = polyLineRenderer.RotateGripperThumbStyle;
automationManager.RenderingEngine.Renderers[AnnObject.LineObjectId] = cutomerRenderer;
Set the custom ending style to the line object. Add a new button to the main form on a suitable location. Set the button's text field to Add Custom Line Ending and its click handler the following code:
private void _btnAddCustomLineEnding_Click(object sender, EventArgs e)
{
AnnAutomation automation = _annotations.AutomationManager.Automations[0];
AnnCrossLineEnding startStyle = new AnnCrossLineEnding();
startStyle.Length = automation.Container.Mapper.LengthToContainerCoordinates(20);
AnnCrossLineEnding endStyle = new AnnCrossLineEnding();
endStyle.Length = automation.Container.Mapper.LengthToContainerCoordinates(20);
endStyle.Closed = true;
if (automation.Container.Children.Count > 0)
{
AnnPolylineObject annPolylineObject = automation.Container.Children[0] as AnnPolylineObject;
if (annPolylineObject != null)
{
annPolylineObject.StartStyle = startStyle;
annPolylineObject.EndStyle = endStyle;
automation.Invalidate(LeadRectD.Empty);
}
}
}
To save and load your custom ending style, add a new button to the main form on a suitable location and set its text field to Save/Load custom style. On the button's click handler add the following code:
private void _btnSaveLoadCustomStyle_Click(object sender, EventArgs e)
{
AnnAutomation automation = _annotations.AutomationManager.Automations[0];
AnnContainer container = automation.Container;
AnnCodecs annCodecs = new AnnCodecs();
AnnDeserializeOptions options = new AnnDeserializeOptions();
annCodecs.DeserializeOptions = options;
// Hook to the DeserializeObject event to create the custom ending style instance when loading from xml
options.DeserializeObject += delegate(object sender2, AnnSerializeObjectEventArgs args)
{
AnnPolylineObject polyLine = args.AnnObject as AnnPolylineObject;
if (polyLine != null && polyLine.Id == AnnObject.LineObjectId)
{
if (polyLine.StartStyle == null)
{
if (args.TypeName == "1") // Our custom ending style id is 1
{
polyLine.StartStyle = new AnnCrossLineEnding();
}
}
else if (polyLine.EndStyle == null)
{
if (args.TypeName == "1") // Our custom ending style id is 1
{
polyLine.EndStyle = new AnnCrossLineEnding();
}
}
}
};
annCodecs.Save(@"C:\LEADTOOLS21\Resources\Images\temp.xml", container, AnnFormat.Annotations, 1);
// Remove all container children , we want to load them from xml file.
container.Children.Clear();
// Load the saved container childerns , this includes line object with custom ending style
AnnContainer tempContainer = annCodecs.Load(@"C:\LEADTOOLS21\Resources\Images\temp.xml", 1);
foreach (AnnObject annObject in tempContainer.Children)
container.Children.Add(annObject);
automation.Invalidate(LeadRectD.Empty);
}
Build and run the demo. In the demo's toolbar, select the line object and draw a line. Next, click on the Add Custom Line Ending button and you should see your custom ending style.
Now click on the Save/Load custom style button. Notice that your custom ending style loads correctly from the XML file.