tags:

views:

46

answers:

2

Is it possible to restrict the drag source to only move within the boundaries of a circular path when dragging it?

A: 

Create a circle of points and then when the mouse moves (and we are dragging) calculate the nearest point and snap to that point.

CircularDrag.xaml

<UserControl x:Class="DraggingBoundaries.CircularDrag"
         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
         xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
         mc:Ignorable="d" 
         d:DesignHeight="300" d:DesignWidth="300"
         SizeChanged="UserControl_SizeChanged"
         MouseMove="UserControl_MouseMove"
         MouseLeave="UserControl_MouseLeave"
         MouseLeftButtonUp="UserControl_MouseLeftButtonUp"
         >
    <Grid Background="White">
        <Border 
            x:Name="Marker"
            HorizontalAlignment="Left"
            VerticalAlignment="Top"
            Width="14" 
            Height="14" 
            Background="CornflowerBlue" 
            CornerRadius="2" 
            BorderThickness="1" 
            BorderBrush="DarkGray"
            MouseLeftButtonDown="Marker_MouseLeftButtonDown"
            />
    </Grid>
</UserControl>

CircularDrag.xaml.cs

using System;
using System.Linq;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;

namespace DraggingBoundaries
{
    public partial class CircularDrag : UserControl
    {
        List<Point> allowedWheelMarkerPositions;
        bool isDraggingMarker;

        public CircularDrag()
        {
            InitializeComponent();
        }

        private void UserControl_SizeChanged(object sender, SizeChangedEventArgs e)
        {
            var center = new Point(e.NewSize.Width / 2, e.NewSize.Height / 2);

            var radius = (center.X < center.Y ? center.X : center.Y) - 15;

            allowedWheelMarkerPositions = CreateCirclePath(center, radius);
            SetMarkerPosition(allowedWheelMarkerPositions.First());
        }

        private List<Point> CreateCirclePath(Point center, double radius)
        {
            var result = new List<Point>();
            for (double angle = 0; angle <= 360; angle++)
            {
                double angleR = angle * (Math.PI / 180);
                double x = center.X + Math.Cos(angleR) * radius;
                double y = center.Y - Math.Sin(angleR) * radius;

                result.Add(new Point(x, y));
            }
            return result;
        }

        private void UserControl_MouseMove(object sender, MouseEventArgs e)
        {
            if (!isDraggingMarker)
                return;
            var position = e.GetPosition(this);

            var closest = allowedWheelMarkerPositions
                .OrderBy(p => GetDistance(position, p))
                .First();

            SetMarkerPosition(closest);
        }

        private void SetMarkerPosition(Point closest)
        {
            Marker.Margin = new Thickness(closest.X - Marker.Width / 2, closest.Y - Marker.Height / 2, 0, 0);
        }

        private double GetDistance(Point a, Point b)
        {
            var deltaX = a.X - b.X;
            var deltaY = a.Y - b.Y;

            return Math.Sqrt(Math.Pow(deltaX, 2) + Math.Pow(deltaY, 2));
        }

        private void Marker_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            isDraggingMarker = true;
        }

        private void UserControl_MouseLeave(object sender, MouseEventArgs e)
        {
            isDraggingMarker = false;
        }

        private void UserControl_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
        {
            isDraggingMarker = false;
        }
    }
}
loraderon
+2  A: 

You don't need the 360-point path. Instead, as you are dragging, compute the current angle using Math.Atan2(Y,X), and then generate the point on the circle. You would still need to compute center and radius on resize and store them, or compute them inside MouseMove.

    private void UserControl_MouseMove(object sender, MouseEventArgs e)
    {
        if (!isDraggingMarker)
            return;
        var position = e.GetPosition(this);

        double angle = Math.Atan2(position.Y - center.Y, position.X - center.X);
        var closest = new Point(center.X + radius*Math.Cos(angle),
                                center.Y + radius*Math.Sin(angle));

        SetMarkerPosition(closest);
    }
Justin