Client GUI Plugins


This plugin enables you to build custom UI plugins for Stream SCADA Client using the new SDK-style plugin system. It covers the architecture, lifecycle, configuration, tag access, update loop, error handling, deployment, and a step-by-step tutorial.



What is a GUI Plugin?

  • A plugin is a .NET class that implements IPluginView and returns a WinForms Control (typically a UserControl) that the host places onto a diagram at runtime.
  • The plugin references only Stream.Common.Shared, keeping it decoupled from the Stream GUI host.


Architecture Overview

Interfaces & Base Classes (defined in Stream.Common.Shared)

  • IPluginView: Core contract with Initialize(IHostServices) and GetControl() As Control
  • PluginViewBase: Optional abstract base class providing helper methods and lifecycle hooks (for programmatic plugins only)
  • IHostServices: Host-provided services for plugins
  • ITagService: Read/write/subscribe to SCADA tags


Plugin Approaches

  • Option 1: UserControl-based (Recommended for Designer Support) ✅


public class MyPlugin : UserControl, IPluginView

{

    // Use WinForms designer, implement interface directly

}


  • Option 2: Programmatic (Inherit from PluginViewBase) ⚠️


public class MyPlugin : PluginViewBase

{

    // Get helper methods, but no designer support

}


  • Loader

The host discovers plugin types from DLLs inside application folder\Plugins\[SubfolderName]\.


  • Design-time

The GUI Plugin editor lets users choose a plugin type and configure its public writable properties. Values are strings and converted to target property types at runtime.


  • Runtime

the host instantiates the plugin, injects IHostServices, applies settings with dictionary substitution.


Plugin Lifecycle in the Host (Stream Client)

  1. Discover and instantiate IPluginView
  2. Call plugin.Initialize(hostServices)
  3. Call plugin.GetControl() and add the returned Control to the diagram overlay
  4. Apply settings via reflection to the control's public writable properties. The host substitutes values using the Dynamic Parameters, then sets matching public properties by name
  5. Call optional lifecycle method OnLoad() (if plugin inherits from PluginViewBase)
  6. Each cycle, the host tries to call (if present): PluginUpdate()



Code Examples

C# Example (UserControl-based)


using System.Windows.Forms;

using Stream.Common.Shared;


// Note: Inherits UserControl for designer support, implements IPluginView directly

// Cannot inherit from PluginViewBase due to single inheritance limitation

public class UserControl1 : UserControl, IPluginView

{

    private IHostServices _host;


    // Public properties for configuration

    public string u1 { get; set; }

    public string u2 { get; set; }

    public string u3 { get; set; }


    public void Initialize(IHostServices host)

    {

        _host = host;

        // Initialization logic here

    }


    public Control GetControl()

    {

        return this;

    }


    // Optional: Called by host each cycle

    public void PluginUpdate()

    {

        // Update UI based on tag values

    }


    // Rest of your code...

}


VB.NET Example (UserControl-based)


Imports System.Windows.Forms

Imports Stream.Common.Shared


' Note: Inherits UserControl for designer support, implements IPluginView directly

' Cannot inherit from PluginViewBase due to single inheritance limitation

Public Class UserControl1

    Inherits UserControl

    Implements IPluginView


    Private _host As IHostServices


    ' Public properties for configuration

    Public Property u1 As String

    Public Property u2 As String

    Public Property u3 As String


    Public Sub Initialize(host As IHostServices) Implements IPluginView.Initialize

        _host = host

        ' Initialization logic here

    End Sub


    Public Function GetControl() As Control Implements IPluginView.GetControl

        Return Me

    End Function


    ' Optional: Called by host each cycle

    Public Sub PluginUpdate() Implements IPluginView.PluginUpdate

        ' Update UI based on tag values

    End Sub


    ' Rest of your code...

End Class


C# Example (Programmatic with PluginViewBase)


using System.Windows.Forms;

using Stream.Common.Shared;


// For programmatic plugins that don't need designer support

public class MyCustomPlugin : PluginViewBase

{

    private Label _label;


    public override void Initialize(IHostServices host)

    {

        base.Initialize(host); // Important: call base

        

        // Create UI programmatically

        _label = new Label { Text = "Hello", Dock = DockStyle.Fill };

    }


    public override Control GetControl()

    {

        return _label;

    }


    // Optional: Override lifecycle hooks

    protected override void OnLoad()

    {

        base.OnLoad();

        // Called after settings are applied

    }


    // Optional: Override update loop

    public override void PluginUpdate()

    {

        // Use helper methods from base class

        var value = SafeReadTag("MyTag");

        _label.Text = value?.ToString() ?? "N/A";

    }

}


Important Notes

  • Single Inheritance Limitation: VB.NET/C# classes can only inherit from one base class. Since UserControl is already a base class, you cannot also inherit from PluginViewBase. This is why most plugins implement IPluginView directly.
  • Backward Compatibility: Both approaches are fully supported. Existing plugins continue to work without changes.
  • Extensibility: New helper methods can be added to PluginViewBase in the future without breaking existing plugins.


Settings and Placeholder Substitution

  • Users configure your plugin in ConfGUIPlugin. The grid displays your public writable properties.



Tag Access and Live Updates

  • Use host.TagService in Initialize:

Read(TagName As String) returns the current value.



Update Loop Contract

  • The host attempts a parameterless method PluginUpdate() if present


Error Resilience

  • The host guards plugin boundaries (Initialize/GetControl/PluginUpdate/Read/Write) with try/catch and can disable a faulted control to avoid repeated errors.
  • Best practice: also handle exceptions inside your own functions to keep your control healthy.