ArionView class Core
A view is a data-shape + behavior variant (grid, pivot, cards, later gantt/timeline). It owns its projection stack (what ArionView.Source the engine scrolls) and its drawing model (ArionView.Template), and it interprets the semantic gestures the host forwards. Fully backend-neutral and unit-testable without a window — it talks to the host only through IViewHost.
public abstract class ArionView
- Namespace
- ArionUI.Presentation
- Package
- ArionUI.Presentation ·
dotnet add package ArionUI.Presentation --prerelease
Constructors
ArionView
protected ArionView();
Properties
Host
protected IViewHost Host { get; }The host driving this view. Valid after ArionView.Attach.
HostOrNull
protected internal IViewHost? HostOrNull { get; }The host, or null when detached — for callbacks that may arrive on a PRODUCER thread, where ArionView.Host's throw-if-detached would turn an ordinary race into an exception on somebody else's thread.
IsAttached
public bool IsAttached { get; }
PaneHost
public IGridPaneHost? PaneHost { get; set; }Set by the rendering host: shows the grid-owned standard panes for requests the app did not override (see IGridPaneHost).
RecommendedFrozenColumns
public virtual int? RecommendedFrozenColumns { get; }How many leading columns this view WANTS frozen — the host applies it on attach.
A chart or a timeline only works with a fixed axis: the Y axis stays left, the plot scrolls. The view knows this, so the application does not have to.
Null means »no opinion«: the host then leaves its setting unchanged. That is the normal case for an ordinary table, where the user decides what stays put.
Source
public abstract IItemsSource Source { get; }The item source the engine virtualizes (top of this view's projection stack — e.g. a grouping over a sort/filter over the data).
Template
public abstract ITemplate Template { get; }The drawing model the renderer uses for this view.
Methods
Attach
public void Attach(IViewHost host);Called by the host when the view becomes its MainView.
A view belongs to exactly ONE host (see ArionView.EnsureCanAttachTo). Attaching again to the same host does nothing — both controls attach in the constructor and once more on the first visual-tree event.
Transactional: if ArionView.OnAttached throws, host AND PaneHost are rolled back.
Detach
public void Detach();Called by the host when this view stops being its MainView, and when the control unloads. The counterpart ArionView.Attach never had.
Without it a view swap left the OLD view attached: it kept its host reference, and it kept reacting to its data source — so mutating the old source repainted a grid nobody is looking at, and, worse, a long-lived source held the old view, which held the control. Swapping views in a shell that keeps one collection around therefore accumulated whole grids, complete with their GPU surfaces.
Detaching does NOT unsubscribe from the data source: a view may be attached again later, and its filter/sort/footer state is expected to be current when it comes back. It severs the view → host half, which is the half that holds the control. Use IDisposable where a view is truly finished.
EnsureCanAttachTo
public void EnsureCanAttachTo(IViewHost host);May this view attach to host? Throws if not — and changes NOTHING.
A host calls this before it reconfigures anything on itself. Otherwise, by the time it throws, it has already rewired template, presenter and input and then claims to show a view that drives another grid.
Mutate
protected void Mutate(Action change);Run a structural mutation so the render thread cannot read through the middle of it.
The GPU frame holds Engine.RenderGate for its whole pass, because it reads the source and the metrics LIVE. A lock on the reader's side alone is not synchronisation, though — and the view-side writers did not take it. Rebuilding a pivot mutates its dictionaries, lists and sets IN PLACE, so a frame in flight could walk a dictionary that was being rehashed: a hang or a corrupt read, not a wrong pixel.
Unattached the mutation just runs — there is no render thread to protect it from, and a view is expected to be configurable before it is ever shown.
OnAttached
protected virtual void OnAttached();
OnCellDoubleClick
public virtual void OnCellDoubleClick(int itemIndex, int elementId);Double-click on a read-only, non-action cell (forwarded from InteractionController.CellDoubleClicked) — the hook for view-specific gestures like the pivot's drill-down.
OnColumnFiltersChanged
public virtual void OnColumnFiltersChanged(IReadOnlyDictionary<int, string> filters);The full current per-column filter-row text state (element id → text), pushed by the host after its debounce. The view applies the whole set at once — matching "apply all active filters", robust against several columns changing within one debounce window.
OnDetached
protected virtual void OnDetached();
OnFrameTick
protected internal virtual bool OnFrameTick(TimeSpan now);Apply work that must happen ONCE PER FRAME (accumulated live changes), and report whether more is already waiting — that return value is what keeps the host's pump running under a feed and stops it the moment the feed goes quiet.
Default: nothing to do, never pump. A view that does not have live data pays nothing, and neither host ever ticks for it.
Parameters
nowThe frame's timestamp, injected — no view reads a clock itself, so anything paced (settle intervals, decays) stays deterministically testable.
OnHeaderClick
public virtual void OnHeaderClick(double contentX, double y, ArionModifiers mods, HeaderButtonKind? button = default);A click on the column header's leaf caption row.
Parameters
buttonThe caption button the click hit (funnel, summary), or null for the caption itself. The grid decides it with the same button layout it draws and hovers with, including the pin marker's slot, so a view never re-derives it.
