Skip to content
Haiku OSDeep Dive Published Updated 7 min readViews unavailable

Haiku BTextControl: Edit Notifications, Validation, and Submission

Use Haiku BTextControl without mixing edit notifications with submission, while preserving user input and validating the current text safely.

BTextControl is a single-line text-entry control composed around a label and an internal text view. It is useful for names, paths, identifiers, and other short values, but its convenience API can hide an important distinction: the text changing is not the same event as the user submitting a command. Model edit notifications separately from an explicit action such as Save, Connect, or Search.

The control exposes Text() and SetText() for its displayed string, TextView() for access to the contained BTextView, and a modification-message API for reporting edits. It also has the invocation behavior inherited from BControl and BInvoker. Keep those paths distinct so live validation can happen while a user types without accidentally starting the operation on every keystroke.

Separate editing from submission

Use a modification message for work that should respond to edits, such as updating a character count, refreshing a local preview, or marking a form dirty. Use the control’s normal invocation message or a separate button for the final operation. The receiver should read the current text and validate it when applying the action; an edit notification can be delayed or coalesced while the user continues typing.

enum : uint32 { kEndpointEdited = 'eped', kConnectRequested = 'conn' };

void ConnectionWindow::MessageReceived(BMessage* message)
{
	if (message->what == kEndpointEdited) {
		UpdateEndpointPreview(fEndpoint->Text());
		return;
	}

	if (message->what == kConnectRequested) {
		BString endpoint(fEndpoint->Text());
		status_t status = ValidateEndpoint(endpoint);
		if (status != B_OK) {
			fEndpoint->MarkAsInvalid(true);
			ShowValidationMessage(status);
			return;
		}
		fEndpoint->MarkAsInvalid(false);
		StartConnection(endpoint);
		return;
	}

	BWindow::MessageReceived(message);
}

This sketch treats the preview as advisory and validates a copied string again at submission. BString avoids retaining a borrowed pointer into the control across later edits. Use a command code and target arrangement that match the constructor or setters for your control, and test the actual focus and Enter behavior. MarkAsInvalid() provides an invalid-state indication; it does not parse, normalize, reject, or sanitize the input on your behalf.

Do not make edit notifications perform expensive network calls synchronously on the window’s looper. Debounce or cancel superseded preview work, attach a request generation to asynchronous results, and discard an older result if a newer edit has arrived. Otherwise a slow response for the previous text can overwrite the preview for the current text.

Manage modification-message ownership

SetModificationMessage() assigns a BMessage used for modification notifications, freeing the previously assigned message. Passing NULL removes the current message. Treat the passed pointer as transferred ownership; do not keep another owner that deletes or mutates the same object. Set the target so that both the modification and invocation messages reach the intended handler according to the control’s routing contract.

Keep the modification message small and stable. Put the event kind in what, and add only fields that represent durable context such as a form section ID or control role. The current value is available from the control when the handler processes the message; if exact per-edit snapshots are required, confirm that the concrete API supplies that data or create an application-level snapshot at the event source. Do not assume a delayed notification always represents the current visible text.

When setting text programmatically, decide whether the change should trigger the same preview or dirty-state path as user input. Programmatic initialization, loading a saved profile, and applying a server response often should not be treated as a fresh user edit. Use an explicit update guard or separate model-to-view update path, then refresh validation state deliberately. Do not rely on undocumented timing to prevent feedback loops.

Validate complete values, not partial keystrokes

A text field can temporarily contain an incomplete but legitimate editing state: a user may type a URL scheme, decimal point, path separator, or multi-character identifier in stages. Rejecting every intermediate state as final invalidity can make input frustrating and can conflict with international input methods. Use lightweight edit-time feedback where helpful, then validate the complete value at commit time.

Validation should check syntax and semantics separately. Syntax checks confirm the shape of a URL or identifier; semantic checks confirm that the host is permitted, a resource exists, or the current user can access it. Do not treat text validation as a security boundary for the operation. The service that opens a path, connects to a host, or writes a configuration must still enforce its own policy at the moment it uses the value.

Normalize only when the domain defines a canonical form. Trimming accidental surrounding whitespace may be appropriate for a host name; changing case may not be appropriate for a password or case-sensitive identifier. Preserve the user’s original text until the application has a clear normalization rule, and show the canonical value if it will be stored differently.

Bound accepted input at the model boundary as well. Pasted values can be much longer than values typed one character at a time, and downstream protocols or storage schemas may have stricter limits than the control itself. Define limits in the representation the domain actually stores, report an actionable validation error, and never truncate UTF-8 at an arbitrary byte offset. For filesystem paths, enforce the allowed-root policy when opening the resolved object; a textual prefix check alone can confuse sibling paths or symbolic links. The control’s invalid marker is feedback, not an access-control or resource-limit mechanism.

Keep focus, keyboard, and labels predictable

MakeFocus() on BTextControl passes focus to its internal text view. This matters when showing validation errors: moving focus should place the caret in the editable portion, not merely focus the composite outer view. Test tab order, selection preservation, text replacement, and focus after an error. When the window becomes active, the control’s focus indication and text view need to remain coherent.

The label and editable area share one control. Use the layout API to allocate enough width for the label and text field at the current font size, and account for translated labels and larger system fonts. Do not assume a fixed divider position works for every locale. If the field has a long value, define whether it scrolls horizontally, truncates only in a companion display, or exposes a separate full-value view.

Enter can interact with the focused text view, the control’s invocation message, and the window’s default button. Test the exact combination. A dialog with a Connect default button may start a connection when Enter is pressed even if a live validation message has not finished; the submission handler must validate the current string again.

Treat the internal TextView as a specialized boundary

TextView() returns the contained BTextView, which can be useful for selection, text styling, font, or scroll configuration. The internal view is still part of the composite control’s ownership and layout. Do not reparent it, delete it, or retain it after the BTextControl is destroyed. If using lower-level text APIs, ensure that the text control’s label, divider, layout, and modification behavior remain consistent.

For multiline text or styled editing, use a dedicated BTextView rather than stretching a single-line BTextControl beyond its design. The APIs overlap because the control wraps text functionality, but their interaction contracts differ. In particular, caret geometry, line wrapping, styled runs, and text offsets belong to the text view’s coordinate and editing model.

Test event order and stale results

Test initialization from saved values, typing, paste, deletion, IME composition, invalid input, corrected input, submission by button, submission by Enter, and a window closing while asynchronous validation is pending. Confirm an older preview cannot replace newer state and that an invalid visual mark clears only when the current value meets the policy.

Also verify that a modification message is not treated as a submit command, the default button cannot bypass validation, and programmatic changes do not accidentally mark a configuration dirty. Log command identifiers and validation status rather than full sensitive field contents.

BTextControl is small, but a production form needs a state model around it. Treat the text as mutable input, edits as notifications, and submission as a separately validated command; then its focus, message, and visual state remain coherent under real user interaction.

Related:

Sources:

Comments