今回のアプリのダウンロード先
クリックしたところをアンカーポイントにして
ベジェ曲線の終端に矢印
デザイン画面
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace _20180616_ベジェ曲線に矢印
{
public partial class MainWindow : Window
{
PolyBezierSegment MySegment;
Point OffsetFinePoint;
Point ContactPoint;
public MainWindow()
{
InitializeComponent();
MyInitialize();
MyCanvas.MouseLeftButtonDown += MyCanvas_MouseLeftButtonDown;
MyCanvas.MouseMove += MyCanvas_MouseMove;
MyCanvas.MouseRightButtonDown += MyCanvas_MouseRightButtonDown;
this.Loaded += MainWindow_Loaded;
}
private void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
OffsetFinePoint = new Point(ArrowHead.ActualWidth / 2, 0);
ArrowHead.RenderTransformOrigin = new Point(0.5, 0.0);
ContactPoint = new Point(ArrowHead.ActualWidth / 2, ArrowHead.ActualHeight - 2);
}
private void MyCanvas_MouseRightButtonDown(object sender, MouseButtonEventArgs e)
{
MySegment.Points.Clear();
}
private void MyInitialize()
{
MySegment = new PolyBezierSegment();
var pf = new PathFigure();
pf.Segments.Add(MySegment);
var pg = new PathGeometry();
pg.Figures.Add(pf);
MyPath.Data = pg;
}
private void MyCanvas_MouseMove(object sender, MouseEventArgs e)
{
PointCollection ps = MySegment.Points;
Point mouseP = e.GetPosition(MyCanvas);
if (ps.Count > 5)
{
ps[ps.Count - 1] = mouseP;
Point preAP = ps[ps.Count - 4];
Point prepreAP;
if (ps.Count < 7)
{
var pg = (PathGeometry)MyPath.Data;
var pathFigureCollection = pg.Figures;
prepreAP = pathFigureCollection[0].StartPoint;
}
else
{
prepreAP = ps[ps.Count - 7];
}
double xDiff = (mouseP.X - prepreAP.X) / 4.0;
double yDiff = (mouseP.Y - prepreAP.Y) / 4.0;
ps[ps.Count - 5] = new Point(preAP.X - xDiff, preAP.Y - yDiff);
ps[ps.Count - 3] = new Point(preAP.X + xDiff, preAP.Y + yDiff);
double angle = Math.Atan2(mouseP.Y - ps[ps.Count - 3].Y, mouseP.X - ps[ps.Count - 3].X);
angle = angle / Math.PI * 180;
angle += 90;
ArrowHead.RenderTransform = new RotateTransform(angle);
MyLabel.Content = "Angle = " + angle.ToString();
mouseP.Offset(-OffsetFinePoint.X, -OffsetFinePoint.Y);
Canvas.SetLeft(ArrowHead, mouseP.X);
Canvas.SetTop(ArrowHead, mouseP.Y);
GeneralTransform gt = ArrowHead.TransformToVisual(MyCanvas);
Point lastAnc = gt.Transform(ContactPoint);
ps[ps.Count - 1] = lastAnc;
xDiff = (lastAnc.X - ps[ps.Count - 3].X) / 4.0;
yDiff = (lastAnc.Y - ps[ps.Count - 3].Y) / 4.0;
Point lastControlP = new Point(lastAnc.X - xDiff, lastAnc.Y - yDiff);
ps[ps.Count - 2] = lastControlP;
}
else if (ps.Count > 0)
{
ps[ps.Count - 1] = e.GetPosition(MyCanvas);
mouseP.Offset(-OffsetFinePoint.X, -OffsetFinePoint.Y);
Canvas.SetLeft(ArrowHead, mouseP.X);
Canvas.SetTop(ArrowHead, mouseP.Y);
}
}
private void MyCanvas_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
var p = e.GetPosition(MyCanvas);
if (MySegment.Points.Count == 0)
{
var pathGeometry = (PathGeometry)MyPath.Data;
var pathFigureCollection = pathGeometry.Figures;
pathFigureCollection[0].StartPoint = p;
MySegment.Points.Add(p);
MySegment.Points.Add(p);
MySegment.Points.Add(p);
}
else
{
MySegment.Points[MySegment.Points.Count - 1] = p;
MySegment.Points.Add(p);
MySegment.Points.Add(p);
MySegment.Points.Add(p);
}
}
}
}
元になったコードは
制御点座標の決め方も同じでアンカーからの距離は、前後のアンカー間の1/4で
角度は前後のアンカー間を結んだときの直線と平行になるように
しているけど制御点座標の決め方はもっといい方法があるはず
灰色がアンカー、紫が制御点
00が始点、06が終点(終端)
終端(マウスカーソル座標)の制御点は、終端と一個前のアンカーの手前側の制御点との直線上に置くことに決めてあるので、上の画像だと06と04を結ぶ線上に05を置く
矢印の角度はこの線に合わせると自然な感じがしたので、この線の角度を求める
変数のpsは
ベジェ曲線のセグメントのPointsプロパティで、座標リストみたいなの
ps[ps.Count - 3]はさっきの図だと04で制御点になる、これとマウス座標mousePからMath.
Atan2を使って角度を求めている、103、104行目
矢印の回転と移動ができたら、
ベジェ曲線の終端座標を決める
マウスカーソル位置を終端にすると
不自然なので
矢印の横は中央、縦は下端の座標(接続座標)を
ベジェ曲線の終端座標にすれば、くっついて見える
今回はその接続座標をTransformToVisualメソッド使って求めてみた
これを使うとコン
トロールの変形前の座標から変形後の座標を取得したりできる
矢印(ArrowHead)は
Canvas(MyCanvas)に表示しているのでそのTransformを
117行目で取得して、
Transformメソッドに接続座標(ContactPoint)を渡して変形後の接続座標を取得している、118行目
これが終端座標になるので119行目で指定
接続座標ContactPointはアプリの起動直後に
矢印以外の形
●アンカーらしいアンカー
矢印に指定するのはPolygonじゃなくてもできそうなので
20行目、Ellipseを指定すると
今回のコード
関連記事
昨日、2018/06/20
2018/6/11は10日前