How to Build the OGI Operator Interface

Build AvaloniaUI screens that bind to the Core-Engine and wire up C++ event handlers.

This guide shows you how to author an AXAML control, bind it to Core-Engine data with SimBinding, describe its visual states with a Tag, and hook up the generated C++ event handlers.

1. Understand the Architecture

APIC OGI uses the AvaloniaUI platform for cross-platform user interface applications. UI applications communicate with the Core-Engine application via events and the LRUs shared-memory infrastructure.

2. Declare a Control in AXAML

Every control needs a unique name so it can be found by code and by the UI parser.

  1. Add an x:Name attribute to the control. Without it, the parser skips the control entirely and it becomes unusable in custom processing.
  2. Bind data-driven properties with {sim:SimBinding LRU.Block.Element} syntax so values update automatically whenever the underlying sim element changes.
  3. Add a Tag block to store the metadata that drives state and event behavior (see section 4).
<ProgressBar x:Name="ControlName"
    Minimum="{sim:SimBinding UI.OGI.ProgressMin}"
    Maximum="{sim:SimBinding UI.OGI.ProgressMax}"
    Value="{sim:SimBinding UI.OGI.ProgressPos}"
    Height="10" Margin="0 10 0 0">
    <ProgressBar.Tag>
        [Tag Data goes here]
    </ProgressBar.Tag>
</ProgressBar>
A control must have an x:Name assigned. Without it, the UI parser will ignore and omit that control.
The axaml file header must declare the SimBinding namespace: xmlns:sim="clr-namespace:DynamicSimBinding;assembly=OGIDI"

3. Bind Properties with SimBinding

Use the syntax [Control property name] = "{sim:SimBinding [LRU.Block.Element]}" to bind a control property directly to a Core-Engine value. This behaves like a standard binding but updates automatically whenever the sim element changes.

4. Write the Control Tag

The Tag property stores the metadata that controls how a control changes state and handles events. Build it from these pieces:

  1. Notifications[notify{mouse};] enables mouse-based notifications sent to the Core-Engine App.
  2. Control Typetype{button | toggle | tab | check | imagebutton | stateImage | image | progressbar}; defines how the control behaves visually and functionally.
  3. Group (checkbox only) — [group{...};] groups check-type controls together.
  4. States — define images, text, or control references for each numeric state ID.
  5. Default State[defaultState={stateName or empty}]; sets the initial state, or hides the control if left empty.
  6. State MachinestatesMachine{...} maps mouse events to state transitions.

Simplest case - a plain text button with a single enabled state, no images:

<Button x:Name="sendButton">
    <Button.Tag>
        notify{};
        states{
            1="Send", 1(hot)="Send",
            enable=1};
        type{button};
    </Button.Tag>
</Button>

The same Tag syntax also supports images per state, hot-state variants, and multi-state buttons:

states{
    1 = .\Bitmaps\btn-new_enable.bmp,
    1(hot) = .\Bitmaps\btn-new_hot.bmp,

    2 = .\Bitmaps\btn-new_selected.bmp,
    2(hot) = .\Bitmaps\btn-new_selectedhot.bmp,

    3 = .\Bitmaps\btn-new_disable.bmp,
    3(hot) = .\Bitmaps\btn-new_disableHot.bmp,

    4 = "Text",
    4(hot) = "TextHot",

    5 = #controlName,
    5(hot) = #controlName,

    enable = 1,
    click = 2,
    notclick = 1,
    disable = 3,
    MouseLeftButtonDown = 2
};
statesMachine{
    MouseLeftButtonDown: 1=2, 2=3, 3=1 |
    MouseRightButtonDown: 1=2, 2=3, 3=1 |
    MouseEnter: ... |
    MouseLeave: ... |
    MouseLeftButtonUp: ... |
    MouseRightButtonUp: ...
};

States are content (image or text) assigned to a numeric value representing the state ID.

5. Auto-Generated Files vs. What You Write

Running the OGI application against your AXAML regenerates exactly two files - do not hand-edit these, your changes will be overwritten on the next run:

FilePurpose
OGIControls.hOne integer ID and one dotted path constant per named control (e.g. SENDBUTTON / SENDBUTTON_CTRL for x:Name="sendButton"), plus a name lookup table.
OGIAppSetup.cppA creation array mapping each control ID to its concrete C++ wrapper type (e.g. a Button becomes COGIMultiStateButton), and the startup code that registers them.

You write one more pair of files yourself (not regenerated), named after your project - for a project called ROMAH that's OGIROMAH.h / OGIROMAH.cpp:

There is no Model involved anywhere in this pattern. The class you write derives from COGICmdTarget, not from the Model base class (CModelBase) used elsewhere in CoreEngine development - OGI event handling is its own, separate context.

6. Register and Implement Event Handlers

  1. Declare the class and its message map in OGI{ProjectName}.h (e.g. OGIROMAH.h):
class CROMAH : public COGICmdTarget
{
public:
    CROMAH() : COGICmdTarget() {}
    ~CROMAH() {}

    DECLARE_OGI_MESSAGE_MAP();

    void OnButtonClick();
};
  1. Implement the message map and handler body in OGI{ProjectName}.cpp (e.g. OGIROMAH.cpp), and provide the one-time init function:
#include "OGIROMAH.h"
#include "OGIFC/OGIObject.h"
#include "OGIFC/OGIDatabase/UIControlsFactory.h"
#include "OGIFC/OGIMultiStateButton.h"
#include "OGIControls.h"

//////////////////////////
//OGI Events handler
BEGIN_OGI_MESSAGE_MAP(CROMAH, COGICmdTarget)
    ON_OGICOMMAND(SENDBUTTON, &CROMAH::OnButtonClick)
END_OGI_MESSAGE_MAP()

IMPLEMENT_OGI_MESSAGE_MAP(CROMAH);

void CROMAH::OnButtonClick()
{
    COGIMultiStateButton* pObj = (COGIMultiStateButton*)CUIControlsFactory::FindControl(SENDBUTTON);
    pObj->SetState(3);
}

//////////////////////////////////////////////
//Must call this function once, from exactly one Model's OnInit()
void OGIInit()
{
    static CROMAH ROMAH;
    ROMAH.Init();
}
OGIInit() is the one place a Model is involved at all: call it once, from exactly one Model's OnInit(), to register the OGI event handlers with the engine. The handler class and its logic above are otherwise independent of any Model.