由于工作需要,调研过一段时间的工业控制方面的“组态软件”(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 millisecondsint 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 GraphPaneGraphPane myPane = zedGraphControl1.GraphPane;// Set the titlesmyPane.Title.Text = "实时数据";myPane.YAxis.Title.Text = "数据";myPane.XAxis.Title.Text = "时间";// Change the color of the titlemyPane.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 valuesRollingPointPairList list = new RollingPointPairList(1200);// Initially, a curve is added with no data points (list is empty)// Color is blue, and there will be no symbolsLineItem myCurve = myPane.AddCurve("温度值", list, Color.Blue, SymbolType.None);// Fill the area under the curvesmyCurve.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 whitemyCurve.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 jumpsmyPane.XAxis.Scale.Min = 0;myPane.XAxis.Scale.Max = 100;myPane.XAxis.Scale.MinorStep = 1;myPane.XAxis.Scale.MajorStep = 5;// Add gridlines to the plotmyPane.XAxis.MajorGrid.IsVisible = true;myPane.XAxis.MajorGrid.Color = Color.LightGray;myPane.YAxis.MajorGrid.IsVisible = true;myPane.YAxis.MajorGrid.Color = Color.LightGray;// Scale the axeszedGraphControl1.AxisChange();// Save the beginning time for referencetickStart = Environment.TickCount;}void AirMachine_Load(object sender, EventArgs e){CreateGraph();}private void UpdateZedGraph(double yValue){// Make sure that the curvelist has at least one curveif (zedGraphControl1.GraphPane.CurveList.Count <= 0)return;// Get the first CurveItem in the graphLineItem curve = zedGraphControl1.GraphPane.CurveList[0] as LineItem;if (curve == null)return;// Get the PointPairListIPointListEdit 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 itif (list == null)return;// Time is measured in secondsdouble 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 axisScale 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 datazedGraphControl1.AxisChange();// Force a redrawzedGraphControl1.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/

C#自定义工业控件开发相关推荐

  1. VB.NET怎样开发自定义Windows控件

    前言 Microsoft® Visual Basic® 的组件支持历来都是它的一大卖点,于是第三方软件开发商们纷纷开发出各种具有新功能性的可视控件 (也有少数非可视控件) 供 Visual Basic ...

  2. Android插件化开发指南——实践之仿酷狗音乐首页(自定义ImageView控件)

    文章目录 1. 前言 2. 基础环境--实现RecyclerView的网格布局 3. 自定义ImageView 3. 后记 1. 前言 拟定实现效果部分为下图的歌单列表部分,也就是图中红线框出来的部分 ...

  3. Asp.net控件开发学习笔记(三)-控件开发基础

    封装      在asp.net中,控件被分为两类.用户控件和自定义服务器控件.前者就是我们经常用来将一些可复用的内容封装成的.ascx文件.这里主要研究后者. 创建自定义服务器控件      创建自 ...

  4. ASP.NET自定义控件组件开发 第四章 组合控件开发CompositeControl

    第四章 组合控件开发CompositeControl 大家好,今天我们来实现一个自定义的控件,之前我们已经知道了,要开发自定义的控件一般继承三个基 类:Control,WebControl,还有一个就 ...

  5. 今天你写控件了吗?----ASP.net控件开发系列(三)

    属性全接触(一) 本系列上篇文章有几位抬爱,鼓励了几句,所以劲头又足了,这不,这篇文章就出得快了,:) 希望能继续得到鼓励和指正. 这次我们来探讨下控件开发中的属性设计的方方面面,属性本是各种.net ...

  6. [转] 使用模板自定义 WPF 控件

      [转] 使用模板自定义 WPF 控件                                                                                 ...

  7. android 原理 组合控件_Android自定义控件进阶01-自定义控件开发套路与流程

    Android自定义控件进阶01-自定义控件开发套路与流程本章节为什么要叫进阶篇?(虽然讲的是基础内容),因为从本篇开始,将会逐渐揭开自定义View的神秘面纱,每一篇都将比上一篇内容更加深入,利用所学 ...

  8. [转]利用ASP.NET 2.0创建自定义Web控件(1)

    原址:http://hi.baidu.com/sjbh/blog/item/cc58fd1bd35d3ad2ad6e7593.html   简介 从使用基本的文本编辑器到创作标记页面,Web 开发已经 ...

  9. .Net(C#)自定义WinForm控件之小结篇(强力推荐)

    强力推荐的网站:http://www.myfirm.cn/News/dotNetGUIAPI/ 本文转载:http://www.myfirm.cn/News/dotNetUserInterface/2 ...

最新文章

  1. 将会改变未来IT世界的十种编程语言
  2. Eclipse SDK构建J2EE开发环境
  3. 如何修复“ sudo:不存在tty且未指定AskPass程序”错误?
  4. jQuery 追加元素的方法如append、prepend、before
  5. python如何爬虫-如何入门 Python 爬虫?
  6. 在 Android Studio 2.2 中愉快地使用 C/C++
  7. 数据类型_分享redis中除5种基础数据类型以外的高级数据类型
  8. [剑指offer]面试题17:合并两个排序的链表
  9. idea连接sqlserver及数据库操作
  10. OpenStack Glance(镜像服务)基础架构:Basic architecture
  11. 导航栏透明度渐变; 下拉头视图拉伸效果;勾号动画; 一段文字中点击部分可响应不同事件...
  12. mysql vc运行库,VC运行库版本 - robslove的个人页面 - OSCHINA - 中文开源技术交流社区...
  13. python调用r语言函数_Python 中使用R语言
  14. 操作系统进程通信实验
  15. Ubuntu输入简体中文变成繁体
  16. 飞凌OK6410A 多媒体视频编解码 player-qt4 QT视频播放器
  17. 网卡调优RSS、RPS、RFS和XPS
  18. 2022-2023级西安交通大学MBA提前面试(预报名即将开启)6月初
  19. HTML5期末大作业:在线电影网站设计——电影速递网(12页面)含登录注册HTML+CSS+JavaScript
  20. Python直方图美化:多颜色二维直方图(内附色卡)

热门文章

  1. 项目服务器有15个能说明什么,15.1 我的面试经历 by smyhvae - 前端入门进阶
  2. 聚类分析在用户行为中的实例_聚类分析在用户分类中的应用
  3. bmp调色板颜色信息重复_PASCAL VOC数据集-分割标签索引颜色对照及程序
  4. oracle数据库风险链接,数据库的风险主要来源
  5. php 降序 保留索引,PHP asort():对数组排序(升序),并保持索引关系
  6. java 生成 防伪码,优秀的生成防伪码的代码应该如何写?百万千万量级别的。
  7. python 网页自动处理_推荐一款 10 行 Python 代码实现网页自动化工具
  8. mysql数据库的字符集设置_mysql数据库字符集设置
  9. powershell获取linux文件,powershell如何读取文件名并赋值到变量?
  10. 测试几款大型LED的反向电流大小