using System; using System.ComponentModel; using System.Drawing; using System.Drawing.Design; using System.Drawing.Drawing2D; using System.Windows.Forms; namespace DevComponents.Instrumentation { public class StateIndicator : GaugeIndicator { #region Private variables private StateIndicatorStyle _Style; private StateRangeCollection _Ranges; private Image _Image; private float _Angle; private float _RoundRectangleArc; private Color _TextColor; private TextAlignment _TextAlignment; private float _TextVerticalOffset; private float _TextHorizontalOffset; private Font _AbsFont; #endregion public StateIndicator() { InitIndicator(); } #region InitIndicator private void InitIndicator() { _Style = StateIndicatorStyle.Circular; _RoundRectangleArc = .75f; _TextColor = Color.Black; _TextAlignment = TextAlignment.MiddleCenter; BackColor.Color1 = Color.PaleGreen; BackColor.Color2 = Color.DarkGreen; } #endregion #region Public properties #region Angle /// /// Gets or sets the amount to rotate the indicator, specified in degrees. /// [Browsable(true)] [Category("Layout"), DefaultValue(0f)] [Editor("DevComponents.Instrumentation.Design.AngleRangeValueEditor, DevComponents.Instrumentation.Design, Version=14.1.0.37, Culture=neutral, PublicKeyToken=76cb4c6eb576bca5", typeof(UITypeEditor))] [Description("Determines the amount to rotate the indicator, specified in degrees.")] public float Angle { get { return (_Angle); } set { if (_Angle != value) { _Angle = value; OnGaugeItemChanged(true); } } } #endregion #region Image /// /// Gets or sets the Image to use /// [Browsable(true), Category("Appearance"), DefaultValue(null)] [Description("Indicates the Image to use.")] public Image Image { get { return (_Image); } set { if (_Image != value) { _Image = value; OnGaugeItemChanged(true); } } } #endregion #region Ranges /// /// Gets the collection of Ranges associated with the Indicator /// [Browsable(true), Category("Behavior")] [Description("Indicates the collection of Ranges associated with the Indicator.")] [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)] [Editor("DevComponents.Instrumentation.Design.GaugeCollectionEditor, DevComponents.Instrumentation.Design, Version=14.1.0.37, Culture=neutral, PublicKeyToken=76cb4c6eb576bca5", typeof(UITypeEditor))] public StateRangeCollection Ranges { get { if (_Ranges == null) { _Ranges = new StateRangeCollection(); _Ranges.CollectionChanged += StateRanges_CollectionChanged; } return (_Ranges); } set { if (_Ranges != null) _Ranges.CollectionChanged -= StateRanges_CollectionChanged; _Ranges = value; if (_Ranges != null) _Ranges.CollectionChanged += StateRanges_CollectionChanged; OnGaugeItemChanged(true); } } #endregion #region RoundRectangleArc /// /// Gets or sets the RoundRectangle corner radius, /// measured as a percentage of the width/height. /// [Browsable(true)] [Category("Appearance"), DefaultValue(.75f)] [Description("Indicates the RoundRectangle corner radius, measured as a percentage of the width/height.")] [Editor("DevComponents.Instrumentation.Design.WidthMaxRangeValueEditor, DevComponents.Instrumentation.Design, Version=14.1.0.37, Culture=neutral, PublicKeyToken=76cb4c6eb576bca5", typeof(UITypeEditor))] public float RoundRectangleArc { get { return (_RoundRectangleArc); } set { if (_RoundRectangleArc != value) { if (value < 0 || value > 1f) throw new ArgumentException("Radius must be between 0 and 1"); _RoundRectangleArc = value; OnGaugeItemChanged(true); } } } #endregion #region Style /// /// Gets or sets the Indicator Style /// [Browsable(true), Category("Behavior"), DefaultValue(StateIndicatorStyle.Circular)] [Description("Indicates the Indicator Style.")] public StateIndicatorStyle Style { get { return (_Style); } set { if (_Style != value) { _Style = value; OnGaugeItemChanged(true); } } } #endregion #region TextAlignment /// /// Gets or sets the alignment of the text /// [Browsable(true)] [Category("Layout"), DefaultValue(TextAlignment.MiddleCenter)] [Description("Indicates the alignment of the text.")] public TextAlignment TextAlignment { get { return (_TextAlignment); } set { if (_TextAlignment != value) { _TextAlignment = value; OnGaugeItemChanged(true); } } } #endregion #region TextColor /// /// Gets or sets the text Color /// [Browsable(true), Category("Appearance"), DefaultValue(typeof(Color), "Black")] [Description("Indicates the text Color.")] public Color TextColor { get { return (_TextColor); } set { if (_TextColor != value) { _TextColor = value; OnGaugeItemChanged(true); } } } #endregion #region TextHorizontalOffset /// /// Gets or sets the horizontal distance to offset the Indicator Text, measured as a percentage /// [Browsable(true)] [Category("Layout"), DefaultValue(0f)] [Editor("DevComponents.Instrumentation.Design.OffsetRangeValueEditor, DevComponents.Instrumentation.Design, Version=14.1.0.37, Culture=neutral, PublicKeyToken=76cb4c6eb576bca5", typeof(UITypeEditor))] [Description("Indicates the horizontal distance to offset the Indicator Text, measured as a percentage.")] public float TextHorizontalOffset { get { return (_TextHorizontalOffset); } set { if (_TextHorizontalOffset != value) { _TextHorizontalOffset = value; OnGaugeItemChanged(true); } } } #endregion #region TextVerticalOffset /// /// Gets or sets the vertical distance to offset the Indicator Text, measured as a percentage /// [Browsable(true)] [Category("Layout"), DefaultValue(0f)] [Editor("DevComponents.Instrumentation.Design.OffsetRangeValueEditor, DevComponents.Instrumentation.Design, Version=14.1.0.37, Culture=neutral, PublicKeyToken=76cb4c6eb576bca5", typeof(UITypeEditor))] [Description("Indicates the vertical distance to offset the Indicator Text, measured as a percentage.")] public float TextVerticalOffset { get { return (_TextVerticalOffset); } set { if (_TextVerticalOffset != value) { _TextVerticalOffset = value; OnGaugeItemChanged(true); } } } #endregion #endregion #region Internal properties #region AbsFont internal override Font AbsFont { get { if (AutoSize == false) return (Font); if (_AbsFont == null) { int n = Math.Min(Bounds.Width, Bounds.Height); float emSize = Font.SizeInPoints; emSize = (emSize / 40) * n; AbsFont = new Font(Font.FontFamily, emSize, Font.Style); } return (_AbsFont); } set { if (_AbsFont != null) _AbsFont.Dispose(); _AbsFont = value; } } #endregion #endregion #region Event processing #region StateRange processing void StateRanges_CollectionChanged(object sender, EventArgs e) { foreach (StateRange range in _Ranges) { range.StateIndicator = this; range.IndicatorRangeChanged -= StateRange_ItemChanged; range.IndicatorRangeChanged += StateRange_ItemChanged; } } void StateRange_ItemChanged(object sender, EventArgs e) { OnGaugeItemChanged(true); } #endregion #endregion #region RecalcLayout public override void RecalcLayout() { if (NeedRecalcLayout == true) { Size size = Bounds.Size; base.RecalcLayout(); if (size.Equals(Bounds.Size) == false) AbsFont = null; } } #endregion #region OnPaint #region OnPaint public override void OnPaint(PaintEventArgs e) { Graphics g = e.Graphics; RecalcLayout(); if (GaugeControl.OnPreRenderIndicator(e, this) == false) { InterpolationMode im = g.InterpolationMode; g.InterpolationMode = InterpolationMode.HighQualityBicubic; g.TranslateTransform(Center.X, Center.Y); g.RotateTransform(_Angle % 360); Rectangle r = new Rectangle(0, 0, Bounds.Width, Bounds.Height); r.X -= Bounds.Width / 2; r.Y -= Bounds.Height / 2; StateRange range = GetStateRange(); DrawStateImage(g, r, range); DrawStateText(g, r, range); GaugeControl.OnPostRenderIndicator(e, this); g.ResetTransform(); g.InterpolationMode = im; } } #region GetStateRange private StateRange GetStateRange() { return (_Ranges != null ? _Ranges.GetValueRange(DValue) : null); } #endregion #region DrawStateImage private void DrawStateImage(Graphics g, Rectangle r, StateRange range) { Image image = (range != null) ? range.Image : _Image; if (image != null) { if (AutoSize == true) { g.DrawImage(image, r); } else { r.X += (r.Width - image.Width) / 2; r.Y += (r.Height - image.Height) / 2; g.DrawImageUnscaled(image, r); } } else { GradientFillColor fillColor = (range != null) ? range.BackColor : BackColor; using (GraphicsPath path = GetStatePath(r)) RenderBackPath(g, path, r, fillColor); } } #region GetStatePath private GraphicsPath GetStatePath(Rectangle r) { switch (_Style) { case StateIndicatorStyle.Circular: return (GetCircularState(r)); case StateIndicatorStyle.Rectangular: return (GetRectState(r)); default: return (GetRoundRectState(r)); } } #endregion #region GetCircularState private GraphicsPath GetCircularState(Rectangle bounds) { GraphicsPath path = new GraphicsPath(); path.AddEllipse(bounds); return (path); } #endregion #region GetRectState private GraphicsPath GetRectState(Rectangle bounds) { GraphicsPath path = new GraphicsPath(); path.AddRectangle(bounds); return (path); } #endregion #region GetRoundRectState private GraphicsPath GetRoundRectState(Rectangle bounds) { GraphicsPath path = new GraphicsPath(); int m = Math.Min(bounds.Width, bounds.Height); int n = (int)(m * _RoundRectangleArc) + 1; Rectangle t = new Rectangle(bounds.Right - n, bounds.Bottom - n, n, n); path.AddArc(t, 0, 90); t.X = bounds.X; path.AddArc(t, 90, 90); t.Y = bounds.Y; path.AddArc(t, 180, 90); t.X = bounds.Right - n; path.AddArc(t, 270, 90); path.CloseAllFigures(); return (path); } #endregion #region RenderBackPath private void RenderBackPath(Graphics g, GraphicsPath path, Rectangle bounds, GradientFillColor fillColor) { Rectangle r = bounds; GradientFillType fillType = fillColor.GradientFillType; if (fillColor.End.IsEmpty) { fillType = GradientFillType.None; } else { if (fillColor.GradientFillType == GradientFillType.Auto) fillType = GradientFillType.Center; } switch (fillType) { case GradientFillType.Angle: using (Brush br = fillColor.GetBrush(r)) { if (br is LinearGradientBrush) ((LinearGradientBrush)br).WrapMode = WrapMode.TileFlipXY; g.FillPath(br, path); } break; case GradientFillType.StartToEnd: using (Brush br = fillColor.GetBrush(r, 90)) { if (br is LinearGradientBrush) ((LinearGradientBrush)br).WrapMode = WrapMode.TileFlipXY; g.FillPath(br, path); } break; case GradientFillType.HorizontalCenter: r.Height /= 2; if (r.Height <= 0) r.Height = 1; using (LinearGradientBrush br = new LinearGradientBrush(r, fillColor.Start, fillColor.End, 90)) { br.WrapMode = WrapMode.TileFlipXY; g.FillPath(br, path); } break; case GradientFillType.VerticalCenter: r.Width /= 2; if (r.Width <= 0) r.Width = 1; using (LinearGradientBrush br = new LinearGradientBrush(r, fillColor.Start, fillColor.End, 0f)) { br.WrapMode = WrapMode.TileFlipXY; g.FillPath(br, path); } break; case GradientFillType.Center: using (PathGradientBrush br = new PathGradientBrush(path)) { br.CenterPoint = new PointF(bounds.X + bounds.Width / 2, bounds.Y + bounds.Height / 2); br.CenterColor = fillColor.Start; br.SurroundColors = new Color[] { fillColor.End }; g.FillPath(br, path); } break; default: using (Brush br = new SolidBrush(fillColor.Start)) g.FillPath(br, path); break; } if (fillColor.BorderWidth > 0 && fillColor.BorderColor.IsEmpty == false) { using (Pen pen = new Pen(fillColor.BorderColor, fillColor.BorderWidth)) g.DrawPath(pen, path); } else { if (fillType == GradientFillType.Center) { using (Pen pen = new Pen(fillColor.End)) g.DrawPath(pen, path); } } } #endregion #endregion #region DrawStateText private void DrawStateText(Graphics g, Rectangle r, StateRange range) { string text = (range != null) ? range.Text : GetOverrideString(); if (string.IsNullOrEmpty(text) == false) { using (StringFormat sf = new StringFormat()) { SetStringAlignment(sf); Color color = _TextColor; if (range != null) { color = range.TextColor; int dx = (int)(r.Width * range.TextHorizontalOffset); int dy = (int)(r.Height * range.TextVerticalOffset); r.Offset(dx, dy); } else { int dx = (int)(r.Width * _TextHorizontalOffset); int dy = (int)(r.Height * _TextVerticalOffset); r.Offset(dx, dy); } using (Brush br = new SolidBrush(color)) g.DrawString(text, AbsFont, br, r, sf); } } } #region SetStringAlignment private void SetStringAlignment(StringFormat sf) { switch (_TextAlignment) { case TextAlignment.TopLeft: sf.LineAlignment = StringAlignment.Near; sf.Alignment = StringAlignment.Near; break; case TextAlignment.TopCenter: sf.LineAlignment = StringAlignment.Near; sf.Alignment = StringAlignment.Center; break; case TextAlignment.TopRight: sf.LineAlignment = StringAlignment.Near; sf.Alignment = StringAlignment.Far; break; case TextAlignment.MiddleLeft: sf.LineAlignment = StringAlignment.Center; sf.Alignment = StringAlignment.Near; break; case TextAlignment.MiddleCenter: sf.LineAlignment = StringAlignment.Center; sf.Alignment = StringAlignment.Center; break; case TextAlignment.MiddleRight: sf.LineAlignment = StringAlignment.Center; sf.Alignment = StringAlignment.Far; break; case TextAlignment.BottomLeft: sf.LineAlignment = StringAlignment.Far; sf.Alignment = StringAlignment.Near; break; case TextAlignment.BottomCenter: sf.LineAlignment = StringAlignment.Far; sf.Alignment = StringAlignment.Center; break; case TextAlignment.BottomRight: sf.LineAlignment = StringAlignment.Far; sf.Alignment = StringAlignment.Far; break; } } #endregion #endregion #endregion #endregion #region Refresh internal void Refresh() { if (NeedRecalcLayout == false) OnGaugeItemChanged(false); } #endregion #region Contains internal override bool Contains(Point pt) { using (GraphicsPath path = GetStatePath(Bounds)) { Matrix matrix = new Matrix(); matrix.RotateAt(_Angle, Center); path.Transform(matrix); return (path.IsVisible(pt)); } } #endregion #region CopyToItem public override void CopyToItem(GaugeItem copy) { StateIndicator c = copy as StateIndicator; if (c != null) { base.CopyToItem(c); c.Angle = _Angle; c.Image = _Image; if (_Ranges != null) c.Ranges = (StateRangeCollection)_Ranges.Clone(); c.RoundRectangleArc = _RoundRectangleArc; c.Style = _Style; c.TextAlignment = _TextAlignment; c.TextColor = _TextColor; c.TextHorizontalOffset = _TextHorizontalOffset; c.TextVerticalOffset = _TextVerticalOffset; } } #endregion } #region Enums public enum StateIndicatorStyle { Rectangular, RoundedRectangular, Circular } #endregion }