在组态软件中开发脚本功能的方法

一、概述

大多数的组态软件都具有脚本功能,脚本可能是VBS、Lua、C#等语言,也可能是厂家自定义的一种语言。通过脚本,组态软件可以实现非常灵活的功能。

脚本的功能,基本可以定义为:读入外部数据,改变可视化元素。外部数据,最重要的是采集到的数据,也可能是用户创建的配置文件。而改变可视化元素,则包括创建、改变位置、改变状态、删除等操作。

本文通过一个示例,介绍在组态软件中实现脚本功能的方法。示例有以下假定:

开发语言 C#
脚本语言 C#
软件类型 WPF

二、示例描述

本文所使用的示例是简单且具有代表性的。本文所使用的脚本为:

string x = GetParameter("A"); //读取参数A,作为X位移
string y = GetParameter("B"); //读取参数B,作为Y位移

string id = CreateComponent("rect"); //在画布中创建一个矩形
MoveComponent(id, x, y); //将矩形移动到参数所定义的位置

可以看出,脚本首先读入外部数据(通过GetParameter方法),然后对可视化元素进行了改变(通过CreateComponent创建,通过MoveComponent移动)。读者在此肯定会提出疑问,GetParameter这些函数是哪里来的?这些是脚本跟主程序交互的API,我们会在一个类中事先定义好,然后传递到脚本中,下文会有详细介绍。

然后我们看软件的界面,如下图所示:

 界面左侧,有两个框输入两个参数,然后是输入脚本内容,还有一个运行按钮。在右侧,则是一个画布,用于绘制脚本产生的效果。

三、开发过程

3.1 项目准备

1、新建一个WPF工程,然后通过NuGet获取Microsoft.CodeAnalysis.CSharp.Scripting包。

2、在WPF中创建好界面需要的内容(也就是各输入框、按钮、Canvas)。

3.2 书写实际执行代码

对应于脚本,实际执行的代码包括获取参数、创建矩形和移动矩形。在本示例中,我们全在MainWindow.cs中实现功能。

private string GetParameter(string name)
{
    switch (name)
    {
        case "A":
            return ParaA.Text;
        case "B":
            return ParaB.Text;
        default:
            return "";
    }
}

private string CreateComponent(string type)
{
    if (type == "rect")
    {
        Random rand = new Random();
        string id = $"com{rand.Next(10000):D4}";
        var rect = new Rectangle() { Name = id, Width = 200, Height = 100, Fill = Brushes.Red };
        RegisterName(id, rect);
        DisplayCanvas.Children.Add(rect);
        return id;
    }
    return "";
}

private void MoveComponent(string para)
{
    string[] parts = para.Split(',');
    string id = parts[0];
    double x = double.Parse(parts[1]);
    double y = double.Parse(parts[2]);

    var rect = FindName(id) as Rectangle;
    Canvas.SetLeft(rect, x);
    Canvas.SetTop(rect, y);
}

GetParameter方法,根据参数的内容,返回相应输入框的内容。在实际情况中,也可能是根据采集数据的ID,获取采集数据的值。

CreateComponent方法,创建了一个红色矩形,添加到画布中,并返回了矩形的Name。

MoveComponent方法,假定参数的格式是"id,x,y",分解后,找到矩形并移动它。

3.3 书写脚本交互类

也就是定义脚本与主程序交互的API。本示例中,没有传入数据对象,而是直接传入函数委托,这样的脚本中可以主动拉取数据或推送动作。

新建一个ScriptInteract类,内容如下所示:

public class ScriptInteract
{
    private Func<string, string, string> Callback;

    public ScriptInteract(Func<string, string, string> callback)
    {
        Callback = callback;
    }

    public string GetParameter(string name)
    {
        return Callback("GetParameter", name);
    }

    public string CreateComponent(string type)
    {
        return Callback("CreateComponent", type);
    }

    public void MoveComponent(string id, string x, string y)
    {
        Callback("MoveComponent", $"{id},{x},{y}");
    }
}

由于API很多,为每一个API方法创建一个委托过于繁琐,故只创建一个通用委托,输入函数名和参数,返回一个字符串。

可以看到,上文中的脚本所调用的“未知函数”,就是ScriptInteract类中的方法。

3.4 主程序与交互类的关联

交互类中只有一个通用委托,我们在MainWindow.cs中定义此委托的实现:

private string Callback(string func, string para)
{
    switch (func)
    {
        case "GetParameter":
            return GetParameter(para);
        case "CreateComponent":
            return CreateComponent(para);
        case "MoveComponent":
            MoveComponent(para);
            return "";
        default:
            return "";
    }
}

可以看到,通过回调函数调用了之前在MainWindow.cs中定义的实际执行函数。

然后把此函数作为参数传入到ScriptInteract对象中即可。

ScriptInteract interact = new ScriptInteract(Callback);

3.5 执行脚本

执行脚本,也就是点击运行按钮的动作。先摆出代码:

private void RunBtn_Click(object sender, RoutedEventArgs e)
{
    ScriptInteract interact = new ScriptInteract(Callback);

    var code = ScriptText.Text;
    var options = ScriptOptions.Default
        .WithImports("System")
        .WithReferences(typeof(ScriptInteract).Assembly);

    var script = CSharpScript.Create(code, options, typeof(ScriptInteract));
    script.RunAsync(interact).Wait();
}

此代码,先创建一个ScriptInteract对象,关联了MainWindow.cs中的回调函数。然后把此参数传给脚本对象。那么,在脚本中就可以调用ScriptInteract所定义的API。

至此,所有功能开发完成。

示例Demo下载

猜你喜欢

转载自blog.csdn.net/lweiyue/article/details/131082331