SunnyUI/SunnyUI/Controls/UIKnobControl.cs
2023-11-06 14:08:09 +08:00

1474 lines
47 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/******************************************************************************
* SunnyUI 开源控件库、工具类库、扩展类库、多页面开发框架。
* CopyRight (C) 2012-2023 ShenYongHua(沈永华).
* QQ群56829229 QQ17612584 EMailSunnyUI@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.
* 如果您使用此代码,请保留此说明。
******************************************************************************
* 文件名称: UIKnob.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>
/// Summary description for KnobControl.
/// </summary>
public class UIKnob : UserControl, IStyleInterface, IZoomScale
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.Container components = null;
/// <summary>
/// Styles of pointer button
/// </summary>
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 *)
/// <summary>
/// Font of graduations
/// </summary>
[Description("Font of graduations")]
[Category("KnobControl")]
public Font ScaleFont
{
get { return _scaleFont; }
set
{
_scaleFont = value;
// Redraw
SetDimensions();
Invalidate();
}
}
/// <summary>
/// Autosize or not for font of graduations
/// </summary>
[Description("Autosize Font of graduations")]
[Category("KnobControl")]
[DefaultValue(true)]
public bool ScaleFontAutoSize
{
get { return _scaleFontAutoSize; }
set
{
_scaleFontAutoSize = value;
// Redraw
SetDimensions();
Invalidate();
}
}
/// <summary>
/// Start angle to display graduations
/// </summary>
/// <value>The start angle to display graduations.</value>
[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();
}
}
}
/// <summary>
/// End angle to display graduations
/// </summary>
/// <value>The end angle to display graduations.</value>
[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();
}
}
}
/// <summary>
/// Style of pointer: circle or line
/// </summary>
[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();
}
}
/// <summary>
/// Gets or sets the mouse wheel bar partitions.
/// </summary>
/// <value>The mouse wheel bar partitions.</value>
/// <exception cref="T:System.ArgumentOutOfRangeException">exception thrown when value isn't greather than zero</exception>
[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");
}
}
/// <summary>
/// Draw string graduations inside or outside knob circle
/// </summary>
///
[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();
}
}
/// <summary>
/// Color of graduations
/// </summary>
[Description("Color of graduations")]
[Category("KnobControl")]
public Color ScaleColor
{
get { return _scaleColor; }
set
{
_scaleColor = value;
// Redraw
Invalidate();
}
}
/// <summary>
/// Color of graduations
/// </summary>
[Description("Color of knob")]
[Category("KnobControl")]
public Color KnobBackColor
{
get { return _knobBackColor; }
set
{
_knobBackColor = value;
SetDimensions();
// Redraw
Invalidate();
}
}
/// <summary>
/// How many divisions of maximum?
/// </summary>
[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();
}
}
}
/// <summary>
/// How many subdivisions for each division
/// </summary>
[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();
}
}
}
/// <summary>
/// Shows Small Scale marking.
/// </summary>
[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();
}
}
}
/// <summary>
/// Shows Large Scale marking
/// </summary>
[Description("Show or hide graduations")]
[Category("KnobControl")]
public bool ShowLargeScale
{
get { return _showLargeScale; }
set
{
_showLargeScale = value;
// need to redraw
SetDimensions();
// Redraw
Invalidate();
}
}
/// <summary>
/// Minimum Value for knob Control
/// </summary>
[Description("set the minimum value for the knob control")]
[Category("KnobControl")]
public int Minimum
{
get { return _minimum; }
set
{
_minimum = value;
// Redraw
Invalidate();
}
}
/// <summary>
/// Maximum value for knob control
/// </summary>
[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();
}
}
}
/// <summary>
/// value set for large change
/// </summary>
[Description("set the value for the large changes")]
[Category("KnobControl")]
public int LargeChange
{
get { return _LargeChange; }
set
{
_LargeChange = value;
// Redraw
Invalidate();
}
}
/// <summary>
/// value set for small change.
/// </summary>
[Description("set the minimum value for the small changes")]
[Category("KnobControl")]
public int SmallChange
{
get { return _SmallChange; }
set
{
_SmallChange = value;
// Redraw
Invalidate();
}
}
/// <summary>
/// Current Value of knob control
/// </summary>
[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);
}
}
}
/// <summary>
/// Color of the button
/// </summary>
[Description("set the color of the pointer")]
[Category("KnobControl")]
public Color PointerColor
{
get { return _PointerColor; }
set
{
_PointerColor = value;
SetDimensions();
// Redraw
Invalidate();
}
}
#endregion properties
~UIKnob()
{
DottedPen?.Dispose();
knobFont?.Dispose();
brushKnob?.Dispose();
brushKnobPointer.Dispose();
gOffScreen?.Dispose();
OffScreenImage?.Dispose();
_scaleFont?.Dispose();
}
public UIKnob()
{
// 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
/// <summary>
/// Paint event: draw all
/// </summary>
/// <param name="e"></param>
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(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.
}
/// <summary>
/// Mouse down event: select control
/// </summary>
/// <param name="e"></param>
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);
}
/// <summary>
/// Mouse up event: display new value
/// </summary>
/// <param name="e"></param>
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;
}
/// <summary>
/// Mouse move event: drag the pointer to the mouse position
/// </summary>
/// <param name="e"></param>
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;
}
}
/// <summary>
/// Mousewheel: change value
/// </summary>
/// <param name="e"></param>
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;
}
}
/// <summary>
/// Leave event: disallow knob rotation
/// </summary>
/// <param name="e"></param>
protected override void OnLeave(EventArgs e)
{
// unselect the control (remove dotted border)
isFocused = false;
isKnobRotating = false;
Invalidate();
base.OnLeave(new EventArgs());
}
/// <summary>
/// Key down event: change value
/// </summary>
/// <param name="e"></param>
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();
}
}
}
/// <summary>
/// Clean up any resources being used.
/// </summary>
protected override void Dispose(bool disposing)
{
if (disposing)
{
if (components != null)
{
components.Dispose();
}
}
base.Dispose(disposing);
}
#endregion
#region Component Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
//
// KnobControl
//
this.Name = "KnobControl";
this.Resize += new System.EventHandler(this.KnobControl_Resize);
}
#endregion
#region Draw
/// <summary>
/// Draw the pointer of the knob (a small button inside the main button)
/// </summary>
/// <param name="Gr"></param>
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);
using Pen pen = new Pen(_PointerColor, w);
Gr.DrawLine(pen, 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 = TextRenderer.MeasureText(str, new Font(_scaleFont.FontFamily, fSize));
}
else
{
// Use font family = _scaleFont, but size = fixed
fSize = _scaleFont.Size;
strsize = TextRenderer.MeasureText(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);
using Pen pen = new Pen(GetLightColor(_PointerColor, 55));
DrawInsetCircle(ref Gr, rPointer, pen);
Gr.FillEllipse(brushKnobPointer, rPointer);
}
}
catch (Exception ex)
{
Console.Write(ex.Message);
}
}
/// <summary>
/// Draw graduations
/// </summary>
/// <param name="Gr"></param>
/// <param name="rc">Knob rectangle</param>
/// <returns></returns>
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;
using Pen penL = new Pen(_scaleColor, (2 * drawRatio));
using Pen penS = new Pen(_scaleColor, (1 * drawRatio));
using 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;
}
using var font = new Font(_scaleFont.FontFamily, fSize);
strsize = TextRenderer.MeasureText(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
using Font tmpFont = _scaleFontAutoSize ? new Font(_scaleFont.FontFamily, fSize) : new Font(_scaleFont.FontFamily, _scaleFont.Size);
strsize = TextRenderer.MeasureText(str, tmpFont);
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
}
}
return true;
}
/// <summary>
/// Set position of button inside its rectangle to insure that divisions will fit.
/// </summary>
private void SetDimensions()
{
// 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)
{
using 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;
Font font;
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 = TextRenderer.MeasureText(str, font);
font.Dispose();
// 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);
}
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
/// <summary>
/// Resize event
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void KnobControl_Resize(object sender, System.EventArgs e)
{
// Control remains square
Height = Width;
SetDimensions();
Invalidate();
}
#endregion
#region private functions
/// <summary>
/// Sets the trackbar value so that it wont exceed allowed range.
/// </summary>
/// <param name="val">The value.</param>
private void SetProperValue(int val)
{
if (val < _minimum) Value = _minimum;
else if (val > _maximum) Value = _maximum;
else Value = val;
}
/// <summary>
/// gets knob position that is to be drawn on control minus a small amount in order that the knob position stay inside the circle.
/// </summary>
/// <returns>Point that describes current knob position</returns>
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;
}
/// <summary>
/// return 2 points of a line starting from the center of the knob to the periphery
/// </summary>
/// <param name="l"></param>
/// <returns></returns>
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 = TextRenderer.MeasureText(str, _scaleFont);
}
else
{
fSize = (float)(6F * drawRatio);
if (fSize < 6)
fSize = 6;
knobFont = new Font(_scaleFont.FontFamily, fSize);
strsize = TextRenderer.MeasureText(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;
}
/// <summary>
/// converts geometrical position into value..
/// </summary>
/// <param name="p">Point that is to be converted</param>
/// <returns>Value derived from position</returns>
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
private float DefaultFontSize = -1;
public void SetDPIScale()
{
if (!UIDPIScale.NeedSetDPIFont()) return;
if (DefaultFontSize < 0) DefaultFontSize = this.Font.Size;
this.SetDPIScaleFont(DefaultFontSize);
}
public string Version
{
get;
}
/// <summary>
/// Tag字符串
/// </summary>
[DefaultValue(null)]
[Description("获取或设置包含有关控件的数据的对象字符串"), Category("SunnyUI")]
public string TagString
{
get; set;
}
/// <summary>
/// 自定义主题风格
/// </summary>
[DefaultValue(false), Browsable(false)]
[Description("获取或设置可以自定义主题风格"), Category("SunnyUI")]
public bool StyleCustomMode { get; set; }
protected UIStyle _style = UIStyle.Blue;
/// <summary>
/// 主题样式
/// </summary>
[DefaultValue(UIStyle.Blue), Description("主题样式"), Category("SunnyUI")]
public UIStyle Style
{
get => _style;
set => SetStyle(value);
}
/// <summary>
/// 设置主题样式
/// </summary>
/// <param name="style">主题样式</param>
private void SetStyle(UIStyle style)
{
if (!style.IsCustom())
{
SetStyleColor(style.Colors());
Invalidate();
}
_style = style == UIStyle.Inherited ? UIStyle.Inherited : UIStyle.Custom;
}
public void SetInheritedStyle(UIStyle style)
{
SetStyle(style);
_style = UIStyle.Inherited;
}
public virtual void SetStyleColor(UIBaseStyle uiColor)
{
}
/// <summary>
/// 禁止控件跟随窗体缩放
/// </summary>
[DefaultValue(false), Category("SunnyUI"), Description("禁止控件跟随窗体缩放")]
public bool ZoomScaleDisabled { get; set; }
/// <summary>
/// 控件缩放前在其容器里的位置
/// </summary>
[Browsable(false), DefaultValue(typeof(Rectangle), "0, 0, 0, 0")]
public Rectangle ZoomScaleRect { get; set; }
/// <summary>
/// 设置控件缩放比例
/// </summary>
/// <param name="scale">缩放比例</param>
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;
}
/// <summary>
/// Method which checks is particular point is in rectangle
/// </summary>
/// <param name="p">Point to be Chaecked</param>
/// <param name="r">Rectangle</param>
/// <returns>true is Point is in rectangle, else false</returns>
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)
{
using Pen p1 = new Pen(GetDarkColor(p.Color, 50));
using 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
}
}