Windows and controls
page.custom is the eighth page and the only one that is not a MUI2 macro: it
is a Page custom, and both functions behind it are the compiler’s to write
. A control is a call listed in the page’s controls; the local it is
bound to is how a running program addresses it, and decides nothing about where
it sits.
Usage page.custom { <headerText>, controls = { … }, timers = { … }, dialog = false, pre = …, show = …, leave = … }
dialog = false is a page some plugin draws, such as StartMenu::Select or
InstallOptions::dialog. Without it, show runs inside an nsDialogs dialog the
compiler creates and shows, and the plugin’s page would be followed by that
empty one. With it, the page’s function is pre, the header and show, and
nothing else, so it takes no controls and no timers.
local startMenu = plugin "StartMenu"
page.custom { "Start Menu Folder", dialog = false, show = function() local outcome, folder = startMenu.select("Example") end,}A program whose pages are all custom gets a MUI2 header all the same: the
compiler defines MUI_DISABLE_INSERT_LANGUAGE_AFTER_PAGES_WARNING for it, the
switch MUI2 offers for that case. Such a program has no page.instFiles, so
no section ever runs, and makensis warns about that (warning 8000). Silencing
it is the program’s call, not the compiler’s:
raw.head [[!pragma warning disable 8000]]The control types
Section titled “The control types”label · text · password · number · button · checkbox ·
radioButton · groupBox · hLine · dropList · listBox · fileRequest ·
dirRequest · bitmap · link · progressBar
Usage <type> { <text>, x = …, y = …, width = …, height = …, items = { … }, image = …, url = …, onClick = …, onChange = … }
→ a control handle
y and height are required — there is no auto-flow. x defaults to 0 and
width to the dialog’s.
A number is dialog units: x = 25 is 25u. A string passes through as
nsDialogs reads it — "25u", "100%", "-13u", or plain digits, which are
pixels: x = "25" is 25 pixels, the unit most nsDialogs scripts use.
local agree = checkbox { "I have read the notes", y = 60, height = 12 }local serial = text { x = 60, y = 90, width = 120, height = 14 }
installer { page.custom { "Details", controls = { agree, serial }, leave = function() if not agree.checked then abort("please read the notes") end end, }, page.instFiles {},}A progressBar has no value: its position is a number, moved with
sendMessage and the PBM_* messages, such as
sendMessage(bar, PBM_DELTAPOS, 10, 0).
Timers
Section titled “Timers”Usage timer { interval = …, onTimer = function() … end } → a timer handle
A timer runs onTimer every interval milliseconds while its page is shown,
through nsDialogs::CreateTimer. A page’s timers lists the timers it starts,
and starts them just before nsDialogs::Show. interval is a whole number known
at build time.
The handle has one field, running, which is write-only and takes true or
false. tick.running = false stops the timer (nsDialogs::KillTimer), and
tick.running = true starts it again at its interval. A timer can stop itself
from its own onTimer, but a local is not in scope inside its own
initializer, so declare it first:
local bar = progressBar { y = 10, height = 12 }
local ticktick = timer { interval = 200, onTimer = function() if sendMessage(bar, PBM_GETPOS, 0, 0) >= 100 then tick.running = false else sendMessage(bar, PBM_DELTAPOS, 2, 0) end end,}
installer { page.custom { controls = { bar }, timers = { tick } },}A control’s fields
Section titled “A control’s fields”| NSIS | Installua | Direction |
|---|---|---|
SendMessage |
handle.value — its text |
read and write |
SendMessage |
handle.checked — a checkbox’s or radioButton’s tick |
read and write |
EnableWindow |
handle.enabled |
write only |
ShowWindow |
handle.visible |
write only |
SetCtlColors |
handle.colors = { text = …, background = … } |
write only |
CreateFont |
handle.font = { face = …, size = …, bold = … } |
write only |
LoadAndSetImage |
handle.image = "check.bmp" |
write only |
GetFunctionAddress |
onClick / onChange on the declaration, and onTimer |
— |
Five of the seven are write-only because Windows offers no instruction that reports them: NSIS can set a control’s colours and cannot ask what they are.
Either colour may be left out and keeps the control’s own. The same holds for
headerColors and a directory page’s colors, where MUI2’s default fills in.
On a dropList or listBox, value is the selected row: "" when none is,
and a write selects the first row that starts with the text, ignoring case — what
NSD_CB_SelectString does. handle.add("text") appends a row at install time,
where items holds the rows known at build time.
handle.focus() moves the keyboard focus to the control — ${NSD_SetFocus}. Call
it in show, since focus set in pre is lost when the page is drawn. A
getDlgItem handle has it too: local next = getDlgItem(HWNDPARENT, 1) then
next.focus() focuses MUI2’s Next button.
A raw block reaches a control through a global: h = serial, then $h in the
raw. The global keeps the fields a getDlgItem handle has, so h.enabled and
h.focus() work in any function.
local agree = checkbox { "I have read the notes", y = 60, height = 12 }local serial = text { x = 60, y = 90, width = 120, height = 14 }
installer { page.custom { "Details", controls = { agree, serial }, show = function() serial.font = { face = "Tahoma", size = 8 } serial.colors = { text = "800000", background = "transparent" } agree.enabled = false end, },}GetDlgItem / FindWindow / IsWindow / SendMessage
Section titled “GetDlgItem / FindWindow / IsWindow / SendMessage”Reaching a window Installua did not draw — MUI2’s own Cancel button, or another process’s.
| NSIS | Installua |
|---|---|
GetDlgItem |
getDlgItem(dialog, itemId) → handle |
FindWindow |
findWindow(class[, { title, parent, childAfter }]) → handle |
IsWindow |
isWindow(hwnd) → boolean |
SendMessage |
sendMessage(hwnd, message, wParam, lParam[, { timeout }]) → int |
local cancel = getDlgItem(HWNDPARENT, 2)cancel.enabled = falseA one-off write needs no local: getDlgItem(HWNDPARENT, 2).enabled = false.
sendMessage is every message a field is not. The message is a
window message constant or a number, and
wParam and lParam are each a number, a handle or a string. A string is
passed as a pointer to its text, which is what STR: means in NSIS. timeout
is in milliseconds.
The directory page’s path field is item 1019 of the dialog inside the installer window, and this caps it at 100 characters:
page.directory { show = function() local inner = findWindow("#32770", { parent = HWNDPARENT }) sendMessage(getDlgItem(inner, 1019), EM_LIMITTEXT, 100, 0) end,},HideWindow / BringToFront / LockWindow / SetBrandingImage / SetDetailsView
Section titled “HideWindow / BringToFront / LockWindow / SetBrandingImage / SetDetailsView”| NSIS | Installua |
|---|---|
HideWindow |
hideWindow() |
BringToFront |
bringToFront() |
LockWindow |
lockWindow("on" | "off") |
SetBrandingImage |
setBrandingImage(path[, { … }]) |
SetDetailsView |
setDetailsView("hide" | "show") |