@dhq_boiler

Are you sure you want to delete the question?

Leaving a resolved question undeleted may help others!

ポイントスナップ機能が正しく動作しない

Q&A

Closed

お世話になっております。

解決したいこと

C# + WPFでベクターグラフィックスドローイングツールを開発しています。

2021-06-20 (1).png

※下にソースコードへの案内を記載しております。よろしければそちらを参照ください。

このツールにポイントスナップ機能という、図形等を描画する時、他の図形のコーナーに吸着して描画できるものです。

発生している問題・エラー

以下のGIF画像をご覧ください。

point_snap.gif

既にある四角形の左上のコーナーに吸着させて、新たに四角形を描画しようというものです。左上のコーナーに吸着させる時、1つのコーナーに吸着するポイントが3つあるように見えます。
しかし、XAML上では1つのコーナーには1つのResizeThumbしか配置していません。(※DesignerItems.xaml参照)

DesignerItems.xaml
<ControlTemplate x:Key="ResizeDecoratorTemplate" TargetType="{x:Type Control}">
        <Grid>
            <Grid Opacity="0.7" SnapsToDevicePixels="true">
                <!-- 上の回転ツマミ部分 -->
                <control:RotateThumb
                    Width="7"
                    Height="7"
                    Margin="0,-20,0,0"
                    HorizontalAlignment="Center"
                    VerticalAlignment="Top"
                    Cursor="Hand"
                    Template="{StaticResource RotateThumbTemplate}" />
                <!--  -->
                <control:ResizeThumb
                    Height="3"
                    Margin="0,-4,0,0"
                    HorizontalAlignment="Stretch"
                    VerticalAlignment="Top"
                    Cursor="SizeNS"
                    Template="{StaticResource HorizontalResizeHandleTemplate}" />
                <!--  -->
                <control:ResizeThumb
                    Width="3"
                    Margin="-4,0,0,0"
                    HorizontalAlignment="Left"
                    VerticalAlignment="Stretch"
                    Cursor="SizeWE"
                    Template="{StaticResource VerticalResizeHandleTemplate}" />
                <!--  -->
                <control:ResizeThumb
                    Width="3"
                    Margin="0,0,-4,0"
                    HorizontalAlignment="Right"
                    VerticalAlignment="Stretch"
                    Cursor="SizeWE"
                    Template="{StaticResource VerticalResizeHandleTemplate}" />
                <!--  -->
                <control:ResizeThumb
                    Height="3"
                    Margin="0,0,0,-4"
                    HorizontalAlignment="Stretch"
                    VerticalAlignment="Bottom"
                    Cursor="SizeNS"
                    Template="{StaticResource HorizontalResizeHandleTemplate}" />
                <!-- 左上 -->
                <control:ResizeThumb
                    Width="7"
                    Height="7"
                    Margin="-6,-6,0,0"
                    HorizontalAlignment="Left"
                    VerticalAlignment="Top"
                    Cursor="SizeNWSE"
                    Template="{StaticResource ResizeHandleTemplate}" />
                <!-- 右上 -->
                <control:ResizeThumb
                    Width="7"
                    Height="7"
                    Margin="0,-6,-6,0"
                    HorizontalAlignment="Right"
                    VerticalAlignment="Top"
                    Cursor="SizeNESW"
                    Template="{StaticResource ResizeHandleTemplate}" />
                <!-- 左下 -->
                <control:ResizeThumb
                    Width="7"
                    Height="7"
                    Margin="-6,0,0,-6"
                    HorizontalAlignment="Left"
                    VerticalAlignment="Bottom"
                    Cursor="SizeNESW"
                    Template="{StaticResource ResizeHandleTemplate}" />
                <!-- 右下 -->
                <control:ResizeThumb
                    Width="7"
                    Height="7"
                    Margin="0,0,-6,-6"
                    HorizontalAlignment="Right"
                    VerticalAlignment="Bottom"
                    Cursor="SizeNWSE"
                    Template="{StaticResource ResizeHandleTemplate}" />
            </Grid>
            <Grid
                Margin="-3"
                IsHitTestVisible="False"
                Opacity="1">
                <Line
                    Margin="0,-11,0,0"
                    HorizontalAlignment="Center"
                    VerticalAlignment="Top"
                    Stroke="#6c809a"
                    StrokeThickness="1"
                    X1="0"
                    X2="0"
                    Y1="0"
                    Y2="11" />
            </Grid>
        </Grid>
    </ControlTemplate>

どうして1つのコーナー上に吸着するポイントが3つできるのでしょうか。

参考情報

ポイントスナップ機能は現在のところ、四角形を描画する時にしか実装していません。

四角形を描画する仕組みは以下をご覧ください。

ToolBarViewModel.cs
public class ToolBarViewModel
    {
        private IDialogService dlgService = null;
        public ObservableCollection<ToolItemData> ToolItems { get; } = new ObservableCollection<ToolItemData>();

        public ToolBarViewModel(IDialogService dialogService)
        {
            this.dlgService = dialogService;
            :
            ToolItems.Add(new ToolItemData("rectangle", "pack://application:,,,/Assets/img/rectangle.png", new DelegateCommand(() =>
            {
                var behavior = new NDrawRectangleBehavior();
                var designerCanvas = App.Current.MainWindow.GetChildOfType<DesignerCanvas>();
                var behaviors = Interaction.GetBehaviors(designerCanvas);
                behaviors.Clear();
                if (!behaviors.Contains(behavior))
                {
                    behaviors.Add(behavior);
                }
                SelectOneToolItem("rectangle");
            })));
            :
        }

        private void SelectOneToolItem(string toolName)
        {
            var toolItem = ToolItems.Where(i => i.Name == toolName).Single();
            toolItem.IsChecked = true;

            ToolItems.Where(i => i.Name != toolName).ToList().ForEach(i => i.IsChecked = false);
        }
    }
NDrawRectangleBehavior.cs
internal class NDrawRectangleBehavior : Behavior<DesignerCanvas>
    {
        private Point? _rectangleStartPoint;

        protected override void OnAttached()
        {
            this.AssociatedObject.MouseDown += AssociatedObject_MouseDown;
            this.AssociatedObject.MouseMove += AssociatedObject_MouseMove;
            base.OnAttached();
        }

        protected override void OnDetaching()
        {
            this.AssociatedObject.MouseDown -= AssociatedObject_MouseDown;
            this.AssociatedObject.MouseMove -= AssociatedObject_MouseMove;
            base.OnDetaching();
        }

        private void AssociatedObject_MouseDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
        {
            if (e.LeftButton == MouseButtonState.Pressed)
            {
                if (e.Source == AssociatedObject)
                {
                    _rectangleStartPoint = e.GetPosition(AssociatedObject);

                    e.Handled = true;
                }
            }
        }

        private void AssociatedObject_MouseMove(object sender, System.Windows.Input.MouseEventArgs e)
        {
            var canvas = AssociatedObject as DesignerCanvas;
            if (canvas.SourceConnector == null)
            {
                if (e.LeftButton != MouseButtonState.Pressed)
                    _rectangleStartPoint = null;

                if (_rectangleStartPoint.HasValue)
                {
                    (App.Current.MainWindow.DataContext as MainWindowViewModel).CurrentOperation.Value = "描画";

                    AdornerLayer adornerLayer = AdornerLayer.GetAdornerLayer(canvas);
                    if (adornerLayer != null)
                    {
                        var adorner = new Adorners.RectangleAdorner(canvas, _rectangleStartPoint);
                        if (adorner != null)
                        {
                            adornerLayer.Add(adorner);
                        }
                    }
                }
            }
        }
    }
RectangleAdorner.cs
internal class RectangleAdorner : Adorner
    {
        private DesignerCanvas _designerCanvas;
        private Point? _startPoint;
        private Point? _endPoint;
        private Pen _rectanglePen;
        private Dictionary<Point, Adorner> _adorners;

        public RectangleAdorner(DesignerCanvas designerCanvas, Point? dragStartPoint)
            : base(designerCanvas)
        {
            _designerCanvas = designerCanvas;
            _startPoint = dragStartPoint;
            var parent = (AdornedElement as DesignerCanvas).DataContext as IDiagramViewModel;
            var brush = new SolidColorBrush(parent.EdgeColors.First());
            brush.Opacity = 0.5;
            _rectanglePen = new Pen(brush, 1);
            _adorners = new Dictionary<Point, Adorner>();
        }

        protected override void OnMouseMove(MouseEventArgs e)
        {
            if (e.LeftButton == MouseButtonState.Pressed)
            {
                if (!this.IsMouseCaptured)
                    this.CaptureMouse();

                //ドラッグ終了座標を更新
                _endPoint = e.GetPosition(this);

                var mainWindowVM = (App.Current.MainWindow.DataContext as MainWindowViewModel);
                var designerCanvas = App.Current.MainWindow.GetChildOfType<DesignerCanvas>();

                var diagramVM = mainWindowVM.DiagramViewModel;
                var snapPoints = diagramVM.SnapPoints;
                Point? snapped = null;
                foreach (var snapPoint in snapPoints)
                {
                    if (_endPoint.Value.X > snapPoint.X - mainWindowVM.SnapPower.Value
                     && _endPoint.Value.X < snapPoint.X + mainWindowVM.SnapPower.Value
                     && _endPoint.Value.Y > snapPoint.Y - mainWindowVM.SnapPower.Value
                     && _endPoint.Value.Y < snapPoint.Y + mainWindowVM.SnapPower.Value)
                    {
                        //スナップする座標を一時変数へ保存
                        snapped = snapPoint;
                    }
                }

                //スナップした場合
                if (snapped != null)
                {
                    AdornerLayer adornerLayer = AdornerLayer.GetAdornerLayer(designerCanvas);
                    RemoveFromAdornerLayerAndDictionary(snapped, adornerLayer);

                    //ドラッグ終了座標を一時変数で上書きしてスナップ
                    _endPoint = snapped;
                    if (adornerLayer != null)
                    {
                        Trace.WriteLine($"Snap={snapped.Value}");
                        if (!_adorners.ContainsKey(snapped.Value))
                        {
                            var adorner = new Adorners.SnapPointAdorner(designerCanvas, snapped.Value);
                            if (adorner != null)
                            {
                                adornerLayer.Add(adorner);

                                //ディクショナリに記憶する
                                _adorners.Add(snapped.Value, adorner);
                            }
                        }
                    }
                }
                else //スナップしなかった場合
                {
                    RemoveAllAdornerFromAdornerLayerAndDictionary(designerCanvas);
                }

                (App.Current.MainWindow.DataContext as MainWindowViewModel).Details.Value = $"({_startPoint.Value.X}, {_startPoint.Value.Y}) - ({_endPoint.Value.X}, {_endPoint.Value.Y}) (w, h) = ({_endPoint.Value.X - _startPoint.Value.X}, {_endPoint.Value.Y - _startPoint.Value.Y})";

                this.InvalidateVisual();
            }
            else
            {
                if (this.IsMouseCaptured) this.ReleaseMouseCapture();
            }

            e.Handled = true;
        }

        private void RemoveAllAdornerFromAdornerLayerAndDictionary(DesignerCanvas designerCanvas)
        {
            AdornerLayer adornerLayer = AdornerLayer.GetAdornerLayer(designerCanvas);
            var removes = _adorners.ToList();

            removes.ForEach(x =>
            {
                if (adornerLayer != null)
                {
                    adornerLayer.Remove(x.Value);
                }
                _adorners.Remove(x.Key);
            });
        }

        private void RemoveFromAdornerLayerAndDictionary(Point? snapped, AdornerLayer adornerLayer)
        {
            var removes = _adorners.Where(x => x.Key != snapped)
                                                       .ToList();
            removes.ForEach(x =>
            {
                if (adornerLayer != null)
                {
                    adornerLayer.Remove(x.Value);
                }
                _adorners.Remove(x.Key);
            });
        }

        protected override void OnMouseUp(System.Windows.Input.MouseButtonEventArgs e)
        {
            // release mouse capture
            if (this.IsMouseCaptured) this.ReleaseMouseCapture();

            // remove this adorner from adorner layer
            AdornerLayer adornerLayer = AdornerLayer.GetAdornerLayer(_designerCanvas);
            if (adornerLayer != null)
            {
                adornerLayer.Remove(this);

                foreach (var adorner in _adorners)
                    adornerLayer.Remove(adorner.Value);

                _adorners.Clear();
            }

            if (_startPoint.HasValue && _endPoint.HasValue)
            {
                var rand = new Random();
                var item = new NRectangleViewModel();
                item.Owner = (AdornedElement as DesignerCanvas).DataContext as IDiagramViewModel;
                item.Left.Value = Math.Min(_startPoint.Value.X, _endPoint.Value.X);
                item.Top.Value = Math.Min(_startPoint.Value.Y, _endPoint.Value.Y);
                item.Width.Value = Math.Max(_startPoint.Value.X - _endPoint.Value.X, _endPoint.Value.X - _startPoint.Value.X);
                item.Height.Value = Math.Max(_startPoint.Value.Y - _endPoint.Value.Y, _endPoint.Value.Y - _startPoint.Value.Y);
                item.EdgeColor = item.Owner.EdgeColors.First();
                item.FillColor = item.Owner.FillColors.First();
                item.ZIndex.Value = item.Owner.Items.Count;
                item.IsSelected = true;
                item.Owner.DeselectAll();
                ((AdornedElement as DesignerCanvas).DataContext as IDiagramViewModel).AddItemCommand.Execute(item);

                _startPoint = null;
                _endPoint = null;
            }

            (App.Current.MainWindow.DataContext as MainWindowViewModel).CurrentOperation.Value = "";
            (App.Current.MainWindow.DataContext as MainWindowViewModel).Details.Value = "";

            e.Handled = true;
        }

        protected override void OnRender(DrawingContext dc)
        {
            base.OnRender(dc);

            dc.DrawRectangle(Brushes.Transparent, null, new Rect(RenderSize));

            if (_startPoint.HasValue && _endPoint.HasValue)
                dc.DrawRectangle(Brushes.Transparent, _rectanglePen, new Rect(_startPoint.Value, _endPoint.Value));
        }
    }

自分で試したこと

吸着可能なポイントをViewから吸い上げる処理を下記のように実装しました。
1つの四角形が描画されている時に、このSnapPointsプロパティをVisual Studioのデバッグ機能で確認したところ、ポイントが8つ生成されていました。コーナーの左上、上、右上、右、右下、下、左下、左の8個なので間違いはなさそうなのですが...。

DiagramViewModel.cs
public class DiagramViewModel : BindableBase, IDiagramViewModel, IDisposable
    {
        :
        public IEnumerable<Point> SnapPoints
        {
            get {
                var designerCanvas = App.Current.MainWindow.GetChildOfType<DesignerCanvas>();
                return designerCanvas.EnumerateChildOfType<ResizeThumb>()
                      .Where(x => !(x is null))
                      .Select(x => x.TransformToAncestor(designerCanvas).Transform(new Point(0, 0)))
                      .Distinct(new SnapPointDistincter());
            }
        }
        :
    }
SnapPointDistincter.cs
class SnapPointDistincter : IEqualityComparer<Point>
    {
        public bool Equals(Point a, Point b)
        {
            var mainWindowVM = (App.Current.MainWindow.DataContext as MainWindowViewModel);
            var x = Math.Abs(a.X - b.X);
            var y = Math.Abs(a.Y - b.Y);
            var r = Math.Sqrt(Math.Pow(x, 2) + Math.Pow(y, 2));
            return r < mainWindowVM.SnapPower.Value / 2;
        }

        public int GetHashCode(Point obj)
        {
            return obj.GetHashCode();
        }
    }

2021-07-04 (1).png

ソースコード

boiler's Graphics
https://github.com/dhq-boiler/boiler-s-Graphics

gitリポジトリ
https://github.com/dhq-boiler/boiler-s-Graphics.git

ブランチ:feature/PointSnap

コミット:ce42470

何か私の見落とし、致命的な勘違いなど気づいたところがあれば、回答していただけると助かります。よろしくお願いいたします。

0 likes

3Answer

下記コードを追加して、DiagramViewModel.SnapPointsを可視化しようと試みました。

RectangleAdorner.cs
        [Conditional("DEBUG")]
        private void DebugPrint(int width, int height, IEnumerable<Point> snapPoints)
        {
            var designerCanvas = App.Current.MainWindow.GetChildOfType<DesignerCanvas>();
            var rtb = new RenderTargetBitmap((int)designerCanvas.ActualWidth, (int)designerCanvas.ActualHeight, 96, 96, PixelFormats.Pbgra32);

            DrawingVisual visual = new DrawingVisual();
            using (DrawingContext context = visual.RenderOpen())
            {
                VisualBrush brush = new VisualBrush(designerCanvas);
                context.DrawRectangle(brush, null, new Rect(new Point(), new Size(designerCanvas.Width, designerCanvas.Height)));

                foreach (var snapPoint in snapPoints)
                {
                    context.DrawEllipse(Brushes.Red, new Pen(Brushes.Red, 1), snapPoint, 2, 2);
                }
            }

            rtb.Render(visual);

            //OpenCvSharp.Cv2.ImShow()するためには src_depth != CV_16F && src_depth != CV_32S である必要があるから、予めBgr24に変換しておく
            FormatConvertedBitmap newFormatedBitmapSource = new FormatConvertedBitmap();
            newFormatedBitmapSource.BeginInit();
            newFormatedBitmapSource.Source = rtb;
            newFormatedBitmapSource.DestinationFormat = PixelFormats.Bgr24;
            newFormatedBitmapSource.EndInit();

            var mat = OpenCvSharp.WpfExtensions.BitmapSourceConverter.ToMat(newFormatedBitmapSource);
            OpenCvSharp.Cv2.ImShow("DebugPrint", mat);
        }

赤い点がSnapPointです。

2021-07-05.png

左上コーナー:3点
右上コーナー:2点
左下コーナー:2点
右下コーナー:1点

という結果になりました。
なぜこうなるのかはまだわかっていません。

0Like

さらにデバッグ用の表示を追加しました。

ResizeThumbには識別子としてTagプロパティにコーナーの場所を示す文字列を入れました。

DesignerItems.xaml
    <ControlTemplate x:Key="ResizeDecoratorTemplate" TargetType="{x:Type Control}">
        <Grid>
            <Grid Opacity="0.7" SnapsToDevicePixels="true">
                <!-- 上の回転ツマミ部分 -->
                <control:RotateThumb
                    Width="7"
                    Height="7"
                    Margin="0,-20,0,0"
                    HorizontalAlignment="Center"
                    VerticalAlignment="Top"
                    Cursor="Hand"
                    Template="{StaticResource RotateThumbTemplate}"
                    Tag="回転ツマミ" />
                <!-- 上 -->
                <control:ResizeThumb
                    Height="3"
                    Margin="0,-4,0,0"
                    HorizontalAlignment="Stretch"
                    VerticalAlignment="Top"
                    Cursor="SizeNS"
                    Template="{StaticResource HorizontalResizeHandleTemplate}"
                    Tag="上" />
                <!-- 左 -->
                <control:ResizeThumb
                    Width="3"
                    Margin="-4,0,0,0"
                    HorizontalAlignment="Left"
                    VerticalAlignment="Stretch"
                    Cursor="SizeWE"
                    Template="{StaticResource VerticalResizeHandleTemplate}"
                    Tag="左" />
                <!-- 右 -->
                <control:ResizeThumb
                    Width="3"
                    Margin="0,0,-4,0"
                    HorizontalAlignment="Right"
                    VerticalAlignment="Stretch"
                    Cursor="SizeWE"
                    Template="{StaticResource VerticalResizeHandleTemplate}"
                    Tag="右" />
                <!-- 下 -->
                <control:ResizeThumb
                    Height="3"
                    Margin="0,0,0,-4"
                    HorizontalAlignment="Stretch"
                    VerticalAlignment="Bottom"
                    Cursor="SizeNS"
                    Template="{StaticResource HorizontalResizeHandleTemplate}"
                    Tag="下" />
                <!-- 左上 -->
                <control:ResizeThumb
                    Width="7"
                    Height="7"
                    Margin="-6,-6,0,0"
                    HorizontalAlignment="Left"
                    VerticalAlignment="Top"
                    Cursor="SizeNWSE"
                    Template="{StaticResource ResizeHandleTemplate}"
                    Tag="左上" />
                <!-- 右上 -->
                <control:ResizeThumb
                    Width="7"
                    Height="7"
                    Margin="0,-6,-6,0"
                    HorizontalAlignment="Right"
                    VerticalAlignment="Top"
                    Cursor="SizeNESW"
                    Template="{StaticResource ResizeHandleTemplate}"
                    Tag="右上" />
                <!-- 左下 -->
                <control:ResizeThumb
                    Width="7"
                    Height="7"
                    Margin="-6,0,0,-6"
                    HorizontalAlignment="Left"
                    VerticalAlignment="Bottom"
                    Cursor="SizeNESW"
                    Template="{StaticResource ResizeHandleTemplate}"
                    Tag="左下" />
                <!-- 右下 -->
                <control:ResizeThumb
                    Width="7"
                    Height="7"
                    Margin="0,0,-6,-6"
                    HorizontalAlignment="Right"
                    VerticalAlignment="Bottom"
                    Cursor="SizeNWSE"
                    Template="{StaticResource ResizeHandleTemplate}"
                    Tag="右下" />
            </Grid>
            <Grid
                Margin="-3"
                IsHitTestVisible="False"
                Opacity="1">
                <Line
                    Margin="0,-11,0,0"
                    HorizontalAlignment="Center"
                    VerticalAlignment="Top"
                    Stroke="#6c809a"
                    StrokeThickness="1"
                    X1="0"
                    X2="0"
                    Y1="0"
                    Y2="11" />
            </Grid>
        </Grid>
    </ControlTemplate>

TagをCv2.ImShow()で描画させるようにします。

DiagramViewModel.cs
        public IEnumerable<Point> SnapPoints
        {
            get {
                var designerCanvas = App.Current.MainWindow.GetChildOfType<DesignerCanvas>();
                var resizeThumbs = designerCanvas.EnumerateChildOfType<ResizeThumb>();
                var sets = resizeThumbs
                                .Select(x => new Tuple<ResizeThumb, Point>(x, x.TransformToAncestor(designerCanvas).Transform(new Point(0, 0))));
                                //.Distinct(new SnapPointDistincter());
                DebugPrint(Width, Height, sets);
                return sets.Select(x => x.Item2);
            }
        }

        [Conditional("DEBUG")]
        private void DebugPrint(int width, int height, IEnumerable<Tuple<ResizeThumb, Point>> sets)
        {
            var designerCanvas = App.Current.MainWindow.GetChildOfType<DesignerCanvas>();
            var rtb = new RenderTargetBitmap((int)designerCanvas.ActualWidth, (int)designerCanvas.ActualHeight, 96, 96, PixelFormats.Pbgra32);

            DrawingVisual visual = new DrawingVisual();
            using (DrawingContext context = visual.RenderOpen())
            {
                VisualBrush brush = new VisualBrush(designerCanvas);
                context.DrawRectangle(brush, null, new Rect(new Point(), new Size(designerCanvas.Width, designerCanvas.Height)));

                Random rand = new Random();
                foreach (var set in sets)
                {
                    context.DrawText(new FormattedText((string)set.Item1.Tag, CultureInfo.CurrentCulture, FlowDirection.LeftToRight, new Typeface("メイリオ"), 12, RandomBrush(rand), VisualTreeHelper.GetDpi(designerCanvas).PixelsPerDip), set.Item2);
                    context.DrawEllipse(Brushes.Red, new Pen(Brushes.Red, 1), set.Item2, 2, 2);
                }
            }

            rtb.Render(visual);

            //OpenCvSharp.Cv2.ImShow()するためには src_depth != CV_16F && src_depth != CV_32S である必要があるから、予めBgr24に変換しておく
            FormatConvertedBitmap newFormatedBitmapSource = new FormatConvertedBitmap();
            newFormatedBitmapSource.BeginInit();
            newFormatedBitmapSource.Source = rtb;
            newFormatedBitmapSource.DestinationFormat = PixelFormats.Bgr24;
            newFormatedBitmapSource.EndInit();

            var mat = OpenCvSharp.WpfExtensions.BitmapSourceConverter.ToMat(newFormatedBitmapSource);
            OpenCvSharp.Cv2.ImShow("DebugPrint", mat);
        }

        private Brush RandomBrush(Random rand)
        {
            var brush = new SolidColorBrush(Color.FromRgb((byte)rand.Next(), (byte)rand.Next(), (byte)rand.Next()));
            return brush;
        }

2021-07-05 (2).png

この画像を見ると、左上コーナーに「左」と「左上」と「上」のスナップポイントが重なり合って配置されていることがわかります。左下コーナーは「左下」と「下」。右上コーナーは「右上」と「右」。右下コーナーは「右下」のみです。

0Like

バグの原因がわかりました。

四角形の一辺の中間の点、すなわち左、上、右、下のスナップポイントが左上の方に向かってずれていたのは、VerticalAlignmentやHorizontalAlignmentにStretchを設定していたからでした。
これをCenterに修正したところ、正しい位置に配置されました。

正しいコードは以下の通りです。

DesignerItems.xaml
    <ControlTemplate x:Key="ResizeDecoratorTemplate" TargetType="{x:Type Control}">
        <Grid>
            <Grid Opacity="0.7" SnapsToDevicePixels="true">
                <!-- 上の回転ツマミ部分 -->
                <control:RotateThumb
                    Width="7"
                    Height="7"
                    Margin="0,-20,0,0"
                    HorizontalAlignment="Center"
                    VerticalAlignment="Top"
                    Cursor="Hand"
                    Template="{StaticResource RotateThumbTemplate}"
                    Tag="回転ツマミ" />
                <!-- 上 -->
                <control:ResizeThumb
                    Height="3"
                    Margin="0,-4,0,0"
                    HorizontalAlignment="Center" <!-- Stretch→Center -->
                    VerticalAlignment="Top"
                    Cursor="SizeNS"
                    Template="{StaticResource HorizontalResizeHandleTemplate}"
                    Tag="上" />
                <!-- 左 -->
                <control:ResizeThumb
                    Width="3"
                    Margin="-4,0,0,0"
                    HorizontalAlignment="Left"
                    VerticalAlignment="Center" <!-- Stretch→Center -->
                    Cursor="SizeWE"
                    Template="{StaticResource VerticalResizeHandleTemplate}"
                    Tag="左" />
                <!-- 右 -->
                <control:ResizeThumb
                    Width="3"
                    Margin="0,0,-4,0"
                    HorizontalAlignment="Right"
                    VerticalAlignment="Center" <!-- Stretch→Center -->
                    Cursor="SizeWE"
                    Template="{StaticResource VerticalResizeHandleTemplate}"
                    Tag="右" />
                <!-- 下 -->
                <control:ResizeThumb
                    Height="3"
                    Margin="0,0,0,-4"
                    HorizontalAlignment="Center" <!-- Stretch→Center -->
                    VerticalAlignment="Bottom"
                    Cursor="SizeNS"
                    Template="{StaticResource HorizontalResizeHandleTemplate}"
                    Tag="下" />
                <!-- 左上 -->
                <control:ResizeThumb
                    Width="7"
                    Height="7"
                    Margin="-6,-6,0,0"
                    HorizontalAlignment="Left"
                    VerticalAlignment="Top"
                    Cursor="SizeNWSE"
                    Template="{StaticResource ResizeHandleTemplate}"
                    Tag="左上" />
                <!-- 右上 -->
                <control:ResizeThumb
                    Width="7"
                    Height="7"
                    Margin="0,-6,-6,0"
                    HorizontalAlignment="Right"
                    VerticalAlignment="Top"
                    Cursor="SizeNESW"
                    Template="{StaticResource ResizeHandleTemplate}"
                    Tag="右上" />
                <!-- 左下 -->
                <control:ResizeThumb
                    Width="7"
                    Height="7"
                    Margin="-6,0,0,-6"
                    HorizontalAlignment="Left"
                    VerticalAlignment="Bottom"
                    Cursor="SizeNESW"
                    Template="{StaticResource ResizeHandleTemplate}"
                    Tag="左下" />
                <!-- 右下 -->
                <control:ResizeThumb
                    Width="7"
                    Height="7"
                    Margin="0,0,-6,-6"
                    HorizontalAlignment="Right"
                    VerticalAlignment="Bottom"
                    Cursor="SizeNWSE"
                    Template="{StaticResource ResizeHandleTemplate}"
                    Tag="右下" />
            </Grid>
            <Grid
                Margin="-3"
                IsHitTestVisible="False"
                Opacity="1">
                <Line
                    Margin="0,-11,0,0"
                    HorizontalAlignment="Center"
                    VerticalAlignment="Top"
                    Stroke="#6c809a"
                    StrokeThickness="1"
                    X1="0"
                    X2="0"
                    Y1="0"
                    Y2="11" />
            </Grid>
        </Grid>
    </ControlTemplate>

2021-07-05 (3).png

スナップポイントがほぼ正しい位置に配置されました!!!

0Like

Your answer might help someone💌