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.
- Add an
x:Nameattribute to the control. Without it, the parser skips the control entirely and it becomes unusable in custom processing. - Bind data-driven properties with
{sim:SimBinding LRU.Block.Element}syntax so values update automatically whenever the underlying sim element changes. - Add a
Tagblock 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>
x:Name assigned. Without it, the UI parser will ignore and omit that control.
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:
- Notifications —
[notify{mouse};]enables mouse-based notifications sent to the Core-Engine App. - Control Type —
type{button | toggle | tab | check | imagebutton | stateImage | image | progressbar};defines how the control behaves visually and functionally. - Group (checkbox only) —
[group{...};]groups check-type controls together. - States — define images, text, or control references for each numeric state ID.
- Default State —
[defaultState={stateName or empty}];sets the initial state, or hides the control if left empty. - State Machine —
statesMachine{...}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:
| File | Purpose |
|---|---|
OGIControls.h | One 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.cpp | A 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:
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
- 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();
};
- 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.