using System; using System.Drawing; using System.Windows.Forms; using System.Drawing.Drawing2D; namespace DevComponents.Instrumentation.Primitives { public class BaseKnob { #region Private variables private bool _Reset = true; private int _KnobIndicatorPointerBorderWidth = -1; #endregion #region Protected variables protected KnobControl Knob; // Associated KnobControl protected int MajorTicks; // Number of major ticks protected int MinorTicks; // Number of minor ticks protected Size MajorTickSize; // Size of a major tick protected Size MinorTickSize; // Size of a minor tick protected int KnobWidth; // Normalized knob width protected int IndTickHeight; // Ind tick height; protected int MaxLabelWidth; // Maximum label width protected float ZoneIndWidth; // Zone indicator width protected KnobColorTable DefaultColorTable; // Default Knob color table #endregion #region Internal variables internal Rectangle TickLabelBounds; // Tick Label bounding rectangle internal Rectangle KnobFaceBounds; // Knob face bounding rectangle internal Rectangle KnobIndicatorBounds; // Knob Indicator bounding rectangle internal Rectangle FocusRectBounds; // Focus bounding rectangle internal Rectangle ZoneIndicatorBounds; // Zone indicator bounding rectangle #endregion /// /// Constructor /// /// Associated knob control public BaseKnob(KnobControl knobControl) { Knob = knobControl; DefaultColorTable = new KnobColorTable(); } #region Public Properties #region MajorTickColor /// /// MajorTickColor /// protected Color MajorTickColor { get { Color c = Knob.KnobColor.MajorTickColor; return (c.IsEmpty == false ? c : DefaultColorTable.MajorTickColor); } } #endregion #region MinorTickColor /// /// MinorTickColor /// protected Color MinorTickColor { get { Color c = Knob.KnobColor.MinorTickColor; return (c.IsEmpty == false ? c : DefaultColorTable.MinorTickColor); } } #endregion #region KnobIndicatorPointerBorderColor /// /// KnobIndicatorPointerBorderColor /// protected Color KnobIndicatorPointerBorderColor { get { Color c = Knob.KnobColor.KnobIndicatorPointerBorderColor; return (c.IsEmpty == false ? c : DefaultColorTable.KnobIndicatorPointerBorderColor); } } #endregion #region KnobIndicatorPointerBorderWidth /// /// KnobIndicatorPointerBorderWidth /// protected int KnobIndicatorPointerBorderWidth { get { int width = Knob.KnobColor.KnobIndicatorPointerBorderWidth; if (width <= 0) return (DefaultColorTable.KnobIndicatorPointerBorderWidth); return (width); } } #endregion #region KnobIndicatorPointerColor /// /// KnobIndicatorPointerColor /// protected Color KnobIndicatorPointerColor { get { Color c = Knob.KnobColor.KnobIndicatorPointerColor; return (c.IsEmpty == false ? c : DefaultColorTable.KnobIndicatorPointerColor); } } #endregion #region ZoneIndicatorColor /// /// ZoneIndicatorBaseColor /// protected Color ZoneIndicatorColor { get { Color c = Knob.KnobColor.ZoneIndicatorColor; return (c.IsEmpty == false ? c : DefaultColorTable.ZoneIndicatorColor); } } #endregion #region KnobFaceColor /// /// KnobFaceColor /// protected LinearGradientColorTable KnobFaceColor { get { return (ApplyColor(Knob.KnobColor.KnobFaceColor, DefaultColorTable.KnobFaceColor)); } } #endregion #region KnobIndicatorColor /// /// KnobIndicatorColor /// protected LinearGradientColorTable KnobIndicatorColor { get { return (ApplyColor(Knob.KnobColor.KnobIndicatorColor, DefaultColorTable.KnobIndicatorColor)); } } #endregion #region LeftZoneIndicatorColor /// /// LeftZoneIndicatorColor /// protected LinearGradientColorTable LeftZoneIndicatorColor { get { return (ApplyColor(Knob.KnobColor.MinZoneIndicatorColor, DefaultColorTable.MinZoneIndicatorColor)); } } #endregion #region MiddleZoneIndicatorColor /// /// MiddleZoneIndicatorColor /// protected LinearGradientColorTable MiddleZoneIndicatorColor { get { return (ApplyColor(Knob.KnobColor.MidZoneIndicatorColor, DefaultColorTable.MidZoneIndicatorColor)); } } #endregion #region RightZoneIndicatorColor /// /// RightZoneIndicatorColor /// protected LinearGradientColorTable RightZoneIndicatorColor { get { return (ApplyColor(Knob.KnobColor.MaxZoneIndicatorColor, DefaultColorTable.MaxZoneIndicatorColor)); } } #endregion #region TickLabelFormat /// /// MajorTickColor /// protected string TickLabelFormat { get { return (Knob.TickLabelFormat); } } #endregion #endregion #region ApplyColor /// /// ApplyColor /// /// /// /// private LinearGradientColorTable ApplyColor( LinearGradientColorTable c, LinearGradientColorTable d) { if (c.IsEmpty == true) return (d); if (c.Start.IsEmpty == false && c.End.IsEmpty == false) return (c); return (new LinearGradientColorTable( c.Start.IsEmpty ? d.Start : c.Start, c.End.IsEmpty ? d.End : c.End, c.GradientAngle)); } #endregion #region Knob configuration #region ConfigureKnob /// /// Main control configuration routine /// /// public virtual void ConfigureKnob(PaintEventArgs e) { // Calculate the bounding width for the control to be the // minimum of either the width and the height KnobWidth = Math.Min(Knob.Width, Knob.Height); KnobWidth = Math.Max(KnobWidth, Knob.MinKnobSize); if ((KnobWidth % 2) != 0) KnobWidth -= 1; // Calculate the number of Major and Minor ticks and then // measure each associated label so that we can mane sure we // have enough room for them in the control rectangle CalculateTicksCounts(); MeasureTickLabels(); } #endregion #region ResetKnob /// /// Sets the reset state to true, signifying /// that the control needs to be reconfigured /// before it is redrawn to the screen /// public void ResetKnob() { _Reset = true; } #endregion #region InitRender /// /// Initializes the rendering process by making /// sure that the control is reconfigured if /// necessary /// /// public void InitRender(PaintEventArgs e) { if (_Reset == true) { ConfigureKnob(e); _Reset = false; } } #endregion #endregion #region Render processing public virtual void RenderZoneIndicator(PaintEventArgs e) { } public virtual void RenderKnobFace(PaintEventArgs e) { } public virtual void RenderKnobIndicator(PaintEventArgs e) { } #region RenderTickMinor /// /// Renders the minor tick marks /// /// public virtual void RenderTickMinor(PaintEventArgs e) { Graphics g = e.Graphics; using (Pen pen = new Pen(MinorTickColor, MinorTickSize.Width)) { for (int i = 0; i < MinorTicks; i++) { // Don't draw a minor tick if it overlaps // with a previous major tick if (Knob.MajorTickAmount > 0) { if ((i == 0 || i == MinorTicks - 1) || ((Knob.MinorTickAmount * i) % Knob.MajorTickAmount == 0)) continue; } g.DrawLines(pen, GetMinorTickPoints(i)); } } } /// /// Calculates a series of points /// that defines the tick mark /// /// Tick to calculate /// An array of points that defines the tick private Point[] GetMinorTickPoints(int tick) { float degree = GetTickDegree((float)Knob.MinorTickAmount, tick); double rad = GetRadians(degree); int dx = ZoneIndicatorBounds.X + ZoneIndicatorBounds.Width / 2; int dy = ZoneIndicatorBounds.Y + ZoneIndicatorBounds.Height / 2; int h = (int)(ZoneIndicatorBounds.Width / 2 + ZoneIndWidth / 2 - 1); Point[] pts = new Point[2]; pts[0].X = (int)(Math.Cos(rad) * h + dx); pts[0].Y = (int)(Math.Sin(rad) * h + dy); pts[1].X = (int)(Math.Cos(rad) * (h + MinorTickSize.Height) + dx); pts[1].Y = (int)(Math.Sin(rad) * (h + MinorTickSize.Height) + dy); return (pts); } #endregion #region RenderTickMajor /// /// Renders the Major Tick marks /// /// public virtual void RenderTickMajor(PaintEventArgs e) { Graphics g = e.Graphics; // Loop through each tick using (Pen pen1 = new Pen(MajorTickColor, MajorTickSize.Width)) { for (int i = 0; i < MajorTicks; i++) g.DrawLines(pen1, GetMajorTickPoints(i)); } } /// /// Calculates a series of points /// that defines the tick mark /// /// Tick to calculate /// An array of points that defines the tick private Point[] GetMajorTickPoints(int tick) { float degree = GetTickDegree((float)Knob.MajorTickAmount, tick); double rad = GetRadians(degree); int dx = ZoneIndicatorBounds.X + ZoneIndicatorBounds.Width / 2; int dy = ZoneIndicatorBounds.Y + ZoneIndicatorBounds.Height / 2; int h = (int)(ZoneIndicatorBounds.Width / 2 + ZoneIndWidth / 2) - 1; Point[] pts = new Point[2]; pts[0].X = (int)(Math.Cos(rad) * h + dx); pts[0].Y = (int)(Math.Sin(rad) * h + dy); pts[1].X = (int)(Math.Cos(rad) * (h + MajorTickSize.Height) + dx); pts[1].Y = (int)(Math.Sin(rad) * (h + MajorTickSize.Height) + dy); return (pts); } #endregion #region RenderTickLabel /// /// Renders the major tick label /// /// public virtual void RenderTickLabel(PaintEventArgs e) { Graphics g = e.Graphics; // Allocate a StrFormat to use in the display // of out tick label text using (StringFormat strFormat = new StringFormat(StringFormatFlags.NoClip)) { strFormat.Alignment = StringAlignment.Center; float radius = TickLabelBounds.Width / 2; // Grab our tick label font - which is // dynamically sized according to the control Font labelFont = Knob.Font; int dx = KnobWidth / 2; int dy = (KnobWidth - (int) labelFont.SizeInPoints) / 2; // Loop through each major tick float dpt = (float) (Knob.SweepAngle * Knob.MajorTickAmount / ValueCount); for (int i = 0; i < MajorTicks; i++) { if (Math.Abs(dpt * i) < 360) { float degree = dpt * i + Knob.StartAngle; if (dpt < 0) { if (degree < Knob.StartAngle + Knob.SweepAngle) degree = Knob.StartAngle + Knob.SweepAngle; } else { if (degree > Knob.StartAngle + Knob.SweepAngle) degree = Knob.StartAngle + Knob.SweepAngle; } decimal x = Math.Min(Knob.MinValue + (i * Knob.MajorTickAmount), Knob.MaxValue); string s = ((int)x == x) ? ((int)x).ToString(TickLabelFormat) : x.ToString(TickLabelFormat); Size sz = TextRenderer.MeasureText(s, labelFont); double currentAngle = GetRadians(degree); Point pt = new Point((int) (dx - sz.Width / 2 + radius * (float) Math.Cos(currentAngle)), (int) (dy + radius * (float) Math.Sin(currentAngle))); Rectangle r = new Rectangle(pt, new Size(1000, 1000)); TextRenderer.DrawText(g, s, labelFont, r, Knob.ForeColor, TextFormatFlags.Left); } } } } #endregion #region RenderFocusRect /// /// Renders the base focus rect /// /// public virtual void RenderFocusRect(PaintEventArgs e) { Graphics g = e.Graphics; using (Pen pen = new Pen(Color.DarkGray)) { pen.DashStyle = DashStyle.Dash; g.DrawEllipse(pen, FocusRectBounds); } } #endregion #endregion #region GetValueFromPoint /// /// Determines the control Value from /// a specified Point on the control /// /// Point on the control /// Value public decimal GetValueFromPoint(Point pt) { // Calculate the relative X and Y // coordinate pair for the given point int radius = ZoneIndicatorBounds.Width / 2; Point cpt = new Point(ZoneIndicatorBounds.X + radius, ZoneIndicatorBounds.Y + radius); int dx = pt.X - cpt.X; int dy = pt.Y - cpt.Y; // Determine the radians for the given coord pair // based upon which quadrant it is located in double radians; if (dx >= 0) { if (dy >= 0) radians = Math.Atan((double)dy / dx); else radians = -Math.Atan((double)dx / dy) + Math.PI * 1.5; } else { if (dy >= 0) radians = -Math.Atan((double)dx / dy) + Math.PI / 2; else radians = Math.Atan((double)dy / dx) + Math.PI; } // Convert our calculated Radians to Degrees // and then 'normalize' our values to the // current StartAngle int degrees = (int)GetDegrees(radians); decimal normal = degrees - Knob.StartAngle; if (Knob.SweepAngle < 0) { normal = 360 - normal; normal = normal % 360; } if (normal < 0) normal += 360; // If our normalized angle is within the // SweepAngle, then return the associated // range value int n = Math.Abs(Knob.SweepAngle); if (normal >= 0 && normal <= n) return (normal * ValueCount / n + Knob.MinValue); // The normalized angle is outside the SweepAngle, so // return either the MinValue or MaxValue (based upon // which one is closer) return ((normal >= n + (360 - n) / 2) ? Knob.MinValue : Knob.MaxValue); } #endregion #region CalculateTicksCounts /// /// Calculate how many major and /// minor ticks are presented on the control /// private void CalculateTicksCounts() { decimal count = ValueCount; // Calculate the number of major ticks MajorTicks = 0; if (Knob.MajorTickAmount > 0) { MajorTicks = (int)(count / Knob.MajorTickAmount); if (MajorTicks * Knob.MajorTickAmount < count) MajorTicks++; MajorTicks++; } // Calculate the number of minor ticks MinorTicks = 0; if (Knob.MinorTickAmount > 0) { MinorTicks = (int)(count / Knob.MinorTickAmount); if (MinorTicks * Knob.MinorTickAmount < count) MinorTicks++; MinorTicks++; } } #endregion #region MeasureTickLabels /// /// Measure the width of each text label in order to /// make sure we have room for it in the control /// private void MeasureTickLabels() { MaxLabelWidth = 0; if (Knob.ShowTickLabels == true) { Font labelFont = Knob.Font; for (int i = 0; i < MajorTicks; i++) { // Save the text and width for later use decimal n = Knob.MinValue + (i * Knob.MajorTickAmount); string s = n.ToString(TickLabelFormat); int w = TextRenderer.MeasureText(s, labelFont).Width + 2; // Keep track of the maximum width if (w > MaxLabelWidth) MaxLabelWidth = w; } } } #endregion #region GetTickDegree /// /// Gets the arc degree associated with /// the given gauge tick /// /// Major or minor tick amount /// The tick to convert /// protected float GetTickDegree(float tickAmount, int tick) { float dpt = (Knob.SweepAngle * tickAmount) / (float)ValueCount; float degree = dpt * tick + Knob.StartAngle; if (dpt < 0) { if (degree < Knob.StartAngle + Knob.SweepAngle) degree = Knob.StartAngle + Knob.SweepAngle; } else { if (degree > Knob.StartAngle + Knob.SweepAngle) degree = Knob.StartAngle + Knob.SweepAngle; } return (degree); } #endregion #region GetRadians /// /// Converts Degrees to Radians /// /// Degrees /// Radians public double GetRadians(float theta) { return (theta * Math.PI / 180); } #endregion #region GetDegrees /// /// Converts Radians to Degrees /// /// Radians /// Degrees public double GetDegrees(double radians) { return (radians * 180 / Math.PI); } #endregion #region ValueCount /// /// Gets the value range, expressed as a count /// internal decimal ValueCount { get { return (Knob.MaxValue - Knob.MinValue); } } #endregion #region PointInControl /// /// Determines if a given Point is within /// the bounds of the control /// /// /// public virtual bool PointInControl(Point pt) { // Allow a little leeway around the control Rectangle r = new Rectangle(ZoneIndicatorBounds.Location, ZoneIndicatorBounds.Size); r.Inflate(40, 40); int radius = r.Width / 2; Point cpt = new Point(r.X + radius, r.Y + radius); return (PointInCircle(pt, cpt, radius)); } #endregion #region PointInCircle /// /// Determines if a given point is within a given circle /// /// Point in question /// Center Point /// Circle radius /// public bool PointInCircle(Point pt, Point cpt, int radius) { int a = pt.X - cpt.X; int b = pt.Y - cpt.Y; int c = (int)Math.Sqrt(a * a + b * b); return (c < radius); } #endregion } }