四联光电智能照明论坛

 找回密码
 立即注册
搜索
热搜: 活动 交友 discuz
查看: 2274|回复: 1
打印 上一主题 下一主题

C#自定义工业控件开发

[复制链接]
  • TA的每日心情
    开心
    2022-6-10 09:59
  • 366

    主题

    741

    帖子

    9649

    积分

    超级版主

    Rank: 8Rank: 8

    积分
    9649
    跳转到指定楼层
    楼主
    发表于 2016-11-3 11:38:22 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式

    由于工作需要,调研过一段时间的工业控制方面的“组态软件”(SCADA)的开发,组态软件常用于自动化工业控制领域,其中包括实时数据采集、数据储存、设备控制和数据展现等功能。其中工控组件的界面展现的实现类似于Windows系统下的各种开发控件,通过各种控件的组装,和硬件协议的集成,就可以实现对相应设备的控制和实时状态的显示。

    每个对应的硬件UI展示都可以用一个自定义控件来实现,如下图的一个温度计,就可以使用UserControl来实现。



    对应的实现代码如下:


    1. using System;
    2. using System.Collections.Generic;
    3. using System.ComponentModel;
    4. using System.Drawing;
    5. using System.Data;
    6. using System.Linq;
    7. using System.Text;
    8. using System.Windows.Forms;

    9. namespace HMIControls
    10. {
    11.     public partial class ThermometerControl : UserControl
    12.     {
    13.         /// <summary>
    14.         /// 初始化控件
    15.         /// 预设绘图方式:双缓冲、支持透明背景色、自定义绘制
    16.         /// </summary>
    17.         public ThermometerControl()
    18.         {
    19.             SetStyle(ControlStyles.AllPaintingInWmPaint, true);
    20.             SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
    21.             SetStyle(ControlStyles.ResizeRedraw, true);
    22.             SetStyle(ControlStyles.Selectable, true);
    23.             SetStyle(ControlStyles.SupportsTransparentBackColor, true);
    24.             SetStyle(ControlStyles.UserPaint, true);

    25.             InitializeComponent();
    26.         }

    27.         // 温度
    28.         private float temperature = 0;
    29.         [Category("温度"), Description("当前温度")]
    30.         public float Temperature
    31.         {
    32.             set { temperature = value; }
    33.             get { return temperature; }
    34.         }

    35.         // 最高温度
    36.         private float highTemperature = 50;
    37.         [Category("温度"), Description("最高温度")]
    38.         public float HighTemperature
    39.         {
    40.             set { highTemperature = value; }
    41.             get { return highTemperature; }
    42.         }

    43.         // 最低温度
    44.         private float lowTemperature = -20;
    45.         [Category("温度"), Description("最低温度")]
    46.         public float LowTemperature
    47.         {
    48.             set { lowTemperature = value; }
    49.             get { return lowTemperature; }
    50.         }

    51.         // 当前温度数值的字体
    52.         private Font tempFont = new Font("宋体", 12);
    53.         [Category("温度"), Description("当前温度数值的字体")]
    54.         public Font TempFont
    55.         {
    56.             set { tempFont = value; }
    57.             get { return tempFont; }
    58.         }

    59.         // 当前温度数值的颜色
    60.         private Color tempColor = Color.Black;
    61.         [Category("温度"), Description("当前温度数值的颜色")]
    62.         public Color TempColor
    63.         {
    64.             set { tempColor = value; }
    65.             get { return tempColor; }
    66.         }

    67.         // 大刻度线数量
    68.         private int bigScale = 5;
    69.         [Category("刻度"), Description("大刻度线数量")]
    70.         public int BigScale
    71.         {
    72.             set { bigScale = value; }
    73.             get { return bigScale; }
    74.         }

    75.         // 小刻度线数量
    76.         private int smallScale = 5;
    77.         [Category("刻度"), Description("小刻度线数量")]
    78.         public int SmallScale
    79.         {
    80.             set { smallScale = value; }
    81.             get { return smallScale; }
    82.         }

    83.         // 刻度字体
    84.         private Font drawFont = new Font("Aril", 9);
    85.         [Category("刻度"), Description("刻度数字的字体")]
    86.         public Font DrawFont
    87.         {
    88.             get { return drawFont; }
    89.             set { drawFont = value; }
    90.         }

    91.         // 字体颜色
    92.         private Color drawColor = Color.Black;
    93.         [Category("刻度"), Description("刻度数字的颜色")]
    94.         public Color DrawColor
    95.         {
    96.             set { drawColor = value; }
    97.             get { return drawColor; }
    98.         }

    99.         // 刻度盘最外圈线条的颜色
    100.         private Color dialOutLineColor = Color.Gray;
    101.         [Category("背景"), Description("刻度盘最外圈线条的颜色")]
    102.         public Color DialOutLineColor
    103.         {
    104.             set { dialOutLineColor = value; }
    105.             get { return dialOutLineColor; }
    106.         }

    107.         // 刻度盘背景颜色
    108.         private Color dialBackColor = Color.Gray;
    109.         [Category("背景"), Description("刻度盘背景颜色")]
    110.         public Color DialBackColor
    111.         {
    112.             set { dialBackColor = value; }
    113.             get { return dialBackColor; }
    114.         }

    115.         // 大刻度线颜色
    116.         private Color bigScaleColor = Color.Black;
    117.         [Category("刻度"), Description("大刻度线颜色")]
    118.         public Color BigScaleColor
    119.         {
    120.             set { bigScaleColor = value; }
    121.             get { return bigScaleColor; }
    122.         }

    123.         // 小刻度线颜色
    124.         private Color smallScaleColor = Color.Black;
    125.         [Category("刻度"), Description("小刻度线颜色")]
    126.         public Color SmallScaleColor
    127.         {
    128.             set { smallScaleColor = value; }
    129.             get { return smallScaleColor; }
    130.         }

    131.         // 温度柱背景颜色
    132.         private Color mercuryBackColor = Color.LightGray;
    133.         [Category("刻度"), Description("温度柱背景颜色")]
    134.         public Color MercuryBackColor
    135.         {
    136.             set { mercuryBackColor = value; }
    137.             get { return mercuryBackColor; }
    138.         }

    139.         // 温度柱颜色
    140.         private Color mercuryColor = Color.Red;
    141.         [Category("刻度"), Description("温度柱颜色")]
    142.         public Color MercuryColor
    143.         {
    144.             set { mercuryColor = value; }
    145.             get { return mercuryColor; }
    146.         }
    复制代码


    1.         /// <summary>
    2.         ///  变量
    3.         /// </summary>
    4.         private float X;
    5.         private float Y;
    6.         private float H;
    7.         private Pen p, s_p;
    8.         private Brush b;

    9.         /// <summary>
    10.         /// 绘制温度计
    11.         /// </summary>
    12.         /// <param name="sender"></param>
    13.         /// <param name="e"></param>
    14.         private void ThermometerControl_Paint(object sender, PaintEventArgs e)
    15.         {
    16.             // 温度值是否在温度表最大值和最小值范围内
    17.             if (temperature > highTemperature)
    18.             {
    19.                 //MessageBox.Show("温度值超出温度表范围,系统自动设置为默认值!");
    20.                 temperature = highTemperature;
    21.             }
    22.             if (temperature < lowTemperature)
    23.             {
    24.                 temperature = lowTemperature;
    25.             }

    26.             e.Graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
    27.             e.Graphics.TranslateTransform(2, 2);

    28.             X = this.Width - 4;
    29.             Y = this.Height - 4;

    30.             // 绘制边框(最外边的框)
    31.             p = new Pen(dialOutLineColor, 2);
    32.             e.Graphics.DrawLine(p, 0, X / 2, 0, (Y - X / 2));
    33.             e.Graphics.DrawLine(p, X, X / 2, X, (Y - X / 2));
    34.             e.Graphics.DrawArc(p, 0, 0, X, X, 180, 180);
    35.             e.Graphics.DrawArc(p, 0, (Y - X), X, X, 0, 180);

    36.             // 绘制背景色
    37.             X = X - 8;
    38.             Y = Y - 8;
    39.             b = new SolidBrush(dialBackColor);
    40.             e.Graphics.TranslateTransform(4, 4);
    41.             e.Graphics.FillRectangle(b, 0, X / 2, X, (Y - X));
    42.             e.Graphics.FillEllipse(b, 0, 0, X, X);
    43.             e.Graphics.FillEllipse(b, 0, (Y - X), X, X);

    44.             // 绘制指示柱
    45.             b = new SolidBrush(mercuryBackColor);
    46.             e.Graphics.FillEllipse(b, X * 2 / 5, (X / 2 - X / 10), X / 5, X / 5);
    47.             b = new SolidBrush(mercuryColor);
    48.             e.Graphics.FillEllipse(b, X / 4, (Y - X * 9 / 16), X / 2, X / 2);
    49.             e.Graphics.FillRectangle(b, X * 2 / 5, (X / 2 + 1), X / 5, (Y - X));

    50.             // 在温度计底部,绘制当前温度数值
    51.             b = new SolidBrush(tempColor);
    52.             StringFormat format = new StringFormat();
    53.             format.LineAlignment = StringAlignment.Center;
    54.             format.Alignment = StringAlignment.Center;
    55.             e.Graphics.DrawString((temperature.ToString() + "℃"), tempFont, b, X / 2, (Y - X / 4), format);

    56.             // 绘制大刻度线,线宽为2
    57.             // 绘制小刻度线,线宽为1
    58.             // 绘制刻度数字,字体,字号,字的颜色在属性中可改
    59.             p = new Pen(bigScaleColor, 2);                        // 设置大刻度线的颜色,线粗
    60.             s_p = new Pen(smallScaleColor, 1);                      // 设置小刻度线的颜色,线粗
    61.             SolidBrush drawBrush = new SolidBrush(drawColor);   // 设置绘制数字的颜色
    62.             format.Alignment = StringAlignment.Near;            // 设置数字水平对齐为中间,垂直对其为左边
    63.             // 计算要绘制数字的数值
    64.             int interval = (int)(highTemperature - lowTemperature) / bigScale;
    65.             int tempNum = (int)highTemperature;
    66.             for (int i = 0; i <= bigScale; i++)
    67.             {
    68.                 float b_s_y = X / 2 + i * ((Y - X - X / 2) / bigScale);       // 绘制大刻度线的垂直位置
    69.                 e.Graphics.DrawLine(p, X / 5, b_s_y, (X * 2 / 5 - 2), b_s_y); // 绘制大刻度线
    70.                 e.Graphics.DrawString(tempNum.ToString(), drawFont, drawBrush, X * 3 / 5, b_s_y, format);   // 绘制刻度数字
    71.                 tempNum -= interval;    // 计算下一次要绘制的数值

    72.                 // 绘制小刻度线
    73.                 if (i < bigScale)
    74.                 {
    75.                     for (int j = 1; j < smallScale; j++)
    76.                     {
    77.                         float s_s_y = b_s_y + ((X / 2 + (i + 1) * ((Y - X - X / 2) / bigScale) - b_s_y) / smallScale) * j;
    78.                         e.Graphics.DrawLine(s_p, (X * 3 / 10), s_s_y, (X * 2 / 5 - 2), s_s_y);
    79.                     }
    80.                 }
    81.             }
    82.             
    83.             // 计算当前温度的位置            
    84.             float L = Y - X * 3 / 2;
    85.             H = L * (temperature - lowTemperature) / (highTemperature - lowTemperature);
    86.             // 绘制当前温度的位置
    87.             b = new SolidBrush(mercuryBackColor);
    88.             e.Graphics.FillRectangle(b, X * 2 / 5, X / 2, X / 5, (L - H));
    89.         }
    90.     }
    91. }
    复制代码


    类似的一些实现,如下图:



    对应一些动态线条的绘制,可以采用ZedGraph这个开源的控件来实现,如下图:



    模拟的一些随时间变化的温度曲线图,一些参考代码如下:


    1. using System;
    2. using System.Collections.Generic;
    3. using System.ComponentModel;
    4. using System.Drawing;
    5. using System.Data;
    6. using System.Linq;
    7. using System.Text;
    8. using System.Windows.Forms;
    9. using ZedGraph;

    10. namespace HMIControls
    11. {
    12.     public partial class AirMachine : UserControl
    13.     {
    14.         private bool isValveOn;
    15.         private Timer timer;
    16.         private double temperature;
    17.         private Random random = new Random();

    18.         private Point arrowLocation1;
    19.         private Point arrowLocation2;
    20.         private Point arrowLocation3;

    21.         // Starting time in milliseconds
    22.         int tickStart = 0;

    23.         public AirMachine()
    24.         {
    25.             InitializeComponent();
    26.             InitUI();
    27.         }

    28.         private void InitUI()
    29.         {
    30.             isValveOn = false;
    31.             this.labelTemperature.Text = "0";
    32.             this.button1.Text = "开";
    33.             this.button1.BackColor = Color.Snow;
    34.             timer = new Timer();
    35.             timer.Interval = 1000;
    36.             timer.Tick += new EventHandler(timer_Tick);
    37.             this.Load += new EventHandler(AirMachine_Load);

    38.             this.labelArrow1.Visible = false;
    39.             this.labelArrow2.Visible = false;
    40.             this.labelArrow3.Visible = false;

    41.             arrowLocation1 = this.labelArrow1.Location;
    42.             arrowLocation2 = this.labelArrow2.Location;
    43.             arrowLocation3 = this.labelArrow3.Location;

    44.             this.button1.Click += new EventHandler(button1_Click);
    45.         }

    46.         private void CreateGraph()
    47.         {
    48.             zedGraphControl1.IsEnableZoom = false;
    49.             zedGraphControl1.IsShowContextMenu = false;

    50.             // Get a reference to the GraphPane
    51.             GraphPane myPane = zedGraphControl1.GraphPane;

    52.             // Set the titles
    53.             myPane.Title.Text = "实时数据";
    54.             myPane.YAxis.Title.Text = "数据";
    55.             myPane.XAxis.Title.Text = "时间";

    56.             // Change the color of the title
    57.             myPane.Title.FontSpec.FontColor = Color.Green;
    58.             myPane.XAxis.Title.FontSpec.FontColor = Color.Green;
    59.             myPane.YAxis.Title.FontSpec.FontColor = Color.Green;


    60.             // Save 1200 points.  At 50 ms sample rate, this is one minute
    61.             // The RollingPointPairList is an efficient storage class that always
    62.             // keeps a rolling set of point data without needing to shift any data values
    63.             RollingPointPairList list = new RollingPointPairList(1200);

    64.             // Initially, a curve is added with no data points (list is empty)
    65.             // Color is blue, and there will be no symbols
    66.             LineItem myCurve = myPane.AddCurve("温度值", list, Color.Blue, SymbolType.None);

    67.             // Fill the area under the curves
    68.             myCurve.Line.Fill = new Fill(Color.White, Color.Blue, 45F);

    69.             myCurve.Line.IsSmooth = true;
    70.             myCurve.Line.SmoothTension = 0.5F;

    71.             // Increase the symbol sizes, and fill them with solid white
    72.             myCurve.Symbol.Size = 8.0F;
    73.             myCurve.Symbol.Fill = new Fill(Color.Red);
    74.             myCurve.Symbol.Type = SymbolType.Circle;

    75.             // Just manually control the X axis range so it scrolls continuously
    76.             // instead of discrete step-sized jumps
    77.             myPane.XAxis.Scale.Min = 0;
    78.             myPane.XAxis.Scale.Max = 100;
    79.             myPane.XAxis.Scale.MinorStep = 1;
    80.             myPane.XAxis.Scale.MajorStep = 5;

    81.             // Add gridlines to the plot
    82.             myPane.XAxis.MajorGrid.IsVisible = true;
    83.             myPane.XAxis.MajorGrid.Color = Color.LightGray;
    84.             myPane.YAxis.MajorGrid.IsVisible = true;
    85.             myPane.YAxis.MajorGrid.Color = Color.LightGray;

    86.             // Scale the axes
    87.             zedGraphControl1.AxisChange();

    88.             // Save the beginning time for reference
    89.             tickStart = Environment.TickCount;
    90.         }

    91.         void AirMachine_Load(object sender, EventArgs e)
    92.         {
    93.             CreateGraph();
    94.         }

    95.         private void UpdateZedGraph(double yValue)
    96.         {
    97.             // Make sure that the curvelist has at least one curve
    98.             if (zedGraphControl1.GraphPane.CurveList.Count <= 0)
    99.                 return;

    100.             // Get the first CurveItem in the graph
    101.             LineItem curve = zedGraphControl1.GraphPane.CurveList[0] as LineItem;
    102.             if (curve == null)
    103.                 return;

    104.             // Get the PointPairList
    105.             IPointListEdit list = curve.Points as IPointListEdit;
    106.             // If this is null, it means the reference at curve.Points does not
    107.             // support IPointListEdit, so we won't be able to modify it
    108.             if (list == null)
    109.                 return;

    110.             // Time is measured in seconds
    111.             double time = (Environment.TickCount - tickStart) / 1000.0;

    112.             // 3 seconds per cycle
    113.             //list.Add(time, Math.Sin(2.0 * Math.PI * time / 3.0));
    114.             list.Add(time, yValue);

    115.             // Keep the X scale at a rolling 30 second interval, with one
    116.             // major step between the max X value and the end of the axis
    117.             Scale xScale = zedGraphControl1.GraphPane.XAxis.Scale;
    118.             if (time > xScale.Max - xScale.MajorStep)
    119.             {
    120.                 xScale.Max = time + xScale.MajorStep;
    121.                 xScale.Min = xScale.Max - 100.0;
    122.             }

    123.             // Make sure the Y axis is rescaled to accommodate actual data
    124.             zedGraphControl1.AxisChange();
    125.             // Force a redraw
    126.             zedGraphControl1.Invalidate();
    127.         }

    128.         private void UpdataArrowPosition()
    129.         {
    130.             this.labelArrow1.Location = new Point(this.labelArrow1.Location.X + 30, this.labelArrow1.Location.Y);
    131.             if (this.labelArrow1.Location.X >= this.panelPic.Location.X + this.panelPic.Width)
    132.             {
    133.                 this.labelArrow1.Location = arrowLocation1;
    134.             }

    135.             this.labelArrow2.Location = new Point(this.labelArrow2.Location.X + 30, this.labelArrow2.Location.Y);
    136.             if (this.labelArrow2.Location.X >= this.panelPic.Location.X + this.panelPic.Width)
    137.             {
    138.                 this.labelArrow2.Location = arrowLocation2;
    139.             }

    140.             this.labelArrow3.Location = new Point(this.labelArrow3.Location.X + 30, this.labelArrow3.Location.Y);
    141.             if (this.labelArrow3.Location.X >= this.panelPic.Location.X + this.panelPic.Width)
    142.             {
    143.                 this.labelArrow3.Location = arrowLocation3;
    144.             }
    145.         }

    146.         void timer_Tick(object sender, EventArgs e)
    147.         {
    148.             temperature = random.NextDouble() * 100;
    149.             this.labelTemperature.Text = Convert.ToInt32(temperature).ToString();

    150.             UpdateZedGraph(temperature);

    151.             UpdataArrowPosition();
    152.         }

    153.         private void button1_Click(object sender, EventArgs e)
    154.         {
    155.             isValveOn = !isValveOn;
    156.             if (isValveOn)
    157.             {
    158.                 timer.Start();
    159.                 this.button1.Text = "关";
    160.                 this.button1.BackColor = Color.LawnGreen;
    161.                 this.labelTemperature.BackColor = Color.LawnGreen;
    162.                 this.labelArrow1.Visible = isValveOn;
    163.                 this.labelArrow2.Visible = isValveOn;
    164.                 this.labelArrow3.Visible = isValveOn;
    165.             }
    166.             else
    167.             {
    168.                 timer.Stop();
    169.                 this.button1.Text = "开";
    170.                 this.button1.BackColor = Color.Snow;
    171.                 this.labelTemperature.Text = "0";
    172.                 this.labelTemperature.BackColor = Color.Snow;
    173.                 this.labelArrow1.Visible = isValveOn;
    174.                 this.labelArrow2.Visible = isValveOn;
    175.                 this.labelArrow3.Visible = isValveOn;
    176.             }
    177.         }
    178.     }
    179. }
    复制代码



    整个组态软件的开发,从底层硬件相关的设备协议到上层的展现都是比较有难度的,特别是现在硬件协议不统一,业界没有统一的标准,虽然有OPC和BACnet等一些标准协议,但是在实际项目中,有很多的设备是没有实现OPC的,都是自己的私有协议,要基于这类的硬件做二次开发,需要向商家买协议,这也是成本的问题。



    代码下载:http://download.csdn.net/detail/luxiaoxun/8256371



    组态界面开发的一些参考资源:

    http://www.codeproject.com/Articles/36116/Industrial-Controls

    http://www.codeproject.com/Artic ... nd-performing-gauge

    http://dashboarding.codeplex.com/
  • TA的每日心情
    开心
    2022-6-10 09:59
  • 366

    主题

    741

    帖子

    9649

    积分

    超级版主

    Rank: 8Rank: 8

    积分
    9649
    沙发
     楼主| 发表于 2016-11-3 11:39:17 | 只看该作者
    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则

    QQ|Archiver|手机版|小黑屋|Silian Lighting+ ( 蜀ICP备14004521号-1 )

    GMT+8, 2024-5-17 11:35 , Processed in 1.062500 second(s), 23 queries .

    Powered by Discuz! X3.2

    © 2001-2013 Comsenz Inc.

    快速回复 返回顶部 返回列表