PBToolboxAI v5 ← Site

markdownview — u_pbt_markdownview #

← Component reference · Guide contents

markdownview shows a Markdown document, read-only, at the colours of the theme: headings, lists, tables, task lists, coloured code, pictures. You set the text in is_markdown, or the path of a file in is_source; a link never navigates, it raises ue_link_clicked and the application decides. The raw HTML of a document is never run.

▶ See it live — Demo application, Markdown viewer tile, with the code and this page side by side.


At a glance #

Userobjectu_pbt_markdownview
InputA Markdown text (is_markdown), or a file of the workstation or an https:// URL (is_source)
SyntaxCommonMark, plus GitHub's tables, task lists, strikethrough, bare links and e-mail addresses, alerts (> [!NOTE]) and emoji (:+1:)
Feedbackue_link_clicked for a link, with its kind (the application decides), ue_loaded / ue_load_failed for a document, ue_search_result for the search, ue_task_toggled for a ticked box
SafetyRaw HTML is shown as text; a javascript:, vbscript: or data: link is not clickable; an http:// picture is refused, and so is a picture on a network share; a document from the web shows no file of the workstation

Quick start #

// Show a Markdown file : its pictures and relative links are read against its folder
uo_md.is_source = "C:\Acme\Help\guide.md"

// ... or any text the application has
uo_md.is_markdown = "# Release notes~n~n- [x] Orders~n- [ ] Invoices"

// ue_link_clicked : the viewer never navigates, the application decides where to open as_url

Properties #

PropertyTypeDefaultDescription
is_markdownstring""The Markdown text to show. Setting it replaces the document (is_source then reads back empty); empty clears the view. A new text of the SAME document — the editing recipe — keeps the reading position. The raw HTML it holds is displayed as text, never run (see ib_allow_html). Reads back exactly as it was set
is_sourcestring""A Markdown file to show: a path of the workstation (.md, .markdown, .txt…), a file:/// address or an https:// URL — read by the native HTTPS client of the library (the proxy and the Windows identity of the workstation, no CORS wall). Its relative pictures and links are read against its folder. http:// is refused (REASON_INSECURE), as is any file that is not text (REASON_UNSUPPORTED); over 16 MB, file or URL, REASON_TOO_LARGE. The file is read in the background: read the table of contents or move in it once ue_loaded has come (while it is read, of_toc returns 0 and of_scroll_to waits for the file). Setting it replaces the document (is_markdown then reads back empty)
is_base_pathstring""The folder (or the https:// address) the relative pictures and links of a TEXT set by is_markdown are read against — a help text kept in a database beside its pictures. Empty: the current folder of the application. A file shown by is_source always uses its own folder. Read back live
ib_allow_htmlbooleanfalsetrue draws a SHORT closed list of inert HTML tags of the document — b, i, u, s, kbd, sub, sup, mark, small, br, img (src, alt, width, height: the rules of a Markdown picture), a (href: the rules of a Markdown link) and details / summary. Any other tag, a script, a style, an event handler stay text. false: all the HTML is shown as written
ib_network_picturesbooleanfalsefalse: a picture of the document that lies on the NETWORK (a share of a server) is not loaded — opening a share hands the Windows identity of the user to that server — unless it lies on the share the document itself was read from. true: for an application that shows its OWN documents from a share. A document read from the web never shows a file of the workstation
ib_remote_picturesbooleantruetrue loads the https:// pictures of a document; false loads none — nothing leaves the workstation just because a document is displayed (a tracking pixel stays a placeholder)
ib_tasks_editablebooleanfalsetrue lets the user tick the boxes of a task list (a click, or Space on a box): ue_task_toggled gives the LINE of the source and the new state; the TEXT is the application's to rewrite. false: the boxes are read-only
ib_track_headingbooleanfalsetrue raises ue_heading_visible each time another heading reaches the top of the view while the reader scrolls — a table of contents that follows the reading
ii_zoominteger100The reading size, 50 to 300 % (100: the size of the theme). Ctrl+wheel in the document changes it by 10; read back live
is_positionstring""The reading position, a text such as 12:35 (the first block in view and how far it starts above the top). Read it before closing a long document, set it back to reopen it where the reader left it. Read back live; empty when nothing is shown. A file being read takes it once it is on screen
ib_search_enabledbooleantrueCtrl+F in the document opens a search box — the same search as of_search: typing highlights, Enter or F3 next, Shift+Enter or Shift+F3 previous, Escape closes. false leaves the shortcut to an application that searches from its own box
ib_find_match_casebooleanfalseSearch option: finds only the text with the same case. The Aa toggle of the search box is the same switch. Read back live; of_reset puts it back to false
ib_find_whole_wordbooleanfalseSearch option: finds the text only as a whole word (pen is not found inside pencil). The ab toggle of the search box is the same switch. Read back live; of_reset puts it back to false
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""Simple tooltip shown when hovering the component
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

Methods #

MethodDescription
of_clear ( ) → longEmpties the view: is_markdown and is_source read back empty; ib_search_enabled and the search options keep their values (of_reset is the one that puts every property back). An open search says so: ue_search_result(0, 0). Returns 0, -2 when the component is not created
of_clear_search ( ) → longDrops the search: no more highlight nor current occurrence (of_match_count returns 0), the search box closes. Returns 0, -2 when the component is not created
of_copy ( ) → longCopies to the clipboard the text the user selected — what Ctrl+C and Ctrl+Insert do in the document. Returns 0, -5 when nothing is selected, -2 when the component is not created
of_match_count ( ) → longReturns how many occurrences the current search found, read LIVE — the 12 of a 3 / 12 status line; 50,000 at most are counted. 0 without a search
of_match_index ( ) → longReturns the 1-based position of the current occurrence, read LIVE — the 3 of a 3 / 12 status line. 0 without an occurrence
of_refresh ( ) → longReads is_source again (from the disk or the network): the way to show a file rewritten at the same path; the reading position is kept; ue_loaded or ue_load_failed follows. Returns 0, -5 when no document is shown, -2 when the component is not created
of_scroll_to ( string as_anchor ) → longBrings the heading as_anchor to the top of the view: its anchor as of_toc gives it, with or without #, or its text; a heading inside a closed details opens it. While a file set by is_source is being read, the anchor waits for it and the call returns 0. Returns 0, -5 when no heading matches, -2 when the component is not created
of_search ( string as_query ) → longHighlights every occurrence (whatever the case by default — see ib_find_match_case —, as a whole word with ib_find_whole_word) and jumps to the first; an occurrence may cross a bold, a link or a line break of the source, never a hard line break. ue_search_result says how many (50,000 at most). An empty query is of_clear_search. While a file is being read, the search runs on it when it arrives. The search box opens with the query in it; with ib_search_enabled false, the document only highlights. Returns 0, -2 when the component is not created
of_search_next ( ) → longMoves to the NEXT occurrence (back to the first after the last); ue_search_result gives the new position. Returns 0, -2 when the component is not created
of_search_prev ( ) → longMoves to the PREVIOUS occurrence (back to the last before the first); ue_search_result gives the new position. Returns 0, -2 when the component is not created
of_selected_text ( ) → stringReturns the text the user selected in the document, read LIVE — empty when nothing is selected
of_toc ( ref string as_titles[], ref long al_levels[], ref string as_anchors[] ) → longThe table of contents, read LIVE: one entry per heading, in order — its text, its level (1 to 6) and its anchor (what of_scroll_to and a #anchor link take, GitHub style: “Release checklist” gives release-checklist). The arrays are emptied first. Returns the number of headings, 0 for a document without any — and while a file set by is_source is being read: ask for it in ue_loaded

Events #

EventWhen
ue_heading_visible (string as_anchor)With ib_track_heading: another heading reached the top of the view. as_anchor is its anchor as of_toc gives it, empty above the first heading
ue_link_clicked (string as_url, string as_kind)The user followed a link (a click, or Enter on a focused link). The component does NOT navigate: open as_url where you want. as_kind says what it is — LINK_WEB, LINK_MAIL, LINK_DOCUMENT (a Markdown or text file this component shows), LINK_FILE (any other file: never run it without asking), LINK_OTHER. A relative link of a file shown by is_source arrives resolved against its folder; a #heading link scrolls by itself and is not raised; a javascript: or data: link is not even clickable
ue_loaded (string as_source)The document is on screen: as_source is is_source as you wrote it, or an empty string for a text set by is_markdown
ue_load_failed (string as_source, string as_reason)The document could not be shown: as_reason is one of the REASON_* constants — REASON_NOT_FOUND (no such file), REASON_TOO_LARGE (over 16 MB), REASON_INSECURE (http://), REASON_UNSUPPORTED (not a text file, or another scheme), REASON_FAILED (a read or network error, or a text that cannot be read). as_source is empty for a text set by is_markdown; the previous document stays on screen. ue_loaded is not raised
ue_search_result (long al_count, long al_index)A search ran or moved: al_count occurrences in all (50,000 at most), the current one is al_index (1-based, 0 when none) — the makings of “3 / 12”
ue_task_toggled (long al_line, boolean ab_checked)With ib_tasks_editable: the user ticked (ab_checked true) or unticked a box of a task list. al_line is the line of the Markdown source (1-based). The component does not rewrite the text: the application changes [ ] / [x] on that line and sets the text back (see the example)

Example #

A document often comes from elsewhere: sort by the kind of the link, never by its text. A web page or a mail opens in its program, another document shows in the same component, and a file — an .exe, a .bat — is never run without asking.

// ue_link_clicked of uo_md : the viewer never navigates, the application decides by the kind
choose case as_kind
	case u_pbt_markdownview.LINK_WEB, u_pbt_markdownview.LINK_MAIL

		// A web page, a mail : their program
		ShellExecuteW(0, "open", as_url, "", "", 1)
	case u_pbt_markdownview.LINK_DOCUMENT

		// Another page of the help : in this viewer
		uo_md.is_source = as_url
	case else

		// A file of the disk or of the network : never run without asking
		MessageBox("Link", "This link leads to a file : " + as_url)
end choose

Editing Markdown: a codeeditor and a markdownview #

The viewer keeps its reading position when the text changes: you edit the end of a long document and the preview does not jump to the top.

// Local variables
string ls_notes

// The notes of the customer, in Markdown
ls_notes = "# Notes~n~n- [ ] Call back on Monday"

// The editor colours Markdown ; the viewer shows the page
uo_editor.is_syntax = u_pbt_codeeditor.SYNTAX_MARKDOWN
uo_editor.is_text = ls_notes
uo_md.is_markdown = ls_notes

// ue_changed of uo_editor : the whole recipe
uo_md.is_markdown = uo_editor.is_text

A table of contents in a list #

For a file set by is_source, fill the list in ue_loaded: before it, the file is still being read.

// Local variables
string ls_titles[], ls_anchors[]
long ll_levels[], ll_i

// ue_loaded of uo_md : one line per heading, indented by its level
lb_toc.Reset()
for ll_i = 1 to uo_md.of_toc(/*titles*/ ls_titles, /*levels*/ ll_levels, /*anchors*/ ls_anchors)
	lb_toc.AddItem(Fill(" ", (ll_levels[ll_i] - 1) * 3) + ls_titles[ll_i])
next
// Local variables
string ls_titles[], ls_anchors[]
long ll_levels[]

// SelectionChanged of lb_toc : bring that heading to the top
uo_md.of_toc(/*titles*/ ls_titles, /*levels*/ ll_levels, /*anchors*/ ls_anchors)
uo_md.of_scroll_to(/*anchor*/ ls_anchors[index])

A check-list the user ticks #

// Local variables
string ls_text
long ll_at, ll_n

// The user may tick the boxes of the text
uo_md.ib_tasks_editable = true

// ue_task_toggled of uo_md : the start of line al_line of the text
ls_text = uo_md.is_markdown
ll_at = 1
for ll_n = 2 to al_line
	ll_at = Pos(ls_text, "~n", ll_at) + 1
next

// Its box, the first "[" of the line, becomes [x] or [ ]
ll_at = Pos(ls_text, "[", ll_at)
if ab_checked then
	ls_text = Replace(ls_text, ll_at, 3, "[x]")
else
	ls_text = Replace(ls_text, ll_at, 3, "[ ]")
end if

// The text set back : the view keeps its place
uo_md.is_markdown = ls_text

The component never rewrites the text itself: the application keeps control of what it saves.

Reopening a document where the reader left it #

// Before closing the window : remember the place in the guide
SetProfileString("acme.ini", "help", "position", uo_md.is_position)

// When the guide is shown again : the place is taken once the file is on screen
uo_md.is_source = "C:\Acme\Help\guide.md"
uo_md.is_position = ProfileString("acme.ini", "help", "position", "")

Good practice #


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_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
is_accessible_name · is_accessible_descriptionThe name and description a screen reader says3.4 Optional properties and events

← Component reference · Guide contents