WPF、PolyLineの頂点にThumb表示、マウスドラッグで頂点移動
動作テスト結果
- PolyLine要素の頂点の追加と削除
- 頂点にはThumbを表示
- Thumbをマウスドラッグ移動で頂点も移動
コード
2022WPF/20220608_PathとThumb/20220608_PathとThumb at master · gogowaten/2022WPF
MainWindow.xaml
<Window x:Class="_20220608_PathとThumb.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:_20220608_PathとThumb" mc:Ignorable="d" Title="MainWindow" Height="460" Width="600"> <Grid UseLayoutRounding="False"> <Grid.ColumnDefinitions> <ColumnDefinition/> <ColumnDefinition Width="200"/> </Grid.ColumnDefinitions> <StackPanel Grid.Column="1"> <Button x:Name="MyButton1" Content="add" Click="MyButton1_Click"/> <Button x:Name="MyButton2" Content="remove" Click="MyButton2_Click"/> <Button x:Name="MyButton3" Content="thumbVisibility" Click="MyButton3_Click"/> </StackPanel> <Canvas Name="MyCanvas"> </Canvas> </Grid> </Window>
MainWindow.xaml.cs
using System; using System.Windows; using System.Windows.Controls; using System.Windows.Input; using System.Windows.Media; using System.Windows.Shapes; using System.Windows.Controls.Primitives; using System.Collections.ObjectModel; //PolyLineで折れ線表示 //アンカーポイントにThumbを表示 //Thumbをマウスドラッグ移動でアンカーポイントも移動 //アンカーポイントの動的追加と削除 //アンカーポイントとThumbは手動で同期させる //同期ってのは個数と順番 //アンカーポイント追加するときはThumbも追加する //アンカーポイント削除するときは最後にクリックされたThumbと対応するアンカーポイントを削除 namespace _20220608_PathとThumb { /// <summary> /// Interaction logic for MainWindow.xaml /// </summary> public partial class MainWindow : Window { //PolyLineのアンカーポイント public PointCollection MyPointC { get; set; } = new(); //アンカーポイントに表示するThumbを入れておく用コレクション public ObservableCollection<Thumb> MyThumbs { get; set; } = new(); private int MyCount; private Thumb? MyActiveThumb;//最後にクリックされたThumbを入れておく用 bool IsThumbVisible = true;//Thumbの表示の有無フラグ public MainWindow() { #if DEBUG Left = 100; Top = 100; #endif InitializeComponent(); MyCanvas.Children.Add(MakePolyline(MyPointC, Brushes.Magenta, 10)); AddPoint(new Point(100, 100)); AddPoint(new Point(200, 100)); #region 負荷テスト用 //AddPointRound(100);//円環 //AddPoint円環はお断り(10000); //1ピクセル斜めに追加 //for (int i = 0; i < 5000; i++) //{ // AddPoint(new Point(i, i)); //} #endregion 負荷テスト用 } private void AddPointRound(int count) { double r = 200;//半径 double s = 360.0 / count; double x; double y; for (double i = 0.0; i < 360.0; i += s) { double rad = Radian(i); x = (Math.Cos(rad) * r) + r; y = (Math.Sin(rad) * r) + r; AddPoint(new Point(x, y)); } } private void AddPoint円環はお断り(int count) { double r = 200; double s = 3600.0 / count; double rr = r / (count * 2.0); double rrr = r; double x; double y; for (double i = 0.0; i < 3600.0; i += s) { double rad = Radian(i); x = (Math.Cos(rad) * rrr) + r; y = (Math.Sin(rad) * rrr) + r; AddPoint(new Point(x, y)); rrr -= rr; } } public static double Radian(double degrees) { return Math.PI / 180.0 * degrees; } private void AddPoint(Point p) { MyPointC.Add(p); var t = MakeThumb(p); MyThumbs.Add(t); MyCanvas.Children.Add(t); } private void RemovePoint(Thumb? t) { if (t is not Thumb) return; int i = MyThumbs.IndexOf(t); MyPointC.RemoveAt(i); MyThumbs.Remove(t); MyCanvas.Children.Remove(t); MyActiveThumb = null; } private Thumb MakeThumb(Point p) { Thumb t = new() { Width = 20, Height = 20 }; t.DragDelta += Thumb_DragDelta; t.PreviewMouseDown += Thumb_PreviewMouseDown; Canvas.SetLeft(t, p.X); Canvas.SetTop(t, p.Y); return t; } private Polyline MakePolyline(PointCollection pc, Brush stroke, double thickness) { Polyline pl = new(); pl.Points = pc; pl.Stroke = stroke; pl.StrokeThickness = thickness; return pl; } private void Thumb_PreviewMouseDown(object sender, MouseButtonEventArgs e) { if (sender is Thumb t) { MyActiveThumb = t; } } private void Thumb_DragDelta(object sender, DragDeltaEventArgs e) { if (sender is not Thumb t) { return; } double x = Canvas.GetLeft(t) + e.HorizontalChange; double y = Canvas.GetTop(t) + e.VerticalChange; Canvas.SetLeft(t, x); Canvas.SetTop(t, y); ; int i = MyThumbs.IndexOf(t); MyPointC[i] = new Point(x, y); } private void MyButton1_Click(object sender, RoutedEventArgs e) { AddPoint(new Point(MyCount * 30, MyCount * 20)); MyCount++; } private void MyButton2_Click(object sender, RoutedEventArgs e) { RemovePoint(MyActiveThumb); } private void MyButton3_Click(object sender, RoutedEventArgs e) { if (IsThumbVisible) { //Thumbの表示を最初の1つだけにする for (int i = 1; i < MyThumbs.Count; i++) { MyThumbs[i].Visibility = Visibility.Collapsed; } IsThumbVisible = false; } else { //全てのThumbを表示する for (int i = 1; i < MyThumbs.Count; i++) { MyThumbs[i].Visibility = Visibility.Visible; } IsThumbVisible = true; } } } }
どこからでも参照できる変数
Thumbをまとめておくコレクションには、変更通知があるコレクションのObservableCollection型を使っているけど、変更通知は使っていないので普通のList型でもよかった
PolyLineを作成してCanvasに追加
頂点を2つ追加
頂点座標PointをPointCollectionに追加
Thumb作成してThumbコレクションに追加
Canvasに追加して表示
ドラッグ移動イベントのDragDeltaに処理追加
マウスダウンイベント時の処理も追加
表示位置を指定
マウスダウンイベントではフィールドのMyActiveThumbに自身を追加、これでクリックしたThumbがどれなのか取得できる、削除するときに使用している
ドラッグ移動イベントでは移動と、PolyLineの頂点の移動もしている
動かしているThumbが、どの頂点と対応しているかの判別は、単純にコレクションのIndex番号でしている。これはもっとまともな方法があるだろうし、ホントはBindingしたかったんだけど方法がわからなかった
最後(直前)にクリックしたThumbを指定して削除
ここでもIndexを取得して、対応するPointをPointCollectionから削除
CanvasからもThumb削除
できなかったこと
PolyLineの頂点座標も、それに対応するThumbの表示座標も同じPoint型の集まりのPointCollection型だから、これを一つにまとめたかった、けどできなかった
2日くらいググったりダクっ(duckduckgo)たり、捏ねくり回していたんだけど、Point型っていうか構造体がBindingに向いていない感じ、できないことはなさそうなんだけど難しそうだから諦めた
表示数を増やしたときの負荷
環境は
CPU AMD Ryzen 5 2400G
Visual Studio 2022
10,000個の頂点を1ピクセル右斜したに並べて、Thumbを表示
このGIFアニメーションのfpsは10で作成している
割りと動く、瞬間的にPolyLineが変なところに表示されたりするけど動く
CPUの負荷は20%前後
実際の要素数?
ビジュアルツリーで見ると要素数は60,000個!
Thumbは立体(エンボス)的に表示して見せるためにBorder要素を4つとGrid要素1つの合計5要素と自身を含めて6要素で構成されている?から10000x6で60,000個ってことみたい
ってことはThumbのTemplateをいじって、Gridだけの平坦なThumbにすれば、20,000要素まで減らせば動きは良くなるかも?機会があれば試したい
10,000個の座標を半径200の円環に並べてThumbを表示
定期的に流行る三角関数を使ってみたかっただけ
カックカク、最初は「動かない!?」と思ったくらいマウスの動きに追従できない、fpsだと1か2くらい
CPU負荷は23~25%、CPUは物理4コアの論理8コアなので、物理コア1個を使い切っている感じ
Thumbを1個だけ表示して動かした場合は、さっきの斜めに表示したときと同じくらいの動きになった。CPU負荷は14%前後で、これだと論理コア1個を使い切っている?もしかしてPolyLineの表示とThumb群の表示は別スレッドで動いているのかしら
Thumb1,000個表示
多少カクカクするけど破綻した動きはなくなった
Thumbを1個表示なら、普通になめらかに動く
Thumb100個表示
問題なくなめらかに動く
CPU負荷は7%
指定個数の頂点を半径の半分まで10周で並べる
10,000個並べると、鋼の錬金バームクーヘンできた
PolyLineは蚊取り線香みたいになっているはず
PolyLineの線が太すぎた
先の太さを10から4にした
やっぱりバームクーヘンじゃないか!
これならThumbが並んでいるのがわかる
当たるも十卦、当たらぬも十卦
これが目的の訳じゃないけど流れで
あんまり見なくなってからも久しいねえ
関連記事
268日後、ついにAdornerを使う
gogowaten.hatenablog.com
次回
gogowaten.hatenablog.com
動作管理部分を別クラスに分けてみた
前回のWPF記事は3日前
gogowaten.hatenablog.com
4年前
マウスクリックでCanvasに直線を描画その2、Polyline、WPFとC# - 午後わてんのブログ
8年前
複数の画像を並べて重ねて1枚の画像にするPixtack紫陽花2.4.9.80、図形2頂点の追加と削除 - 午後わてんのブログ
gogowaten.hatenablog.com
複数の画像を並べて重ねて1枚の画像にするPixtack紫陽花2.4.8.79、直線の描画 - 午後わてんのブログ
gogowaten.hatenablog.com
このときはWindowsForm+VBで、WindowsFormでの描画は半透明処理がないから期待しない表示になっている。これを改善するのにかなり時間がかかったんだけど、WPFでは自動で半透明処理してくれるから、当時はかなり驚いたとともに、これからはWPFだ!これならもっといいものを作れる!!って思ったんだよなあ、あれから6年位は経ったのかな、未だにできてないw