zoukankan      html  css  js  c++  java
  • C#自定义工业控件开发

    转自阿凡卢原文C#自定义工业控件开发

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

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

    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Drawing;
    using System.Data;
    using System.Linq;
    using System.Text;
    using System.Windows.Forms;
    
    namespace HMIControls
    {
        public partial class ThermometerControl : UserControl
        {
            /// <summary>
            /// 初始化控件
            /// 预设绘图方式:双缓冲、支持透明背景色、自定义绘制
            /// </summary>
            public ThermometerControl()
            {
                SetStyle(ControlStyles.AllPaintingInWmPaint, true);
                SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
                SetStyle(ControlStyles.ResizeRedraw, true);
                SetStyle(ControlStyles.Selectable, true);
                SetStyle(ControlStyles.SupportsTransparentBackColor, true);
                SetStyle(ControlStyles.UserPaint, true);
    
                InitializeComponent();
            }
    
            // 温度
            private float temperature = 0;
            [Category("温度"), Description("当前温度")]
            public float Temperature
            {
                set { temperature = value; }
                get { return temperature; }
            }
    
            // 最高温度
            private float highTemperature = 50;
            [Category("温度"), Description("最高温度")]
            public float HighTemperature
            {
                set { highTemperature = value; }
                get { return highTemperature; }
            }
    
            // 最低温度
            private float lowTemperature = -20;
            [Category("温度"), Description("最低温度")]
            public float LowTemperature
            {
                set { lowTemperature = value; }
                get { return lowTemperature; }
            }
    
            // 当前温度数值的字体
            private Font tempFont = new Font("宋体", 12);
            [Category("温度"), Description("当前温度数值的字体")]
            public Font TempFont
            {
                set { tempFont = value; }
                get { return tempFont; }
            }
    
            // 当前温度数值的颜色
            private Color tempColor = Color.Black;
            [Category("温度"), Description("当前温度数值的颜色")]
            public Color TempColor
            {
                set { tempColor = value; }
                get { return tempColor; }
            }
    
            // 大刻度线数量
            private int bigScale = 5;
            [Category("刻度"), Description("大刻度线数量")]
            public int BigScale
            {
                set { bigScale = value; }
                get { return bigScale; }
            }
    
            // 小刻度线数量
            private int smallScale = 5;
            [Category("刻度"), Description("小刻度线数量")]
            public int SmallScale
            {
                set { smallScale = value; }
                get { return smallScale; }
            }
    
            // 刻度字体
            private Font drawFont = new Font("Aril", 9);
            [Category("刻度"), Description("刻度数字的字体")]
            public Font DrawFont
            {
                get { return drawFont; }
                set { drawFont = value; }
            }
    
            // 字体颜色
            private Color drawColor = Color.Black;
            [Category("刻度"), Description("刻度数字的颜色")]
            public Color DrawColor
            {
                set { drawColor = value; }
                get { return drawColor; }
            }
    
            // 刻度盘最外圈线条的颜色
            private Color dialOutLineColor = Color.Gray;
            [Category("背景"), Description("刻度盘最外圈线条的颜色")]
            public Color DialOutLineColor
            {
                set { dialOutLineColor = value; }
                get { return dialOutLineColor; }
            }
    
            // 刻度盘背景颜色
            private Color dialBackColor = Color.Gray;
            [Category("背景"), Description("刻度盘背景颜色")]
            public Color DialBackColor
            {
                set { dialBackColor = value; }
                get { return dialBackColor; }
            }
    
            // 大刻度线颜色
            private Color bigScaleColor = Color.Black;
            [Category("刻度"), Description("大刻度线颜色")]
            public Color BigScaleColor
            {
                set { bigScaleColor = value; }
                get { return bigScaleColor; }
            }
    
            // 小刻度线颜色
            private Color smallScaleColor = Color.Black;
            [Category("刻度"), Description("小刻度线颜色")]
            public Color SmallScaleColor
            {
                set { smallScaleColor = value; }
                get { return smallScaleColor; }
            }
    
            // 温度柱背景颜色
            private Color mercuryBackColor = Color.LightGray;
            [Category("刻度"), Description("温度柱背景颜色")]
            public Color MercuryBackColor
            {
                set { mercuryBackColor = value; }
                get { return mercuryBackColor; }
            }
    
            // 温度柱颜色
            private Color mercuryColor = Color.Red;
            [Category("刻度"), Description("温度柱颜色")]
            public Color MercuryColor
            {
                set { mercuryColor = value; }
                get { return mercuryColor; }
            }
    
            /// <summary>
            ///  变量
            /// </summary>
            private float X;
            private float Y;
            private float H;
            private Pen p, s_p;
            private Brush b;
    
            /// <summary>
            /// 绘制温度计
            /// </summary>
            /// <param name="sender"></param>
            /// <param name="e"></param>
            private void ThermometerControl_Paint(object sender, PaintEventArgs e)
            {
                // 温度值是否在温度表最大值和最小值范围内
                if (temperature > highTemperature)
                {
                    //MessageBox.Show("温度值超出温度表范围,系统自动设置为默认值!");
                    temperature = highTemperature;
                }
                if (temperature < lowTemperature)
                {
                    temperature = lowTemperature;
                }
    
                e.Graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
                e.Graphics.TranslateTransform(2, 2);
    
                X = this.Width - 4;
                Y = this.Height - 4;
    
                // 绘制边框(最外边的框)
                p = new Pen(dialOutLineColor, 2);
                e.Graphics.DrawLine(p, 0, X / 2, 0, (Y - X / 2));
                e.Graphics.DrawLine(p, X, X / 2, X, (Y - X / 2));
                e.Graphics.DrawArc(p, 0, 0, X, X, 180, 180);
                e.Graphics.DrawArc(p, 0, (Y - X), X, X, 0, 180);
    
                // 绘制背景色
                X = X - 8;
                Y = Y - 8;
                b = new SolidBrush(dialBackColor);
                e.Graphics.TranslateTransform(4, 4);
                e.Graphics.FillRectangle(b, 0, X / 2, X, (Y - X));
                e.Graphics.FillEllipse(b, 0, 0, X, X);
                e.Graphics.FillEllipse(b, 0, (Y - X), X, X);
    
                // 绘制指示柱
                b = new SolidBrush(mercuryBackColor);
                e.Graphics.FillEllipse(b, X * 2 / 5, (X / 2 - X / 10), X / 5, X / 5);
                b = new SolidBrush(mercuryColor);
                e.Graphics.FillEllipse(b, X / 4, (Y - X * 9 / 16), X / 2, X / 2);
                e.Graphics.FillRectangle(b, X * 2 / 5, (X / 2 + 1), X / 5, (Y - X));
    
                // 在温度计底部,绘制当前温度数值
                b = new SolidBrush(tempColor);
                StringFormat format = new StringFormat();
                format.LineAlignment = StringAlignment.Center;
                format.Alignment = StringAlignment.Center;
                e.Graphics.DrawString((temperature.ToString() + ""), tempFont, b, X / 2, (Y - X / 4), format);
    
                // 绘制大刻度线,线宽为2
                // 绘制小刻度线,线宽为1
                // 绘制刻度数字,字体,字号,字的颜色在属性中可改
                p = new Pen(bigScaleColor, 2);                        // 设置大刻度线的颜色,线粗
                s_p = new Pen(smallScaleColor, 1);                      // 设置小刻度线的颜色,线粗
                SolidBrush drawBrush = new SolidBrush(drawColor);   // 设置绘制数字的颜色
                format.Alignment = StringAlignment.Near;            // 设置数字水平对齐为中间,垂直对其为左边
                // 计算要绘制数字的数值
                int interval = (int)(highTemperature - lowTemperature) / bigScale;
                int tempNum = (int)highTemperature;
                for (int i = 0; i <= bigScale; i++)
                {
                    float b_s_y = X / 2 + i * ((Y - X - X / 2) / bigScale);       // 绘制大刻度线的垂直位置
                    e.Graphics.DrawLine(p, X / 5, b_s_y, (X * 2 / 5 - 2), b_s_y); // 绘制大刻度线
                    e.Graphics.DrawString(tempNum.ToString(), drawFont, drawBrush, X * 3 / 5, b_s_y, format);   // 绘制刻度数字
                    tempNum -= interval;    // 计算下一次要绘制的数值
    
                    // 绘制小刻度线
                    if (i < bigScale)
                    {
                        for (int j = 1; j < smallScale; j++)
                        {
                            float s_s_y = b_s_y + ((X / 2 + (i + 1) * ((Y - X - X / 2) / bigScale) - b_s_y) / smallScale) * j;
                            e.Graphics.DrawLine(s_p, (X * 3 / 10), s_s_y, (X * 2 / 5 - 2), s_s_y);
                        }
                    }
                }
                
                // 计算当前温度的位置            
                float L = Y - X * 3 / 2;
                H = L * (temperature - lowTemperature) / (highTemperature - lowTemperature);
                // 绘制当前温度的位置
                b = new SolidBrush(mercuryBackColor);
                e.Graphics.FillRectangle(b, X * 2 / 5, X / 2, X / 5, (L - H));
            }
     }

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

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

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

    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Drawing;
    using System.Data;
    using System.Linq;
    using System.Text;
    using System.Windows.Forms;
    using ZedGraph;
    
    namespace HMIControls
    {
        public partial class AirMachine : UserControl
        {
            private bool isValveOn;
            private Timer timer;
            private double temperature;
            private Random random = new Random();
    
            private Point arrowLocation1;
            private Point arrowLocation2;
            private Point arrowLocation3;
    
            // Starting time in milliseconds
            int tickStart = 0;
    
            public AirMachine()
            {
                InitializeComponent();
                InitUI();
            }
    
            private void InitUI()
            {
                isValveOn = false;
                this.labelTemperature.Text = "0";
                this.button1.Text = "";
                this.button1.BackColor = Color.Snow;
                timer = new Timer();
                timer.Interval = 1000;
                timer.Tick += new EventHandler(timer_Tick);
                this.Load += new EventHandler(AirMachine_Load);
    
                this.labelArrow1.Visible = false;
                this.labelArrow2.Visible = false;
                this.labelArrow3.Visible = false;
    
                arrowLocation1 = this.labelArrow1.Location;
                arrowLocation2 = this.labelArrow2.Location;
                arrowLocation3 = this.labelArrow3.Location;
    
                this.button1.Click += new EventHandler(button1_Click);
            }
    
            private void CreateGraph()
            {
                zedGraphControl1.IsEnableZoom = false;
                zedGraphControl1.IsShowContextMenu = false;
    
                // Get a reference to the GraphPane
                GraphPane myPane = zedGraphControl1.GraphPane;
    
                // Set the titles
                myPane.Title.Text = "实时数据";
                myPane.YAxis.Title.Text = "数据";
                myPane.XAxis.Title.Text = "时间";
    
                // Change the color of the title
                myPane.Title.FontSpec.FontColor = Color.Green;
                myPane.XAxis.Title.FontSpec.FontColor = Color.Green;
                myPane.YAxis.Title.FontSpec.FontColor = Color.Green;
    
    
                // Save 1200 points.  At 50 ms sample rate, this is one minute
                // The RollingPointPairList is an efficient storage class that always
                // keeps a rolling set of point data without needing to shift any data values
                RollingPointPairList list = new RollingPointPairList(1200);
    
                // Initially, a curve is added with no data points (list is empty)
                // Color is blue, and there will be no symbols
                LineItem myCurve = myPane.AddCurve("温度值", list, Color.Blue, SymbolType.None);
    
                // Fill the area under the curves
                myCurve.Line.Fill = new Fill(Color.White, Color.Blue, 45F);
    
                myCurve.Line.IsSmooth = true;
                myCurve.Line.SmoothTension = 0.5F;
    
                // Increase the symbol sizes, and fill them with solid white
                myCurve.Symbol.Size = 8.0F;
                myCurve.Symbol.Fill = new Fill(Color.Red);
                myCurve.Symbol.Type = SymbolType.Circle;
    
                // Just manually control the X axis range so it scrolls continuously
                // instead of discrete step-sized jumps
                myPane.XAxis.Scale.Min = 0;
                myPane.XAxis.Scale.Max = 100;
                myPane.XAxis.Scale.MinorStep = 1;
                myPane.XAxis.Scale.MajorStep = 5;
    
                // Add gridlines to the plot
                myPane.XAxis.MajorGrid.IsVisible = true;
                myPane.XAxis.MajorGrid.Color = Color.LightGray;
                myPane.YAxis.MajorGrid.IsVisible = true;
                myPane.YAxis.MajorGrid.Color = Color.LightGray;
    
                // Scale the axes
                zedGraphControl1.AxisChange();
    
                // Save the beginning time for reference
                tickStart = Environment.TickCount;
            }
    
            void AirMachine_Load(object sender, EventArgs e)
            {
                CreateGraph();
            }
    
            private void UpdateZedGraph(double yValue)
            {
                // Make sure that the curvelist has at least one curve
                if (zedGraphControl1.GraphPane.CurveList.Count <= 0)
                    return;
    
                // Get the first CurveItem in the graph
                LineItem curve = zedGraphControl1.GraphPane.CurveList[0] as LineItem;
                if (curve == null)
                    return;
    
                // Get the PointPairList
                IPointListEdit list = curve.Points as IPointListEdit;
                // If this is null, it means the reference at curve.Points does not
                // support IPointListEdit, so we won't be able to modify it
                if (list == null)
                    return;
    
                // Time is measured in seconds
                double time = (Environment.TickCount - tickStart) / 1000.0;
    
                // 3 seconds per cycle
                //list.Add(time, Math.Sin(2.0 * Math.PI * time / 3.0));
                list.Add(time, yValue);
    
                // Keep the X scale at a rolling 30 second interval, with one
                // major step between the max X value and the end of the axis
                Scale xScale = zedGraphControl1.GraphPane.XAxis.Scale;
                if (time > xScale.Max - xScale.MajorStep)
                {
                    xScale.Max = time + xScale.MajorStep;
                    xScale.Min = xScale.Max - 100.0;
                }
    
                // Make sure the Y axis is rescaled to accommodate actual data
                zedGraphControl1.AxisChange();
                // Force a redraw
                zedGraphControl1.Invalidate();
            }
    
            private void UpdataArrowPosition()
            {
                this.labelArrow1.Location = new Point(this.labelArrow1.Location.X + 30, this.labelArrow1.Location.Y);
                if (this.labelArrow1.Location.X >= this.panelPic.Location.X + this.panelPic.Width)
                {
                    this.labelArrow1.Location = arrowLocation1;
                }
    
                this.labelArrow2.Location = new Point(this.labelArrow2.Location.X + 30, this.labelArrow2.Location.Y);
                if (this.labelArrow2.Location.X >= this.panelPic.Location.X + this.panelPic.Width)
                {
                    this.labelArrow2.Location = arrowLocation2;
                }
    
                this.labelArrow3.Location = new Point(this.labelArrow3.Location.X + 30, this.labelArrow3.Location.Y);
                if (this.labelArrow3.Location.X >= this.panelPic.Location.X + this.panelPic.Width)
                {
                    this.labelArrow3.Location = arrowLocation3;
                }
            }
    
            void timer_Tick(object sender, EventArgs e)
            {
                temperature = random.NextDouble() * 100;
                this.labelTemperature.Text = Convert.ToInt32(temperature).ToString();
    
                UpdateZedGraph(temperature);
    
                UpdataArrowPosition();
            }
    
            private void button1_Click(object sender, EventArgs e)
            {
                isValveOn = !isValveOn;
                if (isValveOn)
                {
                    timer.Start();
                    this.button1.Text = "";
                    this.button1.BackColor = Color.LawnGreen;
                    this.labelTemperature.BackColor = Color.LawnGreen;
                    this.labelArrow1.Visible = isValveOn;
                    this.labelArrow2.Visible = isValveOn;
                    this.labelArrow3.Visible = isValveOn;
                }
                else
                {
                    timer.Stop();
                    this.button1.Text = "";
                    this.button1.BackColor = Color.Snow;
                    this.labelTemperature.Text = "0";
                    this.labelTemperature.BackColor = Color.Snow;
                    this.labelArrow1.Visible = isValveOn;
                    this.labelArrow2.Visible = isValveOn;
                    this.labelArrow3.Visible = isValveOn;
                }
            }
        }
    }

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

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

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

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

    http://www.codeproject.com/Articles/17559/A-fast-and-performing-gauge

    http://dashboarding.codeplex.com/

  • 相关阅读:
    gain 基尼系数
    luogu P5826 【模板】子序列自动机 主席树 vector 二分
    牛客挑战赛39 树与异或 离线 树上莫队 树状数组 约数
    4.22 省选模拟赛 三元组 manacher 回文自动机
    4.22 省选模拟赛 最优价值 网络流 最大权闭合子图
    4.18 省选模拟赛 消息传递 树剖 倍增 线段树维护等比数列
    luogu P4008 [NOI2003]文本编辑器 splay 块状链表
    牛客挑战赛39 密码系统 后缀数组
    luogu P1526 [NOI2003]智破连环阵 搜索+最大匹配+剪枝
    luogu P4095 [HEOI2013]Eden 的新背包问题 多重背包 背包的合并
  • 原文地址:https://www.cnblogs.com/arxive/p/6266489.html
Copyright © 2011-2022 走看看