PBToolboxAI v4 ← Site

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 #

Userobjectu_pbt_scheduler
Item classesn_pbt_scheduler_appointment (an appointment, of_appointment) · n_pbt_scheduler_resource (a resource, of_resource) · n_pbt_scheduler_category (a category, of_category)
Used forA team's schedule, booking rooms or machines, a sales rep's diary, patient appointments — anything that sits on days and hours
Demo mode limitThe 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.

ViewWhat it shows
VIEW_DAYOne day on a time grid
VIEW_WORK_WEEKThe working days of the week (is_work_days) — the default view
VIEW_WEEKThe seven days, from ii_first_day_of_week
VIEW_MONTHSix weeks; an appointment of several days stretches as a bar across its days
VIEW_AGENDAA 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.

// 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:

RoleNames recognised without of_map
ROLE_SUBJECTsubject, title
ROLE_START · ROLE_ENDstart, start_date, starts · end, end_date, ends
ROLE_ALL_DAY · ROLE_READ_ONLYall_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_CANCELLEDthe name of the role: key, location, organizer, description, resource, category, status, recurring, private, reminder, cancelled

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 #

PropertyTypeDefaultRole
is_viewstringVIEW_WORK_WEEKThe 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_datestringtodayThe 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_nowstring"""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_weekinteger1First day of the week, in ISO numbers like is_work_days: 1 = Monday … 7 = Sunday; 0 is accepted for Sunday
is_work_daysstring"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_endstring"08:00" · "17:00"The working hours, "hh:mm": the rest of the day is shaded
is_day_start · is_day_endstring"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_minutesinteger30The grid step in minutes: 5, 10, 15, 20, 30 or 60. A drag snaps to it
ii_hour_heightinteger48The height of one hour, in pixels
is_scroll_timestring"08:00"The time the grid scrolls to when a view opens
ib_show_nowbooleantrueThe red line of the current time
ib_toolbarbooleantrueThe toolbar: Today, previous, next, the title, the view menu
ib_week_numbersbooleanfalseISO week numbers, in the corner of the time grid and before each month row
ib_read_onlybooleanfalseNothing can be moved, resized or created with the mouse; Delete no longer asks anything
ib_all_day_bandbooleantrueThe all-day band above the time grid
ib_tooltipsbooleantrueThe tooltip of each appointment, written by the tooltip templates
ib_veto_changesbooleanfalseAsks before a move or a resize is applied: raises ue_appointment_changing, which may refuse
is_card_templatestring"[b]{subject}[/b]{?location}; {location}{/location}"The template of the card: day and week views, agenda, month bars — see Templates
is_month_templatestring"{!all_day}{start} {/all_day}{subject}"One line of the month view, for an appointment within a day
is_tooltip_title_templatestring"{subject}"The title of an appointment's tooltip
is_tooltip_templatestringthe time, the place, the organizerThe text of an appointment's tooltip — see Templates
is_group_bystringGROUP_NONEGROUP_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_filterstring""Shows only the appointments that contain this text (subject, location, organizer, description, fields); "" shows them all
is_time_formatstring"hh:mm"How a time is written: "hh:mm", "h:mm AM/PM"… (tokens of Templates)
ii_agenda_daysinteger7How many days the agenda view lists
is_theme_stylestring""Visual style of the component (THEME_STYLE_* constants); empty = the application's, followed at every change
is_theme_modestring""Light or dark variant (THEME_MODE_* constants); empty = the application's, followed at every change
il_theme_accentlong-1Accent colour of this component (-1 = the application accent, or the theme's)
is_tooltipstring""Plain tooltip of the component (an appointment has its own, written by the templates)
is_super_tooltip_titlestring""Title of the rich tooltip (takes precedence over is_tooltip)
is_super_tooltip_textstring""Text of the rich tooltip (rich markup accepted)
is_super_tooltip_imagestring""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
PropertyTypeDefaultRole
is_subjectstringthat of of_add_appointmentThe subject, what the card shows first ({subject})
is_start · is_endstringthose of of_add_appointmentStart 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_daybooleanfalseTakes whole days: drawn in the all-day band
is_location · is_organizer · is_descriptionstring""The place ({location}), the organizer ({organizer}), a longer text ({description})
is_resourcestring""The key of its resource: its column when the calendar groups by resource, its colour when it has no category
is_categorystring""The key of its category: the colour of the card
is_statusstringSTATUS_BUSYOutlook's "Show as" (STATUS_*), drawn by the strip on the left
ib_recurring · ib_private · ib_reminderbooleanfalseSmall signs on the card: a recurrence, a private appointment, a reminder
ib_cancelledbooleanfalseA cancelled appointment is drawn hollow
ib_read_onlybooleanfalseThe user can neither move it, resize it nor ask for its deletion
ib_visiblebooleantrueHides 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 #

PropertyHandleRole
is_labeln_pbt_scheduler_resourceThe name shown above its column when the calendar groups by resource ({resource_label})
il_colorn_pbt_scheduler_resourceThe colour of its appointments that have no category; -1 = the accent
ib_visiblen_pbt_scheduler_resourcefalse hides its column and its appointments
is_labeln_pbt_scheduler_categoryIts name ({category_label})
il_colorn_pbt_scheduler_categoryThe 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 #

FamilyConstantsCarried by
ViewVIEW_DAY, VIEW_WORK_WEEK, VIEW_WEEK, VIEW_MONTH, VIEW_AGENDAthe component (is_view)
GroupingGROUP_NONE, GROUP_RESOURCEthe component (is_group_by)
Column roleROLE_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_ONLYthe component (of_map)
StatusSTATUS_BUSY, STATUS_TENTATIVE, STATUS_FREE, STATUS_OOF, STATUS_ELSEWHEREthe appointment handle (is_status)

Constants are read on the object that carries them: u_pbt_scheduler.VIEW_MONTH for a component property, n_pbt_scheduler_appointment.STATUS_OOF for an appointment property.


Methods #

Appointments #

MethodRole
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 #

MethodRole
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 #

MethodRole
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
MethodRole
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 #

EventRaised 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 #

WrittenEffect
{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.

TokenWrites
yyyy · yyThe year, 2026 · 26
mmmm · mmmThe month name, long · short
mm · mThe month, 09 · 9 — or the minutes right after an hour, as in PowerBuilder
dddd · dddThe day name, long · short
dd · dThe day of the month, 05 · 5
hh · hThe hour, 09 · 9 (12-hour with AM/PM)
nn · nThe minutes, 05 · 5
AM/PM · am/pmThe morning or afternoon marker

Fields #

FieldValue
key · subject · location · organizer · descriptionThe key and the texts of the appointment
resource · resource_labelThe key of its resource · its name
category · category_labelThe key of its category · its name
status · status_labelThe status (busy…) · its label, translated into the display language
all_day · recurring · private · reminder · cancelledtrue, or empty: made for {?…} and {!…}
start · endThe 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
dateThe first day, written out in full; with a format, the formatted start
time09:00-10:30, or "All day"
whenThe full sentence: day, hours, and the end day when it differs
duration45 min, 1 h 30, 2 days (translated)
your ownAny 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 #

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:

KeyEffect
EnterOpens the selected appointment → ue_appointment_opened
DeleteAsks for the deletion of the selected appointment → ue_delete_requested (nothing when the calendar or the appointment is read-only)
Up arrow / Down arrowSelects the previous / next appointment of the view, in the order of their start → ue_selection_changed
Page Up / Page DownThe previous / next day, week or month
Alt+HomeBack to today
EscCancels 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 #

Limits of the 4.0 #

What the calendar does not do — worth knowing before you choose it:

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.

MembersRoleDetailed in
of_count · of_keys_at · of_hasWalk what the component holds3.2 Items
of_resetPut the component back to zero3.6 Resetting a component: of_reset()
of_register_shortcut · of_clear_shortcutsThe component's keyboard chords3.5 Keyboard shortcuts
of_is_created · of_is_ready · of_get_last_errorWhether it was born, whether it is ready, what failed3.7 Diagnostics
of_save_as_png · of_save_as_jpgExport the rendering as an image3.8 Exporting the rendering as an image
of_set_redrawGroup changes into a single repaint3.10 Best practices
of_preload_iconsIcons shown with no delayInstant display: of_icon
of_set_translationTranslate one of the component's labels5.2 Adapting a label: of_set_translation
of_focus_webviewGive the component the focus6.4 Keyboard and focus
of_print · of_print_to_pdfPrint, or write a PDF6.9 Printing
of_set_property · of_get_property · of_component_nameDriving a property by its name3.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.

← Component reference · Guide contents