#region License /* Copyright (c) 2017 Fabrice Lacharme * This code was originally written by Jigar Desai * http://www.c-sharpcorner.com/article/knob-control-using-windows-forms-and-gdi/ * Note that another implementation exists in vb.net by Blong * https://www.codeproject.com/Articles/2563/VB-NET-Knob-Control-using-Windows-Forms-and-GDI?msg=1884770#xx1884770xx * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ #endregion #region Contact /* * Fabrice Lacharme * Email: fabrice.lacharme@gmail.com */ #endregion using System; using System.ComponentModel; using System.Drawing; using System.Drawing.Drawing2D; using System.Windows.Forms; namespace KnobControl { /* Original code from Jigar Desai on C-SharpCorner.com * see https://www.c-sharpcorner.com/article/knob-control-using-windows-forms-and-gdi/ * KnobControl is a knob control written in C# * * CodeProject: https://www.codeproject.com/Tips/1187460/Csharp-Knob-Control-using-Windows-Forms * Github: https://github.com/fabricelacharme/KnobControl * * 22/08/18 - version 1.0.O.1 * Fixed: erroneous display in case of minimum value <> 0 (negative or positive) * Modified: DrawColorSlider, OnMouseMove * * Added: Font selection * * * 25/08/18 - version 1.0.0.2 * Fixed: mouse click event: pointer button is not displayed correctly when the minimum is set to a non zero value. * Modified: getValueFromPosition * * * 04/01/2019 - version 1.0.0.3 * Font & Size selection for graduations: * New property ScaleFontAutoSize: * - false = no AutoSize => Allow font selection * - true = AutoSize by program */ // A delegate type for hooking up ValueChanged notifications. public delegate void ValueChangedEventHandler(object Sender); // A delegate type for hooking up mouse clicks on middle area notifications. public delegate void MiddleAreaMouseClickEventHandler(object Sender); /// /// Summary description for KnobControl. /// public class KnobControl : System.Windows.Forms.UserControl { /// /// Required designer variable. /// private System.ComponentModel.Container components = null; /// /// Styles of pointer button /// public enum KnobPointerStyles { circle, line, } #region private properties private KnobPointerStyles _knobPointerStyle = KnobPointerStyles.circle; private int _minimum = 0; private int _maximum = 25; private int _LargeChange = 5; private int _SmallChange = 1; private int _scaleDivisions; private int _scaleSubDivisions; private Font _scaleFont; private bool _scaleFontAutoSize = true; private bool _drawDivInside; private bool _showSmallScale = false; private bool _showLargeScale = true; private float _startAngle = 135; private float _endAngle = 405; private float deltaAngle; private int _mouseWheelBarPartitions = 10; private float drawRatio = 1; private float gradLength = 4; // Color of the pointer private Color _PointerColor = Color.SlateBlue; private Color _knobBackColor = Color.LightGray; private Color _scaleColor = Color.Black; private int _Value = 0; private bool isFocused = false; private bool isKnobRotating = false; private bool isMiddleArea = false; private Rectangle rKnob; private Rectangle rMiddleArea; private Point pKnob; private Pen DottedPen; Brush brushKnob; Brush brushMiddleArea; Brush brushKnobPointer; private Font knobFont; //------------------------------------------------------- // declare Off screen image and Offscreen graphics //------------------------------------------------------- private Image OffScreenImage; private Graphics gOffScreen; #endregion #region event //------------------------------------------------------- // An event that clients can use to be notified whenever // the Value is Changed. //------------------------------------------------------- public event ValueChangedEventHandler ValueChanged; //------------------------------------------------------- // Invoke the ValueChanged event; called when value // is changed //------------------------------------------------------- protected virtual void OnValueChanged(object sender) { ValueChanged?.Invoke(sender); } //------------------------------------------------------- // An event that clients can use to be notified whenever // the middle area of this control is clicked. //------------------------------------------------------- public event MiddleAreaMouseClickEventHandler MiddleAreaClicked; //------------------------------------------------------- // Invoke the M event; called when value // is changed //------------------------------------------------------- protected virtual void OnMiddleAreaClicked(object sender) { MiddleAreaClicked?.Invoke(sender); } #endregion #region (* public Properties *) /// /// Font of graduations /// [Description("Font of graduations")] [Category("KnobControl")] public Font ScaleFont { get { return _scaleFont; } set { _scaleFont = value; // Redraw SetDimensions(); Invalidate(); } } /// /// Autosize or not for font of graduations /// [Description("Autosize Font of graduations")] [Category("KnobControl")] [DefaultValue(true)] public bool ScaleFontAutoSize { get { return _scaleFontAutoSize; } set { _scaleFontAutoSize = value; // Redraw SetDimensions(); Invalidate(); } } /// /// Start angle to display graduations /// /// The start angle to display graduations. [Description("Set the start angle to display graduations (min 90)")] [Category("KnobControl")] [DefaultValue(135)] public float StartAngle { get { return _startAngle; } set { if (value >= 90 && value < _endAngle) { _startAngle = value; deltaAngle = _endAngle - StartAngle; // Redraw Invalidate(); } } } /// /// End angle to display graduations /// /// The end angle to display graduations. [Description("Set the end angle to display graduations (max 450)")] [Category("KnobControl")] [DefaultValue(405)] public float EndAngle { get { return _endAngle; } set { if (value <= 450 && value > _startAngle) { _endAngle = value; deltaAngle = _endAngle - _startAngle; // Redraw Invalidate(); } } } /// /// Style of pointer: circle or line /// [Description("Set the style of the knob pointer: a circle or a line")] [Category("KnobControl")] public KnobPointerStyles KnobPointerStyle { get { return _knobPointerStyle; } set { _knobPointerStyle = value; // Redraw Invalidate(); } } /// /// Gets or sets the mouse wheel bar partitions. /// /// The mouse wheel bar partitions. /// exception thrown when value isn't greather than zero [Description("Set to how many parts is bar divided when using mouse wheel")] [Category("KnobControl")] [DefaultValue(10)] public int MouseWheelBarPartitions { get { return _mouseWheelBarPartitions; } set { if (value > 0) _mouseWheelBarPartitions = value; else throw new ArgumentOutOfRangeException("MouseWheelBarPartitions has to be greather than zero"); } } /// /// Draw string graduations inside or outside knob circle /// /// [Description("Draw graduation strings inside or outside the knob circle")] [Category("KnobControl")] [DefaultValue(false)] public bool DrawDivInside { get { return _drawDivInside; } set { _drawDivInside = value; // Redraw SetDimensions(); Invalidate(); } } /// /// Color of graduations /// [Description("Color of graduations")] [Category("KnobControl")] public Color ScaleColor { get { return _scaleColor; } set { _scaleColor = value; // Redraw Invalidate(); } } /// /// Color of graduations /// [Description("Color of knob")] [Category("KnobControl")] public Color KnobBackColor { get { return _knobBackColor; } set { _knobBackColor = value; SetDimensions(); // Redraw Invalidate(); } } /// /// How many divisions of maximum? /// [Description("Set the number of intervals between minimum and maximum")] [Category("KnobControl")] public int ScaleDivisions { get { return _scaleDivisions; } set { if (value > 1) { _scaleDivisions = value; // Redraw Invalidate(); } } } /// /// How many subdivisions for each division /// [Description("Set the number of subdivisions between main divisions of graduation.")] [Category("KnobControl")] public int ScaleSubDivisions { get { return _scaleSubDivisions; } set { if (value > 0 && _scaleDivisions > 0 && (_maximum - _minimum) / (value * _scaleDivisions) > 0) { _scaleSubDivisions = value; // Redraw Invalidate(); } } } /// /// Shows Small Scale marking. /// [Description("Show or hide subdivisions of graduations")] [Category("KnobControl")] public bool ShowSmallScale { get { return _showSmallScale; } set { if (value == true) { if (_scaleDivisions > 0 && _scaleSubDivisions > 0 && (_maximum - _minimum) / (_scaleSubDivisions * _scaleDivisions) > 0) { _showSmallScale = value; // Redraw Invalidate(); } } else { _showSmallScale = value; // Redraw Invalidate(); } } } /// /// Shows Large Scale marking /// [Description("Show or hide graduations")] [Category("KnobControl")] public bool ShowLargeScale { get { return _showLargeScale; } set { _showLargeScale = value; // need to redraw SetDimensions(); // Redraw Invalidate(); } } /// /// Minimum Value for knob Control /// [Description("set the minimum value for the knob control")] [Category("KnobControl")] public int Minimum { get { return _minimum; } set { _minimum = value; // Redraw Invalidate(); } } /// /// Maximum value for knob control /// [Description("set the maximum value for the knob control")] [Category("KnobControl")] public int Maximum { get { return _maximum; } set { if (value > _minimum) { _maximum = value; if (_scaleSubDivisions > 0 && _scaleDivisions > 0 && (_maximum - _minimum) / (_scaleSubDivisions * _scaleDivisions) <= 0) { _showSmallScale = false; } SetDimensions(); // Redraw Invalidate(); } } } /// /// value set for large change /// [Description("set the value for the large changes")] [Category("KnobControl")] public int LargeChange { get { return _LargeChange; } set { _LargeChange = value; // Redraw Invalidate(); } } /// /// value set for small change. /// [Description("set the minimum value for the small changes")] [Category("KnobControl")] public int SmallChange { get { return _SmallChange; } set { _SmallChange = value; // Redraw Invalidate(); } } /// /// Current Value of knob control /// [Description("set the current value of the knob control")] [Category("KnobControl")] public int Value { get { return _Value; } set { if (value >= _minimum && value <= _maximum) { _Value = value; // Redraw Invalidate(); // call delegate OnValueChanged(this); } } } /// /// Color of the button /// [Description("set the color of the pointer")] [Category("KnobControl")] public Color PointerColor { get { return _PointerColor; } set { _PointerColor = value; SetDimensions(); // Redraw Invalidate(); } } #endregion properties public KnobControl() { // This call is required by the Windows.Forms Form Designer. DottedPen = new Pen(Utility.GetDarkColor(this.BackColor, 40)) { DashStyle = System.Drawing.Drawing2D.DashStyle.Dash, DashCap = System.Drawing.Drawing2D.DashCap.Flat }; InitializeComponent(); knobFont = new Font(this.Font.FontFamily, this.Font.Size); _scaleFont = new Font(this.Font.FontFamily, this.Font.Size); // Properties initialisation // "start angle" and "end angle" possible values: // 90 = bottom (minimum value for "start angle") // 180 = left // 270 = top // 360 = right // 450 = bottom again (maximum value for "end angle") // So the couple (90, 450) will give an entire circle and the couple (180, 360) will give half a circle. _startAngle = 135; _endAngle = 405; deltaAngle = _endAngle - _startAngle; _minimum = 0; _maximum = 100; _scaleDivisions = 11; _scaleSubDivisions = 4; _mouseWheelBarPartitions = 10; _scaleColor = Color.Black; _knobBackColor = Color.White; SetDimensions(); } #region override /// /// Paint event: draw all /// /// protected override void OnPaint(PaintEventArgs e) { // Set antialias effect on gOffScreen.SmoothingMode = SmoothingMode.AntiAlias; Graphics g = e.Graphics; // Set background color of Image... gOffScreen.Clear(this.BackColor); if (isMiddleArea) { // Fill knob Background to give knob effect.... gOffScreen.FillEllipse(brushKnob, Utility.shrinkRectangle(rKnob, 4)); // Fill middle area Background to give bulge effect.... gOffScreen.FillEllipse(brushMiddleArea, Utility.shrinkRectangle(rMiddleArea, 4)); // Draw border of knob gOffScreen.DrawEllipse(new Pen(Color.DarkGray, 2f), Utility.shrinkRectangle(rKnob, 4)); } else { gOffScreen.FillEllipse(brushKnob, rKnob); gOffScreen.FillEllipse(brushMiddleArea, rMiddleArea); gOffScreen.DrawEllipse(new Pen(this.BackColor), rKnob); } //if control is focused if (this.isFocused) { gOffScreen.DrawEllipse(DottedPen, rKnob); } // DrawPointer DrawPointer(gOffScreen); //--------------------------------------------- // draw small and large scale //--------------------------------------------- DrawDivisions(gOffScreen, rKnob); // Draw image on screen g.DrawImage(OffScreenImage, 0, 0); } protected override void OnPaintBackground(PaintEventArgs e) { // Empty To avoid Flickring due do background Drawing. } /// /// Mouse down event: select control /// /// protected override void OnMouseDown(MouseEventArgs e) { if (Utility.IsPointinRectangle(new Point(e.X, e.Y), rKnob)) { if (isFocused) { // was already selected // Start Rotation of knob only if it was selected before isKnobRotating = true; if (Utility.IsPointInCircle(new Point(e.X, e.Y), new Point(rKnob.Left + (rKnob.Right - rKnob.Left) / 2, rKnob.Top + (rKnob.Bottom - rKnob.Top) / 2), rKnob.Width / 3)) { isMiddleArea = true; Invalidate(); } } else { // Was not selected before => select it Focus(); isFocused = true; isKnobRotating = false; // disallow rotation, must click again isMiddleArea = false; // draw dotted border to show that it is selected Invalidate(); } } } //---------------------------------------------------------- // we need to override IsInputKey method to allow user to // use up, down, right and bottom keys other wise using this // keys will change focus from current object to another // object on the form //---------------------------------------------------------- protected override bool IsInputKey(Keys key) { switch (key) { case Keys.Up: case Keys.Down: case Keys.Right: case Keys.Left: return true; } return base.IsInputKey(key); } /// /// Mouse up event: display new value /// /// protected override void OnMouseUp(MouseEventArgs e) { if (Utility.IsPointinRectangle(new Point(e.X, e.Y), rKnob)) { if (isFocused == true && isKnobRotating == true && !isMiddleArea) { // Change value is allowed only only after 2nd click this.Value = this.GetValueFromPosition(new Point(e.X, e.Y)); } else { // 1st click = only focus isFocused = true; isKnobRotating = true; } if (isMiddleArea) { if (isFocused && Utility.IsPointInCircle(new Point(e.X, e.Y), new Point(rKnob.Left + (rKnob.Right - rKnob.Left) / 2, rKnob.Top + (rKnob.Bottom - rKnob.Top) / 2), rKnob.Width / 3)) { OnMiddleAreaClicked(this); } isMiddleArea = false; Invalidate(); } } this.Cursor = Cursors.Default; } /// /// Mouse move event: drag the pointer to the mouse position /// /// protected override void OnMouseMove(MouseEventArgs e) { //-------------------------------------- // Following Handles Knob Rotating //-------------------------------------- if (e.Button == MouseButtons.Left && this.isKnobRotating == true && !isMiddleArea) { this.Cursor = Cursors.Hand; Point p = new Point(e.X, e.Y); int posVal = this.GetValueFromPosition(p); Value = posVal; } } /// /// Mousewheel: change value /// /// protected override void OnMouseWheel(MouseEventArgs e) { base.OnMouseWheel(e); if (isFocused && isKnobRotating && !isMiddleArea && Utility.IsPointinRectangle(new Point(e.X, e.Y), rKnob)) { // the Delta value is always 120, as explained in MSDN int v = (e.Delta / 120) * (_maximum - _minimum) / _mouseWheelBarPartitions; SetProperValue(Value + v); // Avoid to send MouseWheel event to the parent container ((HandledMouseEventArgs)e).Handled = true; } } /// /// Leave event: disallow knob rotation /// /// protected override void OnLeave(EventArgs e) { // unselect the control (remove dotted border) isFocused = false; isKnobRotating = false; Invalidate(); base.OnLeave(new EventArgs()); } /// /// Key down event: change value /// /// protected override void OnKeyDown(KeyEventArgs e) { if (isFocused) { //-------------------------------------------------------- // Handles knob rotation with up,down,left and right keys //-------------------------------------------------------- if (e.KeyCode == Keys.Up || e.KeyCode == Keys.Right) { if (_Value < _maximum) Value = _Value + 1; this.Refresh(); } else if (e.KeyCode == Keys.Down || e.KeyCode == Keys.Left) { if (_Value > _minimum) Value = _Value - 1; this.Refresh(); } } } /// /// Clean up any resources being used. /// protected override void Dispose(bool disposing) { if (disposing) { if (components != null) { components.Dispose(); } } base.Dispose(disposing); } #endregion #region Component Designer generated code /// /// Required method for Designer support - do not modify /// the contents of this method with the code editor. /// private void InitializeComponent() { // // KnobControl // this.ImeMode = System.Windows.Forms.ImeMode.On; this.Name = "KnobControl"; this.Resize += new System.EventHandler(this.KnobControl_Resize); } #endregion #region Draw /// /// Draw the pointer of the knob (a small button inside the main button) /// /// private void DrawPointer(Graphics Gr) { try { float radius = (float)(rKnob.Width / 2); // Draw a line if (_knobPointerStyle == KnobPointerStyles.line) { int l = (int)radius / 2; int w = l / 4; Point[] pt = GetKnobLine(Gr, l); Gr.DrawLine(new Pen(_PointerColor, w), pt[0], pt[1]); } else { // Draw a circle int w = 0; int h = 0; int l = 0; string strvalmax = _maximum.ToString(); string strvalmin = _minimum.ToString(); string strval = strvalmax.Length > strvalmin.Length ? strvalmax : strvalmin; double val = Convert.ToDouble(strval); String str = String.Format("{0,0:D}", (int)val); float fSize; SizeF strsize; if (_scaleFontAutoSize) { // Use font family = _scaleFont, but size = automatic fSize = (float)(6F * drawRatio); if (fSize < 6) fSize = 6; strsize = Gr.MeasureString(str, new Font(_scaleFont.FontFamily, fSize)); } else { // Use font family = _scaleFont, but size = fixed fSize = _scaleFont.Size; strsize = Gr.MeasureString(str, _scaleFont); } int strw = (int)strsize.Width; int strh = (int)strsize.Height; //w = Math.Max(strw, strh); w = strh; // radius of small circle l = (int)radius - w / 2; h = w; if (isMiddleArea) { Point Arrow = this.GetKnobPosition(l - 7); // Remove 2 pixels to offset the small circle inside the knob // Draw pointer arrow that shows knob position Rectangle rPointer = new Rectangle(Arrow.X - w / 2 + 1, Arrow.Y - w / 2 + 1, w - 2, h - 2); //Utility.DrawInsetCircle(ref Gr, rPointer, new Pen(_PointerColor)); Utility.DrawInsetCircle(ref Gr, rPointer, new Pen(Utility.GetLightColor(_PointerColor, 55))); Gr.FillEllipse(brushKnobPointer, rPointer); } else { Point Arrow = this.GetKnobPosition(l - 5); // Remove 2 pixels to offset the small circle inside the knob // Draw pointer arrow that shows knob position Rectangle rPointer = new Rectangle(Arrow.X - w / 2, Arrow.Y - w / 2, w, h); //Utility.DrawInsetCircle(ref Gr, rPointer, new Pen(_PointerColor)); Utility.DrawInsetCircle(ref Gr, rPointer, new Pen(Utility.GetLightColor(_PointerColor, 55))); Gr.FillEllipse(brushKnobPointer, rPointer); } } } catch (Exception ex) { Console.Write(ex.Message); } } /// /// Draw graduations /// /// /// Knob rectangle /// private bool DrawDivisions(Graphics Gr, RectangleF rc) { if (this == null) return false; float cx = pKnob.X; float cy = pKnob.Y; float w = rc.Width; float h = rc.Height; float tx; float ty; float incr = Utility.GetRadian((_endAngle - _startAngle) / ((_scaleDivisions - 1) * (_scaleSubDivisions + 1))); float currentAngle = Utility.GetRadian(_startAngle); float radius = (float)(rc.Width / 2); float rulerValue = (float)_minimum; Font font; Pen penL = new Pen(_scaleColor, (2 * drawRatio)); Pen penS = new Pen(_scaleColor, (1 * drawRatio)); SolidBrush br = new SolidBrush(_scaleColor); PointF ptStart = new PointF(0, 0); PointF ptEnd = new PointF(0, 0); int n = 0; if (_showLargeScale) { // Size of maxi string string strvalmax = _maximum.ToString(); string strvalmin = _minimum.ToString(); string strval = strvalmax.Length > strvalmin.Length ? strvalmax : strvalmin; double val = Convert.ToDouble(strval); //double val = _maximum; String str = String.Format("{0,0:D}", (int)val); float fSize; SizeF strsize; if (_scaleFontAutoSize) { fSize = (float)(6F * drawRatio); if (fSize < 6) fSize = 6; } else { fSize = _scaleFont.Size; } font = new Font(_scaleFont.FontFamily, fSize); strsize = Gr.MeasureString(str, font); int strw = (int)strsize.Width; int strh = (int)strsize.Height; int wmax = Math.Max(strw, strh); float l = 0; gradLength = 2 * drawRatio; for (; n < _scaleDivisions; n++) { // draw divisions ptStart.X = (float)(cx + (radius) * Math.Cos(currentAngle)); ptStart.Y = (float)(cy + (radius) * Math.Sin(currentAngle)); ptEnd.X = (float)(cx + (radius + gradLength) * Math.Cos(currentAngle)); ptEnd.Y = (float)(cy + (radius + gradLength) * Math.Sin(currentAngle)); Gr.DrawLine(penL, ptStart, ptEnd); //Draw graduation values val = Math.Round(rulerValue); str = String.Format("{0,0:D}", (int)val); // If autosize if (_scaleFontAutoSize) strsize = Gr.MeasureString(str, new Font(_scaleFont.FontFamily, fSize)); else strsize = Gr.MeasureString(str, new Font(_scaleFont.FontFamily, _scaleFont.Size)); if (_drawDivInside) { // graduations values inside the knob l = (int)radius - (wmax / 2) - 2; tx = (float)(cx + l * Math.Cos(currentAngle)); ty = (float)(cy + l * Math.Sin(currentAngle)); } else { // graduation values outside the knob //l = (Width / 2) - (wmax / 2) ; l = radius + gradLength + wmax / 2; tx = (float)(cx + l * Math.Cos(currentAngle)); ty = (float)(cy + l * Math.Sin(currentAngle)); } Gr.DrawString(str, font, br, tx - (float)(strsize.Width * 0.5), ty - (float)(strsize.Height * 0.5)); rulerValue += (float)((_maximum - _minimum) / (_scaleDivisions - 1)); if (n == _scaleDivisions - 1) { break; } // Subdivisions #region SubDivisions if (_scaleDivisions <= 0) currentAngle += incr; else { for (int j = 0; j <= _scaleSubDivisions; j++) { currentAngle += incr; // if user want to display small graduations if (_showSmallScale) { ptStart.X = (float)(cx + radius * Math.Cos(currentAngle)); ptStart.Y = (float)(cy + radius * Math.Sin(currentAngle)); ptEnd.X = (float)(cx + (radius + gradLength / 2) * Math.Cos(currentAngle)); ptEnd.Y = (float)(cy + (radius + gradLength / 2) * Math.Sin(currentAngle)); Gr.DrawLine(penS, ptStart, ptEnd); } } } #endregion } font.Dispose(); } return true; } /// /// Set position of button inside its rectangle to insure that divisions will fit. /// private void SetDimensions() { Font font; // Rectangle float x, y, w, h; x = 0; y = 0; w = h = Width; // Calculate ratio drawRatio = w / 150; if (drawRatio == 0.0) drawRatio = 1; if (_showLargeScale) { Graphics Gr = this.CreateGraphics(); string strvalmax = _maximum.ToString(); string strvalmin = _minimum.ToString(); string strval = strvalmax.Length > strvalmin.Length ? strvalmax : strvalmin; double val = Convert.ToDouble(strval); //double val = _maximum; String str = String.Format("{0,0:D}", (int)val); float fSize = _scaleFont.Size; if (_scaleFontAutoSize) { fSize = (float)(6F * drawRatio); if (fSize < 6) fSize = 6; font = new Font(_scaleFont.FontFamily, fSize); } else { fSize = _scaleFont.Size; font = new Font(_scaleFont.FontFamily, _scaleFont.Size); } SizeF strsize = Gr.MeasureString(str, font); // Graduations outside gradLength = 4 * drawRatio; if (_drawDivInside) { // Graduations inside : remove only 2*8 pixels //x = y = 8; x = y = gradLength; w = Width - 2 * x; } else { // remove 2 * size of text and length of graduation //gradLength = 4 * drawRatio; int strw = (int)strsize.Width; int strh = (int)strsize.Height; int max = Math.Max(strw, strh); x = max; y = max; w = (int)(Width - 2 * max - gradLength); } if (w <= 0) w = 1; h = w; // Rectangle of the rounded knob this.rKnob = new Rectangle((int)x, (int)y, (int)w, (int)h); // Rectangle of the middle area of the knob this.rMiddleArea = new Rectangle((int)(x + w / 3), (int)(y + h / 3), (int)(w / 3), (int)(h / 3)); Gr.Dispose(); } else { this.rKnob = new Rectangle(0, 0, Width, Height); this.rMiddleArea = new Rectangle((int)(w / 3), (int)(h / 3), (int)(w / 3), (int)(h / 3)); } // Center of knob this.pKnob = new Point(rKnob.X + rKnob.Width / 2, rKnob.Y + rKnob.Height / 2); // create offscreen image this.OffScreenImage = new Bitmap(this.Width, this.Height); // create offscreen graphics this.gOffScreen = Graphics.FromImage(OffScreenImage); // Depends on retangle dimensions // create LinearGradientBrush for creating knob brushKnob = new LinearGradientBrush( rKnob, Utility.GetLightColor(_knobBackColor, 55), Utility.GetDarkColor(_knobBackColor, 55), LinearGradientMode.ForwardDiagonal); // create LinearGradientBrush for creating knob brushMiddleArea = new LinearGradientBrush( rMiddleArea, Utility.GetDarkColor(_knobBackColor, 55), Utility.GetLightColor(_knobBackColor, 55), LinearGradientMode.ForwardDiagonal); // create LinearGradientBrush for knobPointer brushKnobPointer = new LinearGradientBrush( rKnob, Utility.GetLightColor(_PointerColor, 55), Utility.GetDarkColor(_PointerColor, 55), LinearGradientMode.ForwardDiagonal); } #endregion #region resize /// /// Resize event /// /// /// private void KnobControl_Resize(object sender, System.EventArgs e) { // Control remains square Height = Width; SetDimensions(); Invalidate(); } #endregion #region private functions /// /// Sets the trackbar value so that it wont exceed allowed range. /// /// The value. private void SetProperValue(int val) { if (val < _minimum) Value = _minimum; else if (val > _maximum) Value = _maximum; else Value = val; } /// /// gets knob position that is to be drawn on control minus a small amount in order that the knob position stay inside the circle. /// /// Point that describes current knob position private Point GetKnobPosition(int l) { float cx = pKnob.X; float cy = pKnob.Y; // FAB: 21/08/18 float degree = deltaAngle * (this.Value - _minimum) / (_maximum - _minimum); degree = Utility.GetRadian(degree + _startAngle); Point Pos = new Point(0, 0) { X = (int)(cx + l * Math.Cos(degree)), Y = (int)(cy + l * Math.Sin(degree)) }; return Pos; } /// /// return 2 points of a line starting from the center of the knob to the periphery /// /// /// private Point[] GetKnobLine(Graphics Gr, int l) { Point[] pret = new Point[2]; float cx = pKnob.X; float cy = pKnob.Y; float radius = (float)(rKnob.Width / 2); // FAB: 21/08/18 float degree = deltaAngle * (this.Value - _minimum) / (_maximum - _minimum); degree = Utility.GetRadian(degree + _startAngle); double val = _maximum; String str = String.Format("{0,0:D}", (int)val); float fSize; SizeF strsize; if (!_scaleFontAutoSize) { fSize = _scaleFont.Size; strsize = Gr.MeasureString(str, _scaleFont); } else { fSize = (float)(6F * drawRatio); if (fSize < 6) fSize = 6; knobFont = new Font(_scaleFont.FontFamily, fSize); strsize = Gr.MeasureString(str, knobFont); } int strw = (int)strsize.Width; int strh = (int)strsize.Height; int w = Math.Max(strw, strh); Point Pos = new Point(0, 0); if (_drawDivInside) { // Center (from) Pos.X = (int)(cx + (radius / 10) * Math.Cos(degree)); Pos.Y = (int)(cy + (radius / 10) * Math.Sin(degree)); pret[0] = new Point(Pos.X, Pos.Y); // External (to) Pos.X = (int)(cx + (radius - w) * Math.Cos(degree)); Pos.Y = (int)(cy + (radius - w) * Math.Sin(degree)); pret[1] = new Point(Pos.X, Pos.Y); } else { // Internal (from) Pos.X = (int)(cx + (radius - drawRatio * 10 - l) * Math.Cos(degree)); Pos.Y = (int)(cy + (radius - drawRatio * 10 - l) * Math.Sin(degree)); pret[0] = new Point(Pos.X, Pos.Y); // External (to) Pos.X = (int)(cx + (radius - 4) * Math.Cos(degree)); Pos.Y = (int)(cy + (radius - 4) * Math.Sin(degree)); pret[1] = new Point(Pos.X, Pos.Y); } return pret; } /// /// converts geometrical position into value.. /// /// Point that is to be converted /// Value derived from position private int GetValueFromPosition(Point p) { float degree = 0; int v = 0; if (p.X <= pKnob.X) { degree = (float)(pKnob.Y - p.Y) / (float)(pKnob.X - p.X); degree = (float)Math.Atan(degree); degree = (degree) * (float)(180 / Math.PI) + (180 - _startAngle); } else if (p.X > pKnob.X) { degree = (float)(p.Y - pKnob.Y) / (float)(p.X - pKnob.X); degree = (float)Math.Atan(degree); degree = (degree) * (float)(180 / Math.PI) + 360 - _startAngle; } // round to the nearest value (when you click just before or after a graduation!) // FAB: 25/08/18 v = _minimum + (int)Math.Round(degree * (_maximum - _minimum) / deltaAngle); if (v > _maximum) v = _maximum; if (v < _minimum) v = _minimum; return v; } #endregion } }