Created
December 9, 2023 07:24
-
-
Save gekka/2709e29e3eb6e3a84e2edb820a5aea26 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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