Signals vs Events
Klyn separates observable state from UI actions. Use signal property for a value
that can be bound or watched, and use event ... as T for a rich action that
carries an event payload and accepts several handlers.
A signal property is still a property. It has a getter and a setter, stores state, and is read
or written like any other property. The only extra rule is that changes can notify the common
binding engine. The notification channel is deliberately not exposed as a public
valueChanged member.
An event is not state. It is a multicast action stream. A button click, timer tick, menu action,
or terminal button activation is something that happens at a point in time, possibly with a rich
payload. Events are subscribed with += and unsubscribed with -=.
The base event infrastructure lives in klyn.events. It provides the base payload
Event, compiler-created dispatchers, and callback invocation helpers used by every
event source, including non-GUI libraries such as klyn.io.FileWatcher.
GUI-specific payloads live in klyn.gui.event. Import this package when working with
widgets: it contains ActionEvent, KeyEvent, MouseEvent,
ResizeEvent, CloseEvent, PaintEvent, and the GUI facade
GuiEvent.
import klyn.events # base event payload infrastructure
import klyn.gui.event # GUI event payloads
import klyn.gui.windows
| Aspect | signal property |
event ... as T |
|---|---|---|
| Purpose | Observable state. | Rich UI action. |
| Declaration | public signal property value as Int |
public event clicked as ActionEvent |
| Access | Read and write through the property name. | Register handlers with += and -=. |
| Payload | The current property value, read through the property or passed to a binding watcher. | A typed event object such as ActionEvent. |
| Best use | Text field content, slider value, spinner value, selected index, checkbox state. | Button click, timer timeout, menu action, explicit selection action. |
| Common API | Binding.oneWay, Binding.twoWay, Binding.watch. |
+=, -=, emit this.eventName(...). |
| Package | klyn.binding. |
klyn.events for base payloads, klyn.gui.event for GUI payloads. |
Use a signal property when the value itself is what matters. A slider does not expose a public
valueChanged event. Instead, value is observable and can be watched or bound.
import klyn.binding
import klyn.gui.windows
class SliderStatus:
private _label as Label
public SliderStatus(label as Label):
this._label = label
public updateLabel(value as Int) as Void:
this._label.text = "Value = " + value
slider = Slider(value=50, minimum=0, maximum=100)
spinner = Spinner(value=50, minimum=0, maximum=100)
label = Label()
model = SliderStatus(label)
Binding.twoWay(slider::value, spinner::value)
Binding.watch(slider::value, model::updateLabel)
The :: operator creates a typed member reference for the binding engine. This keeps the
public API compact: consumers bind to slider::value, not to a separate notification
object.
Declare signal property on observable state. The compiler emits the notification
automatically when the effective value is different after the setter has run.
class Counter:
private _value as Int = 0
public signal property value as Int:
get:
return this._value
set:
if value == this._value:
return
this._value = value
The compiler compares the value before and after the setter. If the value is unchanged, no notification is sent; this prevents useless repaints and avoids feedback loops in two-way bindings.
Use an event when the application needs to react to an action, not mirror state. Events are multicast, so several independent handlers can observe the same action.
import klyn.gui.event
import klyn.gui.windows
class Controller:
public save(action as ActionEvent) as Void:
print("save requested")
print(action.source)
controller = Controller()
button = Button("Save")
button.clicked += controller::save
button.clicked += lambda(e: ActionEvent): print("audit: clicked")
button.clicked -= controller::save
Declare events with the payload type directly: public event clicked as ActionEvent. The compiler creates the hidden dispatcher and emit this.clicked(...) constructs the payload.
If the name sounds like a value, use a signal property. If the name sounds like something that happened, use an event.
Slider.value,Spinner.value,TextBox.text,CheckBox.isChecked: signal properties.Button.clicked,Button.pressed,Button.released,Timer.timeout,ComboBox.selectionChanged: events.Widget.keyPressed,Widget.mouseMoved,Widget.resized,Window.closed,Widget.paintRequested: rich GUI events.FileWatcher.changed,FileWatcher.created,FileWatcher.modified,FileWatcher.deleted: generic events carryingFileWatchEventpayloads.- Do not create
valueChangedortextChangedevents for ordinary widget state. - Do not use a signal property for one-shot actions that have no stable value to read back.
The binding and event packages are shared by klyn.gui.windows and
klyn.gui.terminal. The same Binding calls can connect state across both widget
families, and the same ActionEvent model is used for button-like actions.
import klyn.binding
import klyn.gui.terminal
input = LineEdit("ready")
status = StatusLabel()
Binding.oneWay(input::text, status::text)
This common model keeps application code portable: the rendering backend changes, but state binding and action subscription keep the same semantics.
Continue with GUI Themes and KSS to control the visual layer, or
open samples/gui/ColorChooser.kn for a compact example using two-way binding between
sliders and spinners.