In this article, you will learn how to create a tool that allows you to draw a curve that zig zags along the path. You will also learn how to set a new tool, modify the code to create a curve, set up custom application preferences, and bind those preferences to the UI in property bar.
In File Explorer, go to C:\Users\<username>\AppData\Roaming\Corel\CorelDRAW Graphics Suite X7\Draw (or Designer) and copy the Workspace folder to a separate location. This backup is important because eventually you will need to hold F8 to reset your workspace after making UI changes. Once you're done developing your tool, you can restore your old workspace by copying the backup back to the original location.
Run Visual Studio and create a new tool using the CorelDRAW and Corel DESIGNER Tool Addon project template (to install the template, see Creating Custom Tools in CorelDRAW X7 and Corel DESIGNER X7). For the project name, enter "ZigZagTool." At this point, you can build the project and press F5 to make sure everything is working.
The template code is a tool to create a line segment between two points. The zig zag will follow a path. Remove Point save; and all code that refers to the save variable.
Disable the mouse snap when drawing by removing everything except Handled = isDrawing; from OnMouseSnap.
While the mouse is dragged across the document, we want to keep an array of points of each mouse move position. Add a new variable to the bottom of the class:
PointRange pts; // mouse move points
We must construct the PointRange object. In OnStartState, add:
pts = Application.Math.CreatePointRange();
When the left mouse button is pressed, we want to add the first point to the point range. In OnLButtonDownLeaveGrace, add the following:
pts.AddPoint(pt);
When the mouse moves, we need to add a point, but only if we're drawing:
if (isDrawing){ pts.AddPoint(pt);
When the tool is done drawing, or the user hits the escape key, we must clear all previous points. In the Reset function, add:
pts.RemoveAll();
To create a zig zag shape, we need to know the height of each zig zag as well as the distance between each zig zag. Define two new application preferences by adding the following in the constructor:
Application.RegisterUserApplicationPreference("ZigZagTool", "Height", 0.125);Application.RegisterUserApplicationPreference("ZigZagTool", "Distance", 0.25);
This will be stored within the registry and the workspace. When the application restarts, it will use the last value that the user set.
To generate the curve, use the following algorithm that fits the points to a curve, then divides the curve into equal parts, and finally create line segments alternating from left to right perpendicular to each of the divided subpaths.
private Curve CreateZigZagCurve() { Curve result = null; if (pts.Count > 1) { result = Application.CreateCurve(); PointRange smoothedPoints = pts.GetCopy() smoothedPoints.Smoothen(3, false); Curve stroke = Application.CreateCurve(); stroke.AppendSubpathFitToPoints(smoothedPoints); if (stroke.SubPaths.Count > 0) { double height = Application.GetApplicationPreferenceValue("ZigZagTool", "Height"); double distance = Application.GetApplicationPreferenceValue("ZigZagTool", "Distance"); int divisions = (int)Math.Round(stroke.Length / distance); if (divisions > 0) stroke.SubPaths.First.EqualDivide(divisions); Point first = stroke.SubPaths.First.GetPointAt(0.0); SubPath path = result.CreateSubPath(first.x, first.y); for (int i = 2; i <= stroke.SubPaths.Count; ++i) { Vector perpendicular = stroke.SubPaths[(i)].GetPerpendicularVectorAt(0.0); Point position = stroke.SubPaths[(i)].GetPointAt(0.0); perpendicular.Length = ((i & 1) == 1) ? height : -height; position.Add(perpendicular); path.AppendLineSegment(position.x, position.y); } Point last = stroke.SubPaths.Last.GetPointAt(1.0); path.AppendLineSegment(last.x, last.y); } } return result; }
Below is a diagram that illustrates how the above function creates the zig-zag curve:
The black dots represent the beginning of each subpath after the equal division. The green lines are the perpendicular vectors at the end of each subpath.
To display a preview of the curve while drawing, update the function OnMouseMove to:
public void OnMouseMove(Point pt) { if (isDrawing) { pts.AddPoint(pt); // update the xor'd line Curve curve = CreateZigZagCurve(); if (curve != null) { ui.SetCurve(curve); ui.Show(); } } }
Finally, to create the shape when the user hits enter or lets go of the left mouse button, modify OnCommit to:
public void OnCommit(Point pt) { if (isDrawing) { Curve curve = CreateZigZagCurve(); Application.ActiveLayer.CreateCurve(curve); Reset(); // cleanup and reset UI and variable } }
In the interest of keeping the code simply, the zig zag fits to the length of the curve and as a result, the XOR'd preview jumps while drawing.
Now that the tool is working, we need to update the toolbar to allow the user to change the height and distance of the zig-zags. Open AppUI.xslt and add the following beneath the existing tool item:
<!-- Zig-Zag Tool Height spinner --> <itemData guid="bf9012b1-da00-ae84-4c97-b0bb84d50288" type="spinner" displayUnit="*Bind(DataSource=DocumentPrefsDS;Path=XRulerUnit)" internalUnit="*Bind(DataSource=DocumentPrefsDS;Path=XRulerUnit)" increment="0.01 in; 0.1 mm; 0.5 pt; 1 px; 0,0.5 cc; 1.0 q" incrementSpeed="*Bind(DataSource=WAppDataSource;Path=FastIncreaseSpeed)" numDecimalPlaces="*Bind(DataSource=WPrefsApp;Path=DrawingPrecision)" rangeMin="0.001" rangeMax="10000" resolution="*Bind(DataSource=DocumentPrefsDS;Path=HPixelResolution)" scale="*Bind(DataSource=DocumentPrefsDS;Path=WorldScale)" showUnit="true" leftLabelIcon="guid://1cedaa4d-680a-a4a8-4dd8-ae5a438377f6" value="*Bind(DataSource=AppPrefMgr;Path=ZigZagTool/Height;BindType=TwoWay)"/> <!-- Zig-Zag Tool Distance spinner --> <itemData guid="0ea2a92d-7706-c190-4ab2-7d3e9c04b897" type="spinner" displayUnit="*Bind(DataSource=DocumentPrefsDS;Path=XRulerUnit)" internalUnit="*Bind(DataSource=DocumentPrefsDS;Path=XRulerUnit)" increment="0.01 in; 0.1 mm; 0.5 pt; 1 px; 0,0.5 cc; 1.0 q" incrementSpeed="*Bind(DataSource=WAppDataSource;Path=FastIncreaseSpeed)" numDecimalPlaces="*Bind(DataSource=WPrefsApp;Path=DrawingPrecision)" rangeMin="0.001" rangeMax="10000" resolution="*Bind(DataSource=DocumentPrefsDS;Path=HPixelResolution)" scale="*Bind(DataSource=DocumentPrefsDS;Path=WorldScale)" showUnit="true" leftLabelIcon="guid://46327bd4-8bad-41c5-aba1-efa770b8e2c8" value="*Bind(DataSource=AppPrefMgr;Path=ZigZagTool/Distance;BindType=TwoWay)"/> <!-- Outline Color--> <itemData guid="4090621f-1936-5aae-4101-ab6dafe418a6" captionRef="ed207303-2973-34ae-4e80-8060268fb0b1" icon="guid://ed207303-2973-34ae-4e80-8060268fb0b1" type="cellCombo" showAmbiguous="*Bind(DataSource=OutlinePropertiesDS;Path=ColorAmbiguous)" currentItem="*Bind(DataSource=OutlinePropertiesDS;Path=ColorItem;BindType=TwoWay)" gridSize="(6,8)" items="*Bind(DataSource=WCuiFillDS;Path=ColorListIncludingNone)" otherBtnText="*Bind(DataSource=WCuiFillDS;Path=ColorListButtonText;BindType=OneTime)" onInvokeOtherBtn="*Bind(DataSource=OutlinePropertiesDS;Path=InvokeColorDialog)" onInvokeEyedropper="*Bind(DataSource=OutlinePropertiesDS;Path=InvokeColorEyedropper)"/>
The value of the first two items refer to the application preferences we set early in this article. The last item uses existing bindings to allow us to create a control that changes the outline color.
Next, to change the tool's property bar to use these new items, open UserUI.xslt and replace the existing property bar ("uiConfig/commandBars/commandBarData[@guid='xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx']/toolbar) with:
<xsl:copy> <xsl:apply-templates select="node()|@*"/> <modeData guid="*** replace this with the guid for your propertybar ***"> <item guidRef="bf9012b1-da00-ae84-4c97-b0bb84d50288"/> <!--height--> <item guidRef="0ea2a92d-7706-c190-4ab2-7d3e9c04b897"/> <!--distance--> <item guidRef="266435b4-6e53-460f-9fa7-f45be187d400"/> <!--separator--> <item guidRef="4090621f-1936-5aae-4101-ab6dafe418a6"/> <!--outline color--> <item guidRef="266435b4-6e53-460f-9fa7-f45be187d400"/> <!--separator--> <item guidRef="97e5c8b9-f7e2-453c-92a2-af7fb61e67b2"/> <!--outline width--> <item guidRef="266435b4-6e53-460f-9fa7-f45be187d400"/> <!--separator--> <item guidRef="76dd9e0c-e9c2-42b6-b8bb-f7717482164e"/> <!--start arrowhead--> <item guidRef="6bd00383-d132-4686-8a21-8d7052b6a81b"/> <!--line style--> <item guidRef="40cc3adc-498a-4256-a98a-d1210a4019f7"/> <!--end arrowhead--> </modeData> </xsl:copy>
Make sure you don't change the GUID from the one already found in the xslt file (third line in the above snippet).
Rebuild the project, hold F8 and launch CorelDRAW or Corel DESIGNER. Every time a change is made to UserUI.xslt, the workspace must be reset so that it loads the new UI changes into the current workspace.
The toolbox item is missing an icon and string.
Full source: ZigZagTool.zip
Download the ZIP file and extract the zigzag.ico file to your hard drive. In Visual Studio, double-click on resources.rct. Right-click on the icon folder and select "Add resources...", then "Icon" and choose "Import..." Navigate to the icon file on your hard drive and open it. You will see a new IDI_ICON1 resource. Right-click on IDI_ICON1, select "Properties" and then rename the ID to IDI_ZIGZAG. Right-click on IDI_ZIGZAG in the .rct file and select "Resource Symbols." Take note of the ID number and close the dialog. Open config.xml and change the 104 to the ID number you just noted.
In the .rct file, open the string table and change the caption to "Zig-Zag Tool." Rebuild the project and run.
The tool can be enhanced further by implementing, for example: