Rotate graphic towards mouse in WPF (like an analog dial)

后端 未结 3 1362
臣服心动
臣服心动 2020-12-29 14:48

In WPF/C# how would I rotate a \"graphic\" to face the current mouse position?

Basically what I want is a \"wheel\" UI Control (like an analog volume dial

相关标签:
3条回答
  • 2020-12-29 15:27

    To add to that post, the angle between the mouse point and the object point is calculated like:

    dot = currentLocation.X * objectPosition.X + currentLocation.Y * objectPosition.Y;
    angle = Math.Acos(dot);
    
    0 讨论(0)
  • 2020-12-29 15:34

    In my case i have dynamically created shapes which shall rotated toward mouse direction. To solve this I used a lightweight function. All I need is following:

    • The centerpoint of the current selected shape
    • The point from the last mouseover step
    • And the point from the current mouseover step

    It is not necessary to use methods from the Math library. I calculate the angle which depends on the difference of the current mouse over point and the previous mouse over point and the position in relation o the center point. Finally I add the angle on the exisitng angle of the current object.

    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);
    }
    
    0 讨论(0)
  • 2020-12-29 15:40

    I haven't seen any controls like this around (though it's been a while since I looked at all of the controls that WPF control vendors were offering), but it's relatively straightforward to create one.

    All you'd have to do is create a custom control containing an Image (or XAML drawing) that you can rotate to follow the mouse. Then, bind a RotateTransform to an 'Angle' DependencyProperty on your custom control so that when 'angle' is updated, the image/drawing rotates to match:

    <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>
    

    Setting RenderTransformOrigin to "0.5, 0.5" ensures that the control rotates around its center, rather than rotating around the top left corner; we'll have to compensate for this in the angle calculation too.

    In the code behind file for your control, add handlers for the mouse and the Angle 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;
                }
            }
        }
    }
    

    Capturing the mouse ensures that your control will continue to get mouse updates even when the user mouses off of the control (until they let go of the click), and by getting the position of the mouse relative to the current element (the control), your calculation should always be the same regardless of where the control actually renders on screen.

    In this example, when the mouse moves we calculate the angle between it and the center of the control, and then set this angle to the Angle DependencyProperty we created. Since the image we're displaying is bound to this angle property, WPF automatically applies the new value, which results in the knob rotating in combination with the mouse moving.

    Using the control in your solution is easy; just add:

    <local:VolumeControl />
    

    You would bind to the Angle property on VolumeControl if you wanted to bind the value of the knob to something in your application; that value is currently in degrees, but could add an additional property to convert between an angle in degrees and a value that makes sense to you (say, a value from 0 - 10).

    0 讨论(0)
提交回复
热议问题