Skip to content

Nexus binding contracts

TNXBindingSource connects current-item values to value endpoints. The source owns navigation. The coordinator delegates it, checks pending edits, and keeps bound consumers synchronized. The core has no GUI, dataset, RTTI property, indexing, sorting, or filtering dependency.

The public declarations are in tpNXBinding.pas. obNXBindingSource.pas implements the coordinator. obNXBindingSubscriptions is owned callback-registration storage used by the coordinator; it is not a data-access layer.

Connecting participants

Given source and target objects that implement the contracts:

lBindingSource := TNXBindingSource.Create;
try
  lResult := lBindingSource.Attach(lSource);
  if not lResult.Succeeded then
    raise Exception.Create('Source could not be attached.');

  lResult := lBindingSource.Bind('Name', lTarget, bmTwoWay, btExplicit, lHandle);
  // Check lResult; a nonzero handle also exposes an initialization failure.
  // Target changes create pending input. Application code chooses when:
  lResult := lBindingSource.Submit(lHandle);
finally
  lBindingSource.Free;
end;

Registration always initializes the target from the source. A conversion or target-write failure remains inspectable through GetState; it does not submit the target's pre-existing contents to the source. A target lacking required interfaces is rejected before registration, returning handle zero.

Attach(nil) detaches explicitly. Attachment and detachment refuse while edits remain pending. Attachment preserves the source's current record.

Interfaces and discovery

All interfaces use {$interfaces corba} and string identifiers. They are non-owning. Attach, Bind, and member resolution exchange borrowed TObject references at connection points so the coordinator can use standard Supports(AObject, INX..., AInterface) checks. Every data operation then goes through the discovered interface; no concrete model/control class is required.

Object-to-CORBA is/as, Supports, and GetInterface work, while CORBA-interface-to-interface is/as and Supports do not. A failed check is a missing capability, not an invitation to cast an interface pointer into an object. There are no capability getters or custom query registries. See the FPC Supports overloads and CORBA interface rules.

Contract Behavior
INXItemSource Current, First/Prior/Next/Last, and source notifications.
INXBindingItem Resolve a case-sensitive opaque member key to an endpoint object. Keys are not property paths.
INXBindingValue Descriptor, read, and notifications.
INXBindingWritableValue Optional write capability, including runtime writable state.
INXBindingEditSession Optional capability on the source object for BeginEdit/Commit/Cancel and dirty state.
INXBindingConverter Pure conversion in either direction.
INXBindingValidator Pure validation of a proposed source value and its current-item/member context.
INXBindingObservable / INXBindingObserver Synchronous subscription and notifications.
INXBindingSource Public coordinator operations and observable state.

A readable source without write capability can initialize a two-way binding's display. Submission returns bcReadOnly. Targets must expose value and write interfaces; two-way operation requires a readable target. Source edit-session notifications use the same source subject as normal data/state notifications.

Values and results

TNXBindingValue distinguishes Boolean, Int64, Double, UTF8String, DateTime, and Currency. Null, unset, and a present value are separate; an empty string is present. Descriptors specify kind, readability, and null/unset acceptance. DateTime and Currency have typed fields. There is no implicit numeric conversion, timezone conversion, formatting, or floating-point Currency intermediate. Only the payload selected by Kind is meaningful.

Record constructors initialize values. SameValue compares kind, presence, and the active payload. Converters must produce a value accepted by the destination descriptor. Without a converter, kinds must match.

Expected failures return TNXBindingResult: code, copied diagnostic text, and the affected handle where applicable. Success and NoMovement satisfy Succeeded. Failed writes must leave the endpoint unchanged. Successful writes may normalize a value; the coordinator reads back accepted values.

Unexpected exceptions propagate after guards are released. They are not validation results and do not imply rollback of a write already performed. Observer exceptions violate the callback contract. Closing still reaches the remaining subscribers before the first exception is re-raised.

Cursor means the source's navigation state. Current is its current item. Currency refers exclusively to the monetary datatype. There is no Count, Position, numeric lookup, or independent coordinator cursor.

First/Prior/Next/Last check edits and invoke the corresponding source method once. Empty/boundary behavior belongs to the source; it reports Success, NoMovement, or a failure that leaves Current unchanged. Current may be nil. The coordinator processes synchronous CurrentChanged once, without repeating rebinding when the navigation method returns.

The source may reuse the same current-item facade and endpoints for successive records. It must report every actual record transition even when object addresses are unchanged. The coordinator detaches old member subscriptions, resolves against the new Current, and initializes clean targets. No stable per-record object or record-identity lookup is required.

Sources establish their new Current before reporting a transition and keep old endpoints alive through synchronous detachment. Missing members or unreadable sources produce unavailable bindings. Displayed values are not replaced by invented defaults. Source data-change or Refresh retries unavailable resolution.

Edits and caller decisions

bmOneWay never submits target changes. bmTwoWay captures them; btOnChange attempts submission immediately, while btExplicit waits for Submit. A future focus-loss adapter calls Submit. There is no core focus event or policy callback.

GetState exposes source availability/value, pending input, orphaned state, source-baseline changes, and the last result. GetDirty combines local pending input with the optional source session's dirty state. Returning a target to its accepted representation clears a local edit when its source baseline has not changed; otherwise the caller resolves the baseline explicitly.

Submission converts, validates, begins the optional edit session, writes, then reads back and refreshes clean bindings. Rejection, incomplete input such as "-", and validation failure preserve the proposal. Successful source writes are distinct from committing the source edit session.

Operation Effect
Submit(handle) Submit that pending proposal; no pending proposal is a no-op.
SubmitAll Submit pending bindings in registration order.
CancelPending(handle) Discard local input and refresh; does not undo an accepted source write.
CommitEdit Submit pending input, then commit the source session. Unsupported without that capability.
CancelEdit Cancel the source session, discard current-item proposals, and refresh. Unsupported without that capability.
Rebase(handle) Acknowledge the latest source baseline while retaining input. Does not write or bypass validation.
Refresh Refresh clean targets. Does not discard pending input or acknowledge changed baselines.

Navigation with pending input or a dirty session returns bcPendingEdits before calling the source, even if movement might be a no-op. The caller submits/commits or cancels explicitly and retries navigation.

When source values change under pending input, input is preserved and ordinary Submit returns bcSourceChanged. The caller chooses CancelPending or Rebase followed by Submit. Another source change requires another acknowledgement.

When the current record changes externally, including removal, pending input becomes orphaned. It remains inspectable but cannot be submitted or rebased. CancelPending discards it and displays the new Current.

SubmitAll is not atomic. Later failure leaves earlier writes accepted (or in the source session), with later proposals untouched. Multiple proposals to one member have all involved conversions/validators checked before any write: conflicting source values return bcAmbiguousProposal. Equivalent validated proposals are written once and refreshed together. Submitting one binding individually does not silently accept a pending sibling.

Source BeginEdit is idempotent for the active session. Failed Commit keeps the session; failed Cancel leaves it unchanged. Successful Cancel restores its baseline. The coordinator provides no cross-item transaction.

Notifications, reentrancy, and lifetime

Subscribe returns a subject-local token; zero means refused. Delivery follows registration order. Unsubscribe is idempotent and removes a callback immediately, including a later callback in the current dispatch. New subscribers start with the next dispatch. Subscription storage never owns observers.

Notifications carry the borrowed subject object as identity and a typed event. This avoids treating distinct interface references on one object as different subjects. Value access still uses interfaces, not the object's concrete class.

Getters, resolution, conversion, and validation must not cause independent data mutations or navigation. Coordinator mutations during active operations or notifications return bcBusy. Remove and Unsubscribe remain safe; internal binding reclamation waits until calls unwind. Coordinator-generated target writes use local echo guards, including nested target normalization.

CurrentChanged is published after rebinding, followed by affected BindingChanged events and DataChanged. Source and binding state is coherent at these callbacks. Targets can observe individual writes before the final coordinator event; there is no promise of atomic visual updates across targets.

Application owners explicitly own and free all participants. The coordinator owns bindings and subscriptions. Converters/validators outlive their bindings or are detached by Remove. Source edit capabilities share the source lifetime.

Before destruction, source/target owners issue Closing while their contracts are alive. Closing clears borrowed references and tokens; recipients never call that subject again. Observer owners unsubscribe before destroying themselves. Destroying a currently executing participant or the coordinator inside its own callback is prohibited: disconnect/remove there, free after it returns. The core uses no reference counting, worker thread, or event-loop queue.