• 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/

  • 相关阅读:
    Python学习之列表
    Python学习笔记
    Linux基础命令总结
    CentOS6.6安装mysql-5.7.25二进制安装包简易教程
    执行 cobbler get-loaders报错
    windows下 Qt 安装 taglib 获取媒体信息windows
    Qt dropEvent和dragEnterEvent问题
    Qt---去掉标题栏后,最大化应用程序窗口时,窗口遮住了任务栏的问题
    Qt 单击任务栏图标实现最小化
    Qt 无边框窗口的两种实现
  • 原文地址:https://www.cnblogs.com/Alex80/p/6535322.html
Copyright © 2020-2023  润新知