If you've missed it, check out this post, this will get you up to speed on custom tools.


In this post I'll describe how to create a tool that allows you to draw a curve that zig zags along the path. You'll learn how to set up a new tool, modify the code to create a curve, set up custom application preferences, and bind those preferences to UI in property bar.

Step 1 - Backup your workspace:

Navigate to C:\Users\<you>\AppData\Roaming\Corel\CorelDRAW Graphics Suite X7\Draw, and zip up the Workspace folder. We'll eventually need to hold F8 to reset your workspace after we make UI changes. After you're done developing your tool, you can restore your old workspaces by unzipping the backup.

Step 2 - Create the project:

Open up Visual Studio. Create a new tool using the CorelDRAW Tool Addon project template (to install the template, see here). Name it "ZigZagTool". At this point, you can build the project and press F5 to test it out if you like, just to make sure everything is working.

Step 3 - Write the code:

The template code is a tool to create a line segment between two points. We want our zig zags to follow a path. Delete 'Point save' and all code that refers to the 'save' variable.  

We don't want the mouse to snap when we're drawing, in the function OnMouseSnap only keep 'Handled = isDrawing'.

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'd need to clear the previous points. Go to the Reset() function and add the following:

pts.RemoveAll();

When we create the zig zag shape, we need to know the height of each zig-zag, and the distance between each zig-zag.  We'll do this by defining two new application preferences.  In the constructor, register the two preferences with the application.

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.
 
Next we need a function to generate the curve.  This function will fit the points to a curve, then divide the curve into equal parts, then create line segments alternate 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 divide.  The green lines are the perpendicular vectors at the end of each subpath.
We need to make a preview of the curve while we draw, update 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, we need to create the shape when the user hits enter or lets go of the left mouse button. Change OnCommit to:
public void OnCommit(Point pt)
{
  if (isDrawing)
  {
    Curve curve = CreateZigZagCurve();
    Application.ActiveLayer.CreateCurve(curve);
    Reset(); // cleanup and reset UI and variable
  }
}
Cool, try it out!  In the interest of keeping the code simple, the zig-zags fit to the length of the curve, as a result, the XOR'd preview jumps around while you draw.
Step 4 - Property bar UI

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 up 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 first two items refer to the preference we registered in the tool.  The last item uses pre-existing bindings to allow us to create a control that changes the outline color.
 
Next, change the tool's property bar to use these new items.  Open up 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 (3rd line in the above code).
Try it out.  Rebuild the project, Hold F8 and launch CorelDRAW.  Any time we change UserUI.xslt, the workspace must be reset so that it loads the new UI changes into the current workspace.
 
Step 5 - Update the Icon and String resources

The last thing we're missing is an icon and string for the toolbox item.  
 
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...", Select "Icon" and choose "Import...".  Navigate to the icon file on your hard drive and open it.  It will add a new IDI_ICON1 resource.  Right+click on IDI_ICON1, select "Properties", 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 (it should be 105).  Close the dialog.  Open up config.xml, and change 104 to 105.  

Go back to the .rct file, and open the string table.  Change the caption to "Zig-Zag Tool".
Build it and run!  There you go, you now have a fully functioning tool with custom UI.
The tool can be enhanced further, for an extra challenge, here's some stuff to try:
  • Hook up the right+mouse drag so that you can move the object while drawing
  • Allow it to join to existing curves
  • Allow it to close the curve when you connect
  • Improve the curve creation to not stretch the curve based on the length of the curve
  • Add more custom icons and strings for the distance and height controls
  • Add new modes, like sin or square wave
  • Make it faster - we don't need to recalculate the entire curve on each mouse move - we can instead update the last zig-zag and keep track of the previous portion of the curve
I've attached the full source.  You'll need to change the post build events to point to your own install of CorelDRAW to compile this.
I'm looking forward to seeing what tools you create!
ZigZagTool.zip