最近学习测试用C#开发OPC客户端连接OPC服务器,防止遗忘,记录学习测试结果。
1、OPC基础知识
1.1、OPC概述
OPC是Object Linking and Embedding(OLE)for Process Control 的缩写,它是微软公司的对象链接和嵌入技术在过程控制方面的应用。OPC以OLE/COM/DCOM技术为基础,采用客户/服务器模式,为工业自动化软件面向对象的开发提供了统一的标准,这个标准定义了应用Microsoft操作系统在基于PC的客户机之间交换自动化实时数据的方法,采用这项标准后,硬件开发商将取代软件开发商为自己的硬件产品开发统一的OPC接口程序,而软件开发者可免除开发驱动程序的工作,充分发挥自己的特长,把更多的精力投入到其核心产品的开发上。
1.2、OPC读写方式
在实际使用中,主要包括对现场数据的读写操作。
OPC读数有三种方式:同步、异步、订阅。
同步通讯时,OPC客户程序向OPC服务器进行请求时,OPC客户程序必须等到OPC服务器对应的响应全部完成以后才能返回,在此期间OPC客户程序一直处于等待状态,若进行读操作,那么必须等待OPC服务器响应后才返回。因此在同步通讯时,如果有大量数据进行操作或者有很多OPC客户程序对OPC服务器进行读操作,必然造成OPC客户程序的阻塞现象。因此同步通讯适用于OPC客户程序较少,数据量较小时的场合。
异步通讯时,OPC客户程序对服务器进行请求时,OPC客户程序请求后立刻返回,不用等待OPC服务器的响应,可以进行其它操作。OPC服务器完成响应后再通知OPC客户程序,如进行读操作,OPC客户程序通知OPC服务器后离开返回,不等待OPC服务器的读完成,而OPC服务器完成读后,会自动的通知OPC客户程序,把读结果传送给OPC客户程序。因此相对于同步通讯,异步通讯的效率更高。
订阅方式时,OPC客户程序对服务器进行请求时,OPC客户程序操作后立刻返回,不用等待OPC服务器的操作,可以进行其它操作, OPC 服务器的Group组在组内有数据发生改变时,自动根据更新周期刷新相应的客户端数据,如下图,客户端只向OPC服务发送一次请求,之后不再对服务器请求。
OPC写数有两种方式:同步、异步。区别与上面讲的机制一样,在生产应用中,如果写数据参与控制,一般采用同步方式。
OPC访问接口方式:
OPC主要包含两种接口:CUSTOM标准接口和OLE自动化标准接口,自定义接口是服务商必须提供的,而自动化接口则是可选的。
自定义接口是一组COM接口,主要用于采用C++语言的应用程序开发;
自动化接口是一组OLE接口,主要用于采用VB,DELPHI,Excel等基于脚本编程语言的应用程序开发。
2、OPC客户端接口方式
开发OPC客户端程序,其访问接口方式有多种,根据官方提供的资料大约有如下几种方式:
1>、使用OPCNetAPI,需要用到OPCNetAPI.dll,OPCNetAPI.Com.dll
2>、使用自动化接口,需要用到OPCDAAuto.dll
3>、使用自定义接口,需要用到多个Wrapper:OpcRcw.Ae.dll,OpcRcw.Batch.dll,OpcRcw.Comn.dll,OpcRcw.Da.dll,OpcRcw.Dx.dll,OpcRcw.Hda.dll,OpcRcw.Sec.dll
对于像C++这样的语言来开发OPC客户端时,一般需要使用自定义接口的方式。而如果采用VB和C#这样的语言来开发OPC客户端时,一般就采用自动化接口。要使用OPC自动化接口,首先要引用OPCDAAuto.dll文件,并在开发环境中做好相关的引用配置。
3、自动化接口简介
自动化接口是OPC基金会组织为了方便并统一OPC客户端开发而发布的一个接口、属性和方法的协议集。
自动化接口中共定义了6类对象:OPCServer对象、OPCBrowser对象、OPCGroups对象、OPCGroup对象、OPCItems对象、OPCItem对象。接下来简要描述一下这些对象的主要功能。
3.1、OPCServer对象
由客户端创建的OPCServer自动化对象。然后客户端通过其方法实现连接到OPC数据访问自定义接口。OPCServer对象现在可以用来获取关于OPC服务器的一般信息,并创建和操作OPCGroup对象的集合。
主要的属性:
StartTime | CurrentTime | LastUpdateTime |
---|---|---|
MajorVersion | MinorVersion | BuildNumber |
VendorInfo | ServerState | LocaleID |
Bandwidth | OPCGroups | PublicGroupNames |
ServerName | ServerNode | ClientName |
主要的方法:
GetOPCServers | Connect | Disconnect |
---|---|---|
CreateBrowser | GetErrorString | QueryAvailableLocaleIDs |
QueryAvailableProperties | GetItemProperties | LookupItemIDs |
3.2、OPCBrowser对象
OPCBrowser对象是在服务器中存在的分支或项目名称的集合。其是可选的。如果服务器不支持,将不会创建这个对象。
主要的属性:
Organization | Filter | DataType |
---|---|---|
AccessRights | CurrentPosition | Count |
主要的方法:
Item | ShowBranches | ShowLeafs |
---|---|---|
MoveUp | MoveToRoot | MoveDown |
MoveTo | GetItemID | GetAccessPaths |
3.3、OPCGroups对象
OPCGroups是OPCGroup对象的集合,以及创建、删除和管理它们的方法。
该对象还具有OPCGroup默认属性。当添加OPCGroups时,DefaultGroupXXXX属性设置其初始状态。可以更改默认值,以添加具有不同初始状态的opc组。更改默认值并不会影响已经创建的组。添加OPCGroup后,它的属性可以被修改。这减少了调用Add方法所需的参数数量。
主要的属性:
Parent | DefaultGroupIsActive | DefaultGroupUpdateRate |
---|---|---|
DefaultGroupDeadband | DefaultGroupLocaleID | DefaultGroupTimeBias |
Count |
主要的方法:
Item | Add | GetOPCGroup |
---|---|---|
Remove | RemoveAll | ConnectPublicGroup |
RemovePublicGroup |
3.4、OPCGroup对象
OPC组为客户组织数据提供了一种方式。例如,组可能表示特定操作符显示或报告中的项。数据可以读写。基于异常的连接也可以在客户端和组中的项之间创建,可以根据需要启用和禁用。OPC客户机可以配置OPC服务器应该向OPC客户机提供数据更改的速率。
主要的属性:
Parent | Name | IsPublic |
---|---|---|
IsActive | IsSubscribed | ClientHandle |
ServerHandle | LocaleID | TimeBias |
DeadBand | UpdateRate | OPCItems |
主要的方法:
SyncRead | SyncWrite | AsyncRead |
---|---|---|
AsyncWrite | AsyncRefresh | AsyncCancel |
3.5、OPCItems对象
这个对象还具有OPCItem默认的属性。当添加OPCItem时,DefaultXXXX属性设置其初始状态。可以更改默认值,以添加具有不同初始状态的OPCItems。当然,一旦添加了OPCItem,它的属性可以被修改。这减少了调用Add方法所需的参数数量。
主要的属性:
Parent | DefaultRequestedDataType | DefaultAccessPath |
---|---|---|
DefaultIsActive | Count |
主要的方法:
Item | GetOPCItem | AddItem |
---|---|---|
AddItems | Remove | Validate |
SetActive | SetClientHandles | SetDataTypes |
3.6、OPCItem对象
OPC项表示与服务器中的数据源的连接。与每个项目相关联的是一个值,质量和时间戳。值以变量的形式出现,质量类似于Fieldbus指定的值。
主要的属性:
Parent | ClientHandle | ServerHandle |
---|---|---|
AccessPath | AccessRights | ItemID |
IsActive | RequestedDataType | Value |
Quality | TimeStamp | CanonicalDataType |
EUType | EUInfo |
主要的方法:
Read | Write |
4、OPC Client的开发
4.1、OPC Client 访问OPC 服务器的步骤
1、 连接 OPC 服务器
2、 创建 Group 到连接
3、遍历OPC服务器的所有的leaf item, 添加需要的 leaf item 到 Group
4、 现在可以
读取 item 的值
监听 Group 的 DataChange 事件,获取 item 的最新值
调用 Group 的方法异步修改 item 的值
调用 item 的方法同步修改值
4.2、代码示例
4.2.1、代码示例1
首先,封装一斜对象、用于存取相关的属性,如:OPC服务器信息(OPCServerInfo)、OPC数据项(OPCDataItem)、组属性(GroupProperty)等。对于组属性我们还需要赋予默认值。代码如下:
public class OPCServerInfo
{
public DateTime StartTime { get; set; }
public string ServerVersion { get; set; }
}
public class OPCDataItem
{
public object ItemName { get; set; }
public object ItemValue { get; set; }
public object Quality { get; set; }
public object TimeStamp { get; set; }
}
public class GroupProperty
{
public bool DefaultGroupIsActive { get; set; }
public float DefaultGroupDeadband { get; set; }
public int UpdateRate { get; set; }
public bool IsActive { get; set; }
public bool IsSubscribed { get; set; }
public GroupProperty()
{
DefaultGroupIsActive = true;
DefaultGroupDeadband = 0;
UpdateRate = 250;
IsActive = true;
IsSubscribed = true;
}
}
接下来,为了使用方便我们封装了一个ClientHelper类用于实现相关的操作,应为在一个客户端应用中,该对象是唯一的我们为了使用方便将其声明为静态类,以便于使用。具体代码如下:
public class ClientHelper
{
/// <summary>
/// 获取可以使用的OPC服务器
/// </summary>
/// <param name="hostName">获取OPC服务器的主机名称</param>
/// <returns>返回OPC服务器列表</returns>
public static List<string> GetOPCServerName(string hostName)
{
try
{
OPCServer OpcServer = new OPCServer();
object opcServers = OpcServer.GetOPCServers(hostName);
List<string> serverList = new List<string>();
foreach (string opcServer in (Array)opcServers)
{
serverList.Add(opcServer);
}
return serverList;
}
catch(Exception ex)
{
throw ex;
}
}
/// <summary>
/// 连接到指定的OPC服务器
/// </summary>
/// <param name="serverName">服务器名称</param>
/// <param name="serverIP">服务器IP</param>
/// <returns>返回的OPC服务器</returns>
public static OPCServer ConnectToServer(string serverName, string serverIP)
{
OPCServer opcServer = new OPCServer();
try
{
opcServer.Connect(serverName, serverIP);
if (opcServer.ServerState != (int)OPCServerState.OPCRunning)
{
opcServer.Disconnect();
return null;
}
}
catch
{
opcServer.Disconnect();
return null;
}
return opcServer;
}
/// <summary>
/// 获取OPC服务器的相关信息
/// </summary>
/// <param name="opcServer">OPC服务器对象</param>
/// <returns>OPC服务器信息</returns>
public static OPCServerInfo GetServerInfo(OPCServer opcServer)
{
OPCServerInfo serverInfo = new OPCServerInfo();
serverInfo.StartTime=opcServer.StartTime;
serverInfo.ServerVersion = opcServer.MajorVersion.ToString() + "." + opcServer.MinorVersion.ToString() + "." + opcServer.BuildNumber.ToString();
return serverInfo;
}
/// <summary>
/// 展开OPC服务器的节点
/// </summary>
/// <param name="opcServer">OPC服务器</param>
/// <returns>返回展开后的节点数据</returns>
public static OPCBrowser RecurBrowse(OPCServer opcServer)
{
OPCBrowser opcBrowser = opcServer.CreateBrowser();
//展开分支
opcBrowser.ShowBranches();
//展开叶子
opcBrowser.ShowLeafs(true);
return opcBrowser;
}
public static OPCGroup CreateGroup(OPCServer opcServer, OPCItems opcItems, string opcGroupName, GroupProperty groupProperty)
{
try
{
OPCGroup opcGroup = opcServer.OPCGroups.Add(opcGroupName);
opcServer.OPCGroups.DefaultGroupIsActive = groupProperty.DefaultGroupIsActive;
opcServer.OPCGroups.DefaultGroupDeadband = groupProperty.DefaultGroupDeadband;
opcGroup.UpdateRate = groupProperty.UpdateRate;
opcGroup.IsActive = groupProperty.IsActive;
opcGroup.IsSubscribed = groupProperty.IsSubscribed;
//opcGroup.DataChange += new DIOPCGroupEvent_DataChangeEventHandler(OpcGroupDataChange);
//opcGroup.AsyncWriteComplete += new DIOPCGroupEvent_AsyncWriteCompleteEventHandler(KepGroup_AsyncWriteComplete);
//opcItems = opcGroup.OPCItems;
return opcGroup;
}
catch (Exception err)
{
throw err;
}
}
}
4.2.2、代码示例2
C#自动化接口
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Collections;
using OPCSiemensDAAutomation;//引用连接库
第一步,添加下列命名空间(首先在COM组件中添加相应组件)
using OPCSiemensDAAutomation;
第二步,定义OPC相关变量
OPCServer MyOpcServer; //定义OPCServer
OPCGroup MyOpcGroup; //定义组
OPCItem MyOpcItem1; //Item
OPCItem MyOpcItem2; //值
long[] ServerHandle = new long[2]; //Item的句柄
第三步,建立连接及对象
MyOpcServer = new OPCServer();
MyOpcServer.Connect("OPC.SimaticNet", "192.168.0.102");
MyOpcGroup = MyOpcServer.OPCGroups.Add("MyGroup1");
MyOpcItem1 = MyOpcGroup.OPCItems.AddItem("S7:[S7 connection_1]DB10,INT0",1);
MyOpcItem2 = MyOpcGroup.OPCItems.AddItem("S7:[S7 connection_1]DB10,INT2", 2);
ServerHandle[0] = MyOpcItem1.ServerHandle;
ServerHandle[1] = MyOpcItem2.ServerHandle;
第四步,同步读数据,
private void Btn_Read_S_Click(object sender,EventArgs e)//同步读数据
{
……
MyOpcItem1.Read(1,out ItemValues,out Qualities, out TimeStamps);
//ItemValues,Qualities,TimeStamps分别是值,质量码
//也可以通过调用SyncRead函数,参数可参考异步读函数
……
}
第四步,同步写数据
private void Btn_Write_S_Click(object sender,EventArgs e)
{
……
MyOpcItem1.Write(Txt_W1.Text);
//也可以通过调用SyncWrite函数,参数可参考异步写函数
……
}
第五步,异步事件定义,
在异步操作情况下,需要定义相应的异步事件
MyOpcGroup.DataChange +=new DIOPCGroupEvent_DataChangeEventHandler(MyOpcGroup_DataChange); //
//订阅方式下数据改变
iteComplete +=new DIOPCGroupEvent_AsyncWriteCompleteEventHandler(MyOpcGroup_WriteComplete);
//写完成事件
MyOpcGroup.AsyncReadComplete += new DIOPCGroupEvent_AsyncReadCompleteEventHandler(MyOpcGroup_ReadComplete);
//读完成事件
MyOpcGroup.AsyncCancelComplete += new DIOPCGroupEvent_AsyncCancelCompleteEventHandler(MyOpcGroup_CancelComplete);
//取消操作事件
在使用中注意,其事件函数要按照特定接口:
void MyOpcGroup_DataChange(int TransactionID, int NumItems,ref Array ClientHandles,ref Array ItemValues,ref Array Qualities, ref Array TimeStamps)
void MyOpcGroup_WriteComplete(int TransactionID, int NumItems, ref Array ClientHandles,ref Array Errors)
void MyOpcGroup_ReadComplete(int TransactionID, int NumItems,ref System.Array ClientHandles,ref System.Array ItemValues,ref System.Array Qualities,ref System.Array TimeStamps,ref System.Array Errors)
void MyOpcGroup_CancelComplete(int CancelID)
第六步订阅方式读
void MyOpcGroup_DataChange(int TransactionID, int NumItems,ref Array ClientHandles,ref Array ItemValues,ref Array Qualities, ref Array TimeStamps)
{
……
//注意数据改变时,Item数量要通过NumItems得到,也就是说只有数据改变时,才对一遍,所以降低了服务器负担。要注意读语句写法。
……
}
第七步异步读
private void Btn_Read_A_Click(object sender,EventArgs e)//异步读事件
{
int[] handle = new int[3] {ServerHandle[0], ServerHandle[1],0};//注意方式
Array MyServerHandles = (Array)handle;
Array errors;
int cancelID;
……
MyOpcGroup.AsyncRead(2, ref MyServerHandles, out errors, READASYNC_ID, out cancelID);
……
}
void MyOpcGroup_ReadComplete(int TransactionID, int NumItems,ref System.Array ClientHandles,ref System.Array ItemValues,ref System.Array Qualities,ref System.Array TimeStamps,ref System.Array Errors)
{
……
//注意TransactionID的对应
……
}
注意array在函数内部做参数时,数据下标是从1开始的,所以要考虑将第0位空出来,n个Item,就要定义n+1列数组,添加一个0,但在函数使用时,又是从左开始读的。否则会报错。
第八步异步写
private void Btn_Write_A_Click(object sender,EventArgs e)
{
……
MyOpcGroup.AsyncWrite(2,ref MyServerHandles, ref Myvalues,out errors,WRITEASYNC_ID,out cancelID);
……
}
void MyOpcGroup_WriteComplete(int TransactionID, int NumItems, ref Array ClientHandles ref Array Errors)
{
……
}
同样要注意Array在函数内部做参数的传递。
第九步释放对象
private void Btn_Disconn_Click(object sender,EventArgs e)
{
……
}
OPCItem的数据类型:
在通过自定义接口访问时,
ItemArray[1].szAccessPath = "";
ItemArray[1].szItemID = "S7:[S7 connection_1]DB10,Real4";//地址,不同数据类型表示
ItemArray[1].bActive = 1;//是否激活
ItemArray[1].hClient = 2;//表示ID
ItemArray[1].dwBlobSize = 0;
ItemArray[1].pBlob =IntPtr.Zero;
ItemArray[1].vtRequestedDataType = 5;
ItemArray[2].szAccessPath = "";
ItemArray[2].szItemID = "S7:[S7 connection_1]DB10,STRING26.10";//地址,不同数据类型表示方法不同
ItemArray[2].bActive = 1;//是否激活
ItemArray[2].hClient = 3;//表示ID
ItemArray[2].dwBlobSize = 0;
ItemArray[2].pBlob =IntPtr.Zero;
ItemArray[2].vtRequestedDataType = 8;
4.2.3、AsyncReadComplete说明
Private Sub GroupObj_AsyncReadComplete(ByVal TransactionID As Long, ByVal NumItems As Long, ClientHandles() As Long, ItemValues() As Variant, Qualities() As Long, TimeStamps() As Date, Errors() As Long)
这里的参数ClientHandles和AddItems方法中的ClientHandles是对应的,用于判断哪一个OPC Item在被读取。其它参数的说明如下:
TransactionID:客户端自由使用,应用程序开发商自定义;
NumItems:表示读取的OPC Item的个数;
ItemValues():各个OPC Item连接的数据源的值,类型为Variant;
Qualities():OPC Item的品质值;
TimeStamps():时间戳;
Errors():记录服务器返回的信息。
5、实例
5.1、OPC应用场景1
5.2、实例展示说明
刷新列表按钮:刷新可选择的服务器节点,计算机名称或IP,选择对应节点,下边选择框可以选择对应节点下的OPC服务器;
连接按钮:选中要连接的OPC服务器名称,点击连接按钮,下端会显示该服务器下所有的点位标签。
双击其中一个变量添加到下端列表,显示所点击变量的标签名称,对应数值,和刷新时间。
打开浏览器按钮:点击打开浏览器按钮可以打开浏览器并显示所要查看的平台连接。
5.3、实例主要代码
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Net;
using System.Windows.Forms;
using Microsoft.Win32;
using OPCAutomation;
namespace OPCClientDemo
{
public partial class Frm_OPCClient : Form
{
public Frm_OPCClient()
{
InitializeComponent();
this.Load += Frm_OPCClient_Load;
}
void Frm_OPCClient_Load(object sender, EventArgs e)
{
this.btn_RefreshList_Click(null, null);
this.timer1.Interval = 1000;
this.dgv_data.AutoGenerateColumns = false;
}
//定义OPC服务器
OPCServer KepServer;
//定义OPC服务器Groups
OPCGroups KepGroups;
//定义OPC服务器Group
OPCGroup KepGroup;
//定义OPC服务器Items
OPCItems KepItems;
//定义OPC服务器Brower
OPCBrowser KepBrower;
//定义OPC变量集合
List<OPCItem> OPCList = new List<OPCItem>();
List<int> ServerHandles = new List<int>();
List<int> ClientHandles = new List<int>();
List<string> TempIDList = new List<string>();
//定义返回的OPC标签错误
Array iErrors;
//定义要添加的OPC标签的标识符
Array strTempIDs;
Array strClientHandles;
Array strServerHandles;
Array readServerHandles;
Array writeServerHandles;
Array writeArrayHandles;
Array readErrors;
Array writeErrors;
int readTransID;
int writeTransID;
int readCancelID;
int writeCancelID;
private void btn_RefreshList_Click(object sender, EventArgs e)
{
//清空cmb_ServerNode下拉菜单
this.cmb_ServerNode.Items.Clear();
IPHostEntry IPHost = Dns.GetHostEntry(Environment.MachineName);
if (IPHost.AddressList.Length > 0)
{
int count = IPHost.AddressList.Length;
for (int i = 0; i < count; i++)
{
//获取计算机名称
string HostName = Dns.GetHostEntry(IPHost.AddressList[i].ToString()).HostName;
//绑定下拉菜单
if (!this.cmb_ServerNode.Items.Contains(HostName))
{
this.cmb_ServerNode.Items.Add(HostName);
}
}
}
else
{
return;
}
}
private void cmb_ServerNode_SelectedIndexChanged(object sender, EventArgs e)
{
//清空cmb_ServerName下拉菜单
this.cmb_ServerName.Items.Clear();
KepServer = new OPCServer();
object serverList = KepServer.GetOPCServers(this.cmb_ServerNode.Text.Trim());
foreach (string item in (Array)serverList)
{
if (!this.cmb_ServerName.Items.Contains(item))
{
this.cmb_ServerName.Items.Add(item);
}
}
}
private void btn_Connect_Click(object sender, EventArgs e)
{
if (this.btn_Connect.Text == "连接")
{
//连接选择的OPC服务器
KepServer = new OPCServer();
try
{
//KepServer.Connect(this.cmb_ServerName.Text.Trim(), this.cmb_ServerNode.Text.Trim());
KepServer.Connect(this.cmb_ServerName.Text, this.cmb_ServerNode.Text);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message, "连接失败");
}
//通信组集合初始化
//KepGroups对象赋值
KepGroups = KepServer.OPCGroups;
//KepGroups属性设置
KepGroups.DefaultGroupDeadband = 0;//死区值,设为0时,服务器端该组内任何数据变化都通知组
KepGroups.DefaultGroupIsActive = true;//激活组
//通信组初始化
KepGroup = KepGroups.Add("Group1");
KepGroup.IsActive = true;
KepGroup.IsSubscribed = true;//使用订阅功能,即可以异步,默认false
KepGroup.UpdateRate = 250;
KepGroup.AsyncReadComplete += KepGroup_AsyncReadComplete;
//获取所有变量
KepBrower = KepServer.CreateBrowser();
KepBrower.ShowBranches();
KepBrower.ShowLeafs(true);
this.List_Items.Items.Clear();
foreach (object item in KepBrower)
{
this.List_Items.Items.Add(item.ToString());
}
//开始读取
this.timer1.Enabled = true;
this.btn_Connect.Text = "断开连接";
}
else
{
if (KepServer != null)
{
KepServer.Disconnect();
KepServer = null;
this.btn_Connect.Text = "连接";
this.List_Items.Items.Clear();
}
}
//异步读完成运行时,Array数组从下标1开始而非0!!!
void KepGroup_AsyncReadComplete(int TransactionID, int NumItems, ref Array ClientHandles, ref Array ItemValues, ref Array Qualities, ref Array TimeStamps, ref Array Errors)
{
try
{
for (int i = 1; i <= NumItems; i++)
{
object Value = ItemValues.GetValue(i);
if (Value != null)
{
this.OPCList[i - 1].Value = Value.ToString();
this.OPCList[i - 1].Time = ((DateTime)TimeStamps.GetValue(i)).AddHours(8).ToLongTimeString();
}
}
this.dgv_data.DataSource = null;
this.dgv_data.DataSource = this.OPCList;
}
catch(Exception ex)
{
}
}
}
private void List_Items_DoubleClick(object sender, EventArgs e)
{
if (this.List_Items.SelectedItem != null)
{
//添加一个变量到集合里
OPCList.Add(new OPCItem()
{
Tag = this.List_Items.SelectedItem.ToString()
});
}
TempIDList.Clear();
ClientHandles.Clear();
TempIDList.Add("0");
ClientHandles.Add(0);
int count = this.OPCList.Count;
for (int i = 0; i < count; i++)
{
TempIDList.Add(this.OPCList[i].Tag);
ClientHandles.Add(i + 1);
}
strTempIDs = (Array)TempIDList.ToArray();
strClientHandles = (Array)ClientHandles.ToArray();
KepItems = KepGroup.OPCItems;
//KepItems添加标签
KepItems.AddItems(this.OPCList.Count, ref strTempIDs, ref strClientHandles, out strServerHandles, out iErrors);
/*AddItems说明
* void AddItems(int NumItems, ref Array ItemIDs, ref Array ClientHandles, out Array ServerHandles, out Array Errors, object RequestedDataTypes, object AccessPaths);
* int NumItems:加几个变量;输入
* ref Array ItemIDs:表示ItermID集合;输入
* ref Array ClientHandles:句柄,不能重复;输入
* out Array ServerHandles:服务器的一个句柄;输出
* out Array Errors:错误的;输出;
*/
ServerHandles.Clear();
ServerHandles.Add(0);
for (int i = 0; i < count; i++)
{
ServerHandles.Add(Convert.ToInt32(strServerHandles.GetValue(i + 1)));
}
readServerHandles = (Array)ServerHandles.ToArray();
}
/// <summary>
/// 定时读取事件
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void timer1_Tick(object sender, EventArgs e)
{
this.lbl_CurrentTime.Text = "当前时间:" + DateTime.Now.ToLongTimeString();
if (KepServer != null)
{
if (KepServer.ServerState == 1)
{
this.lbl_Status.Text = "Connected";
}
else
{
this.lbl_Status.Text = "Disonnected";
}
}
if (this.OPCList.Count > 0)
{
if (KepServer != null)
KepGroup.AsyncRead(this.OPCList.Count, ref readServerHandles, out readErrors, readTransID, out readCancelID);
}
}
private void dgv_data_CellContentClick(object sender, DataGridViewCellEventArgs e)
{
if (this.dgv_data.SelectedRows != null)
{
int index = this.dgv_data.CurrentRow.Index;
OPCItem objItem = this.OPCList[index];
Frm_Modify objFrm = new Frm_Modify(objItem.Value);
DialogResult res = objFrm.ShowDialog();
int[] ServerHandle = new int[] { 0, Convert.ToInt32(strServerHandles.GetValue(index + 1)) };
object[] Values = new object[2];
string[] ModifyResult;
writeServerHandles = (Array)ServerHandle;
if (res == DialogResult.OK)
{
ModifyResult = objFrm.Tag.ToString().Split('|');
Values[1] = ModifyResult[0];
writeArrayHandles = (Array)Values;
if (ModifyResult[1] == "1")
{
KepGroup.AsyncWrite(1, writeServerHandles, writeArrayHandles, out writeErrors, writeTransID, out writeCancelID);
}
else
{
KepGroup.SyncWrite(1, writeServerHandles, writeArrayHandles, out writeErrors);
}
}
}
}
private void dgv_data_RowPostPaint(object sender, DataGridViewRowPostPaintEventArgs e)
{
//new DataGridViewStyle().DgvRowPostPaint(this.dgv_data, e);
}
/// <summary>
/// 打开系统默认浏览器(用户自己设置了默认浏览器)
/// </summary>
/// <param name="url"></param>
public static void OpenDefaultBrowserUrl(string url)
{
try
{
// 方法1
//从注册表中读取默认浏览器可执行文件路径
RegistryKey key = Registry.ClassesRoot.OpenSubKey(@"http\shell\open\command\");
if (key != null)
{
string s = key.GetValue("").ToString();
//s就是你的默认浏览器,不过后面带了参数,把它截去,不过需要注意的是:不同的浏览器后面的参数不一样!
//"D:\Program Files (x86)\Google\Chrome\Application\chrome.exe" -- "%1"
var lastIndex = s.IndexOf(".exe", StringComparison.Ordinal);
if (lastIndex == -1)
{
lastIndex = s.IndexOf(".EXE", StringComparison.Ordinal);
}
var path = s.Substring(1, lastIndex + 3);
var result = Process.Start(path, url);
if (result == null)
{
// 方法2
// 调用系统默认的浏览器
var result1 = Process.Start("explorer.exe", url);
if (result1 == null)
{
// 方法3
Process.Start(url);
}
}
}
else
{
// 方法2
// 调用系统默认的浏览器
var result1 = Process.Start("explorer.exe", url);
if (result1 == null)
{
// 方法3
Process.Start(url);
}
}
}
catch
{
OpenIe(url);
}
}
/// <summary>
/// 用IE打开浏览器
/// </summary>
/// <param name="url"></param>
public static void OpenIe(string url)
{
try
{
Process.Start("iexplore.exe", url);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
// IE浏览器路径安装:C:\Program Files\Internet Explorer
// at System.Diagnostics.process.StartWithshellExecuteEx(ProcessStartInfo startInfo)注意这个错误
try
{
if (File.Exists(@"C:\Program Files\Internet Explorer\iexplore.exe"))
{
ProcessStartInfo processStartInfo = new ProcessStartInfo
{
FileName = @"C:\Program Files\Internet Explorer\iexplore.exe",
Arguments = url,
UseShellExecute = false,
CreateNoWindow = true
};
Process.Start(processStartInfo);
}
else
{
if (File.Exists(@"C:\Program Files (x86)\Internet Explorer\iexplore.exe"))
{
ProcessStartInfo processStartInfo = new ProcessStartInfo
{
FileName = @"C:\Program Files (x86)\Internet Explorer\iexplore.exe",
Arguments = url,
UseShellExecute = false,
CreateNoWindow = true
};
Process.Start(processStartInfo);
}
else
{
if (MessageBox.Show(@"系统未安装IE浏览器,是否下载安装?", null, MessageBoxButtons.YesNoCancel, MessageBoxIcon.Question) == DialogResult.Yes)
{
// 打开下载链接,从微软官网下载
OpenDefaultBrowserUrl("http://windows.microsoft.com/zh-cn/internet-explorer/download-ie");
}
}
}
}
catch (Exception exception)
{
MessageBox.Show(exception.Message);
}
}
}
private void button1_Click(object sender, EventArgs e)
{
OpenDefaultBrowserUrl("http://123.183.10003.18:33333/introduction?url=%2Fhome%2Fengine");
}
}
}
5.4、实例源码下载
感兴趣的同学可以下载:源码。