Chapter 3.6: Event Handling
Home | << Previous: Programmatic Widget Creation | Event Handling | Next: Styles, Fonts & Images >>
ScriptedWidgetEventHandler
The ScriptedWidgetEventHandler class is the foundation of all widget event handling in DayZ. It provides override methods for every possible widget event.
To receive events from a widget, create a class that extends ScriptedWidgetEventHandler, override the event methods you care about, and attach the handler to the widget with SetHandler().
Complete Event Method List
class ScriptedWidgetEventHandler
{
// Click events
bool OnClick(Widget w, int x, int y, int button);
bool OnDoubleClick(Widget w, int x, int y, int button);
// Selection events
bool OnSelect(Widget w, int x, int y);
bool OnItemSelected(Widget w, int x, int y, int row, int column,
int oldRow, int oldColumn);
// Focus events
bool OnFocus(Widget w, int x, int y);
bool OnFocusLost(Widget w, int x, int y);
// Mouse events
bool OnMouseEnter(Widget w, int x, int y);
bool OnMouseLeave(Widget w, Widget enterW, int x, int y);
bool OnMouseWheel(Widget w, int x, int y, int wheel);
bool OnMouseButtonDown(Widget w, int x, int y, int button);
bool OnMouseButtonUp(Widget w, int x, int y, int button);
// Keyboard events
bool OnKeyDown(Widget w, int x, int y, int key);
bool OnKeyUp(Widget w, int x, int y, int key);
bool OnKeyPress(Widget w, int x, int y, int key);
// Change events (sliders, checkboxes, editboxes)
bool OnChange(Widget w, int x, int y, bool finished);
// Drag and drop events
bool OnDrag(Widget w, int x, int y);
bool OnDragging(Widget w, int x, int y, Widget receiver);
bool OnDraggingOver(Widget w, int x, int y, Widget receiver);
bool OnDrop(Widget w, int x, int y, Widget receiver);
bool OnDropReceived(Widget w, int x, int y, Widget receiver);
// Controller (gamepad) events
bool OnController(Widget w, int control, int value);
// Layout events
bool OnResize(Widget w, int x, int y);
bool OnChildAdd(Widget w, Widget child);
bool OnChildRemove(Widget w, Widget child);
// Other
bool OnUpdate(Widget w);
bool OnModalResult(Widget w, int x, int y, int code, int result);
}Return Value: Consumed vs. Pass-Through
Every event handler returns a bool:
return true;-- The event is consumed. No other handler will receive it. The event stops propagating up the widget hierarchy.return false;-- The event is passed through to the parent widget's handler (if any).
This is critical for building layered UIs. For example, a button click handler should return true to prevent the click from also triggering a panel behind it.
Registering Handlers with SetHandler()
The simplest way to handle events is to call SetHandler() on a widget:
class MyPanel : ScriptedWidgetEventHandler
{
protected Widget m_Root;
protected ButtonWidget m_SaveBtn;
protected ButtonWidget m_CancelBtn;
void MyPanel()
{
m_Root = GetGame().GetWorkspace().CreateWidgets(
"MyMod/gui/layouts/panel.layout");
m_SaveBtn = ButtonWidget.Cast(m_Root.FindAnyWidget("SaveButton"));
m_CancelBtn = ButtonWidget.Cast(m_Root.FindAnyWidget("CancelButton"));
// Register this class as the event handler for both buttons
m_SaveBtn.SetHandler(this);
m_CancelBtn.SetHandler(this);
}
override bool OnClick(Widget w, int x, int y, int button)
{
if (w == m_SaveBtn)
{
Save();
return true; // Consumed
}
if (w == m_CancelBtn)
{
Cancel();
return true;
}
return false; // Not our widget, pass through
}
}A single handler instance can be registered on multiple widgets. Inside the event method, compare w (the widget that generated the event) against your cached references to determine which widget was interacted with.
Common Events in Detail
OnClick
bool OnClick(Widget w, int x, int y, int button)Fired when a ButtonWidget is clicked (mouse released over the widget).
w-- The clicked widgetx, y-- Mouse cursor position (screen pixels)button-- Mouse button index:0= left,1= right,2= middle
override bool OnClick(Widget w, int x, int y, int button)
{
if (button != 0) return false; // Only handle left click
if (w == m_MyButton)
{
DoAction();
return true;
}
return false;
}OnChange
bool OnChange(Widget w, int x, int y, bool finished)Fired by SliderWidget, CheckBoxWidget, EditBoxWidget, and other value-based widgets when their value changes.
w-- The widget whose value changedfinished-- For sliders:truewhen the user releases the slider handle. For edit boxes:truewhen Enter is pressed.
override bool OnChange(Widget w, int x, int y, bool finished)
{
if (w == m_VolumeSlider)
{
SliderWidget slider = SliderWidget.Cast(w);
float value = slider.GetCurrent();
// Only apply when user finishes dragging
if (finished)
{
ApplyVolume(value);
}
else
{
// Preview while dragging
PreviewVolume(value);
}
return true;
}
if (w == m_NameInput)
{
EditBoxWidget edit = EditBoxWidget.Cast(w);
string text = edit.GetText();
if (finished)
{
// User pressed Enter
SubmitName(text);
}
return true;
}
if (w == m_EnableCheckbox)
{
CheckBoxWidget cb = CheckBoxWidget.Cast(w);
bool checked = cb.IsChecked();
ToggleFeature(checked);
return true;
}
return false;
}OnMouseEnter / OnMouseLeave
bool OnMouseEnter(Widget w, int x, int y)
bool OnMouseLeave(Widget w, Widget enterW, int x, int y)Fired when the mouse cursor enters or leaves a widget's bounds. The enterW parameter in OnMouseLeave is the widget the cursor moved to.
Common use: hover effects.
override bool OnMouseEnter(Widget w, int x, int y)
{
if (w == m_HoverPanel)
{
m_HoverPanel.SetColor(ARGB(255, 80, 130, 200)); // Highlight
return true;
}
return false;
}
override bool OnMouseLeave(Widget w, Widget enterW, int x, int y)
{
if (w == m_HoverPanel)
{
m_HoverPanel.SetColor(ARGB(255, 50, 50, 50)); // Default
return true;
}
return false;
}OnFocus / OnFocusLost
bool OnFocus(Widget w, int x, int y)
bool OnFocusLost(Widget w, int x, int y)Fired when a widget gains or loses keyboard focus. Important for edit boxes and other text input widgets.
override bool OnFocus(Widget w, int x, int y)
{
if (w == m_SearchBox)
{
m_SearchBox.SetColor(ARGB(255, 100, 160, 220));
return true;
}
return false;
}
override bool OnFocusLost(Widget w, int x, int y)
{
if (w == m_SearchBox)
{
m_SearchBox.SetColor(ARGB(255, 60, 60, 60));
return true;
}
return false;
}OnMouseWheel
bool OnMouseWheel(Widget w, int x, int y, int wheel)Fired when the mouse wheel scrolls over a widget. wheel is positive for scroll up, negative for scroll down.
OnKeyDown / OnKeyUp / OnKeyPress
bool OnKeyDown(Widget w, int x, int y, int key)
bool OnKeyUp(Widget w, int x, int y, int key)
bool OnKeyPress(Widget w, int x, int y, int key)Keyboard events. The key parameter corresponds to KeyCode constants (e.g., KeyCode.KC_ESCAPE, KeyCode.KC_RETURN).
OnDrag / OnDrop / OnDropReceived
bool OnDrag(Widget w, int x, int y)
bool OnDrop(Widget w, int x, int y, Widget receiver)
bool OnDropReceived(Widget w, int x, int y, Widget receiver)Drag and drop events. The widget must have draggable 1 in its layout (or WidgetFlags.DRAGGABLE set in code).
OnDrag-- User started dragging widgetwOnDrop-- Widgetwwas dropped;receiveris the widget underneathOnDropReceived-- Widgetwreceived a drop;receiveris the dropped widget
OnItemSelected
bool OnItemSelected(Widget w, int x, int y, int row, int column,
int oldRow, int oldColumn)Fired by TextListboxWidget when a row is selected.
Vanilla WidgetEventHandler (Callback Registration)
DayZ's vanilla code uses an alternative pattern: WidgetEventHandler, a singleton that routes events to named callback functions. This is commonly used in vanilla menus.
WidgetEventHandler handler = WidgetEventHandler.GetInstance();
// Register event callbacks by function name
handler.RegisterOnClick(myButton, this, "OnMyButtonClick");
handler.RegisterOnMouseEnter(myWidget, this, "OnHoverStart");
handler.RegisterOnMouseLeave(myWidget, this, "OnHoverEnd");
handler.RegisterOnDoubleClick(myWidget, this, "OnDoubleClick");
handler.RegisterOnMouseButtonDown(myWidget, this, "OnMouseDown");
handler.RegisterOnMouseButtonUp(myWidget, this, "OnMouseUp");
handler.RegisterOnMouseWheel(myWidget, this, "OnWheel");
handler.RegisterOnFocus(myWidget, this, "OnFocusGained");
handler.RegisterOnFocusLost(myWidget, this, "OnFocusLost");
handler.RegisterOnDrag(myWidget, this, "OnDragStart");
handler.RegisterOnDrop(myWidget, this, "OnDropped");
handler.RegisterOnDropReceived(myWidget, this, "OnDropReceived");
handler.RegisterOnDraggingOver(myWidget, this, "OnDragOver");
handler.RegisterOnChildAdd(myWidget, this, "OnChildAdded");
handler.RegisterOnChildRemove(myWidget, this, "OnChildRemoved");
// Unregister all callbacks for a widget
handler.UnregisterWidget(myWidget);The callback function signatures must match the event type:
void OnMyButtonClick(Widget w, int x, int y, int button)
{
// Handle click
}
void OnHoverStart(Widget w, int x, int y)
{
// Handle mouse enter
}
void OnHoverEnd(Widget w, Widget enterW, int x, int y)
{
// Handle mouse leave
}SetHandler() vs. WidgetEventHandler
| Aspect | SetHandler() | WidgetEventHandler |
|---|---|---|
| Pattern | Override virtual methods | Register named callbacks |
| Handler per widget | One handler per widget | Multiple callbacks per event |
| Used by | DabsFramework, Expansion, custom mods | Vanilla DayZ menus |
| Flexibility | Must handle all events in one class | Can register different targets for different events |
| Cleanup | Implicit when handler is destroyed | Must call UnregisterWidget() |
For new mods, SetHandler() with ScriptedWidgetEventHandler is the recommended approach.
Complete Example: Interactive Button Panel
A panel with three buttons that change color on hover and perform actions on click:
class InteractivePanel : ScriptedWidgetEventHandler
{
protected Widget m_Root;
protected ButtonWidget m_BtnStart;
protected ButtonWidget m_BtnStop;
protected ButtonWidget m_BtnReset;
protected TextWidget m_StatusText;
protected int m_DefaultColor = ARGB(255, 60, 60, 60);
protected int m_HoverColor = ARGB(255, 80, 130, 200);
protected int m_ActiveColor = ARGB(255, 50, 180, 80);
void InteractivePanel()
{
m_Root = GetGame().GetWorkspace().CreateWidgets(
"MyMod/gui/layouts/interactive_panel.layout");
m_BtnStart = ButtonWidget.Cast(m_Root.FindAnyWidget("BtnStart"));
m_BtnStop = ButtonWidget.Cast(m_Root.FindAnyWidget("BtnStop"));
m_BtnReset = ButtonWidget.Cast(m_Root.FindAnyWidget("BtnReset"));
m_StatusText = TextWidget.Cast(m_Root.FindAnyWidget("StatusText"));
// Register this handler on all interactive widgets
m_BtnStart.SetHandler(this);
m_BtnStop.SetHandler(this);
m_BtnReset.SetHandler(this);
}
override bool OnClick(Widget w, int x, int y, int button)
{
if (button != 0) return false;
if (w == m_BtnStart)
{
m_StatusText.SetText("Started");
m_StatusText.SetColor(m_ActiveColor);
return true;
}
if (w == m_BtnStop)
{
m_StatusText.SetText("Stopped");
m_StatusText.SetColor(ARGB(255, 200, 50, 50));
return true;
}
if (w == m_BtnReset)
{
m_StatusText.SetText("Ready");
m_StatusText.SetColor(ARGB(255, 200, 200, 200));
return true;
}
return false;
}
override bool OnMouseEnter(Widget w, int x, int y)
{
if (w == m_BtnStart || w == m_BtnStop || w == m_BtnReset)
{
w.SetColor(m_HoverColor);
return true;
}
return false;
}
override bool OnMouseLeave(Widget w, Widget enterW, int x, int y)
{
if (w == m_BtnStart || w == m_BtnStop || w == m_BtnReset)
{
w.SetColor(m_DefaultColor);
return true;
}
return false;
}
void Show(bool visible)
{
m_Root.Show(visible);
}
void ~InteractivePanel()
{
if (m_Root)
{
m_Root.Unlink();
m_Root = null;
}
}
}Event Handling Best Practices
Always return
truewhen you handle an event -- Otherwise the event propagates to parent widgets and may trigger unintended behavior.Return
falsefor events you do not handle -- This allows parent widgets to process the event.Cache widget references -- Do not call
FindAnyWidget()inside event handlers. Look up widgets once in the constructor and store references.Null-check widgets in events -- The widget
wis usually valid, but defensive coding prevents crashes.Clean up handlers -- When destroying a panel, unlink the root widget. If using
WidgetEventHandler, callUnregisterWidget().Use
finishedparameter wisely -- For sliders, only apply expensive operations whenfinishedistrue(user released the handle). Use non-finished events for previewing.Defer heavy work -- If an event handler needs to do expensive computation, use
CallLaterto defer it:
override bool OnClick(Widget w, int x, int y, int button)
{
if (w == m_HeavyActionBtn)
{
GetGame().GetCallQueue(CALL_CATEGORY_GUI).CallLater(DoHeavyWork, 0, false);
return true;
}
return false;
}次のステップ
- 3.7 Styles, Fonts & Images -- Visual styling with styles, fonts, and imageset references
- 3.5 Programmatic Widget Creation -- Creating widgets that generate events
