From cc8dc64b0aa2d5f7f1c789a4643fcd03c08f7b60 Mon Sep 17 00:00:00 2001 From: Sunny Date: Sun, 31 Jul 2022 22:31:46 +0800 Subject: [PATCH] =?UTF-8?q?+=20UIKnobControl:=20=E6=96=B0=E5=A2=9E?= =?UTF-8?q?=E6=8E=A7=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- SunnyUI/Controls/UIKnobControl.cs | 1475 +++++++++++++++++++++++++++++ 1 file changed, 1475 insertions(+) create mode 100644 SunnyUI/Controls/UIKnobControl.cs diff --git a/SunnyUI/Controls/UIKnobControl.cs b/SunnyUI/Controls/UIKnobControl.cs new file mode 100644 index 00000000..b64152cf --- /dev/null +++ b/SunnyUI/Controls/UIKnobControl.cs @@ -0,0 +1,1475 @@ +/****************************************************************************** + * SunnyUI 开源控件库、工具类库、扩展类库、多页面开发框架。 + * CopyRight (C) 2012-2022 ShenYongHua(沈永华). + * QQ群:56829229 QQ:17612584 EMail:SunnyUI@QQ.Com + * + * Blog: https://www.cnblogs.com/yhuse + * Gitee: https://gitee.com/yhuse/SunnyUI + * GitHub: https://github.com/yhuse/SunnyUI + * + * SunnyUI can be used for free under the GPL-3.0 license. + * If you use this code, please keep this note. + * 如果您使用此代码,请保留此说明。 + ****************************************************************************** + * 文件名称: UIKnobControl.cs + * 文件说明: C# Knob Control using Windows Forms + * 文件作者: Fabrice Lacharme + * 开源协议: CPOL + * 引用地址: https://www.codeproject.com/Tips/1187460/Csharp-Knob-Control-using-Windows-Forms +******************************************************************************/ + +#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 Sunny.UI +{ + /* 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 + */ + + /// + /// Summary description for KnobControl. + /// + public class UIKnobControl : UserControl, IStyleInterface, IZoomScale + { + /// + /// Required designer variable. + /// + private System.ComponentModel.Container components = null; + + /// + /// Styles of pointer button + /// + public enum KnobPointerStyles + { + circle, + line, + } + + // A delegate type for hooking up ValueChanged notifications. + public delegate void ValueChangedEventHandler(object Sender); + + #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 Rectangle rKnob; + private Point pKnob; + private Pen DottedPen; + + Brush brushKnob; + 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); + } + + #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 UIKnobControl() + { + // This call is required by the Windows.Forms Form Designer. + DottedPen = new Pen(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(); + + Version = UIGlobal.Version; + AutoScaleMode = AutoScaleMode.None; + } + + #region override + + /// + /// Paint event: draw all + /// + /// + protected override void OnPaint(PaintEventArgs e) + { + Graphics g = e.Graphics; + gOffScreen.SetHighQuality(); + g.SetHighQuality(); + + // Set background color of Image... + gOffScreen.Clear(this.BackColor); + // Fill knob Background to give knob effect.... + gOffScreen.FillEllipse(brushKnob, rKnob); + // Set antialias effect on + gOffScreen.SmoothingMode = SmoothingMode.AntiAlias; + // Draw border of knob + 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 (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; + } + else + { + // Was not selected before => select it + Focus(); + isFocused = true; + isKnobRotating = false; // disallow rotation, must click again + // 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 (IsPointinRectangle(new Point(e.X, e.Y), rKnob)) + { + if (isFocused == true && isKnobRotating == true) + { + // 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; + } + } + + 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) + { + 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 && 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.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); + // radius of small circle + l = (int)radius - w / 2; + + h = w; + + Point Arrow = this.GetKnobPosition(l - 2); // 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)); + DrawInsetCircle(ref Gr, rPointer, new Pen(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 = GetRadian((_endAngle - _startAngle) / ((_scaleDivisions - 1) * (_scaleSubDivisions + 1))); + float currentAngle = 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); + + Gr.Dispose(); + } + else + { + this.rKnob = new Rectangle(0, 0, Width, Height); + } + + // 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, GetLightColor(_knobBackColor, 55), GetDarkColor(_knobBackColor, 55), LinearGradientMode.ForwardDiagonal); + + // create LinearGradientBrush for knobPointer + brushKnobPointer = new LinearGradientBrush( + rKnob, GetLightColor(_PointerColor, 55), 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 = 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 = 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 + + [Browsable(false), DefaultValue(false)] + public bool IsScaled { get; set; } + + public virtual void SetDPIScale() + { + if (DesignMode) return; + if (!IsScaled) + { + this.SetDPIScaleFont(); + IsScaled = true; + } + } + + public string Version + { + get; + } + + /// + /// Tag字符串 + /// + [DefaultValue(null)] + [Description("获取或设置包含有关控件的数据的对象字符串"), Category("SunnyUI")] + public string TagString + { + get; set; + } + + /// + /// 自定义主题风格 + /// + [DefaultValue(false)] + [Description("获取或设置可以自定义主题风格"), Category("SunnyUI")] + public bool StyleCustomMode + { + get; set; + } + + protected UIStyle _style = UIStyle.Blue; + + /// + /// 主题样式 + /// + [DefaultValue(UIStyle.Blue), Description("主题样式"), Category("SunnyUI")] + public UIStyle Style + { + get => _style; + set => SetStyle(value); + } + + public void SetStyle(UIStyle style) + { + this.SuspendLayout(); + UIStyleHelper.SetChildUIStyle(this, style); + + if (!style.IsCustom()) + { + SetStyleColor(style.Colors()); + Invalidate(); + } + + _style = style; + this.ResumeLayout(); + } + + public virtual void SetStyleColor(UIBaseStyle uiColor) + { + + } + + /// + /// 禁止控件跟随窗体缩放 + /// + [DefaultValue(false), Category("SunnyUI"), Description("禁止控件跟随窗体缩放")] + public bool ZoomScaleDisabled { get; set; } + + /// + /// 控件缩放前在其容器里的位置 + /// + [Browsable(false)] + public Rectangle ZoomScaleRect { get; set; } + + /// + /// 设置控件缩放比例 + /// + /// 缩放比例 + public virtual void SetZoomScale(float scale) + { + + } + + #region Utility + + public static float GetRadian(float val) + { + return (float)(val * Math.PI / 180); + } + + public static Color GetDarkColor(Color c, byte d) + { + byte r = 0; + byte g = 0; + byte b = 0; + + if (c.R > d) r = (byte)(c.R - d); + if (c.G > d) g = (byte)(c.G - d); + if (c.B > d) b = (byte)(c.B - d); + + Color c1 = Color.FromArgb(r, g, b); + return c1; + } + + public static Color GetLightColor(Color c, byte d) + { + byte r = 255; + byte g = 255; + byte b = 255; + + if (c.R + d < 255) r = (byte)(c.R + d); + if (c.G + d < 255) g = (byte)(c.G + d); + if (c.B + d < 255) b = (byte)(c.B + d); + + Color c2 = Color.FromArgb(r, g, b); + return c2; + } + + /// + /// Method which checks is particular point is in rectangle + /// + /// Point to be Chaecked + /// Rectangle + /// true is Point is in rectangle, else false + public static bool IsPointinRectangle(Point p, Rectangle r) + { + bool flag = false; + if (p.X > r.X && p.X < r.X + r.Width && p.Y > r.Y && p.Y < r.Y + r.Height) + { + flag = true; + } + + return flag; + + } + public static void DrawInsetCircle(ref Graphics g, Rectangle r, Pen p) + { + Pen p1 = new Pen(GetDarkColor(p.Color, 50)); + Pen p2 = new Pen(GetLightColor(p.Color, 50)); + for (int i = 0; i < p.Width; i++) + { + Rectangle r1 = new Rectangle(r.X + i, r.Y + i, r.Width - i * 2, r.Height - i * 2); + g.DrawArc(p2, r1, -45, 180); + g.DrawArc(p1, r1, 135, 180); + } + } + + #endregion + } +} \ No newline at end of file