Skip to content

Instantly share code, notes, and snippets.

@gekka
Created December 9, 2023 07:24
Show Gist options
  • Save gekka/2709e29e3eb6e3a84e2edb820a5aea26 to your computer and use it in GitHub Desktop.
Save gekka/2709e29e3eb6e3a84e2edb820a5aea26 to your computer and use it in GitHub Desktop.
<Canvas x:Name="myCanvas" Cursor="">
<Canvas.Resources>
<ControlTemplate TargetType="Thumb" x:Key="markerTemplate">
<Rectangle x:Name="rect"
Stroke="{TemplateBinding Background}"
Fill="Transparent"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"/>
</ControlTemplate>
<ControlTemplate TargetType="Thumb" x:Key="markerTemplateSelect">
<Rectangle x:Name="rect"
Stroke="{TemplateBinding Background}"
Fill="{TemplateBinding Background}"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"/>
</ControlTemplate>
<Style x:Key="{x:Type local:AnchorThumb}" TargetType="{x:Type local:AnchorThumb}" >
<Setter Property="DataContext" Value="{x:Null}" />
<Setter Property="Panel.ZIndex" Value="100" />
<Setter Property="Background" Value="{Binding Path=Stroke}" />
<Setter Property="Width" Value="10"/>
<Setter Property="Height" Value="10" />
<Setter Property="RenderTransform">
<Setter.Value>
<TranslateTransform X="-5" Y="-5" />
</Setter.Value>
</Setter>
<Setter Property="Cursor" Value="SizeAll"/>
<Setter Property="KeyboardNavigation.IsTabStop" Value="true"/>
<Setter Property="Focusable" Value="True" />
<Setter Property="Template" Value="{StaticResource markerTemplate}" />
<Style.Triggers>
<Trigger Property="IsMoveAllPoint" Value="True">
<Setter Property="Cursor" Value="ScrollAll" />
</Trigger>
<Trigger Property="IsKeyboardFocusWithin" Value="True">
<Setter Property="Template" Value="{StaticResource markerTemplateSelect}" />
</Trigger>
</Style.Triggers>
</Style>
<Style TargetType="{x:Type Line}">
<Setter Property="Cursor" Value="UpArrow" />
<Setter Property="Focusable" Value="False" />
<EventSetter Event="MouseLeftButtonDown" Handler="Line_MouseLeftButtonDown" />
</Style>
<Style TargetType="{x:Type Path}">
<Setter Property="Cursor" Value="UpArrow" />
<Setter Property="Focusable" Value="False" />
<EventSetter Event="MouseLeftButtonDown" Handler="Line_MouseLeftButtonDown" />
</Style>
</Canvas.Resources>
<Line X1="50" Y1="50" X2="200" Y2="200" Stroke="Blue" StrokeThickness="2"/>
<Line X1="100" Y1="20" X2="20" Y2="200" Stroke="Green" StrokeThickness="2"/>
<Path Stroke="Red" StrokeThickness="2" >
<Path.Data>
<LineGeometry StartPoint="20,30" EndPoint="130,60" />
</Path.Data>
</Path>
<Path Stroke="Lime" StrokeThickness="2" >
<Path.Data>
<EllipseGeometry RadiusX="50" RadiusY="30" Center="100,100" />
</Path.Data>
</Path>
</Canvas>
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Shapes;
using System.Windows.Data;
using System.Collections.Generic;
//using MoveLineTest.Controls;
using System.Linq;
using System.Runtime.CompilerServices;
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
thumbItems = new ThumbItems(this.myCanvas);
}
private ThumbItems thumbItems;
private void Line_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
thumbItems.Clear();
var target = (FrameworkElement)sender;
thumbItems.Add(target);
thumbItems.FocusNear(e.GetPosition(this.myCanvas));
e.Handled = true;
}
}
delegate void PointMoveDelegate(AnchorThumb item, double dx, double dy, bool isAll);
class ThumbItems
{
public Canvas Canvas { get; }
public ThumbItems(Canvas canvas)
{
Canvas = canvas;
}
public List<AnchorThumb> Items { get; } = new List<AnchorThumb>();
public void Add(FrameworkElement target, double x, double y, PointMoveDelegate action, bool isMoveAllPoint = false)
{
AnchorThumb thumbItem = new AnchorThumb(action);
thumbItem.DataContext = target;
thumbItem.SetThumbCanvasPoint(x, y);
thumbItem.IsMoveAllPoint = isMoveAllPoint;
thumbItem.LinkItems = this.Items;
this.Items.Add(thumbItem);
this.Canvas.Children.Add(thumbItem);
}
public void Clear()
{
foreach (var item in this.Items)
{
item.RemoveFromParent();
}
Items.Clear();
}
public void FocusNear(Point p)
{
if (Items.Count > 0)
{
this.Items
.Select(item => new { Item = item, Diff = item.GetDistance(p) })
.OrderBy(_ => _.Diff)
.Select(_ => _.Item)
.First()
.Focus();
}
}
}
class AnchorThumb : Thumb
{
public AnchorThumb(PointMoveDelegate action)
{
Action = action;
Panel.SetZIndex(this, int.MaxValue);
this.DragDelta += Thumb_DragDelta;
this.KeyDown += Thumb_KeyDown;
}
public IEnumerable<AnchorThumb> LinkItems { get; set; } = new AnchorThumb[0];
public PointMoveDelegate Action { get; }
public bool IsMoveAllPoint
{
get { return (bool)GetValue(IsMoveAllPointProperty); }
set { SetValue(IsMoveAllPointProperty, value); }
}
public static readonly DependencyProperty IsMoveAllPointProperty
= DependencyProperty.Register
(nameof(IsMoveAllPoint)
, typeof(bool)
, typeof(AnchorThumb)
, new PropertyMetadata(false));
public double X
{
get => Canvas.GetLeft(this);
set => Canvas.SetLeft(this, value);
}
public double Y
{
get => Canvas.GetTop(this);
set => Canvas.SetTop(this, value);
}
public void SetThumbCanvasPoint(double x, double y)
{
X = x;
Y = y;
}
public double GetDistance(Point p)
{
return Math.Sqrt(Math.Pow(p.X - this.X, 2) + Math.Pow(p.Y - this.Y, 2));
}
private void Thumb_DragDelta(object sender, DragDeltaEventArgs e)
{
double x = e.HorizontalChange;
double y = e.VerticalChange;
if (this.IsLineDragMode)
{
MovePoints(x, y, this.LinkItems, true);
}
else
{
MovePoints(x, y, new[] { this }, false);
}
e.Handled = true;
}
private void Thumb_KeyDown(object sender, KeyEventArgs e)
{
double x = 0, y = 0;
switch (e.Key)
{
case Key.Escape:
foreach (var item in this.LinkItems)
{
item.RemoveFromParent();
}
this.RemoveFromParent();
break;
case Key.Left: x = -10; break;
case Key.Right: x = 10; break;
case Key.Up: y = -10; break;
case Key.Down: y = 10; break;
default: return;
}
if (this.IsLineDragMode)
{
MovePoints(x, y, this.LinkItems, true);
}
else
{
MovePoints(x, y, new[] { this }, false);
}
e.Handled = true;
}
private void MovePoints(double dx, double dy, IEnumerable<AnchorThumb> targets, bool isMoveAll)
{
foreach (AnchorThumb item in targets)
{
item.Action(item, dx, dy, isMoveAll);
}
if (isMoveAll || targets.Any(_ => _.IsMoveAllPoint))
{
foreach (var item in this.LinkItems)
{
item.X += dx;
item.Y += dy;
}
}
else
{
foreach (var item in targets)
{
item.X += dx;
item.Y += dy;
}
}
}
public void RemoveFromParent()
{
if (this.Parent is Panel panel)
{
panel.Children.Remove(this);
}
}
private bool IsLineDragMode => Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.RightCtrl);
}
static class AnchorThumbFactory
{
public static void Add(this ThumbItems thumbItems, FrameworkElement element)
{
if (element is Line line)
{
thumbItems.Add(line);
}
else if (element is Path path)
{
thumbItems.Add(path);
}
}
public static void Add(this ThumbItems thumbItems, Line line)
{
thumbItems.Add(line, line.X1, line.Y1, (f, dx, dy, isAll) => { line.X1 += dx; line.Y1 += dy; });
thumbItems.Add(line, line.X2, line.Y2, (f, dx, dy, isAll) => { line.X2 += dx; line.Y2 += dy; });
}
public static void Add(this ThumbItems thumbItems, Path path)
{
if (path.Data is LineGeometry lineGeo)
{
thumbItems.Add(path, lineGeo);
}
if (path.Data is EllipseGeometry ellipseGeo)
{
thumbItems.Add(path, ellipseGeo);
}
}
private static void Add(this ThumbItems thumbItems, Path path, LineGeometry lineGeometry)
{
thumbItems.Add(path, lineGeometry.StartPoint.X, lineGeometry.StartPoint.Y, (t, dx, dy, isAll) =>
{
Point p = lineGeometry.StartPoint;
p.Offset(dx, dy);
lineGeometry.StartPoint = p;
});
thumbItems.Add(target: path, lineGeometry.EndPoint.X, lineGeometry.EndPoint.Y, (t, dx, dy, isAll) =>
{
Point p = lineGeometry.EndPoint;
p.Offset(dx, dy);
lineGeometry.EndPoint = p;
});
}
private static void Add(this ThumbItems thumbItems, Path path, EllipseGeometry ellipseGeometry)
{
thumbItems.Add(path, ellipseGeometry.Center.X + ellipseGeometry.RadiusX, ellipseGeometry.Center.Y + ellipseGeometry.RadiusY, (t, dx, dy, isAll) =>
{
if (!isAll)
{
ellipseGeometry.RadiusX += dx;
ellipseGeometry.RadiusY += dy;
}
});
thumbItems.Add(path, ellipseGeometry.Center.X, ellipseGeometry.Center.Y, (t, dx, dy, isAll) =>
{
Point p = ellipseGeometry.Center;
p.Offset(dx, dy);
ellipseGeometry.Center = p;
}, true);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment