A Knob Slider Control

I replace the template of the Slider control with a knob template and handle some mouse events to implement rotation. The code snippets below shows the details:

Xaml:

<Window x:Class="WpfApplication16.Window1"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:my="clr-namespace:WpfApplication16"
        Title="Window1" Height="300" Width="300">    
    <Window.Resources>
        <my:ValueAngleConverter x:Key="valueAngleConverter"/>
        <my:ValueTextConverter x:Key="valueTextConverter"/>
    </Window.Resources>
    <Grid>
        <Slider Name="knob">
            <Slider.Template>
                <ControlTemplate>
                    <Viewbox>
<Canvas Width="300" Height="300" Margin="5">
    <Ellipse Fill="LightBlue" Width="300" Height="300" Canvas.Left="0" Canvas.Top="0" 
                Stroke="Black" StrokeThickness="10"
                MouseLeftButtonUp="Ellipse_MouseLeftButtonUp"
                MouseMove="Ellipse_MouseMove"/>
    <Ellipse Fill="Black" Width="60" Height="60" Canvas.Left="120" Canvas.Top="120"/>
    <Canvas>
        <Line Stroke="Red" StrokeThickness="5" X1="150" Y1="150" X2="150" Y2="10"
                MouseLeftButtonUp="Ellipse_MouseLeftButtonUp"/>
        <Ellipse Fill="Red" Width="20" Height="20" Canvas.Left="140" Canvas.Top="0" 
                MouseLeftButtonDown="Ellipse_MouseLeftButtonDown"
                    MouseLeftButtonUp="Ellipse_MouseLeftButtonUp">
            <Ellipse.ToolTip>
                <ToolTip>
                    <Binding RelativeSource="{RelativeSource TemplatedParent}"
                                Path="Value" Converter="{StaticResource valueTextConverter}"/>
                </ToolTip>
            </Ellipse.ToolTip>
        </Ellipse>
        <Canvas.RenderTransform>
            <RotateTransform CenterX="150" CenterY="150">
                <RotateTransform.Angle>
                    <MultiBinding Converter="{StaticResource valueAngleConverter}">
                        <Binding RelativeSource="{RelativeSource TemplatedParent}" Path="Value"/>
                        <Binding RelativeSource="{RelativeSource TemplatedParent}" Path="Minimum"/>
                        <Binding RelativeSource="{RelativeSource TemplatedParent}" Path="Maximum"/>
                    </MultiBinding>
                </RotateTransform.Angle>
            </RotateTransform>
        </Canvas.RenderTransform>
    </Canvas>                            
</Canvas>
                    </Viewbox>
                </ControlTemplate>
            </Slider.Template>
        </Slider>
    </Grid>
</Window>

Code:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;

namespace WpfApplication16
{
    /// <summary>
    /// Interaction logic for Window1.xaml
    /// </summary>
    public partial class Window1 : Window
    {
        public Window1()
        {
            InitializeComponent(); 
        }

        private bool _isPressed = false;
        private Canvas _templateCanvas = null;

        private void Ellipse_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            //Enable moving mouse to change the value.
            _isPressed = true;
        }

        private void Ellipse_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
        {
            //Disable moving mouse to change the value.
            _isPressed = false;
        }

        private void Ellipse_MouseMove(object sender, MouseEventArgs e)
        {
            if (_isPressed)
            {
                //Find the parent canvas.
                if (_templateCanvas == null)
                {
                    _templateCanvas = MyHelper.FindParent<Canvas>(e.Source as Ellipse);
                    if (_templateCanvas == null) return;
                }
                //Canculate the current rotation angle and set the value.
                const double RADIUS = 150;
                Point newPos = e.GetPosition(_templateCanvas);
                double angle = MyHelper.GetAngleR(newPos, RADIUS);
                knob.Value = (knob.Maximum - knob.Minimum) * angle / (2 * Math.PI);
            }
        }
    }

    //The converter used to convert the value to the rotation angle.
    public class ValueAngleConverter : IMultiValueConverter
    {
        #region IMultiValueConverter Members

        public object Convert(object[] values, Type targetType, object parameter, 
System.Globalization.CultureInfo culture) { double value = (double)values[0]; double minimum = (double)values[1]; double maximum = (double)values[2]; return MyHelper.GetAngle(value, maximum, minimum); } public object[] ConvertBack(object value, Type[] targetTypes, object parameter,
System.Globalization.CultureInfo culture) { throw new NotImplementedException(); } #endregion } //Convert the value to text. public class ValueTextConverter : IValueConverter { #region IValueConverter Members public object Convert(object value, Type targetType, object parameter,
System.Globalization.CultureInfo culture) { double v = (double)value; return String.Format("{0:F2}", v); } public object ConvertBack(object value, Type targetType, object parameter,
System.Globalization.CultureInfo culture) { throw new NotImplementedException(); } #endregion } public static class MyHelper { //Get the parent of an item. public static T FindParent<T>(FrameworkElement current) where T : FrameworkElement { do { current = VisualTreeHelper.GetParent(current) as FrameworkElement; if (current is T) { return (T)current; } } while (current != null); return null; } //Get the rotation angle from the value public static double GetAngle(double value, double maximum, double minimum) { double current = (value / (maximum - minimum)) * 360; if (current == 360) current = 359.999; return current; } //Get the rotation angle from the position of the mouse public static double GetAngleR(Point pos, double radius) { //Calculate out the distance(r) between the center and the position Point center = new Point(radius, radius); double xDiff = center.X - pos.X; double yDiff = center.Y - pos.Y; double r = Math.Sqrt(xDiff * xDiff + yDiff * yDiff); //Calculate the angle double angle = Math.Acos((center.Y - pos.Y) / r); Console.WriteLine("r:{0},y:{1},angle:{2}.", r, pos.Y, angle); if (pos.X < radius) angle = 2 * Math.PI - angle; if (Double.IsNaN(angle)) return 0.0; else return angle; } } }

This entry was posted in WPF. Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s