Constrained movement
Constrained movement
Override one virtual method to control how a tool responds to dragging — for example, pinning a horizontal line to a single price so it never drifts sideways in time.
What it is
When the user drags a placed tool, the host calls Move(TimeSpan dTime, double dPrice) with
the change in time and price for the drag. The default DrawingToolBase.Move shifts every
anchor by both deltas — exactly right for a free-floating shape like a trend line or rectangle:
DrawingToolBase.cspublic virtual void Move(TimeSpan dTime, double dPrice)
{
for (int i = 0; i < AnchorCount; i++)
{
var a = Anchors[i];
SetAnchor(i, new DrawingAnchor(a.Time + dTime, a.Price + dPrice));
}
}
When a tool has a geometric constraint — a horizontal line that should only change price, a
vertical line that should only change time — override Move and apply just the deltas
that make sense. Commit each new anchor with SetAnchor.
A horizontal line: price only
A horizontal line keeps each anchor's time and adds only the price delta, so dragging it up or down moves the level but never slides it through time.
HorizontalLine.cs[DrawingToolMetadata("Horizontal Line", Category = "Lines", IconKey = "HorizontalLine", AnchorCount = 1)]
public sealed class HorizontalLine : DrawingToolBase
{
public HorizontalLine() : base(anchorCount: 1) { }
public override void Move(TimeSpan dTime, double dPrice)
{
var a = Anchors[0];
SetAnchor(0, new DrawingAnchor(a.Time, a.Price + dPrice)); // price only
}
public override void OnRender(IDrawingToolRenderContext ctx)
{
double y = ctx.Viewport.PriceToPixelY(Anchors[0].Price);
ctx.Renderer.DrawLine(ctx.PlotArea.X, y, ctx.PlotArea.Right, y, Color, LineWidthPx);
if (ctx.IsSelected) ctx.DrawHandle((ctx.PlotArea.X + ctx.PlotArea.Right) / 2, y);
}
public override bool HitTest(double px, double py, IDrawingToolRenderContext ctx, double tol = 6)
{
double y = ctx.Viewport.PriceToPixelY(Anchors[0].Price);
return DrawingGeometry.DistanceToHorizontalLine(py, y) <= tol;
}
}
A vertical line: time only
The mirror image keeps each anchor's price and adds only the time delta. Pair it with the
DrawingAnchor.TimeOnly factory at placement so the unused price stays a sentinel.
VerticalLine.cspublic override void Move(TimeSpan dTime, double dPrice)
{
var a = Anchors[0];
SetAnchor(0, new DrawingAnchor(a.Time + dTime, a.Price)); // time only
}
SetAnchor, not direct mutation. Anchors is exposed
as a read-only list; route every change through SetAnchor(index, anchor) so the base class
keeps anchor count, hit-testing and the rest of the tool's state consistent. SetAnchor
validates the index against AnchorCount and throws on an out-of-range slot.