Поворот графика в сторону мыши в WPF (как аналоговый циферблат)

в WPF / C# как бы я повернул "графику" к текущей позиции мыши?

в основном то, что я хочу, это" колесо " UI Control (например,аналоговый регулятор громкости). Я хочу иметь возможность щелкнуть и перетащить диск, и он будет вращаться, чтобы следовать за мышью. Затем, когда я отпущу мышь, она перестанет следовать (очевидно!).

Как бы я создал один из них? он уже где-то существует?

3 ответов


Я не видел таких элементов управления (хотя прошло некоторое время с тех пор, как я смотрел на все элементы управления, которые предлагали поставщики WPF), но создать их относительно просто.

все, что вам нужно сделать, это создать пользовательский элемент управления, содержащий изображение (или чертеж XAML), которое вы можете повернуть, чтобы следовать за мышью. Затем свяжите RotateTransform с зависимостью "угол" от пользовательского элемента управления, чтобы при обновлении "угол" изображение / чертеж вращается в соответствии:

<UserControl x:Class="VolumeControlLibrary.VolumeControl"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:local="clr-namespace:VolumeControlLibrary"
             Height="60" Width="60">
    <Image Source="/VolumeControl;component/knob.png" RenderTransformOrigin="0.5,0.5" >
        <Image.RenderTransform>
            <RotateTransform Angle="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:VolumeControl}}, Path=Angle}"/>
        </Image.RenderTransform>
    </Image>
</UserControl>

установка RenderTransformOrigin в "0.5, 0.5" гарантирует, что элемент управления вращается вокруг своего центра, а не вращается вокруг верхнего левого угла; нам придется компенсировать это в расчете угла тоже.

в коде за файлом для вашего элемента управления добавьте обработчики для мыши и угол DependencyProperty:

public partial class VolumeControl : UserControl
{
    // Using a DependencyProperty backing store for Angle.
    public static readonly DependencyProperty AngleProperty =
        DependencyProperty.Register("Angle", typeof(double), typeof(VolumeControl), new UIPropertyMetadata(0.0));

    public double Angle
    {
        get { return (double)GetValue(AngleProperty); }
        set { SetValue(AngleProperty, value); }
    }

    public VolumeControl()
    {
        InitializeComponent();
        this.MouseLeftButtonDown += new MouseButtonEventHandler(OnMouseLeftButtonDown);
        this.MouseUp += new MouseButtonEventHandler(OnMouseUp);
        this.MouseMove += new MouseEventHandler(OnMouseMove);
    }

    private void OnMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
    {
        Mouse.Capture(this);
    }

    private void OnMouseUp(object sender, MouseButtonEventArgs e)
    {
        Mouse.Capture(null);
    }

    private void OnMouseMove(object sender, MouseEventArgs e)
    {
        if (Mouse.Captured == this)
        {
            // Get the current mouse position relative to the volume control
            Point currentLocation = Mouse.GetPosition(this);

            // We want to rotate around the center of the knob, not the top corner
            Point knobCenter = new Point(this.ActualHeight / 2, this.ActualWidth / 2);

            // Calculate an angle
            double radians = Math.Atan((currentLocation.Y - knobCenter.Y) / 
                                       (currentLocation.X - knobCenter.X));
            this.Angle = radians * 180 / Math.PI;

            // Apply a 180 degree shift when X is negative so that we can rotate
            // all of the way around
            if (currentLocation.X - knobCenter.X < 0)
            {
                this.Angle += 180;
            }
        }
    }
}

захват мыши гарантирует, что ваш элемент управления будет продолжать получать обновления мыши, даже если пользователь отключает элемент управления (пока не отпустит щелчок), и, получив положение мыши относительно текущего элемента (элемента управления), ваш расчет всегда должен быть одинаковым независимо от того, где элемент управления фактически отображается на экране.

в этом примере при перемещении мыши мы вычисляем угол между ней и центром элемента управления, а затем устанавливаем этот угол в зависимости от угла, который мы создали. Поскольку изображение, которое мы показываем, связано с этим свойство angle, WPF автоматически применяет новое значение, в результате чего ручка вращается в сочетании с перемещением мыши.

использование элемента управления в вашем решении легко; просто добавьте:

<local:VolumeControl />

вы бы привязались к свойству Angle на VolumeControl, если бы вы хотели привязать значение ручки к чему-то в своем приложении; это значение в настоящее время находится в градусах, но может добавить дополнительное свойство для преобразования между углом в градусах и значением, которое делает смысл вам (скажем, значение от 0-10).


чтобы добавить к этому сообщению, угол между точкой мыши и точкой объекта рассчитывается следующим образом:

dot = currentLocation.X * objectPosition.X + currentLocation.Y * objectPosition.Y;
angle = Math.Acos(dot);

в моем случае я динамически создал фигуры, которые должны вращаться в направлении мыши. Для решения этой проблемы я использовал облегченную функцию. Все, что мне нужно, это следующее:

  • точка текущей выбранной фигуры
  • точка с последнего шага наведения мыши
  • и точка от текущего шага наведения мыши

нет необходимости использовать методы из математической библиотеки. Я вычисляю угол, который зависит от разницы текущей мыши над точкой и предыдущей мыши над точкой и положением относительно центральной точки. Наконец, я добавляю угол на угол exisitng текущего объекта.

private void HandleLeftMouseDown(MouseButtonEventArgs eventargs)
{
    //Calculate the center point of selected object
    //...
    //assuming Point1 is the top left point
    var xCenter = (_selectedObject.Point2.X - _selectedObject.Point1.X) / 2 + _selectedObject.Point1.X
    var yCenter = (_selectedObject.Point2.Y - _selectedObject.Point1.Y) / 2 + _selectedObject.Point1.Y
    _selectedObjectCenterPoint = new Point((double) xCenter, (double) yCenter);

    //init set of last mouse over step with the mouse click point
     var clickPoint = eventargs.GetPosition(source);
    _lastMouseOverPoint = new Point(clickPoint.X,clickPoint.Y);
}

private void HandleMouseMove(MouseEventArgs eventArgs)
{
    Point pointMouseOver = eventArgs.GetPosition(_source);                            

    //Get the difference to the last mouse over point
    var xd = pointMouseOver.X - _lastMouseOverPoint.X;
    var yd = pointMouseOver.Y - _lastMouseOverPoint.Y;

    // the direction depends on the current mouse over position in relation to the center point of the shape
    if (pointMouseOver.X < _selectedObjectCenterPoint.X)
        yd *= -1;
    if (pointMouseOver.Y > _selectedObjectCenterPoint.Y)
        xd *= -1;

    //add to the existing Angle   
    //not necessary to calculate the degree measure
    _selectedObject.Angle += (xd + yd);

    //save mouse over point            
    _lastMouseOverPoint = new Point(pointMouseOver.X, pointMouseOver.Y);
}