using System; using System.ComponentModel; using System.Drawing; using System.Drawing.Drawing2D; using System.Windows.Forms; namespace AquaControls { /// /// Aqua Gauge Control - A Windows User Control. /// Author : Ambalavanar Thirugnanam /// Date : 24th August 2007 /// email : ambalavanar.thiru@gmail.com /// This is control is for free. You can use for any commercial or non-commercial purposes. /// [Please do no remove this header when using this control in your application.] /// public partial class AquaGauge : UserControl { #region Constructors, Destructors and Finalizers public AquaGauge() { InitializeComponent(); x = 5; y = 5; width = Width - 10; height = Height - 10; noOfDivisions = 10; noOfSubDivisions = 3; SetStyle(ControlStyles.SupportsTransparentBackColor, true); SetStyle(ControlStyles.ResizeRedraw, true); SetStyle(ControlStyles.AllPaintingInWmPaint, true); SetStyle(ControlStyles.UserPaint, true); SetStyle(ControlStyles.OptimizedDoubleBuffer, true); BackColor = Color.Transparent; Resize += AquaGauge_Resize; requiresRedraw = true; } #endregion #region Private Attributes private float minValue; private float maxValue; private float threshold; private float currentValue; private float recommendedValue; private int noOfDivisions; private int noOfSubDivisions; private string dialText; private Color dialColor = Color.Lavender; private float glossinessAlpha = 25; private int oldWidth, oldHeight; private readonly int x; private readonly int y; private int width, height; private readonly float fromAngle = 135F; private readonly float toAngle = 405F; private bool enableTransparentBackground; private bool requiresRedraw; private Image backgroundImg; private Rectangle rectImg; #endregion #region Public Properties /// /// Mininum value on the scale /// [DefaultValue(0)] [Description("Mininum value on the scale")] public float MinValue { get => minValue; set { if (value < maxValue) { minValue = value; if (currentValue < minValue) { currentValue = minValue; } if (recommendedValue < minValue) { recommendedValue = minValue; } requiresRedraw = true; Invalidate(); } } } /// /// Maximum value on the scale /// [DefaultValue(100)] [Description("Maximum value on the scale")] public float MaxValue { get => maxValue; set { if (value > minValue) { maxValue = value; if (currentValue > maxValue) { currentValue = maxValue; } if (recommendedValue > maxValue) { recommendedValue = maxValue; } requiresRedraw = true; Invalidate(); } } } /// /// Gets or Sets the Threshold area from the Recommended Value. (1-99%) /// [DefaultValue(25)] [Description("Gets or Sets the Threshold area from the Recommended Value. (1-99%)")] public float ThresholdPercent { get => threshold; set { if (value > 0 && value < 100) { threshold = value; requiresRedraw = true; Invalidate(); } } } /// /// Threshold value from which green area will be marked. /// [DefaultValue(25)] [Description("Threshold value from which green area will be marked.")] public float RecommendedValue { get => recommendedValue; set { if (value > minValue && value < maxValue) { recommendedValue = value; requiresRedraw = true; Invalidate(); } } } /// /// Value where the pointer will point to. /// [DefaultValue(0)] [Description("Value where the pointer will point to.")] public float Value { get => currentValue; set { if (value >= minValue && value <= maxValue) { currentValue = value; Refresh(); } } } /// /// Background color of the dial /// [Description("Background color of the dial")] public Color DialColor { get => dialColor; set { dialColor = value; requiresRedraw = true; Invalidate(); } } /// /// Glossiness strength. Range: 0-100 /// [DefaultValue(72)] [Description("Glossiness strength. Range: 0-100")] public float Glossiness { get => glossinessAlpha * 100 / 220; set { var val = value; if (val > 100) { value = 100; } if (val < 0) { value = 0; } glossinessAlpha = value * 220 / 100; Refresh(); } } /// /// Get or Sets the number of Divisions in the dial scale. /// [DefaultValue(10)] [Description("Get or Sets the number of Divisions in the dial scale.")] public int NoOfDivisions { get => noOfDivisions; set { if (value > 1 && value < 25) { noOfDivisions = value; requiresRedraw = true; Invalidate(); } } } /// /// Gets or Sets the number of Sub Divisions in the scale per Division. /// [DefaultValue(3)] [Description("Gets or Sets the number of Sub Divisions in the scale per Division.")] public int NoOfSubDivisions { get => noOfSubDivisions; set { if (value > 0 && value <= 10) { noOfSubDivisions = value; requiresRedraw = true; Invalidate(); } } } /// /// Gets or Sets the Text to be displayed in the dial /// [Description("Gets or Sets the Text to be displayed in the dial")] public string DialText { get => dialText; set { dialText = value; requiresRedraw = true; Invalidate(); } } /// /// Enables or Disables Transparent Background color. /// Note: Enabling this will reduce the performance and may make the control flicker. /// [DefaultValue(false)] [Description( "Enables or Disables Transparent Background color. Note: Enabling this will reduce the performance and may make the control flicker.")] public bool EnableTransparentBackground { get => enableTransparentBackground; set { enableTransparentBackground = value; SetStyle(ControlStyles.OptimizedDoubleBuffer, !enableTransparentBackground); requiresRedraw = true; Refresh(); } } #endregion #region Overriden Control methods /// /// Draws the pointer. /// /// protected override void OnPaint(PaintEventArgs e) { e.Graphics.SmoothingMode = SmoothingMode.AntiAlias; width = Width - x * 2; height = Height - y * 2; DrawPointer(e.Graphics, width / 2 + x, height / 2 + y); } /// /// Draws the dial background. /// /// protected override void OnPaintBackground(PaintEventArgs e) { if (!enableTransparentBackground) { base.OnPaintBackground(e); } e.Graphics.SmoothingMode = SmoothingMode.HighQuality; e.Graphics.FillRectangle(new SolidBrush(Color.Transparent), new Rectangle(0, 0, Width, Height)); if (backgroundImg == null || requiresRedraw) { backgroundImg = new Bitmap(Width, Height); var g = Graphics.FromImage(backgroundImg); g.SmoothingMode = SmoothingMode.HighQuality; width = Width - x * 2; height = Height - y * 2; rectImg = new Rectangle(x, y, width, height); //Draw background color Brush backGroundBrush = new SolidBrush(Color.FromArgb(120, dialColor)); if (enableTransparentBackground && Parent != null) { float gg = width / 60; //g.FillEllipse(new SolidBrush(this.Parent.BackColor), -gg, -gg, this.Width+gg*2, this.Height+gg*2); } g.FillEllipse(backGroundBrush, x, y, width, height); //Draw Rim var outlineBrush = new SolidBrush(Color.FromArgb(100, Color.SlateGray)); var outline = new Pen(outlineBrush, (float) (width * .03)); g.DrawEllipse(outline, rectImg); var darkRim = new Pen(Color.SlateGray); g.DrawEllipse(darkRim, x, y, width, height); //Draw Callibration DrawCalibration(g, rectImg, width / 2 + x, height / 2 + y); //Draw Colored Rim var colorPen = new Pen(Color.FromArgb(190, Color.Gainsboro), Width / 40); var blackPen = new Pen(Color.FromArgb(250, Color.Black), Width / 200); var gap = (int) (Width * 0.03F); var rectg = new Rectangle(rectImg.X + gap, rectImg.Y + gap, rectImg.Width - gap * 2, rectImg.Height - gap * 2); g.DrawArc(colorPen, rectg, 135, 270); //Draw Threshold colorPen = new Pen(Color.FromArgb(200, Color.LawnGreen), Width / 50); rectg = new Rectangle(rectImg.X + gap, rectImg.Y + gap, rectImg.Width - gap * 2, rectImg.Height - gap * 2); var val = MaxValue - MinValue; val = 100 * (recommendedValue - MinValue) / val; val = (toAngle - fromAngle) * val / 100; val += fromAngle; var stAngle = val - 270 * threshold / 200; if (stAngle <= 135) { stAngle = 135; } var sweepAngle = 270 * threshold / 100; if (stAngle + sweepAngle > 405) { sweepAngle = 405 - stAngle; } g.DrawArc(colorPen, rectg, stAngle, sweepAngle); //Draw Digital Value /* RectangleF digiRect = new RectangleF((float)this.Width / 2F - (float)this.width / 5F, (float)this.height / 1.2F, (float)this.width / 2.5F, (float)this.Height / 9F); RectangleF digiFRect = new RectangleF(this.Width / 2 - this.width / 7, (int)(this.height / 1.18), this.width / 4, this.Height / 12); g.FillRectangle(new SolidBrush(Color.FromArgb(30, Color.Gray)), digiRect); DisplayNumber(g, this.currentValue, digiFRect); */ var textSize = g.MeasureString(dialText, Font); var digiFRectText = new RectangleF(Width / 2 - textSize.Width / 2, (int) (height / 1.5), textSize.Width, textSize.Height); g.DrawString(dialText, Font, new SolidBrush(ForeColor), digiFRectText); requiresRedraw = false; } e.Graphics.DrawImage(backgroundImg, rectImg); } protected override CreateParams CreateParams { get { var cp = base.CreateParams; cp.ExStyle |= 0x20; return cp; } } #endregion #region Private methods /// /// Draws the Pointer. /// /// /// /// private void DrawPointer(Graphics gr, int cx, int cy) { var radius = Width / 2 - Width * .12F; var val = MaxValue - MinValue; Image img = new Bitmap(Width, Height); var g = Graphics.FromImage(img); g.SmoothingMode = SmoothingMode.AntiAlias; val = 100 * (currentValue - MinValue) / val; val = (toAngle - fromAngle) * val / 100; val += fromAngle; var angle = GetRadian(val); var gradientAngle = angle; var pts = new PointF[5]; pts[0].X = (float) (cx + radius * Math.Cos(angle)); pts[0].Y = (float) (cy + radius * Math.Sin(angle)); pts[4].X = (float) (cx + radius * Math.Cos(angle - 0.02)); pts[4].Y = (float) (cy + radius * Math.Sin(angle - 0.02)); angle = GetRadian(val + 20); pts[1].X = (float) (cx + Width * .09F * Math.Cos(angle)); pts[1].Y = (float) (cy + Width * .09F * Math.Sin(angle)); pts[2].X = cx; pts[2].Y = cy; angle = GetRadian(val - 20); pts[3].X = (float) (cx + Width * .09F * Math.Cos(angle)); pts[3].Y = (float) (cy + Width * .09F * Math.Sin(angle)); Brush pointer = new SolidBrush(Color.Black); g.FillPolygon(pointer, pts); var shinePts = new PointF[3]; angle = GetRadian(val); shinePts[0].X = (float) (cx + radius * Math.Cos(angle)); shinePts[0].Y = (float) (cy + radius * Math.Sin(angle)); angle = GetRadian(val + 20); shinePts[1].X = (float) (cx + Width * .09F * Math.Cos(angle)); shinePts[1].Y = (float) (cy + Width * .09F * Math.Sin(angle)); shinePts[2].X = cx; shinePts[2].Y = cy; var gpointer = new LinearGradientBrush(shinePts[0], shinePts[2], Color.SlateGray, Color.Black); g.FillPolygon(gpointer, shinePts); var rect = new Rectangle(x, y, width, height); DrawCenterPoint(g, rect, width / 2 + x, height / 2 + y); DrawGloss(g); gr.DrawImage(img, 0, 0); } /// /// Draws the glossiness. /// /// private void DrawGloss(Graphics g) { var glossRect = new RectangleF( x + (float) (width * 0.10), y + (float) (height * 0.07), (float) (width * 0.80), (float) (height * 0.7)); var gradientBrush = new LinearGradientBrush(glossRect, Color.FromArgb((int) glossinessAlpha, Color.White), Color.Transparent, LinearGradientMode.Vertical); g.FillEllipse(gradientBrush, glossRect); //TODO: Gradient from bottom glossRect = new RectangleF( x + (float) (width * 0.25), y + (float) (height * 0.77), (float) (width * 0.50), (float) (height * 0.2)); var gloss = (int) (glossinessAlpha / 3); gradientBrush = new LinearGradientBrush(glossRect, Color.Transparent, Color.FromArgb(gloss, BackColor), LinearGradientMode.Vertical); g.FillEllipse(gradientBrush, glossRect); } /// /// Draws the center point. /// /// /// /// /// private void DrawCenterPoint(Graphics g, Rectangle rect, int cX, int cY) { float shift = Width / 5; var rectangle = new RectangleF(cX - shift / 2, cY - shift / 2, shift, shift); var brush = new LinearGradientBrush(rect, Color.Black, Color.FromArgb(100, dialColor), LinearGradientMode.Vertical); g.FillEllipse(brush, rectangle); shift = Width / 7; rectangle = new RectangleF(cX - shift / 2, cY - shift / 2, shift, shift); brush = new LinearGradientBrush(rect, Color.SlateGray, Color.Black, LinearGradientMode.ForwardDiagonal); g.FillEllipse(brush, rectangle); } /// /// Draws the Ruler /// /// /// /// /// private void DrawCalibration(Graphics g, Rectangle rect, int cX, int cY) { var noOfParts = noOfDivisions + 1; var noOfIntermediates = noOfSubDivisions; var currentAngle = GetRadian(fromAngle); var gap = (int) (Width * 0.01F); float shift = Width / 25; var rectangle = new Rectangle(rect.Left + gap, rect.Top + gap, rect.Width - gap, rect.Height - gap); float x, y, x1, y1, tx, ty, radius; radius = rectangle.Width / 2 - gap * 5; var totalAngle = toAngle - fromAngle; var incr = GetRadian(totalAngle / ((noOfParts - 1) * (noOfIntermediates + 1))); var thickPen = new Pen(Color.Black, Width / 50); var thinPen = new Pen(Color.Black, Width / 100); var rulerValue = MinValue; for (var i = 0; i <= noOfParts; i++) { //Draw Thick Line x = (float) (cX + radius * Math.Cos(currentAngle)); y = (float) (cY + radius * Math.Sin(currentAngle)); x1 = (float) (cX + (radius - Width / 20) * Math.Cos(currentAngle)); y1 = (float) (cY + (radius - Width / 20) * Math.Sin(currentAngle)); g.DrawLine(thickPen, x, y, x1, y1); //Draw Strings var format = new StringFormat(); tx = (float) (cX + (radius - Width / 10) * Math.Cos(currentAngle)); ty = (float) (cY - shift + (radius - Width / 10) * Math.Sin(currentAngle)); Brush stringPen = new SolidBrush(ForeColor); var strFormat = new StringFormat(StringFormatFlags.NoClip); strFormat.Alignment = StringAlignment.Center; var f = new Font(Font.FontFamily, Width / 23, Font.Style); g.DrawString(rulerValue + "", f, stringPen, new PointF(tx, ty), strFormat); rulerValue += (MaxValue - MinValue) / (noOfParts - 1); rulerValue = (float) Math.Round(rulerValue, 2); //currentAngle += incr; if (i == noOfParts - 1) { break; } for (var j = 0; j <= noOfIntermediates; j++) { //Draw thin lines currentAngle += incr; x = (float) (cX + radius * Math.Cos(currentAngle)); y = (float) (cY + radius * Math.Sin(currentAngle)); x1 = (float) (cX + (radius - Width / 50) * Math.Cos(currentAngle)); y1 = (float) (cY + (radius - Width / 50) * Math.Sin(currentAngle)); g.DrawLine(thinPen, x, y, x1, y1); } } } /// /// Converts the given degree to radian. /// /// /// public float GetRadian(float theta) { return theta * (float) Math.PI / 180F; } /// /// Displays the given number in the 7-Segement format. /// /// /// /// private void DisplayNumber(Graphics g, float number, RectangleF drect) { try { var num = number.ToString("000.00"); num.PadLeft(3, '0'); float shift = 0; if (number < 0) { shift -= width / 17; } var drawDPS = false; var chars = num.ToCharArray(); for (var i = 0; i < chars.Length; i++) { var c = chars[i]; if (i < chars.Length - 1 && chars[i + 1] == '.') { drawDPS = true; } else { drawDPS = false; } if (c != '.') { if (c == '-') { DrawDigit(g, -1, new PointF(drect.X + shift, drect.Y), drawDPS, drect.Height); } else { DrawDigit(g, int.Parse(c.ToString()), new PointF(drect.X + shift, drect.Y), drawDPS, drect.Height); } shift += 15 * width / 250f; } else { shift += 2 * width / 250f; } } } catch (Exception) { } } /// /// Draws a digit in 7-Segement format. /// /// /// /// /// /// private void DrawDigit(Graphics g, int number, PointF position, bool dp, float height) { float width; width = 10F * height / 13; var outline = new Pen(Color.FromArgb(40, dialColor)); var fillPen = new Pen(Color.Black); #region Form Polygon Points //Segment A var segmentA = new PointF[5]; segmentA[0] = segmentA[4] = new PointF(position.X + GetX(2.8F, width), position.Y + GetY(1F, height)); segmentA[1] = new PointF(position.X + GetX(10, width), position.Y + GetY(1F, height)); segmentA[2] = new PointF(position.X + GetX(8.8F, width), position.Y + GetY(2F, height)); segmentA[3] = new PointF(position.X + GetX(3.8F, width), position.Y + GetY(2F, height)); //Segment B var segmentB = new PointF[5]; segmentB[0] = segmentB[4] = new PointF(position.X + GetX(10, width), position.Y + GetY(1.4F, height)); segmentB[1] = new PointF(position.X + GetX(9.3F, width), position.Y + GetY(6.8F, height)); segmentB[2] = new PointF(position.X + GetX(8.4F, width), position.Y + GetY(6.4F, height)); segmentB[3] = new PointF(position.X + GetX(9F, width), position.Y + GetY(2.2F, height)); //Segment C var segmentC = new PointF[5]; segmentC[0] = segmentC[4] = new PointF(position.X + GetX(9.2F, width), position.Y + GetY(7.2F, height)); segmentC[1] = new PointF(position.X + GetX(8.7F, width), position.Y + GetY(12.7F, height)); segmentC[2] = new PointF(position.X + GetX(7.6F, width), position.Y + GetY(11.9F, height)); segmentC[3] = new PointF(position.X + GetX(8.2F, width), position.Y + GetY(7.7F, height)); //Segment D var segmentD = new PointF[5]; segmentD[0] = segmentD[4] = new PointF(position.X + GetX(7.4F, width), position.Y + GetY(12.1F, height)); segmentD[1] = new PointF(position.X + GetX(8.4F, width), position.Y + GetY(13F, height)); segmentD[2] = new PointF(position.X + GetX(1.3F, width), position.Y + GetY(13F, height)); segmentD[3] = new PointF(position.X + GetX(2.2F, width), position.Y + GetY(12.1F, height)); //Segment E var segmentE = new PointF[5]; segmentE[0] = segmentE[4] = new PointF(position.X + GetX(2.2F, width), position.Y + GetY(11.8F, height)); segmentE[1] = new PointF(position.X + GetX(1F, width), position.Y + GetY(12.7F, height)); segmentE[2] = new PointF(position.X + GetX(1.7F, width), position.Y + GetY(7.2F, height)); segmentE[3] = new PointF(position.X + GetX(2.8F, width), position.Y + GetY(7.7F, height)); //Segment F var segmentF = new PointF[5]; segmentF[0] = segmentF[4] = new PointF(position.X + GetX(3F, width), position.Y + GetY(6.4F, height)); segmentF[1] = new PointF(position.X + GetX(1.8F, width), position.Y + GetY(6.8F, height)); segmentF[2] = new PointF(position.X + GetX(2.6F, width), position.Y + GetY(1.3F, height)); segmentF[3] = new PointF(position.X + GetX(3.6F, width), position.Y + GetY(2.2F, height)); //Segment G var segmentG = new PointF[7]; segmentG[0] = segmentG[6] = new PointF(position.X + GetX(2F, width), position.Y + GetY(7F, height)); segmentG[1] = new PointF(position.X + GetX(3.1F, width), position.Y + GetY(6.5F, height)); segmentG[2] = new PointF(position.X + GetX(8.3F, width), position.Y + GetY(6.5F, height)); segmentG[3] = new PointF(position.X + GetX(9F, width), position.Y + GetY(7F, height)); segmentG[4] = new PointF(position.X + GetX(8.2F, width), position.Y + GetY(7.5F, height)); segmentG[5] = new PointF(position.X + GetX(2.9F, width), position.Y + GetY(7.5F, height)); //Segment DP #endregion #region Draw Segments Outline g.FillPolygon(outline.Brush, segmentA); g.FillPolygon(outline.Brush, segmentB); g.FillPolygon(outline.Brush, segmentC); g.FillPolygon(outline.Brush, segmentD); g.FillPolygon(outline.Brush, segmentE); g.FillPolygon(outline.Brush, segmentF); g.FillPolygon(outline.Brush, segmentG); #endregion #region Fill Segments //Fill SegmentA if (IsNumberAvailable(number, 0, 2, 3, 5, 6, 7, 8, 9)) { g.FillPolygon(fillPen.Brush, segmentA); } //Fill SegmentB if (IsNumberAvailable(number, 0, 1, 2, 3, 4, 7, 8, 9)) { g.FillPolygon(fillPen.Brush, segmentB); } //Fill SegmentC if (IsNumberAvailable(number, 0, 1, 3, 4, 5, 6, 7, 8, 9)) { g.FillPolygon(fillPen.Brush, segmentC); } //Fill SegmentD if (IsNumberAvailable(number, 0, 2, 3, 5, 6, 8, 9)) { g.FillPolygon(fillPen.Brush, segmentD); } //Fill SegmentE if (IsNumberAvailable(number, 0, 2, 6, 8)) { g.FillPolygon(fillPen.Brush, segmentE); } //Fill SegmentF if (IsNumberAvailable(number, 0, 4, 5, 6, 7, 8, 9)) { g.FillPolygon(fillPen.Brush, segmentF); } //Fill SegmentG if (IsNumberAvailable(number, 2, 3, 4, 5, 6, 8, 9, -1)) { g.FillPolygon(fillPen.Brush, segmentG); } #endregion //Draw decimal point if (dp) { g.FillEllipse(fillPen.Brush, new RectangleF( position.X + GetX(10F, width), position.Y + GetY(12F, height), width / 7, width / 7)); } } /// /// Gets Relative X for the given width to draw digit /// /// /// /// private float GetX(float x, float width) { return x * width / 12; } /// /// Gets relative Y for the given height to draw digit /// /// /// /// private float GetY(float y, float height) { return y * height / 15; } /// /// Returns true if a given number is available in the given list. /// /// /// /// private bool IsNumberAvailable(int number, params int[] listOfNumbers) { if (listOfNumbers.Length > 0) { foreach (var i in listOfNumbers) { if (i == number) { return true; } } } return false; } /// /// Restricts the size to make sure the height and width are always same. /// /// /// private void AquaGauge_Resize(object sender, EventArgs e) { if (Width < 136) { Width = 136; } if (oldWidth != Width) { Height = Width; oldHeight = Width; } if (oldHeight != Height) { Width = Height; oldWidth = Width; } } #endregion } }