Skip to content

Custom Search Dialog

This guide shows how to build a WinForms search dialog that lets users find and insert components from an external system. The dialog is triggered by a menu item with resultType: "component".

How it works

  1. User clicks "Search ERP..." in the Plugin menu.
  2. HydroSym calls OnMenuAction with menuItemId = "erp-search" and parentWindowHandle.
  3. Your plugin shows a WinForms dialog, modal over HydroSym.
  4. User searches, selects a component, and clicks OK.
  5. You return the selected component in the OnMenuAction response.
  6. HydroSym inserts the component into the schematic.

Declare the menu item

new MenuItem
{
    Id = "erp-search",
    Label = "&Search ERP Parts...",
    Location = "plugin",
    Icon = "search",
    ResultType = "component",
    Shortcut = "Ctrl+Shift+F",
}

Implement OnMenuAction

public override OnMenuActionResponse OnMenuAction(OnMenuActionRequest request)
{
    // Use STA thread for WinForms
    OnMenuActionResponse? result = null;
    var thread = new Thread(() =>
    {
        Application.EnableVisualStyles();
        var owner = new NativeWindowWrapper(new IntPtr(request.ParentWindowHandle));
        var dialog = new ErpSearchDialog(_db, request.Context);
        if (dialog.ShowDialog(owner) == DialogResult.OK && dialog.Result is not null)
        {
            result = new OnMenuActionResponse
            {
                ArticleCode        = dialog.Result.ArticleCode,
                Description        = dialog.Result.Description,
                Manufacturer       = dialog.Result.Manufacturer,
                ManufacturerPartNo = dialog.Result.ManufacturerPartNo,
                Properties         = dialog.Result.Properties,
            };
        }
    });
    thread.SetApartmentState(ApartmentState.STA);
    thread.Start();
    thread.Join();

    return result ?? new OnMenuActionResponse(); // empty = user cancelled
}

STA thread requirement

WinForms requires a Single-Threaded Apartment (STA) thread. HydroSym's main thread is MTA. Always create a new STA thread for showing dialogs.

Pass parentWindowHandle to the dialog so it is modal over HydroSym's window. Without this, the dialog can go behind HydroSym.

// Helper wrapper so WinForms ShowDialog accepts a native HWND as owner
internal class NativeWindowWrapper : IWin32Window
{
    public NativeWindowWrapper(IntPtr handle) => Handle = handle;
    public IntPtr Handle { get; }
}

The search dialog

public partial class ErpSearchDialog : Form
{
    private readonly SqlConnection _db;
    private readonly PluginContext _context;
    public ErpSearchResult? Result { get; private set; }

    public ErpSearchDialog(SqlConnection db, PluginContext context)
    {
        _db = db;
        _context = context;
        InitializeComponent();
    }

    private void SearchButton_Click(object sender, EventArgs e)
    {
        string query = searchBox.Text.Trim();
        if (query.Length < 2) return;

        resultsGrid.DataSource = SearchErp(query);
    }

    private List<ErpSearchResult> SearchErp(string query)
    {
        const string sql = """
            SELECT TOP 100
                ArticleCode, Description, Manufacturer, StatusCode
            FROM Articles
            WHERE ArticleCode LIKE @q OR Description LIKE @q
            ORDER BY ArticleCode
            """;

        using var cmd = new SqlCommand(sql, _db);
        cmd.Parameters.AddWithValue("@q", $"%{query}%");

        var results = new List<ErpSearchResult>();
        using var reader = cmd.ExecuteReader();
        while (reader.Read())
        {
            results.Add(new ErpSearchResult
            {
                ArticleCode  = reader.GetString(0),
                Description  = reader.GetString(1),
                Manufacturer = reader.GetString(2),
                Status       = reader.GetString(3),
            });
        }
        return results;
    }

    private void OkButton_Click(object sender, EventArgs e)
    {
        if (resultsGrid.CurrentRow?.DataBoundItem is ErpSearchResult selected)
        {
            Result = selected;
            DialogResult = DialogResult.OK;
            Close();
        }
    }

    private void ResultsGrid_DoubleClick(object sender, EventArgs e)
        => OkButton_Click(sender, e);
}

public class ErpSearchResult
{
    public string ArticleCode  { get; set; } = "";
    public string Description  { get; set; } = "";
    public string Manufacturer { get; set; } = "";
    public string Status       { get; set; } = "";
    public string? ManufacturerPartNo { get; set; }
    public Dictionary<string, string>? Properties { get; set; }
}

Using context in the dialog

The PluginContext passed to OnMenuAction contains the currently open project and selected component. Use it to pre-filter results:

// Pre-filter to components relevant to the current project's pressure class
string? pressureClass = _context.ProjectPath is not null
    ? GetProjectPressureClass(_context.ProjectPath)
    : null;

Handling cancellation

If the user closes the dialog or clicks Cancel, return an empty response:

return new OnMenuActionResponse(); // HydroSym ignores this for resultType=component

HydroSym does not insert anything when the response is empty.

Keyboard navigation tips

  • Implement AcceptButton (OK) and CancelButton (Cancel) on the form for Enter/Escape support.
  • Handle KeyDown on the search box to trigger search on Enter.
  • Support double-click on the results grid to immediately confirm selection.