scheduler — u_pbt_scheduler #
← Component reference · Guide contents
An Outlook-like calendar: day, work week, week, month and agenda views, an all-day band, drag and drop to move and resize, resources side by side, colour categories, and cards and tooltips written by templates. It is filled appointment by appointment, or in one call from a DataStore — and every gesture of the user hands you back the row to update.
▶ See it live — Demo application, Scheduler tile: the preview, the code behind it and this page, side by side.
At a glance #
| Userobject | u_pbt_scheduler |
| Item classes | n_pbt_scheduler_appointment (an appointment, of_appointment) · n_pbt_scheduler_resource (a resource, of_resource) · n_pbt_scheduler_category (a category, of_category) |
| Used for | A team's schedule, booking rooms or machines, a sales rep's diary, patient appointments — anything that sits on days and hours |
| Demo mode limit | The first 12 appointments of the range on screen, by start time; the others stay in memory without being shown — see demo mode |
Quick start #
// open event of the window : a few appointments of the week, in one repaint
uo_sched.of_set_redraw(/*on*/ false)
uo_sched.is_date = "2026-09-21"
// A colour category, then the appointments : a key, a subject, a start and an end
uo_sched.of_add_category(/*key*/ "customer", /*label*/ "Customer", /*color*/ RGB(216, 90, 48))
uo_sched.of_add_appointment(/*key*/ "a1", /*subject*/ "Weekly meeting", /*start*/ "2026-09-21 09:00", /*end*/ "2026-09-21 10:00")
uo_sched.of_appointment(/*key*/ "a1").is_location = "Room 3"
uo_sched.of_add_appointment(/*key*/ "a2", /*subject*/ "ACME visit", /*start*/ "2026-09-23 14:00", /*end*/ "2026-09-23 16:30")
uo_sched.of_appointment(/*key*/ "a2").is_category = "customer"
// Dates only : an all-day appointment, both days included
uo_sched.of_add_appointment(/*key*/ "a3", /*subject*/ "Trade fair", /*start*/ "2026-09-24", /*end*/ "2026-09-25")
uo_sched.of_appointment(/*key*/ "a3").ib_all_day = true
uo_sched.of_set_redraw(/*on*/ true)
// ue_appointment_opened event of uo_sched : (string as_key)
// A double-click, or Enter on the selected appointment : open YOUR editor
wf_edit_appointment(as_key)
Understanding the calendar #
The views #
is_view picks the view, is_date the day it is built around: its week, its month. The user changes both from the component's toolbar (Today, previous, next, the view menu) — both properties are read back live, and ue_view_changed / ue_date_changed say so, whether the change comes from the user or from your code.
| View | What it shows |
|---|---|
VIEW_DAY | One day on a time grid |
VIEW_WORK_WEEK | The working days of the week (is_work_days) — the default view |
VIEW_WEEK | The seven days, from ii_first_day_of_week |
VIEW_MONTH | Six weeks; an appointment of several days stretches as a bar across its days |
VIEW_AGENDA | A list, day by day, over ii_agenda_days days |
Dates #
A date travels as text, "yyyy-mm-dd hh:mm" — in methods, properties and events. It is a floating local time: no time zone, no daylight saving; "2026-09-22 09:00" is shown at 09:00, on every workstation.
- The end is exclusive. An appointment from 09:00 to 10:00 ends at 10:00: the next one, starting at 10:00, does not overlap it. Without an end, an appointment lasts 30 minutes.
- A date without a time includes both its days.
"2026-09-24"→"2026-09-25"withib_all_day = trueis the 24th and the 25th. For an all-day appointment the time is ignored: a DataWindow datetime column, which carries00:00:00, gives days, the last one included. - What the calendar also accepts:
"2026-09-22T09:30", seconds, and the text of a DataWindow datetime column.of_add_appointmenthas an overload that takes twodatetimevalues. - What the events hand back: the dates in the form you wrote them —
"yyyy-mm-dd hh:mm", end exclusive, for a timed appointment; dates only, both days included, for an all-day one. That is also whatis_startandis_endread back on the handle.
// Local variables
datetime ldt_start
// A "yyyy-mm-dd hh:mm" text back into a PowerBuilder datetime
ldt_start = DateTime(Date(Left(as_start, 10)), Time(Mid(as_start, 12)))
The all-day band #
Above the time grid of the day and week views, a band receives the ib_all_day appointments and those lasting 24 hours or more: a three-day seminar fits there as one bar instead of crushing three columns. ib_all_day_band = false removes it. In the month view, those appointments are bars running across their days; the others, one line written by is_month_template.
Appointments from a DataStore #
of_from_datastore(ids) loads the calendar in one call: one row = one appointment, and its key is its row number — that is what every event hands you back, ready for SetItem. A column plays a role when it bears its name, or when of_map gives it one:
| Role | Names recognised without of_map |
|---|---|
ROLE_SUBJECT | subject, title |
ROLE_START · ROLE_END | start, start_date, starts · end, end_date, ends |
ROLE_ALL_DAY · ROLE_READ_ONLY | all_day, allday · read_only, readonly |
ROLE_KEY · ROLE_LOCATION · ROLE_ORGANIZER · ROLE_DESCRIPTION · ROLE_RESOURCE · ROLE_CATEGORY · ROLE_STATUS · ROLE_RECURRING · ROLE_PRIVATE · ROLE_REMINDER · ROLE_CANCELLED | the name of the role: key, location, organizer, description, resource, category, status, recurring, private, reminder, cancelled |
- A boolean column is true for
1,true,Yoryes. Astatuscolumn carries the values ofSTATUS_*(busy,tentative,free,oof,elsewhere). Every name is also recognised with anis_prefix: is_private, sinceprivateis a PowerScript reserved word. - Every column, role or not, is a template field: a
customercolumn is written{customer}on the card, and read withof_get_field. - A column playing
ROLE_KEYreplaces the row number as the key: keep it for data you do not reload from that DataStore. Its values must be unique and not empty: a row that breaks that (or whose value holds/or|) keeps its row number as key, andue_script_errorsays so once. - The calendar never modifies the DataStore: it tells you what the user did (
ue_appointment_moved,ue_appointment_resized), you callSetItem, thenUpdate()when your application decides. - After an
InsertRow, aDeleteRow, aSortor aFilter, the row numbers change: callof_from_datastoreagain.
Resources and grouping #
A resource is what gets booked: a person, a room, a machine (of_add_resource). An appointment names it through is_resource. With is_group_by = GROUP_RESOURCE, each day of the day and week views splits into one column per resource, and dragging a card into another column changes its resource — ue_appointment_moved hands it back in as_resource. ib_visible = false on a resource hides its column and its appointments, like unticking a calendar in Outlook.
Colours, categories and status #
The colour of a card is decided in this order: the il_accent set on this appointment, then the colour of its category (of_add_category, is_category), then that of its resource, then the theme accent. The strip on the left tells the status (is_status, Outlook's "Show as": busy, tentative, free, out of office, elsewhere); an ib_cancelled appointment is drawn hollow, and ib_recurring, ib_private, ib_reminder put a small sign on the card.
Properties #
| Property | Type | Default | Role |
|---|---|---|---|
is_view | string | VIEW_WORK_WEEK | The view shown (VIEW_*). Read back live: the user changes it from the toolbar. Setting it raises ue_view_changed, like the view menu (nothing when the view does not change) |
is_date | string | today | The day shown, "yyyy-mm-dd": the view is built around it (its week, its month). Read back live: the arrows move it. Once set, is_now no longer moves it; "" gives the view back to today |
is_now | string | "" | "Now", for the current-time line and the Today button: "" = the workstation clock; "yyyy-mm-dd hh:mm" pins it (a demonstration, a test, a replay). While is_date was never set, the view goes to its day |
ii_first_day_of_week | integer | 1 | First day of the week, in ISO numbers like is_work_days: 1 = Monday … 7 = Sunday; 0 is accepted for Sunday |
is_work_days | string | "1|2|3|4|5" | The working days, ISO numbers joined by | (1 = Monday … 7 = Sunday, 0 accepted for Sunday): they make the work week, the others are shaded |
is_work_start · is_work_end | string | "08:00" · "17:00" | The working hours, "hh:mm": the rest of the day is shaded |
is_day_start · is_day_end | string | "00:00" · "24:00" | The hours the time grid shows; what falls outside is counted at the edge of its column ("▲ 1 earlier", "▼ 1 later"), with what the scroll hides above or below the view; a click on the mark brings the nearest hidden appointment into view |
ii_slot_minutes | integer | 30 | The grid step in minutes: 5, 10, 15, 20, 30 or 60. A drag snaps to it |
ii_hour_height | integer | 48 | The height of one hour, in pixels |
is_scroll_time | string | "08:00" | The time the grid scrolls to when a view opens |
ib_show_now | boolean | true | The red line of the current time |
ib_toolbar | boolean | true | The toolbar: Today, previous, next, the title, the view menu |
ib_week_numbers | boolean | false | ISO week numbers, in the corner of the time grid and before each month row |
ib_read_only | boolean | false | Nothing can be moved, resized or created with the mouse; Delete no longer asks anything |
ib_all_day_band | boolean | true | The all-day band above the time grid |
ib_tooltips | boolean | true | The tooltip of each appointment, written by the tooltip templates |
ib_veto_changes | boolean | false | Asks before a move or a resize is applied: raises ue_appointment_changing, which may refuse |
is_card_template | string | "[b]{subject}[/b]{?location}; {location}{/location}" | The template of the card: day and week views, agenda, month bars — see Templates |
is_month_template | string | "{!all_day}{start} {/all_day}{subject}" | One line of the month view, for an appointment within a day |
is_tooltip_title_template | string | "{subject}" | The title of an appointment's tooltip |
is_tooltip_template | string | the time, the place, the organizer | The text of an appointment's tooltip — see Templates |
is_group_by | string | GROUP_NONE | GROUP_RESOURCE: one column per resource under each day, plus a (none) column at the end when an appointment has no resource the calendar knows |
is_filter | string | "" | Shows only the appointments that contain this text (subject, location, organizer, description, fields); "" shows them all |
is_time_format | string | "hh:mm" | How a time is written: "hh:mm", "h:mm AM/PM"… (tokens of Templates) |
ii_agenda_days | integer | 7 | How many days the agenda view lists |
is_theme_style | string | "" | Visual style of the component (THEME_STYLE_* constants); empty = the application's, followed at every change |
is_theme_mode | string | "" | Light or dark variant (THEME_MODE_* constants); empty = the application's, followed at every change |
il_theme_accent | long | -1 | Accent colour of this component (-1 = the application accent, or the theme's) |
is_tooltip | string | "" | Plain tooltip of the component (an appointment has its own, written by the templates) |
is_super_tooltip_title | string | "" | Title of the rich tooltip (takes precedence over is_tooltip) |
is_super_tooltip_text | string | "" | Text of the rich tooltip (rich markup accepted) |
is_super_tooltip_image | string | "" | Image of the rich tooltip |
Appointment properties #
An appointment is driven through its handle, of_appointment("key") — created on first access, it stays valid afterwards. A property that is read asks the component what it is now: after a drag, is_start and is_end already say where the user dropped it.
// A handle used once fits on one line
uo_sched.of_appointment(/*key*/ "a1").is_status = n_pbt_scheduler_appointment.STATUS_TENTATIVE
| Property | Type | Default | Role |
|---|---|---|---|
is_subject | string | that of of_add_appointment | The subject, what the card shows first ({subject}) |
is_start · is_end | string | those of of_add_appointment | Start and end, "yyyy-mm-dd hh:mm", end exclusive; for an all-day appointment, dates "yyyy-mm-dd", both days included. Any other writing (22/09/2026 09:00) is kept but never drawn |
ib_all_day | boolean | false | Takes whole days: drawn in the all-day band |
is_location · is_organizer · is_description | string | "" | The place ({location}), the organizer ({organizer}), a longer text ({description}) |
is_resource | string | "" | The key of its resource: its column when the calendar groups by resource, its colour when it has no category |
is_category | string | "" | The key of its category: the colour of the card |
is_status | string | STATUS_BUSY | Outlook's "Show as" (STATUS_*), drawn by the strip on the left |
ib_recurring · ib_private · ib_reminder | boolean | false | Small signs on the card: a recurrence, a private appointment, a reminder |
ib_cancelled | boolean | false | A cancelled appointment is drawn hollow |
ib_read_only | boolean | false | The user can neither move it, resize it nor ask for its deletion |
ib_visible | boolean | true | Hides it without removing it |
Like any item, an appointment also carries the common tooltip (is_tooltip, is_super_tooltip_title, is_super_tooltip_text, is_super_tooltip_image) — which then replaces the one of the templates — and the item colours: il_accent recolours the card and its strip, il_back_color, il_text_color, il_back_color_hover and il_text_color_hover paint it at rest and on hover.
Resource and category properties #
| Property | Handle | Role |
|---|---|---|
is_label | n_pbt_scheduler_resource | The name shown above its column when the calendar groups by resource ({resource_label}) |
il_color | n_pbt_scheduler_resource | The colour of its appointments that have no category; -1 = the accent |
ib_visible | n_pbt_scheduler_resource | false hides its column and its appointments |
is_label | n_pbt_scheduler_category | Its name ({category_label}) |
il_color | n_pbt_scheduler_category | The colour of its appointments; -1 = the accent. A category is not drawn on its own: the tooltip and the five item colours it inherits are kept and read back, with no visible effect |
Constants #
| Family | Constants | Carried by |
|---|---|---|
| View | VIEW_DAY, VIEW_WORK_WEEK, VIEW_WEEK, VIEW_MONTH, VIEW_AGENDA | the component (is_view) |
| Grouping | GROUP_NONE, GROUP_RESOURCE | the component (is_group_by) |
| Column role | ROLE_KEY, ROLE_SUBJECT, ROLE_START, ROLE_END, ROLE_ALL_DAY, ROLE_LOCATION, ROLE_ORGANIZER, ROLE_DESCRIPTION, ROLE_RESOURCE, ROLE_CATEGORY, ROLE_STATUS, ROLE_RECURRING, ROLE_PRIVATE, ROLE_REMINDER, ROLE_CANCELLED, ROLE_READ_ONLY | the component (of_map) |
| Status | STATUS_BUSY, STATUS_TENTATIVE, STATUS_FREE, STATUS_OOF, STATUS_ELSEWHERE | the appointment handle (is_status) |
Constants are read on the object that carries them:
u_pbt_scheduler.VIEW_MONTHfor a component property,n_pbt_scheduler_appointment.STATUS_OOFfor an appointment property.
Methods #
Appointments #
| Method | Role | |
|---|---|---|
of_add_appointment (string as_key, string as_subject, string as_start, string as_end) | Adds one appointment: its key (unique), its subject, its start and end "yyyy-mm-dd hh:mm". Everything else goes through its handle. Returns 0 once added, -5 when the key is empty, holds / or ` | , or is **already held** (an appointment is not updated this way: its handle does that), or when the start — or an end that is given — is not written yyyy-mm-dd or yyyy-mm-dd hh:mm (String(ldt) writes 22/09/2026 on a French workstation: use the datetime overload), -2` when the component is not created |
of_add_appointment (string as_key, string as_subject, datetime adt_start, datetime adt_end) | The same, from two datetime values; a null end equals the start. For a whole day, the end is the last day itself, included: the same date twice = one day. Returns 0 once added, -5 when the key is empty, holds / or ` | or is already held, or when the start is null, -2` when the component is not created |
of_appointment (string as_key) | The handle of an appointment (n_pbt_scheduler_appointment), created on first access — see Appointment properties | |
of_remove_appointment (string as_key) | Removes one appointment. Returns 0 once removed, -5 when the calendar holds no appointment with this key, -2 when the component is not created | |
of_clear_appointments ( ) | Removes every appointment; resources, categories and options stay. Returns 0 once sent, -2 when the component is not created | |
of_set_field (string as_key, string as_field, string as_value) | Gives an appointment a field of your own, for the templates: after of_set_field("a1", "customer", "ACME"), {customer} writes ACME on its card. Returns 0 once set, -5 when the field is empty or the calendar holds no appointment with this key, -2 when the component is not created | |
of_get_field (string as_key, string as_field) | Reads a field of an appointment, live: one of yours (of_set_field) or a column of the DataStore it came from; "" when it has none |
DataStore #
| Method | Role |
|---|---|
of_from_datastore (datastore ads) | The DataStore bridge: one row = one appointment, its key = its row number, every column = a template field. Replaces the appointments shown. Returns 0 once loaded, -5 when the DataStore is not valid or has no column, -2 when the component is not created |
of_map (string as_role, string as_column) | Gives a role (ROLE_*) to a column whose name does not say it: of_map(ROLE_SUBJECT, "title_text"). Before or after of_from_datastore: after, every row is read again as it is now — a removed appointment stays removed, a dragged one stays where it was dropped, the fields of of_set_field and the appointments added by hand stay. Returns 0 once set, -5 when the role is not one of the ROLE_* constants or when the column is not one of the DataStore of the last of_from_datastore; a new ROLE_KEY gives every row a new key, and the handles of the old ones are released, -2 when the component is not created |
Resources and categories #
| Method | Role | |
|---|---|---|
of_add_resource (string as_key, string as_label) | Adds a resource — a person, a room, a machine; its colour and its visibility go through of_resource. Returns 0 once added, -5 when the key is empty, holds / or ` | , or is already held, -2` when the component is not created |
of_resource (string as_key) | The handle of a resource (n_pbt_scheduler_resource), created on first access | |
of_remove_resource (string as_key) | Removes one resource; its appointments stay — grouped by resource, in the (none) column. Returns 0 once removed, -5 when the calendar holds no resource with this key, -2 when the component is not created | |
of_clear_resources ( ) | Removes every resource. Returns 0 once sent, -2 when the component is not created | |
of_add_category (string as_key, string as_label, long al_color) | Adds a colour category: the appointments whose is_category names it take its colour (-1 = the accent). Returns 0 once added, -5 when the key is empty, holds / or ` | , or is already held, -2` when the component is not created |
of_category (string as_key) | The handle of a category (n_pbt_scheduler_category), created on first access | |
of_remove_category (string as_key) | Removes one category; its appointments go back to the accent. Returns 0 once removed, -5 when the calendar holds no category with this key, -2 when the component is not created | |
of_clear_categories ( ) | Removes every category. Returns 0 once sent, -2 when the component is not created |
Navigation and selection #
| Method | Role |
|---|---|
of_next ( ) | The next day, week or month — the arrow of the toolbar. Returns 0 once sent, -2 when the component is not created |
of_previous ( ) | The previous day, week or month. Returns 0 once sent, -2 when the component is not created |
of_go_to_today ( ) | Back to today — the Today button. Returns 0 once sent, -2 when the component is not created |
of_select_appointment (string as_key) | Selects an appointment ("" = none), as a click would: it raises ue_selection_changed (nothing when it already is the selection). Returns 0 once selected, -5 when the calendar holds no appointment with this key, -2 when the component is not created |
of_selected_key ( ) | The key of the selected appointment, "" when none — read live |
of_show_appointment (string as_key) | Brings an appointment into view: goes to its day, scrolls to its time and selects it, as a click would: it raises ue_selection_changed. A day the work week does not show (a Saturday) switches the view to the whole week, and ue_view_changed says so. Returns 0 once shown, -5 when the calendar holds no appointment with this key, -2 when the component is not created |
of_scroll_to_time (string as_time) | Scrolls the time grid to a time, "hh:mm". Returns 0 once sent, -5 when the time is empty, -2 when the component is not created |
of_first_visible_date ( ) · of_last_visible_date ( ) | The first and the last day the view shows, "yyyy-mm-dd" — read live |
of_shown_count ( ) | Returns how many appointments are shown in the range on screen: after is_filter, the hidden ones, the hidden resources and the demo cap (the first 12 of that range); of_count counts the whole calendar |
Events #
| Event | Raised when |
|---|---|
ue_appointment_clicked (string as_key) | An appointment was clicked (and selected) |
ue_appointment_opened (string as_key) | Double-click on an appointment, or Enter on the selected one: open your editor |
ue_appointment_rclicked (string as_key, long al_x, long al_y) | Right-click on an appointment. al_x / al_y are screen pixels |
ue_appointment_changing (string as_key, string as_start, string as_end, string as_resource) | Before a move or a resize is applied, only when ib_veto_changes is true. Return false to put the appointment back where it was; true by default |
ue_appointment_moved (string as_key, string as_start, string as_end, string as_resource) | The user dragged an appointment: its new start, its new end and its resource. The calendar already shows it there; save it (a DataStore row: SetItem on row Long(as_key)) |
ue_appointment_resized (string as_key, string as_start, string as_end) | The user dragged an edge of an appointment: its new start and end |
ue_range_selected (string as_start, string as_end, boolean ab_all_day, string as_resource) | The user swept empty slots with the mouse: the range, to create an appointment on it. Swept in the all-day band or in the month view, it is whole days: ab_all_day is true, dates only, the last one included |
ue_new_requested (string as_start, string as_end, boolean ab_all_day, string as_resource) | The user asks for a new appointment: a double-click on an empty slot or day, or the + of a day header (grouped by resource: of a resource header). A whole day comes as dates only, the end included: the same date twice = one day |
ue_delete_requested (string as_key) | Delete was pressed on the selected appointment. Remove it (of_remove_appointment) once your application agrees |
ue_slot_rclicked (string as_start, boolean ab_all_day, string as_resource, long al_x, long al_y) | Right-click on an empty slot or day; for a day, as_start is its date alone. al_x / al_y are screen pixels |
ue_selection_changed (string as_key) | The selected appointment changed ("" = none): a click, or of_select_appointment / of_show_appointment from your code |
ue_view_changed (string as_view) | The view changed: the user picked another one in the toolbar or opened a day by its « +N », your code set is_view, or of_show_appointment switched the work week to the whole week to show a day off |
ue_date_changed (string as_first, string as_last) | The visible range changed (navigation, view, date): its first and last day, "yyyy-mm-dd", included. Also raised on the first display (on today's range), then again when your code sets is_date or is_view: a window that sets them as it opens gets two; and after every of_reset, which empties the calendar: it is a request for data, not a gesture. This is where the appointments of the range are loaded |
ue_ready ( ) | The component has finished loading; everything sent before has been replayed |
ue_runtime_missing ( ) | The WebView2 runtime is missing: the component stays empty |
ue_bg_color (long al_color) | The component computed its theme background colour; the userobject has already adopted it (backcolor) |
Templates #
What a card, a month line and a tooltip say is not fixed: it is a template you write, in rich text ([b], [br], [color=…], [symbol=…]…) with fields between braces. Four properties carry them: is_card_template (cards of the day and week views, agenda, month bars), is_month_template (one line of the month view), is_tooltip_title_template and is_tooltip_template (the tooltip). The default tooltip template is:
[symbol=clock] {when}{?location}[br][symbol=location] {location}{/location}{?organizer}[br][symbol=person] {organizer}{/organizer}
Syntax #
| Written | Effect |
|---|---|
{subject} | The value of the field, escaped: a [ in the subject is shown, it does not open a tag |
{start:dddd d mmmm} | A date field in a format (tokens below) |
{?location}…{/location} | Writes the block only when the field says something that is not a NO: not empty, and not 0, N, no or false |
{!all_day}…{/all_day} | Writes the block only when the field is empty (or false) |
{description:raw} | Inserts the value as markup, without escaping it — for a field that already holds tags |
{{ · }} | A literal brace |
Date formats #
The tokens are PowerBuilder's; day and month names follow the display language. A text between double quotes is written as is. The same tokens serve is_time_format.
| Token | Writes |
|---|---|
yyyy · yy | The year, 2026 · 26 |
mmmm · mmm | The month name, long · short |
mm · m | The month, 09 · 9 — or the minutes right after an hour, as in PowerBuilder |
dddd · ddd | The day name, long · short |
dd · d | The day of the month, 05 · 5 |
hh · h | The hour, 09 · 9 (12-hour with AM/PM) |
nn · n | The minutes, 05 · 5 |
AM/PM · am/pm | The morning or afternoon marker |
Fields #
| Field | Value |
|---|---|
key · subject · location · organizer · description | The key and the texts of the appointment |
resource · resource_label | The key of its resource · its name |
category · category_label | The key of its category · its name |
status · status_label | The status (busy…) · its label, translated into the display language |
all_day · recurring · private · reminder · cancelled | true, or empty: made for {?…} and {!…} |
start · end | The start · end time, in the is_time_format format (empty for an all-day appointment); with a format, the date and time — for an all-day appointment, {end:…} is the last day, as is_end reads it |
date | The first day, written out in full; with a format, the formatted start |
time | 09:00-10:30, or "All day" |
when | The full sentence: day, hours, and the end day when it differs |
duration | 45 min, 1 h 30, 2 days (translated) |
| your own | Any field set by of_set_field, and every column of the of_from_datastore DataStore, under its name. A value that is a date accepts a format: {due_date:dd/mm} |
An unknown field writes an empty string.
Symbols #
The [symbol=name] rich-text tag puts a built-in symbol, monochrome, drawn in the colour of the text around it — it follows the theme, the hover and the colour of the card, with no file to ship: clock, location, person, people, calendar, repeat, lock, bell, phone, mail, video, note, tag, link, check, star, info, warning. An unknown name is shown as is.
What the user can do without a line of code #
- navigate: Today, previous, next, and the view menu of the toolbar;
- move an appointment to another time, day or resource, by dragging it — the step follows
ii_slot_minutes; - resize an appointment by dragging its edge;
- sweep empty slots to pick a range →
ue_range_selected— in the all-day band or the month view, whole days; - ask for an appointment with a double-click on an empty slot or with the + of a day header (of a resource header when the calendar is grouped) →
ue_new_requested; - open an appointment with a double-click →
ue_appointment_opened, and right-click an appointment or an empty slot for your menu.
The calendar creates, modifies and deletes nothing by itself beyond the drag: it asks, your application decides. ib_read_only switches all these gestures off at once, ib_read_only on an appointment switches them off for that one alone.
With the keyboard, once the calendar has the focus:
| Key | Effect |
|---|---|
| Enter | Opens the selected appointment → ue_appointment_opened |
| Delete | Asks for the deletion of the selected appointment → ue_delete_requested (nothing when the calendar or the appointment is read-only) |
| Up arrow / Down arrow | Selects the previous / next appointment of the view, in the order of their start → ue_selection_changed |
| Page Up / Page Down | The previous / next day, week or month |
| Alt+Home | Back to today |
| Esc | Cancels a drag in progress |
Examples #
Appointments from a DataStore, and what the user does with them #
The ids_appts DataStore (an instance variable) has the columns subject, start_date, end_date, location, room, category and customer. The first four play their role by their name; room receives it from of_map.
// open event of the window : the appointments, retrieved the way your application already does
ids_appts = create datastore
ids_appts.dataobject = "d_appointments"
ids_appts.SetTransObject(SQLCA)
ids_appts.Retrieve()
// "room" is not a role name : say which role it plays, then load every row
uo_sched.of_map(/*role*/ u_pbt_scheduler.ROLE_RESOURCE, /*column*/ "room")
uo_sched.of_from_datastore(/*ads*/ ids_appts)
// ue_appointment_moved event of uo_sched : (string as_key, string as_start, string as_end, string as_resource)
// Local variables
long ll_row
// The key of a row loaded by of_from_datastore is its row number
ll_row = Long(as_key)
ids_appts.SetItem(ll_row, "start_date", DateTime(Date(Left(as_start, 10)), Time(Mid(as_start, 12))))
ids_appts.SetItem(ll_row, "end_date", DateTime(Date(Left(as_end, 10)), Time(Mid(as_end, 12))))
ids_appts.SetItem(ll_row, "room", as_resource)
// Save when YOUR application decides : here, at once
ids_appts.Update()
// ue_appointment_resized event of uo_sched : (string as_key, string as_start, string as_end)
// Local variables
long ll_row
// Only the times change : same row, same two columns
ll_row = Long(as_key)
ids_appts.SetItem(ll_row, "start_date", DateTime(Date(Left(as_start, 10)), Time(Mid(as_start, 12))))
ids_appts.SetItem(ll_row, "end_date", DateTime(Date(Left(as_end, 10)), Time(Mid(as_end, 12))))
ids_appts.Update()
Creating and deleting #
// ue_new_requested event of uo_sched : (string as_start, string as_end, boolean ab_all_day, string as_resource)
// Local variables
long ll_row
// A new row, prefilled with the slot the user chose
ll_row = ids_appts.InsertRow(0)
ids_appts.SetItem(ll_row, "subject", "New appointment")
ids_appts.SetItem(ll_row, "start_date", DateTime(Date(Left(as_start, 10)), Time(Mid(as_start, 12))))
ids_appts.SetItem(ll_row, "end_date", DateTime(Date(Left(as_end, 10)), Time(Mid(as_end, 12))))
ids_appts.SetItem(ll_row, "room", as_resource)
// Reload (the row numbers are the keys), then bring the new one into view
uo_sched.of_from_datastore(/*ads*/ ids_appts)
uo_sched.of_show_appointment(/*key*/ String(ll_row))
// ue_delete_requested event of uo_sched : (string as_key)
// Ask first : the calendar never deletes by itself
if MessageBox("Delete", "Delete this appointment?", Question!, YesNo!) = 2 then return
ids_appts.DeleteRow(Long(as_key))
ids_appts.Update()
// The rows after it changed number : reload
uo_sched.of_from_datastore(/*ads*/ ids_appts)
One column per room #
// The rooms, each with its own colour, side by side under each day
uo_sched.of_set_redraw(/*on*/ false)
uo_sched.of_add_resource(/*key*/ "r1", /*label*/ "Room A")
uo_sched.of_add_resource(/*key*/ "r2", /*label*/ "Room B")
uo_sched.of_resource(/*key*/ "r1").il_color = RGB(15, 108, 189)
uo_sched.of_resource(/*key*/ "r2").il_color = RGB(31, 158, 117)
uo_sched.is_group_by = u_pbt_scheduler.GROUP_RESOURCE
uo_sched.is_view = u_pbt_scheduler.VIEW_DAY
// Each appointment names its room
uo_sched.of_add_appointment(/*key*/ "k1", /*subject*/ "Kickoff", /*start*/ "2026-09-22 09:00", /*end*/ "2026-09-22 10:00")
uo_sched.of_appointment(/*key*/ "k1").is_resource = "r1"
uo_sched.of_add_appointment(/*key*/ "k2", /*subject*/ "Interview", /*start*/ "2026-09-22 10:30", /*end*/ "2026-09-22 11:30")
uo_sched.of_appointment(/*key*/ "k2").is_resource = "r2"
uo_sched.of_set_redraw(/*on*/ true)
// A checkbox of the window hides Room B and its appointments, like unticking a calendar in Outlook
uo_sched.of_resource(/*key*/ "r2").ib_visible = cbx_room_b.checked
Cards and tooltips of your own #
// The card : the time, the subject in bold, the customer (a DataStore column) when there is one
uo_sched.is_card_template = "{start} [b]{subject}[/b]{?customer}[br][symbol=people] {customer}{/customer}"
// The month line : a small lock on the private ones
uo_sched.is_month_template = "{?private}[symbol=lock] {/private}{subject}"
// The tooltip : the date written out, the duration, the status
uo_sched.is_tooltip_title_template = "{subject}"
uo_sched.is_tooltip_template = "[symbol=calendar] {start:dddd d mmmm}, {time} ({duration})[br][symbol=info] {status_label}"
Refusing a move #
// Ask before any move or resize is applied
uo_sched.ib_veto_changes = true
// ue_appointment_changing event of uo_sched : (string as_key, string as_start, string as_end, string as_resource) returns boolean
// Nothing on a Saturday or a Sunday : returning false puts the appointment back
if DayNumber(Date(Left(as_start, 10))) = 1 or DayNumber(Date(Left(as_start, 10))) = 7 then return false
return true
Loading only what is visible #
Over years of history, there is no point reading everything: ue_date_changed gives the range on screen at every navigation, and on the first display. Here d_appointments takes two datetime arguments, the start and end of the range.
// ue_date_changed event of uo_sched : (string as_first, string as_last)
// The last day is INCLUDED : read up to the start of the day after
ids_appts.Retrieve(DateTime(Date(as_first)), DateTime(RelativeDate(Date(as_last), 1)))
uo_sched.of_from_datastore(/*ads*/ ids_appts)
Your menu on an appointment #
// ue_appointment_rclicked event of uo_sched : (string as_key, long al_x, long al_y)
// Local variables
m_appointment lm_menu
// Remember which appointment the menu is about, then open YOUR menu under the pointer
is_menu_key = as_key
lm_menu = create m_appointment
lm_menu.m_popup.PopMenu(parent.PointerX(), parent.PointerY())
destroy lm_menu
Best practices #
- Wrap a series of additions in
of_set_redraw(/*on*/ false)/of_set_redraw(/*on*/ true): the appointments appear at once. - For data that lives in a database, prefer
of_from_datastoreto a loop ofof_add_appointment: one transfer, and every event hands you back the row. - Reload (
of_from_datastore) after anyInsertRow,DeleteRow,SortorFilterof the DataStore: the key of an appointment is a row number. - Write dates as
"yyyy-mm-dd hh:mm", never in the regional format of the workstation: it is the only one the calendar reads everywhere. - In a template, wrap an optional field in
{?field}…{/field}: a card without a place does not show an orphan ";". - Over a long history, load the visible range in
ue_date_changedrather than the whole DataStore. - For a business rule (no appointment at the weekend, no overlap in a room),
ib_veto_changesandue_appointment_changingrefuse before the card moves.
Limits of the 4.0 #
What the calendar does not do — worth knowing before you choose it:
- Recurrence: no series (RRULE rules), no exceptions, no "this occurrence / the whole series" editing.
ib_recurringonly puts a sign on the card: each occurrence is an appointment (a DataStore row) your application expands itself. A recurrence engine is planned for a later version. - Time zones: the time is floating local time (see Dates); no second time scale, no conversion.
- Timeline view (resources as rows, time as columns) and navigation mini-calendar: absent; group by resource (
GROUP_RESOURCE) in the day and week views. - iCalendar: no import or export of
.icsfiles. - Printing: none; the calendar is drawn on screen only.
- Keyboard: you select, open, delete and navigate with the keyboard, but you cannot create or move an appointment without the mouse.
- Date and time in two columns: a start written in two DataStore columns cannot be mapped; join it into one
datetimecolumn in the query.
Inherited from the common base #
These members exist on every visual component — they are not specific to this one. They are detailed once, in the transverse chapters; this table only says where to read them.
| Members | Role | Detailed in |
|---|---|---|
of_count · of_keys_at · of_has | Walk what the component holds | 3.2 Items |
of_reset | Put the component back to zero | 3.6 Resetting a component: of_reset() |
of_register_shortcut · of_clear_shortcuts | The component's keyboard chords | 3.5 Keyboard shortcuts |
of_is_created · of_is_ready · of_get_last_error | Whether it was born, whether it is ready, what failed | 3.7 Diagnostics |
of_save_as_png · of_save_as_jpg | Export the rendering as an image | 3.8 Exporting the rendering as an image |
of_set_redraw | Group changes into a single repaint | 3.10 Best practices |
of_preload_icons | Icons shown with no delay | Instant display: of_icon |
of_set_translation | Translate one of the component's labels | 5.2 Adapting a label: of_set_translation |
of_focus_webview | Give the component the focus | 6.4 Keyboard and focus |
of_print · of_print_to_pdf | Print, or write a PDF | 6.9 Printing |
of_set_property · of_get_property · of_component_name | Driving a property by its name | 3.1 The property engine |
Two helpers are not inherited: of_icon and of_escape_markup live on n_pbt_utils. Declare one — n_pbt_utils lnv_utils, nothing to create — and call them on it.