diff --git a/BUGS-TO-REPORT.md b/BUGS-TO-REPORT.md index b231e561a..df26d8071 100644 --- a/BUGS-TO-REPORT.md +++ b/BUGS-TO-REPORT.md @@ -4482,3 +4482,208 @@ What does not reproduce it: asking for an interface the object has. `IUnknown` a Severity: low. The failure is reported, by the HRESULT, so a caller that tests it is not affected. A caller that tests the pointer instead, as a C or C++ client may, sees the value it passed in. When the variable is reused after an earlier successful call, that value is a pointer the caller has released. + +## A `For Each` that cannot get an enumerator never ends under `On Error Resume Next` + +**Describe the bug** +A `For Each` whose *group* gives no enumerator --- `Nothing`, an object of a class with no `[Enumerator]` member, or an object whose `[Enumerator]` member returns `Nothing` or raises an error --- never ends when it runs under `On Error Resume Next`, or under an error handler that ends with `Resume Next`. The error in the `For Each` line resumes into the loop body; then `Next` raises error 91, *Object variable or With block variable not set*, and resumes into the body again, without end. The expression after `In` is evaluated once only. Nothing is reported, and the program stops responding. + +**To Reproduce** +Steps to reproduce the behavior: +1. Open `for-each-resume-next-loops.twinproj` (attached as `for-each-resume-next-loops.zip`). Its module `Startup` runs five loops under `On Error Resume Next` and four under a handler that ends with `Resume Next`. Each counts how often the expression after `In` is evaluated (`header=`) and how often the body runs, and leaves the loop after six passes so that the program ends. +2. Press F5. +3. See the DEBUG CONSOLE. The control, a `Collection` of one item, runs once. Every other loop is evaluated once and runs its body until the guard ends it, with error 91 at each pass: + +``` +Collection (control): header=1 body=1 errors: 0 | after Next: 0 +Nothing: header=1 body=6 errors: 91 91 91 91 91 91 | after Next: 0 +Class with no enumerator: header=1 body=6 errors: 91 91 91 91 91 91 | after Next: 0 +Enumerator returns Nothing: header=1 body=6 errors: 91 91 91 91 91 91 | after Next: 0 +Enumerator raises error 5: header=1 body=6 errors: 91 91 91 91 91 91 | after Next: 0 +Nothing, handler with Resume Next: header=1 body=4 passes: error -2147467259 error 91 body error 91 body error 91 body error 91 body error 91 +``` + +The narrowed reproduction: +```vb +Module Startup + Public Sub Main() + Dim o As Object, v As Variant, n As Long + On Error Resume Next + For Each v In o ' o is Nothing: an error, resumed + n = n + 1 + If n = 1000 Then Exit For ' without this line the loop never ends + Next + Debug.Print n ' prints 1000; VB6 prints 1 + End Sub +End Module +``` + +**Expected behavior** +The loop ends. VB6 runs the body once, then raises error 92, *For loop not initialized*, at `Next`, and `Resume Next` continues after the loop. Every loop of the attached VB6 project (`for-each-resume-next-loops-vb6.zip`, the same cases, with classes whose `NewEnum` has the procedure identifier -4) prints `body=1`, then error 92: + +``` +Nothing: header=1 body=1 errors: 424 | after Next: 92 +Class with no enumerator: header=1 body=1 errors: 438 | after Next: 92 +Enumerator returns Nothing: header=1 body=1 errors: 451 | after Next: 92 +Enumerator raises error 5: header=1 body=1 errors: 5 | after Next: 92 +``` + +**Desktop:** + - OS: Windows 10 Pro 22H2 (build 19045) + - twinBASIC compiler version: BETA 1005 + +**Additional context** +The same in a 32-bit and a 64-bit run. A package can meet it without the program's knowledge: the contributed package ArrayListLib returns `Nothing` from its enumerator for a list with no items, and its `ToString` method goes through a list with `For Each` under `On Error Resume Next`, so `ToString` of an empty list never returns. + +The first error also differs from VB6's, which the handler cases show (VB6 in parentheses): `Nothing` raises -2147467259, `E_FAIL` (VB6 424, *Object required*); an enumerator that returns `Nothing` raises -2147467259 (VB6 451, *Property let procedure not defined and property get procedure did not return an object*); an error 5 raised in the enumerator arrives as -2147352567, *Exception occurred* (VB6 5); a class with no enumerator raises 438 in both. + +Severity: high. The program stops responding with no error, in code written to go on after an error, and a program that loops over what a package returns cannot see it coming. + + + +## An assignment through a default member with an argument, written on a public field of an object, is refused with TB5090 + +**Describe the bug** +`obj.Field(key) = value`, where `Field` is a public field of a class and holds an object whose class has a default member that takes an argument, is refused with TB5090, *named argument already provided for ''*. The default member is a `[DefaultMember]` `Property Get` and `Property Let` pair of a class of the project, or the `Item` property of a `Scripting.Dictionary`. The message names nothing, and the line is a plain assignment. The same assignment compiles through a local variable, a module variable (qualified or not), a property that returns the object, an `Object` variable, and `Me.Field(key)` or `Field(key)` inside the class that holds the field. The read `obj.Field(key)` compiles, and so does the assignment with the default member written out, `obj.Field.Item(key) = value`. So the compiler refuses the one place where the object is reached through a field of an instance. + +**To Reproduce** +Steps to reproduce the behavior: +1. Open `field-default-member-named-argument.twinproj` (attached as `field-default-member-named-argument.zip`). Its one source file, `Startup.twin`, has a class `Bag` with a default `Item(Key)`, a class `Holder` with public fields, and `Main` with the assignments; the lines that are refused carry the comment `REFUSED`. +2. Let the IDE compile it (it does so on opening). +3. See five errors, one at each line marked `REFUSED`, and no other: `TB5090 named argument already provided for ''`. + +The narrowed reproduction: +```vb +Class Bag + Private mValue As Long + + [DefaultMember] + Property Get Item(ByVal Key As String) As Long + Item = mValue + End Property + + [DefaultMember] + Property Let Item(ByVal Key As String, ByVal Value As Long) + mValue = Value + End Property +End Class + +Class Holder + Public Field As New Bag +End Class + +Module Startup + Public Sub Main() + Dim h As New Holder + h.Field("a") = 1 ' TB5090 + h.Field.Item("a") = 1 ' compiles + End Sub +End Module +``` + +What the refusal depends on, each tried in a project of its own or as a line in one project: +- the type of the field: `As New Bag`, `As Bag`, `As Object`, `As New Scripting.Dictionary` and `As New Collection` are all refused (a `Collection` has no way to assign an item, so it is no use as a comparison); it is the field, not its type; +- how the object holding the field is reached: a variable `As Holder`, `As New Holder`, a parameter, a `With` block and the left-hand side of a compound statement (`h.Field("a") = h.Field("a") + 1`) are refused; `Me.Field(...)` and a bare `Field(...)` inside the class, and a variable `As Object` or `As Variant` that holds the `Holder`, compile; +- a `Property Get Prop() As Bag` in the place of the field: `h.Prop("a") = 1` compiles; a field of a user-defined type that holds a `Bag` (`u.UdtBag("a") = 1`) compiles; a module's public variable, written `Startup.PubBag("a") = 1`, compiles; +- `Set h.ObjField("a") = x`, with a default `Property Set Item(Key)`, is refused too, with another message: TB5001, *Argument for '': cannot coerce type 'String' to '_ObjBag'* (the key is taken for the object the default member returns). + +**Expected behavior** +The assignment compiles and calls the default member's `Property Let` (or `Property Set`), as it does through a property that returns the same object. VB6 does not compile it either: its build stops with the compile error *Wrong number of arguments or invalid property assignment* on each of the five refused lines, and on `Me.Field("a") = 1` inside the class, which twinBASIC compiles. VB6 does compile the property, the local variable, the read, and the late-bound object (the attached VB6 project, `field-default-member-named-argument-vb6.zip`, builds those and lists the refused lines as comments). So the request is for twinBASIC to be consistent with itself, and, if the refusal stays, a message that says why: the one it gives names an argument that does not exist. + +**Desktop:** + - OS: Windows 10 Pro 22H2 (build 19045) + - twinBASIC compiler version: BETA 1005 + +**Additional context** +The same in a 32-bit and a 64-bit compile. Met in the contributed package TBMANLIB, whose `cHttpClient` has a public `RequestHeaders` field that holds a dictionary: `Http.RequestHeaders("X-Key") = "1"` is refused and `Http.RequestHeaders.Item("X-Key") = "1"` is accepted. + +Severity: low. The line is refused at compile time and the default member written out is a workaround. + + + +## Empty parentheses after a function result or a variable are accepted on a typed value and fail at run time on a Variant + +**Describe the bug** +A pair of empty parentheses written after a function's result or a variable, as in `Num = Asc(c)()` or `G = Chr(Num)()`, compiles. What it does when it runs depends on the type of the value in front of it. When the type is not `Variant` (`Long`, `Double`, `String`), the parentheses are ignored and the value is used: `Asc("abc")()` is 97. When it is a `Variant` (the result of `Chr`, `Mid`, `Left`, `Int`, `Sgn`, `Abs` and `InStr`, of a `Function ... As Variant` of the project, or a `Variant` variable), the line raises error -2147467259, *Unspecified error*. When it is an object, the default member is called with no arguments and raises error -2147352561, *Parameter not optional* if it needs one. The compiler gives no diagnostic for any of these. + +**To Reproduce** +Steps to reproduce the behavior: +1. Open `empty-parentheses-after-value.twinproj` (attached as `empty-parentheses-after-value.zip`). Its module `Startup` runs nine such assignments under `On Error Resume Next` and prints each result and error number. +2. Press F5. +3. See the DEBUG CONSOLE: the six lines with a value that is not a `Variant` give their value and error 0; the three with a `Variant` give an empty result and error -2147467259: + +``` +Asc(s)() [Long]: result=[97] error 0 +Len(s)() [Long]: result=[3] error 0 +Twice(3)() [Long]: result=[6] error 0 +n() [Long variable]: result=[5] error 0 +s() [String variable]: result=[abc] error 0 +Chr$(65)() [String]: result=[A] error 0 +Chr(65)() [Variant]: result=[] error -2147467259 +VarLong()() [Variant]: result=[] error -2147467259 +v() [Variant variable]: result=[] error -2147467259 +``` + +What the compiler does refuse: arguments in the second pair (`Len(s)(1)` and `Twice(3)(1)`, TB5030, *Unexpected call arguments*), `(Asc(s))()` (TB5001, *expected a delegate*), and `Coll()()` for a function that returns a `Collection` (TB5092, *Missing argument 'Index'*). + +**Expected behavior** +A diagnostic for the value that is not a `Variant`, as VB6 gives: its build stops with *Type mismatch* on `Asc(s)()`, `Twice(3)()` and `Chr$(65)()`, *Syntax error* on `Len(s)()`, and *Expected array* on `n()` and `s()`. For a `Variant`, VB6 compiles the line and raises error 9, *Subscript out of range*, when it runs (the parentheses are read as an index of an array with no subscripts); twinBASIC raises -2147467259 (the attached VB6 project, `empty-parentheses-after-value-vb6.zip`, builds the three `Variant` lines, and lists the refused ones with VB6's messages as comments). + +**Desktop:** + - OS: Windows 10 Pro 22H2 (build 19045) + - twinBASIC compiler version: BETA 1005 + +**Additional context** +The same in a 32-bit and a 64-bit run. Met in the contributed package TBMANLIB, whose module `ToolsCN` has `Num = Asc(Mid(CnChar, i, 1))()` and `G = Chr(Num)()` in `GetPinYin`: the first line runs and returns the character code, the second raises the error, for every ASCII character. + +Severity: low. Code that VB6 refuses is accepted and, for the types that are not `Variant`, works; for a `Variant` it raises an error that does not say what is wrong. + + + +## An assignment to an element of a public array field of a class crashes the compiler + +**Describe the bug** +`h.Values(1) = 5`, where `Values` is a public array field of a class (`Public Values(1 To 3) As Long`) and `h` is a variable of that class, crashes the compiler. The IDE prints `NATIVE EXCEPTION: ACCESS_VIOLATION` in the DEBUG CONSOLE, restarts the compiler on the same project, and it crashes again, three times in a run, so nothing in the project compiles while the line is there. The declaration alone does not crash it, and nor does the read of an element, which is refused with a diagnostic (TB5033, *Binding error, no datatype*), so the assignment is the one use that crashes the compiler instead of being compiled or refused. + +**To Reproduce** +Steps to reproduce the behavior: +1. Open `class-array-field-element-assignment-crash.twinproj` (attached as `class-array-field-element-assignment-crash.zip`). Its one source file, `Startup.twin`, holds the whole bug: + ``` + Class Holder + Public Values(1 To 3) As Long + End Class + + Module Startup + + Public Sub Main() + Dim h As New Holder + h.Values(1) = 5 + End Sub + + End Module + ``` +2. Let the IDE compile it (it does so on opening). +3. See the DEBUG CONSOLE: `NATIVE EXCEPTION: ACCESS_VIOLATION {no-basic-code} [$1EAFA348:twinBASIC_win32.dll+000FA348]`, then `restarting from MEMORY [...]`, and the same exception twice more, each with a list of the compiler's threads in which the failing one is `>>> thread 0004: ResolveStageZ_CompilationsUnitStart, /ClassArrayFieldElementAssignmentCrash/Sources/Startup.twin; within unit Main; starting line 12`. Without the line `h.Values(1) = 5` the project compiles without a diagnostic. + +What it depends on, each tried in a project of its own: +- the assignment crashes with the array bounds `(1 To 3)` and `(3)`, with `h` declared `As New Holder` or `As Holder`, and in a `With h` block (`.Values(1) = 5`); the class having other members does not change that (a `Sub`, or a field `Public X As Long`, with which the compiler also reports TB5131 at the declaration, as the next paragraph says); +- it compiles when `h` is an `Object` variable that holds the `Holder` (late bound), when the assignment is inside the class, for an array of a user-defined type (`u.A(1) = 5`), and for a public array of a module (`Startup.A(1) = 5`); +- a dynamic array field, `Public Values() As Long`, is refused instead of crashing: `ReDim h.Values(1 To 3)` gives TB5063 and `h.Values(1) = 5` gives TB5001, *cannot coerce type 'Integer' to [ByRef] 'Long()'*; so a public array field of a class cannot be indexed from outside the class at all. + +The declaration is also treated unevenly. TB5131, *class modules cannot expose public fixed-arrays*, is reported at `Public Values(1 To 3) As Long` when the class has a field of another type (`Public X As Long` before or after it), and not when the array is the class's only field, nor when the class has only arrays (two of them) or only arrays and methods. The crash is the same with and without TB5131. + +**Expected behavior** +A diagnostic, or a compiled project. The assignment is refused the way the read of an element is, with a message that says what is wrong, and the project keeps compiling. VB6 refuses the declaration itself: its build stops with *Constants, fixed-length strings, arrays, user-defined types and Declare statements not allowed as Public members of object modules* (the attached VB6 project, `class-array-field-element-assignment-crash-vb6.zip`, has the line as a comment, with an indexed property in its place). + +**Desktop:** + - OS: Windows 10 Pro 22H2 (build 19045) + - twinBASIC compiler version: BETA 1005 + +**Additional context** +The same in a 32-bit and a 64-bit compile. Met while documenting the contributed package TBMANLIB: a probe project with a class that had a public fixed array, and a statement that assigned to one of its elements, crashed the compiler. + +Severity: high for a project that has the line. The compiler crashes on every attempt and restarts into the same crash, so the IDE reports nothing else about the project, and the line has to be found and removed by hand. No wrong code is produced. + + + + diff --git a/WIP.Build.md b/WIP.Build.md index e8b23fe3b..e91dc2bb5 100644 --- a/WIP.Build.md +++ b/WIP.Build.md @@ -485,13 +485,17 @@ the bytes of the [help add-in's project file](#the-help-add-ins-project-file). **Which folders under `docs/` are outputs comes from one list.** A build given `--dest docs/_site-basepath` writes `_site-basepath-offline` and -`_site-basepath-pdf` as well, so naming output folders one at a time misses some -and reads them as sources. The script skips the top-level folders that -`isOutputTree` in [lib/markdown-files.mjs](lib/markdown-files.mjs) names --- the -prefix list (`_site`, `_serve`, `_pdf`) the markdown walk uses --- and keeps only -`.git` and `node_modules` as names of its own. Serve mode prepares `_serve` alone -(`prepDest` in `builder/tbdocs.mjs`), so it leaves no `_serve-offline` or -`_serve-pdf` beside it. +`_site-basepath-pdf` as well, unless `--no-offline` and `--no-pdf` skip them, so +naming output folders one at a time misses some and reads them as sources. The +script skips the top-level folders that `isOutputTree` in +[lib/markdown-files.mjs](lib/markdown-files.mjs) names --- the prefix list +(`_site`, `_serve`, `_pdf`) the markdown walk uses --- and keeps only `.git` and +`node_modules` as names of its own. `prepDest` in `builder/tbdocs.mjs` prepares +only the trees whose passes run (`destinationTrees` in `builder/write.mjs`): a +build removes a skipped sibling rather than leaving it empty, and serve mode +prepares `_serve` alone, so it leaves no `_serve-offline` or `_serve-pdf` beside +it. `check_cli.mjs` probes the rule, because nothing else would notice two empty +folders beside a `--no-offline --no-pdf` build. **Files the build writes into a source folder are not sources.** `page-baseline.json` and `symbol-baseline.json` (`IGNORED_FILES`) are written @@ -610,11 +614,18 @@ and symbol guards share, and the second in that added a page. The script's sources are "the inputs that decide the built bytes", and a baseline decides none of them. `dot` and `vendorAssets` write into `docs/` and escape this only because they run early. -- **Neither CI nor `--serve` may write.** A CI run that rewrote the file would - record the drop it was asked to catch, so there a missing baseline is an error - rather than a first run. `--serve` rebuilds on every save under `docs/`, so a - page half-deleted in an editor would lower the baseline and a half-added one - would raise it. +- **Neither CI nor `--serve` may write, nor a build into another `--dest`.** A + CI run that rewrote the file would record the drop it was asked to catch, so + there a missing baseline is an error rather than a first run. `--serve` + rebuilds on every save under `docs/`, so a page half-deleted in an editor + would lower the baseline and a half-added one would raise it. A build into a + `--dest` other than `docs/_site` is a private copy: agents building side by + side into `.claude/-site` would each rewrite the committed file, and + every other checkout would have to undo it after every build. Such a build + compares and prints `page baseline not raised: ...` for a rise; a fall still + fails it. `baselineWrites` decides all three, and the symbol baseline and the + add-in's index copy follow it too. `--update-page-baseline` writes whatever + the destination. **Mark a build failed through `failBuild`, never by assigning `process.exitCode`.** `failBuild` is the one place `runBuild` sets the found-problems @@ -622,8 +633,8 @@ exit code (1), so no later step can clear it; a refused command line or a crash exits 2 instead. `scripts/check_page_baseline.mjs` is the gate on the gate, in `test.bat` and -both CI workflows: eleven probes against a scratch baseline, no browser, no -built tree. **It is not optional bookkeeping** --- the guard is silent on a +both CI workflows: seventeen probes against a scratch baseline, no browser, no +built tree, six of them on who may write. **It is not optional bookkeeping** --- the guard is silent on a healthy tree, so a green build is exactly what a guard that has stopped working produces. One probe replays the loss of 37 pages; the two that look redundant (foreign source root, missing baseline under CI) each guard one of the rules @@ -691,8 +702,8 @@ Three decisions about the build side, each with the alternative it rules out: **The drift guard is the page-count guard's shape applied to URLs** ([builder/symbol-baseline.mjs](builder/symbol-baseline.mjs)): `builder/symbol-baseline.json` lists every URL the index has published, one to a line. A build that loses one fails and -names it; a build that adds one rewrites the list; CI, `--serve` and `--dry-run` never -write; a source root other than `docs` is skipped; `--update-symbol-baseline` records a +names it; a build that adds one rewrites the list; CI, `--serve`, `--dry-run` and a build +into another `--dest` never write; a source root other than `docs` is skipped; `--update-symbol-baseline` records a removal. It exists because **an anchor has no `redirect_from:`** --- a reworded member heading moves its id, an installed add-in keeps the old URL, and the link check only follows links made inside the site, so nothing else would notice. The failure message @@ -701,7 +712,8 @@ leads with the usual fix, pinning the old id on the reworded heading. **The help add-in's copy of the index** ([builder/addin-index.mjs](builder/addin-index.mjs)): `add-in/Resources/SYMBOLS/symbols.json` is committed so that the add-in builds from its tree as it is, and a build of `docs` rewrites it whenever it differs from the index the -build wrote, under the guards' write rules (not CI, not `--serve`, not `--dry-run`). A +build wrote, under the guards' write rules (not CI, not `--serve`, not `--dry-run`, not +another `--dest`). A difference is reported, never a failure: a stale copy is an older index, not a broken one. The comparison is byte for byte, which is why `.gitattributes` checks `add-in/Resources/` out `-text`: a CRLF checkout of the copy would differ from the LF the build writes on every diff --git a/WIP.md b/WIP.md index 9a36c2a02..661cef1d1 100644 --- a/WIP.md +++ b/WIP.md @@ -34,7 +34,7 @@ sibling. History goes nowhere: git keeps it. Reference documentation is **complete** for all thirteen packages: VBA, VBRUN, VB, WebView2, Assert, CustomControls, CEF, WinEventLogLib, WinNamedPipesLib, WinServicesLib, tbIDE, WinNativeCommonCtls and AppGlobalClassObject. It is adapted from primary sources: Microsoft VBA-Docs (CC-BY-4.0) for the runtime library, `.twin` source for the twinBASIC-specific packages. The CEF and WebView2 packages also carry a tutorial set. -Twenty-six **contributed** packages from TWINSERV have sections under `docs/Reference/Contributed/`: ArrayListLib, FileProps, Fmt, Generics, OLEGuids, WinReg, TwinTimerLib, tbFileSysTools, TBMANLIB, TBMANLIB_Aes, TBMANLIB_ConsoleComponents, TBMANLIB_ConsoleCore, TBMANLIB_Diagnostic, TBMANLIB_Dialog, TBMANLIB_ListBoxHScroll, TBMANLIB_RES, TBMANLIB_Xml, tbProjPacker, tbRegExp, tsJson, tsPwGen, VBA_JSON and xCalendar complete; AutoCompleteLib, BigNumberLib and VBComDlg their main class only. The two console packages carry a NOTE (owner, 2026-10-12) that the samples harness cannot yet capture a console, so their console claims follow the source. Generics (seven tuple classes) and xCalendar (one control) are each one page (owner, 2026-10-11 and 10-12), so `analyze` in audit mode lists their classes and members as undocumented. What xCalendar's page says it does at run time comes from `test/package-probes/xCalendar/`, which runs the control on a form in a built exe. Fmt documents its test code (`HelloWorldClass`, `HelloWorldModule`, `Pootle`) in one paragraph of its landing page, which `exclude_from_docs` names; Generics' `exclude_from_docs` names its test code, `HelloWorldModule`, which the page does not mention. VBComDlg's `exclude_from_docs` names `CommonDialogBase`, a module whose public procedures only its class calls. tbRegExp's `exclude_from_docs` names its private engine module, `StaticRegex`, and tbProjPacker's its helper modules, `modImpExp` and `modWin32`: the indexer counts a Public Enum as public even inside a Private Module, because a built-in package's are reached through its library symbol written with an asterisk (`*WinNativeCommonCtls`, as `ImlDrawConstants` says). By its bare name no such enum is reachable, in a built-in or a contributed package (TB5079 on BETA 1005). OLEGuids documents each interface's methods on the interface's page. What TBMANLIB_Dialog's pages say its dialogs do comes from `test/package-probes/TBMANLIB_Dialog/`, which operates them in a built exe on a private desktop; run it again when the package's version changes. tbRegExp is the first section drafted by the doc-draft workflow ([WIP.Indexer.md](WIP.Indexer.md#draft-and-the-doc-draft-workflow)), then reviewed against the sources, the package's README and VBScript's own **RegExp** under `cscript`. Each one has a chapter in the book's Packages part. **ArrayListLib, BigNumberLib, TBMANLIB, TBMANLIB_Aes, TBMANLIB_ConsoleComponents, TBMANLIB_ConsoleCore, TBMANLIB_Diagnostic, TBMANLIB_Xml, tbRegExp, WinReg and xCalendar are listed under Experimental Packages** (`docs/Reference/Experimental.md`, owner 2026-10-12): ArrayListLib and WinReg do not compile on BETA 1005 (each documented from its source and a copy of it that builds, its samples compile-only; ArrayListLib 1.6.151.1 was run in a copy with the arguments of its three `VSwap` calls passed `ByVal` and `LSetByVal` taking a `LongPtr`, where a For Each over a list with no items fails, ToString of such a list never returns, Reverse keeps the middle pair of an even run in place and a second GetEnumerator over a list ignores its arguments; WinReg's methods were also run in a copy of its source with its four failing calls passed `ByVal`, and ReadString, GetRegistryValueType and the first ConvertValueToString give wrong results there), xCalendar has a grid that keeps the old month's dates when Value is set to another month, and text colour properties that have no effect, BigNumberLib 1.0.2.0 compiles, but loses the minus sign after `IsNegative = True` and in a negative operand of the greatest common divisor and the least common multiple, makes a zero from `AssignFromLong` negative, and does not return from the modular inverse of zero (all four confirmed by a second agent), TBMANLIB crashes the compiler in a 64-bit build and has many members that fail or give wrong results, TBMANLIB_ConsoleComponents cannot take a cConsole of the published TBMANLIB_ConsoleCore (it embeds a copy of it, the same package at 1.0.0.1, whose types the compiler treats as different; checked in a project made by the IDE's own import, in either order), TBMANLIB_ConsoleCore reads keys wrongly, TBMANLIB_Diagnostic cannot create its Dictionary and misplaces List items, TBMANLIB_Aes runs other cipher modes than the ones named (its default CBC is ECB) and fails the NIST test vectors, and TBMANLIB_Xml's parser misreads self-closing elements and entities and does not return for some malformed text (and its XPath engine reads the step `..` and `and` with `or` wrongly), and tbRegExp, with **Global**, reports a match of no characters twice when the search skipped characters to reach it (a defect of the vba-regex engine it is made from, confirmed by a second agent). TBMANLIB_Xml's `exclude_from_docs` names its test module, `mXmlTests`, which its landing page describes in a paragraph, and the five types of its XPath engine (`XPathTokenType`, `XPathAxisType`, `XPathOperator`, `XPathStep`, `XPathPredicate`), which no public member takes or returns and which the mXPath page mentions in a paragraph; tsPwGen's names its five test modules and its six internal modules, and tsJson's its test and internal modules likewise, which their landing pages describe in a paragraph each. See *Where things live* for how a package moves between the two lists. +Twenty-six **contributed** packages from TWINSERV have sections under `docs/Reference/Contributed/`: ArrayListLib, FileProps, Fmt, Generics, OLEGuids, WinReg, TwinTimerLib, tbFileSysTools, TBMANLIB, TBMANLIB_Aes, TBMANLIB_ConsoleComponents, TBMANLIB_ConsoleCore, TBMANLIB_Diagnostic, TBMANLIB_Dialog, TBMANLIB_ListBoxHScroll, TBMANLIB_RES, TBMANLIB_Xml, tbProjPacker, tbRegExp, tsJson, tsPwGen, VBA_JSON and xCalendar complete; AutoCompleteLib, BigNumberLib and VBComDlg their main class only. The two console packages carry a NOTE (owner, 2026-10-12) that the samples harness cannot yet capture a console, so their console claims follow the source. Generics (seven tuple classes) and xCalendar (one control) are each one page (owner, 2026-10-11 and 10-12), so `analyze` in audit mode lists their classes and members as undocumented. What xCalendar's page says it does at run time comes from `test/package-probes/xCalendar/`, which runs the control on a form in a built exe. Fmt documents its test code (`HelloWorldClass`, `HelloWorldModule`, `Pootle`) in one paragraph of its landing page, which `exclude_from_docs` names; Generics' `exclude_from_docs` names its test code, `HelloWorldModule`, which the page does not mention. VBComDlg's `exclude_from_docs` names `CommonDialogBase`, a module whose public procedures only its class calls. tbRegExp's `exclude_from_docs` names its private engine module, `StaticRegex`, and tbProjPacker's its helper modules, `modImpExp` and `modWin32`: the indexer counts a Public Enum as public even inside a Private Module, because a built-in package's are reached through its library symbol written with an asterisk (`*WinNativeCommonCtls`, as `ImlDrawConstants` says). By its bare name no such enum is reachable, in a built-in or a contributed package (TB5079 on BETA 1005). OLEGuids documents each interface's methods on the interface's page. What TBMANLIB_Dialog's pages say its dialogs do comes from `test/package-probes/TBMANLIB_Dialog/`, which operates them in a built exe on a private desktop; run it again when the package's version changes. TBMANLIB_Dialog 1.1.0.0 has a page of its own for each class except cDialog, which keeps a page for each member, so `analyze` in audit mode lists the members of those one-page classes as undocumented; its `exclude_from_docs` names its test module, `TestModule`, and the seven COM interface declarations that cDialogEx calls inside (`IOleWindow`, `IModalWindow`, `IShellItem`, `IShellItemArray`, `IFileDialog`, `IFileOpenDialog`, `IFileSaveDialog`), which its landing page describes in a paragraph. cDialogEx 1.1.0.0 returns the name of the chosen file, not its path, ignores `InitialDir` and ignores a `Filter` that has no closing pipe, each documented as a WARNING. tbRegExp is the first section drafted by the doc-draft workflow ([WIP.Indexer.md](WIP.Indexer.md#draft-and-the-doc-draft-workflow)), then reviewed against the sources, the package's README and VBScript's own **RegExp** under `cscript`. tbRegExp 1.7.0.0 is Contributed although a match is at most about 3,300 characters long: its engine gives up a search from one start position after 10,000 steps, a limit fixed inside the package (`a*` on 5,000 `a` finds 3,331 characters from position 1669; confirmed by a second agent, and the same limit is in upstream vba-regex, where a caller can raise it), stated in a WARNING at the top of its landing page and in its line in `Contributed.md` (owner, 2026-10-13). Each one has a chapter in the book's Packages part. **ArrayListLib, BigNumberLib, TBMANLIB, TBMANLIB_Aes, TBMANLIB_ConsoleComponents, TBMANLIB_ConsoleCore, TBMANLIB_Diagnostic, TBMANLIB_Dialog, TBMANLIB_Xml, WinReg and xCalendar are listed under Experimental Packages** (`docs/Reference/Experimental.md`, owner 2026-10-12): ArrayListLib and WinReg do not compile on BETA 1005 (each documented from its source and a copy of it that builds, its samples compile-only; ArrayListLib 1.6.151.1 was run in a copy with the arguments of its three `VSwap` calls passed `ByVal` and `LSetByVal` taking a `LongPtr`, where a For Each over a list with no items fails, ToString of such a list never returns, Reverse keeps the middle pair of an even run in place and a second GetEnumerator over a list ignores its arguments; WinReg's methods were also run in a copy of its source with its four failing calls passed `ByVal`, and ReadString, GetRegistryValueType and the first ConvertValueToString give wrong results there), xCalendar has a grid that keeps the old month's dates when Value is set to another month, and text colour properties that have no effect, BigNumberLib 1.0.2.0 compiles, but loses the minus sign after `IsNegative = True` and in a negative operand of the greatest common divisor and the least common multiple, makes a zero from `AssignFromLong` negative, and does not return from the modular inverse of zero (all four confirmed by a second agent), TBMANLIB 1.0.0.29 does not compile in a 64-bit build (TB5001 in cSubClass, where the window procedure is passed as a Long) and has many members that fail or give wrong results, TBMANLIB_ConsoleComponents cannot take a cConsole of the published TBMANLIB_ConsoleCore (it embeds a copy of it, the same package at 1.0.0.1, whose types the compiler treats as different; checked in a project made by the IDE's own import, in either order), TBMANLIB_ConsoleCore reads keys wrongly, TBMANLIB_Diagnostic cannot create its Dictionary and misplaces List items, TBMANLIB_Dialog 1.1.0.0 has classes that fail at their main task (cDialogFindReplace reports nothing, cDialogPageSetup ignores paper and orientation, cDialogEx returns names without their folder, cDialogPassword shows the password in its confirmation boxes; every defect confirmed by a second agent, owner 2026-10-13), TBMANLIB_Aes runs other cipher modes than the ones named (its default CBC is ECB) and fails the NIST test vectors, and TBMANLIB_Xml's parser misreads self-closing elements and entities and does not return for some malformed text (and its XPath engine reads the step `..` and `and` with `or` wrongly). TBMANLIB_Xml's `exclude_from_docs` names its test module, `mXmlTests`, which its landing page describes in a paragraph, and the five types of its XPath engine (`XPathTokenType`, `XPathAxisType`, `XPathOperator`, `XPathStep`, `XPathPredicate`), which no public member takes or returns and which the mXPath page mentions in a paragraph; tsPwGen's names its five test modules and its six internal modules, and tsJson's its test and internal modules likewise, which their landing pages describe in a paragraph each. See *Where things live* for how a package moves between the two lists. The rest of this file is the maintenance guide for updating existing pages or adding new ones: package surface notes, page conventions, and the integrity check. @@ -79,7 +79,7 @@ The rest of this file is the maintenance guide for updating existing pages or ad > would file the page as a *static file* and serve the raw markdown. Editors on > Windows add a BOM unasked. `lib/frontmatter.mjs` strips a leading BOM before parsing. - `docs/Reference/Built-In/tbIDE/` — IDE Extensibility package (this is the **addin SDK**). The package is type-only — it ships **public interfaces + CoClasses** that an addin DLL binds to; every implementation behind them lives in the twinBASIC IDE itself. The user-facing surface is one entry-point factory (`tbCreateCompilerAddin`) plus 23 CoClasses grouped by role: the addin contract (`AddIn`), the root API (`Host`), the loaded `Project`, the editors collection (`Editor` / `CodeEditor` / `Editors`), the virtual file system (`FileSystem` / `FileSystemItem` / `Folder` / `File`), the in-IDE UI surface (`Toolbar` / `Toolbars` / `Button` / `ToolWindow` / `ToolWindows`), the HTML DOM inside a tool window (`HtmlElement` / `HtmlElements` / `HtmlElementProperty` / `HtmlElementProperties` / `HtmlEventProperty` / `HtmlEventProperties`), the `DebugConsole`, `KeyboardShortcuts`, `Themes`, and the single concrete user-instantiable helper class `AddinTimer`. Flat layout — one page per CoClass / Class plus the index landing. -- `docs/Reference/Contributed/` — the contributed packages, one folder each, landing page `Contributed.md` beside it. **A package's landing page opens, under its H1, with `**Author:**`** (owner, 2026-10-11): the publisher TWINSERV lists (`publisher` in `indexer/.packages/manifests/contributed.json`), or the author the package's own sources name when that is someone else (VBA_JSON: Tim Hall, published by WaynePhillipsEA). Next to the author, on the same line, `**Version:**`: the version the section documents, which is its `indexed_from` and changes with it (owner, 2026-10-12). Then `**On GitHub:**` with a link, when a repository holds the package, or the library the package says it is made from; search GitHub for the package's name and the author's account, and check that the repository holds the package's code (its history too: `TBMANLIB_*` were never in woeoio's `tbman`). A landing page with one or two sections has no `* TOC` list. A class with member pages is a folder, `/index.md` at `/tB/Packages/Contributed///` with its members beside it, so a member links to its class as `(.)`; a class documented on one page is `.md`. Each package is a chapter of the book's Packages part in `_book.yml`. A section's `indexed_from` is the four-part TWINSERV version, and its snapshot is under `indexer/snapshots/contributed/`. Its samples compile in the `contributed-` sample template, whose `Settings` names the package's version: **change that version with `indexed_from`** ([WIP.ExamplesBuild.md](WIP.ExamplesBuild.md#template-projects)). **A contributed package is documented and its samples compiled against the newest twinBASIC BETA installed, never an older one** (owner, 2026-10-11): the version that matters is the package's, and a statement about the compiler (such as "does not compile on BETA 1005") names that newest build. **A contributed package that does not compile on that build, or has defects that a program would meet, is listed under Experimental Packages** (owner, 2026-10-12); a minor defect that the section documents does not make it Experimental (owner, 2026-10-12: VBComDlg, whose Replace All raises no event, stays Contributed). **A package moves from Contributed to Experimental for its defects only after a separate agent has reproduced each defect independently** and ruled out misuse, a wrong expectation, a twinBASIC defect and the harness (owner, 2026-10-12): `docs/Reference/Experimental.md`, at `/tB/Packages/Experimental/`, after Contributed Packages. Its landing page's `parent:` is `Experimental Packages`, its line is in `Experimental.md` and not in `Contributed.md`, and its book chapter follows the Experimental Packages chapter. Its folder and URLs stay under `Contributed/`, so a package that is fixed moves back by changing only those three, and its pages keep their addresses. +- `docs/Reference/Contributed/` — the contributed packages, one folder each, landing page `Contributed.md` beside it. **A package's landing page opens, under its H1, with `**Author:**`** (owner, 2026-10-11): the publisher TWINSERV lists (`publisher` in `indexer/.packages/manifests/contributed.json`), or the author the package's own sources name when that is someone else (VBA_JSON: Tim Hall, published by WaynePhillipsEA). Next to the author, on the same line, `**Version:**`: the version the section documents, which is its `indexed_from` and changes with it (owner, 2026-10-12). Then `**On GitHub:**` with a link, when a repository holds the package, or the library the package says it is made from; search GitHub for the package's name and the author's account, and check that the repository holds the package's code (its history too: `TBMANLIB_*` were never in woeoio's `tbman`). A landing page with one or two sections has no `* TOC` list. A class with member pages is a folder, `/index.md` at `/tB/Packages/Contributed///` with its members beside it, so a member links to its class as `(.)`; a class documented on one page is `.md`. Each package is a chapter of the book's Packages part in `_book.yml`. A section's `indexed_from` is the four-part TWINSERV version, and its snapshot is under `indexer/snapshots/contributed/`. Its samples compile in the `contributed-` sample template, whose `Settings` names the package's version: **change that version with `indexed_from`** ([WIP.ExamplesBuild.md](WIP.ExamplesBuild.md#template-projects)). **A contributed package is documented and its samples compiled against the newest twinBASIC BETA installed, never an older one** (owner, 2026-10-11): the version that matters is the package's, and a statement about the compiler (such as "does not compile on BETA 1005") names that newest build. **A contributed package that does not compile on that build, or has defects that a program would meet, is listed under Experimental Packages** (owner, 2026-10-12); a minor defect that the section documents does not make it Experimental (owner, 2026-10-12: VBComDlg, whose Replace All raises no event, stays Contributed; owner, 2026-10-13: a fixed limit that the section states at the top of its landing page does not either, as tbRegExp's match length). **A package moves from Contributed to Experimental for its defects only after a separate agent has reproduced each defect independently** and ruled out misuse, a wrong expectation, a twinBASIC defect and the harness (owner, 2026-10-12): `docs/Reference/Experimental.md`, at `/tB/Packages/Experimental/`, after Contributed Packages. Its landing page's `parent:` is `Experimental Packages`, its line is in `Experimental.md` and not in `Contributed.md`, and its book chapter follows the Experimental Packages chapter. Its folder and URLs stay under `Contributed/`, so a package that is fixed moves back by changing only those three, and its pages keep their addresses. - `docs/Reference/Statements.md` — alphabetical index of language statements. - `docs/Reference/Procedures and Functions.md` — alphabetical index of procedures/functions. - `docs/LLVM/` — the LLVM section: compiling with the LLVM back end. A top-level section between Features and Reference Section in the nav, at `nav_order: 6` (Features is 5). `index.md` is the landing page and `Getting-Started.md` the only page so far, with its screenshots under `Images/`. Everything it describes arrived in **BETA 984**. Its language claims are measured on BETA 995 with `tbrun --llvm` and `--exe`: the comma, space and comma-space spellings of `[CompilerOptions]` all turn LLVM and `+optimize` on, `[CompilerOptions("")]` turns it off under project-wide LLVM, the long CPU-flag sample runs, and an error is lost between procedures when either the callee or the caller is LLVM-compiled. Its samples are marked `check_build`, which compiles them; `examples.bat --llvm` also builds them with LLVM. @@ -574,7 +574,7 @@ wrapper: |---|---|---| | `build.bat` | link + integrity check | broken intra-site links, missing pages, malformed `redirect_from`, duplicate ids, remote ``, sitemap / search-index gaps, canonical mismatches. Runs on the worker lanes that produced the HTML, so neither tree is written out only to be read back | | `build.bat` | publish allowlist | refuses any file that may not ship. It aborts the build rather than flipping an exit code, because a tree with a private key in it is one `upload-pages-artifact` from being published | -| `build.bat` | page-count baseline | a rise rewrites `builder/page-baseline.json` and says so; a fall fails the build | +| `build.bat` | page-count baseline | a rise rewrites `builder/page-baseline.json` and says so; a fall fails the build. Only a build into `docs/_site` writes it (or the next row's file, or the add-in's index copy): a build into another `--dest` compares and reports, so builds into private folders never touch the working copy | | `build.bat` | symbol-index URLs | every URL `tB/symbols.json` has published is still in it: a new one rewrites `builder/symbol-baseline.json`, a lost one --- most often a reworded member heading --- fails the build | | `build.bat` | nav integrity | every nav-visible `parent:` resolves to exactly one page | | `build.bat` | Gantt sections | every task handed to the build's Gantt chart has a section, and the chart has a place for it: a band for a main-thread task, a colour for a worker's | diff --git a/bugs/class-array-field-element-assignment-crash/class-array-field-element-assignment-crash.twinproj b/bugs/class-array-field-element-assignment-crash/class-array-field-element-assignment-crash.twinproj new file mode 100644 index 000000000..25d839bd9 Binary files /dev/null and b/bugs/class-array-field-element-assignment-crash/class-array-field-element-assignment-crash.twinproj differ diff --git a/bugs/class-array-field-element-assignment-crash/repro.json b/bugs/class-array-field-element-assignment-crash/repro.json new file mode 100644 index 000000000..db90b39cf --- /dev/null +++ b/bugs/class-array-field-element-assignment-crash/repro.json @@ -0,0 +1,6 @@ +{ + "mode": "compile", + "expect": { + "exit": 4 + } +} diff --git a/bugs/class-array-field-element-assignment-crash/src/Settings b/bugs/class-array-field-element-assignment-crash/src/Settings new file mode 100644 index 000000000..9de538670 --- /dev/null +++ b/bugs/class-array-field-element-assignment-crash/src/Settings @@ -0,0 +1,61 @@ +{ + "configuration.inherits": "Defaults", + "project.appTitle": "ClassArrayFieldElementAssignmentCrash", + "project.buildPath": "${SourcePath}\\Build\\${ProjectName}_${Architecture}.${FileExtension}", + "project.buildType": "Standard EXE", + "project.description": "Reproduces: Assigning to an element of a public array field of a class crashes the compiler", + "project.exportPathIsV2": true, + "project.id": "{D299DBED-CBF8-45C4-B380-6B47C584A6E3}", + "project.name": "ClassArrayFieldElementAssignmentCrash", + "project.optionExplicit": true, + "project.references": [ + { + "id": "{00020430-0000-0000-C000-000000000046}", + "lcid": 0, + "name": "OLE Automation", + "path32": "C:\\Windows\\SysWOW64\\stdole2.tlb", + "path64": "C:\\Windows\\System32\\stdole2.tlb", + "symbolId": "stdole", + "versionMajor": 2, + "versionMinor": 0 + }, + { + "id": "{F50B82D0-DCAB-43FE-9631-11959D4A4728}", + "isCompilerPackage": true, + "licence": "MIT", + "name": "[COMPILER PACKAGE] twinBASIC - VB Compatibility Package (Forms)", + "path32": "", + "path64": "", + "publisher": "TWINBASIC-COMPILER", + "symbolId": "VB", + "versionBuild": 0, + "versionMajor": 0, + "versionMinor": 0, + "versionRevision": 31 + }, + { + "id": "{C192FB39-64CA-4D9B-B477-A5502F48EFCC}", + "isCompilerPackage": true, + "licence": "MIT", + "name": "[COMPILER PACKAGE] twinBASIC - App global class object", + "path32": "", + "path64": "", + "publisher": "TWINBASIC-COMPILER", + "symbolId": "AppGlobalClassProject", + "versionBuild": 0, + "versionMajor": 1, + "versionMinor": 0, + "versionRevision": 0 + } + ], + "project.settingsVersion": 1, + "project.startupObject": "Sub Main", + "project.warnings": { + "errors": [], + "hints": [], + "ignored": [], + "info": [], + "warnings": [] + }, + "runtime.useUnicodeStandardLibrary": true +} diff --git a/bugs/class-array-field-element-assignment-crash/src/Sources/Startup.twin b/bugs/class-array-field-element-assignment-crash/src/Sources/Startup.twin new file mode 100644 index 000000000..dc508b058 --- /dev/null +++ b/bugs/class-array-field-element-assignment-crash/src/Sources/Startup.twin @@ -0,0 +1,17 @@ +' An assignment to an element of a public array field of a class, written outside the class +' (h.Values(1) = 5), crashes the compiler. The declaration of the field alone does not, and +' nor does an assignment inside the class, or the read of an element, which is refused +' (TB5033). VB6 refuses the declaration (the VB6 side is in vb6/). + +Class Holder + Public Values(1 To 3) As Long +End Class + +Module Startup + + Public Sub Main() + Dim h As New Holder + h.Values(1) = 5 + End Sub + +End Module diff --git a/bugs/class-array-field-element-assignment-crash/vb6/Holder.cls b/bugs/class-array-field-element-assignment-crash/vb6/Holder.cls new file mode 100644 index 000000000..c6c21d89f --- /dev/null +++ b/bugs/class-array-field-element-assignment-crash/vb6/Holder.cls @@ -0,0 +1,25 @@ +VERSION 1.0 CLASS +BEGIN + MultiUse = -1 'True +END +Attribute VB_Name = "Holder" +Attribute VB_GlobalNameSpace = False +Attribute VB_Creatable = True +Attribute VB_PredeclaredId = False +Attribute VB_Exposed = False +Option Explicit + +' VB6 refuses this declaration when it builds the project: +' "Constants, fixed-length strings, arrays, user-defined types and Declare statements not +' allowed as Public members of object modules". The line is commented out so that the +' project builds; the message is the one VB6 prints for the line left in. +'Public Values(1 To 3) As Long +Private mValues(1 To 3) As Long + +Public Property Get Values(ByVal Index As Long) As Long + Values = mValues(Index) +End Property + +Public Property Let Values(ByVal Index As Long, ByVal Value As Long) + mValues(Index) = Value +End Property diff --git a/bugs/class-array-field-element-assignment-crash/vb6/Module1.bas b/bugs/class-array-field-element-assignment-crash/vb6/Module1.bas new file mode 100644 index 000000000..02bda4aa7 --- /dev/null +++ b/bugs/class-array-field-element-assignment-crash/vb6/Module1.bas @@ -0,0 +1,20 @@ +Attribute VB_Name = "Module1" +Option Explicit + +' What VB6 does with a public array in a class: it refuses the declaration (see Holder.cls), +' so the project here uses a property with an index in its place, and assigns through it. +' Build and run it with: node scripts/bug_repro.mjs vb6 class-array-field-element-assignment-crash + +Sub Main() + On Error GoTo Fail + Open App.Path & "\out.txt" For Output As #9 + Dim h As New Holder + h.Values(1) = 5 + Print #9, "h.Values(1) after the assignment: " & h.Values(1) + Close #9 + Exit Sub +Fail: + On Error Resume Next + Print #9, "unhandled error " & Err.Number & ": " & Err.Description + Close #9 +End Sub diff --git a/bugs/class-array-field-element-assignment-crash/vb6/Probe.vbp b/bugs/class-array-field-element-assignment-crash/vb6/Probe.vbp new file mode 100644 index 000000000..21e5715d2 --- /dev/null +++ b/bugs/class-array-field-element-assignment-crash/vb6/Probe.vbp @@ -0,0 +1,9 @@ +Type=Exe +Reference=*\G{00020430-0000-0000-C000-000000000046}#2.0#0#C:\Windows\SysWOW64\stdole2.tlb#OLE Automation +Module=Module1; Module1.bas +Class=Holder; Holder.cls +Startup="Sub Main" +ExeName32="Probe.exe" +Name="Probe" +CompilationType=0 +OptimizationType=0 diff --git a/bugs/empty-parentheses-after-value/empty-parentheses-after-value.twinproj b/bugs/empty-parentheses-after-value/empty-parentheses-after-value.twinproj new file mode 100644 index 000000000..a834e15a5 Binary files /dev/null and b/bugs/empty-parentheses-after-value/empty-parentheses-after-value.twinproj differ diff --git a/bugs/empty-parentheses-after-value/repro.json b/bugs/empty-parentheses-after-value/repro.json new file mode 100644 index 000000000..86d2d12ed --- /dev/null +++ b/bugs/empty-parentheses-after-value/repro.json @@ -0,0 +1,17 @@ +{ + "mode": "run", + "expect": { + "exit": 0, + "output": [ + "^Asc\\(s\\)\\(\\) \\[Long\\]: result=\\[97\\] error 0$", + "^Len\\(s\\)\\(\\) \\[Long\\]: result=\\[3\\] error 0$", + "^Twice\\(3\\)\\(\\) \\[Long\\]: result=\\[6\\] error 0$", + "^n\\(\\) \\[Long variable\\]: result=\\[5\\] error 0$", + "^s\\(\\) \\[String variable\\]: result=\\[abc\\] error 0$", + "^Chr\\$\\(65\\)\\(\\) \\[String\\]: result=\\[A\\] error 0$", + "^Chr\\(65\\)\\(\\) \\[Variant\\]: result=\\[\\] error -2147467259$", + "^VarLong\\(\\)\\(\\) \\[Variant\\]: result=\\[\\] error -2147467259$", + "^v\\(\\) \\[Variant variable\\]: result=\\[\\] error -2147467259$" + ] + } +} diff --git a/bugs/empty-parentheses-after-value/src/Settings b/bugs/empty-parentheses-after-value/src/Settings new file mode 100644 index 000000000..b6476ef40 --- /dev/null +++ b/bugs/empty-parentheses-after-value/src/Settings @@ -0,0 +1,61 @@ +{ + "configuration.inherits": "Defaults", + "project.appTitle": "EmptyParenthesesAfterValue", + "project.buildPath": "${SourcePath}\\Build\\${ProjectName}_${Architecture}.${FileExtension}", + "project.buildType": "Standard EXE", + "project.description": "Reproduces: Empty parentheses after a function result or a variable are accepted on a typed value and fail at run time on a Variant", + "project.exportPathIsV2": true, + "project.id": "{3A4CB941-FB0E-43CD-806A-6A875AA63D14}", + "project.name": "EmptyParenthesesAfterValue", + "project.optionExplicit": true, + "project.references": [ + { + "id": "{00020430-0000-0000-C000-000000000046}", + "lcid": 0, + "name": "OLE Automation", + "path32": "C:\\Windows\\SysWOW64\\stdole2.tlb", + "path64": "C:\\Windows\\System32\\stdole2.tlb", + "symbolId": "stdole", + "versionMajor": 2, + "versionMinor": 0 + }, + { + "id": "{F50B82D0-DCAB-43FE-9631-11959D4A4728}", + "isCompilerPackage": true, + "licence": "MIT", + "name": "[COMPILER PACKAGE] twinBASIC - VB Compatibility Package (Forms)", + "path32": "", + "path64": "", + "publisher": "TWINBASIC-COMPILER", + "symbolId": "VB", + "versionBuild": 0, + "versionMajor": 0, + "versionMinor": 0, + "versionRevision": 31 + }, + { + "id": "{C192FB39-64CA-4D9B-B477-A5502F48EFCC}", + "isCompilerPackage": true, + "licence": "MIT", + "name": "[COMPILER PACKAGE] twinBASIC - App global class object", + "path32": "", + "path64": "", + "publisher": "TWINBASIC-COMPILER", + "symbolId": "AppGlobalClassProject", + "versionBuild": 0, + "versionMajor": 1, + "versionMinor": 0, + "versionRevision": 0 + } + ], + "project.settingsVersion": 1, + "project.startupObject": "Sub Main", + "project.warnings": { + "errors": [], + "hints": [], + "ignored": [], + "info": [], + "warnings": [] + }, + "runtime.useUnicodeStandardLibrary": true +} diff --git a/bugs/empty-parentheses-after-value/src/Sources/Startup.twin b/bugs/empty-parentheses-after-value/src/Sources/Startup.twin new file mode 100644 index 000000000..8f677e5c3 --- /dev/null +++ b/bugs/empty-parentheses-after-value/src/Sources/Startup.twin @@ -0,0 +1,54 @@ +' Empty parentheses after a function result or a variable, as in Num = Asc(c)() or +' G = Chr(Num)(). twinBASIC compiles every line below. Where the value is of a type other +' than Variant, the parentheses are ignored and the value is used (Asc(c)() is Asc(c)). +' Where it is a Variant, the parentheses fail when the line runs, with error -2147467259, +' "Unspecified error". VB6 refuses the first group when it compiles ("Type mismatch", +' "Syntax error" or "Expected array") and raises error 9, "Subscript out of range", for the +' second (the VB6 side is in vb6/). + +Module Startup + + Private Function Twice(ByVal n As Long) As Long + Twice = n * 2 + End Function + + Private Function VarLong() As Variant + VarLong = 7& + End Function + + Private Sub Report(ByVal Label As String, ByVal Result As Variant) + Debug.Print Label & ": result=[" & Result & "] error " & Err.Number + Err.Clear + End Sub + + Public Sub Main() + Dim s As String = "abc" + Dim n As Long = 5 + Dim v As Variant = 6 + Dim t As Variant + On Error Resume Next + + ' A value that is not a Variant: the parentheses are ignored. + t = Empty: t = Asc(s)() + Report "Asc(s)() [Long]", t + t = Empty: t = Len(s)() + Report "Len(s)() [Long]", t + t = Empty: t = Twice(3)() + Report "Twice(3)() [Long]", t + t = Empty: t = n() + Report "n() [Long variable]", t + t = Empty: t = s() + Report "s() [String variable]", t + t = Empty: t = Chr$(65)() + Report "Chr$(65)() [String]", t + + ' A Variant: the parentheses raise an error. + t = Empty: t = Chr(65)() + Report "Chr(65)() [Variant]", t + t = Empty: t = VarLong()() + Report "VarLong()() [Variant]", t + t = Empty: t = v() + Report "v() [Variant variable]", t + End Sub + +End Module diff --git a/bugs/empty-parentheses-after-value/vb6/Module1.bas b/bugs/empty-parentheses-after-value/vb6/Module1.bas new file mode 100644 index 000000000..101ea051e --- /dev/null +++ b/bugs/empty-parentheses-after-value/vb6/Module1.bas @@ -0,0 +1,45 @@ +Attribute VB_Name = "Module1" +Option Explicit + +' The same lines as the twinBASIC side in src/. VB6 refuses the ones with a value that is not a +' Variant, so they are commented out below with the message of the build: +' t = Asc(s)() Type mismatch +' t = Len(s)() Syntax error +' t = Twice(3)() Type mismatch +' t = Chr$(65)() Type mismatch +' t = n() Expected array +' t = s() Expected array +' It compiles the lines with a Variant, and raises error 9, "Subscript out of range", where they run. +' Build and run it with: node scripts/bug_repro.mjs vb6 empty-parentheses-after-value + +Private Function VarLong() As Variant + VarLong = 7& +End Function + +Private Sub Report(ByVal Label As String, ByVal Result As Variant) + Print #9, Label & ": result=[" & Result & "] error " & Err.Number + Err.Clear +End Sub + +Sub Main() + On Error GoTo Fail + Open App.Path & "\out.txt" For Output As #9 + Dim s As String, v As Variant, t As Variant + s = "abc": v = 6 + On Error Resume Next + + t = Empty: t = Chr(65)() + Report "Chr(65)() [Variant]", t + t = Empty: t = VarLong()() + Report "VarLong()() [Variant]", t + t = Empty: t = v() + Report "v() [Variant variable]", t + + On Error GoTo Fail + Close #9 + Exit Sub +Fail: + On Error Resume Next + Print #9, "unhandled error " & Err.Number & ": " & Err.Description + Close #9 +End Sub diff --git a/bugs/empty-parentheses-after-value/vb6/Probe.vbp b/bugs/empty-parentheses-after-value/vb6/Probe.vbp new file mode 100644 index 000000000..4d210b0e8 --- /dev/null +++ b/bugs/empty-parentheses-after-value/vb6/Probe.vbp @@ -0,0 +1,7 @@ +Type=Exe +Module=Module1; Module1.bas +Startup="Sub Main" +ExeName32="Probe.exe" +Name="Probe" +CompilationType=0 +OptimizationType=0 diff --git a/bugs/field-default-member-named-argument/field-default-member-named-argument.twinproj b/bugs/field-default-member-named-argument/field-default-member-named-argument.twinproj new file mode 100644 index 000000000..f9f59cf6b Binary files /dev/null and b/bugs/field-default-member-named-argument/field-default-member-named-argument.twinproj differ diff --git a/bugs/field-default-member-named-argument/repro.json b/bugs/field-default-member-named-argument/repro.json new file mode 100644 index 000000000..7c8a711b2 --- /dev/null +++ b/bugs/field-default-member-named-argument/repro.json @@ -0,0 +1,6 @@ +{ + "mode": "compile", + "expect": { + "diagnostics": ["TB5090"] + } +} diff --git a/bugs/field-default-member-named-argument/src/Settings b/bugs/field-default-member-named-argument/src/Settings new file mode 100644 index 000000000..d6e99303b --- /dev/null +++ b/bugs/field-default-member-named-argument/src/Settings @@ -0,0 +1,71 @@ +{ + "configuration.inherits": "Defaults", + "project.appTitle": "FieldDefaultMemberNamedArgument", + "project.buildPath": "${SourcePath}\\Build\\${ProjectName}_${Architecture}.${FileExtension}", + "project.buildType": "Standard EXE", + "project.description": "Reproduces: TB5090 on an assignment through a public field holding an object with a default member that takes an argument", + "project.exportPathIsV2": true, + "project.id": "{346149BF-E7E7-4CFD-AD10-EB6BD8758588}", + "project.name": "FieldDefaultMemberNamedArgument", + "project.optionExplicit": true, + "project.references": [ + { + "id": "{00020430-0000-0000-C000-000000000046}", + "lcid": 0, + "name": "OLE Automation", + "path32": "C:\\Windows\\SysWOW64\\stdole2.tlb", + "path64": "C:\\Windows\\System32\\stdole2.tlb", + "symbolId": "stdole", + "versionMajor": 2, + "versionMinor": 0 + }, + { + "id": "{F50B82D0-DCAB-43FE-9631-11959D4A4728}", + "isCompilerPackage": true, + "licence": "MIT", + "name": "[COMPILER PACKAGE] twinBASIC - VB Compatibility Package (Forms)", + "path32": "", + "path64": "", + "publisher": "TWINBASIC-COMPILER", + "symbolId": "VB", + "versionBuild": 0, + "versionMajor": 0, + "versionMinor": 0, + "versionRevision": 31 + }, + { + "id": "{C192FB39-64CA-4D9B-B477-A5502F48EFCC}", + "isCompilerPackage": true, + "licence": "MIT", + "name": "[COMPILER PACKAGE] twinBASIC - App global class object", + "path32": "", + "path64": "", + "publisher": "TWINBASIC-COMPILER", + "symbolId": "AppGlobalClassProject", + "versionBuild": 0, + "versionMajor": 1, + "versionMinor": 0, + "versionRevision": 0 + }, + { + "id": "{420B2830-E718-11CF-893D-00A0C9054228}", + "lcid": 0, + "name": "Microsoft Scripting Runtime", + "path32": "C:\\Windows\\SysWOW64\\scrrun.dll", + "path64": "C:\\Windows\\System32\\scrrun.dll", + "symbolId": "Scripting", + "versionMajor": 1, + "versionMinor": 0 + } + ], + "project.settingsVersion": 1, + "project.startupObject": "Sub Main", + "project.warnings": { + "errors": [], + "hints": [], + "ignored": [], + "info": [], + "warnings": [] + }, + "runtime.useUnicodeStandardLibrary": true +} diff --git a/bugs/field-default-member-named-argument/src/Sources/Startup.twin b/bugs/field-default-member-named-argument/src/Sources/Startup.twin new file mode 100644 index 000000000..0759d0c23 --- /dev/null +++ b/bugs/field-default-member-named-argument/src/Sources/Startup.twin @@ -0,0 +1,67 @@ +' An assignment through a default member that takes an argument, written on a public field +' of an object (obj.Field(key) = value), is refused with TB5090, "named argument already +' provided". The same assignment through a local variable, a module variable, a property +' that returns the object, Me, or a late-bound object compiles; so does the read of the +' field, and the assignment with the default member written out (obj.Field.Item(key) = value). +' Compile it: the lines marked REFUSED are the ones TB5090 reports. The VB6 side is in vb6/. + +Class Bag + Private mValue As Long + + [DefaultMember] + Property Get Item(ByVal Key As String) As Long + Item = mValue + End Property + + [DefaultMember] + Property Let Item(ByVal Key As String, ByVal Value As Long) + mValue = Value + End Property +End Class + +Class Holder + Public Field As New Bag ' a class of this project + Public Dict As New Scripting.Dictionary ' the Scripting Runtime's Dictionary + Public Plain As Bag ' the same, with no New + Private mBag As New Bag + + Public Property Get Prop() As Bag + Set Prop = mBag + End Property + + Public Sub Inside() + Me.Field("a") = 1 ' compiles + Field("a") = 1 ' compiles + End Sub +End Class + +Module Startup + + Private ModBag As New Bag + + Public Sub Main() + Dim h As New Holder + Dim b As New Bag + Dim o As Object + Dim v As Long + + h.Field("a") = 1 ' REFUSED: TB5090 + h.Dict("a") = 1 ' REFUSED: TB5090 + h.Plain("a") = 1 ' REFUSED: TB5090 + h.Field("a") = h.Field("a") + 1 ' REFUSED: TB5090 (on the left-hand side) + With h + .Field("a") = 1 ' REFUSED: TB5090 + End With + + h.Field.Item("a") = 1 ' compiles: the default member written out + v = h.Field("a") ' compiles: a read + b("a") = 1 ' compiles: a local variable + ModBag("a") = 1 ' compiles: a module variable + Startup.ModBag("a") = 1 ' compiles: a module variable, qualified + h.Prop("a") = 1 ' compiles: a property that returns the object + Set o = h + o.Field("a") = 1 ' compiles: a late-bound object + h.Inside + End Sub + +End Module diff --git a/bugs/field-default-member-named-argument/vb6/Bag.cls b/bugs/field-default-member-named-argument/vb6/Bag.cls new file mode 100644 index 000000000..907490cab --- /dev/null +++ b/bugs/field-default-member-named-argument/vb6/Bag.cls @@ -0,0 +1,21 @@ +VERSION 1.0 CLASS +BEGIN + MultiUse = -1 'True +END +Attribute VB_Name = "Bag" +Attribute VB_GlobalNameSpace = False +Attribute VB_Creatable = True +Attribute VB_PredeclaredId = False +Attribute VB_Exposed = False +Option Explicit + +Private mValue As Long + +Public Property Get Item(ByVal Key As String) As Long +Attribute Item.VB_UserMemId = 0 + Item = mValue +End Property + +Public Property Let Item(ByVal Key As String, ByVal Value As Long) + mValue = Value +End Property diff --git a/bugs/field-default-member-named-argument/vb6/Holder.cls b/bugs/field-default-member-named-argument/vb6/Holder.cls new file mode 100644 index 000000000..33f0f0a82 --- /dev/null +++ b/bugs/field-default-member-named-argument/vb6/Holder.cls @@ -0,0 +1,19 @@ +VERSION 1.0 CLASS +BEGIN + MultiUse = -1 'True +END +Attribute VB_Name = "Holder" +Attribute VB_GlobalNameSpace = False +Attribute VB_Creatable = True +Attribute VB_PredeclaredId = False +Attribute VB_Exposed = False +Option Explicit + +Public Field As New Bag +Public Dict As New Scripting.Dictionary +Public Plain As Bag +Private mBag As New Bag + +Public Property Get Prop() As Bag + Set Prop = mBag +End Property diff --git a/bugs/field-default-member-named-argument/vb6/Module1.bas b/bugs/field-default-member-named-argument/vb6/Module1.bas new file mode 100644 index 000000000..6fc338a36 --- /dev/null +++ b/bugs/field-default-member-named-argument/vb6/Module1.bas @@ -0,0 +1,45 @@ +Attribute VB_Name = "Module1" +Option Explicit + +' The same assignments as the twinBASIC side in src/. VB6 refuses the ones twinBASIC refuses +' with TB5090: each of the five lines below that is commented out is a compile error here, +' "Wrong number of arguments or invalid property assignment" (and so are Me.Field("a") = 1 +' and Field("a") = 1 inside the class Holder, which twinBASIC compiles). The lines left in +' compile, and are the ones twinBASIC compiles too. +' Build and run it with: node scripts/bug_repro.mjs vb6 field-default-member-named-argument + +Sub Main() + On Error GoTo Fail + Open App.Path & "\out.txt" For Output As #9 + Dim h As New Holder + Dim b As New Bag + Dim o As Object + Dim v As Long + Set h.Plain = New Bag + + 'h.Field("a") = 1 ' refused + 'h.Dict("a") = 1 ' refused + 'h.Plain("a") = 1 ' refused + 'h.Field("a") = h.Field("a") + 1 ' refused + 'With h + ' .Field("a") = 1 ' refused + 'End With + + h.Field.Item("a") = 1 + Print #9, "h.Field.Item(""a"") = 1: " & h.Field.Item("a") + v = h.Field("a") + Print #9, "read h.Field(""a""): " & v + b("a") = 2 + Print #9, "local variable b(""a"") = 2: " & b.Item("a") + h.Prop("a") = 3 + Print #9, "property h.Prop(""a"") = 3: " & h.Prop.Item("a") + Set o = h + o.Field("a") = 4 + Print #9, "late-bound o.Field(""a"") = 4: " & h.Field.Item("a") + Close #9 + Exit Sub +Fail: + On Error Resume Next + Print #9, "unhandled error " & Err.Number & ": " & Err.Description + Close #9 +End Sub diff --git a/bugs/field-default-member-named-argument/vb6/Probe.vbp b/bugs/field-default-member-named-argument/vb6/Probe.vbp new file mode 100644 index 000000000..eb6056f70 --- /dev/null +++ b/bugs/field-default-member-named-argument/vb6/Probe.vbp @@ -0,0 +1,11 @@ +Type=Exe +Reference=*\G{00020430-0000-0000-C000-000000000046}#2.0#0#C:\Windows\SysWOW64\stdole2.tlb#OLE Automation +Reference=*\G{420B2830-E718-11CF-893D-00A0C9054228}#1.0#0#C:\Windows\SysWOW64\scrrun.dll#Microsoft Scripting Runtime +Module=Module1; Module1.bas +Class=Bag; Bag.cls +Class=Holder; Holder.cls +Startup="Sub Main" +ExeName32="Probe.exe" +Name="Probe" +CompilationType=0 +OptimizationType=0 diff --git a/bugs/for-each-resume-next-loops/for-each-resume-next-loops.twinproj b/bugs/for-each-resume-next-loops/for-each-resume-next-loops.twinproj new file mode 100644 index 000000000..3b32882a8 Binary files /dev/null and b/bugs/for-each-resume-next-loops/for-each-resume-next-loops.twinproj differ diff --git a/bugs/for-each-resume-next-loops/repro.json b/bugs/for-each-resume-next-loops/repro.json new file mode 100644 index 000000000..4fbe48bce --- /dev/null +++ b/bugs/for-each-resume-next-loops/repro.json @@ -0,0 +1,14 @@ +{ + "mode": "run", + "expect": { + "exit": 0, + "output": [ + "^Collection \\(control\\): header=1 body=1 errors: 0 \\| after Next: 0$", + "^Nothing: header=1 body=6 errors: 91 91 91 91 91 91 ", + "^Class with no enumerator: header=1 body=6 ", + "^Enumerator returns Nothing: header=1 body=6 ", + "^Enumerator raises error 5: header=1 body=6 ", + "^Nothing, handler with Resume Next: header=1 body=4 passes: error -2147467259 error 91 body error 91 body" + ] + } +} diff --git a/bugs/for-each-resume-next-loops/src/Settings b/bugs/for-each-resume-next-loops/src/Settings new file mode 100644 index 000000000..878117584 --- /dev/null +++ b/bugs/for-each-resume-next-loops/src/Settings @@ -0,0 +1,61 @@ +{ + "configuration.inherits": "Defaults", + "project.appTitle": "ForEachResumeNextLoops", + "project.buildPath": "${SourcePath}\\Build\\${ProjectName}_${Architecture}.${FileExtension}", + "project.buildType": "Standard EXE", + "project.description": "Reproduces: A For Each whose enumerator cannot be had repeats forever under On Error Resume Next", + "project.exportPathIsV2": true, + "project.id": "{4496E0A5-BB1D-46C4-B5DF-75F4051D5361}", + "project.name": "ForEachResumeNextLoops", + "project.optionExplicit": true, + "project.references": [ + { + "id": "{00020430-0000-0000-C000-000000000046}", + "lcid": 0, + "name": "OLE Automation", + "path32": "C:\\Windows\\SysWOW64\\stdole2.tlb", + "path64": "C:\\Windows\\System32\\stdole2.tlb", + "symbolId": "stdole", + "versionMajor": 2, + "versionMinor": 0 + }, + { + "id": "{F50B82D0-DCAB-43FE-9631-11959D4A4728}", + "isCompilerPackage": true, + "licence": "MIT", + "name": "[COMPILER PACKAGE] twinBASIC - VB Compatibility Package (Forms)", + "path32": "", + "path64": "", + "publisher": "TWINBASIC-COMPILER", + "symbolId": "VB", + "versionBuild": 0, + "versionMajor": 0, + "versionMinor": 0, + "versionRevision": 31 + }, + { + "id": "{C192FB39-64CA-4D9B-B477-A5502F48EFCC}", + "isCompilerPackage": true, + "licence": "MIT", + "name": "[COMPILER PACKAGE] twinBASIC - App global class object", + "path32": "", + "path64": "", + "publisher": "TWINBASIC-COMPILER", + "symbolId": "AppGlobalClassProject", + "versionBuild": 0, + "versionMajor": 1, + "versionMinor": 0, + "versionRevision": 0 + } + ], + "project.settingsVersion": 1, + "project.startupObject": "Sub Main", + "project.warnings": { + "errors": [], + "hints": [], + "ignored": [], + "info": [], + "warnings": [] + }, + "runtime.useUnicodeStandardLibrary": true +} diff --git a/bugs/for-each-resume-next-loops/src/Sources/Startup.twin b/bugs/for-each-resume-next-loops/src/Sources/Startup.twin new file mode 100644 index 000000000..5f7220afd --- /dev/null +++ b/bugs/for-each-resume-next-loops/src/Sources/Startup.twin @@ -0,0 +1,95 @@ +' A For Each whose enumerator cannot be had, under On Error Resume Next, never ends: the +' error resumes into the loop body, and its Next raises error 91 and goes back to the body, +' again and again. Each case counts how often the expression after In is evaluated +' (header=) and lists the error each pass of the body finds (body=), and leaves the loop +' after six passes, so that a case that would never end ends. VB6 evaluates the expression +' once and continues after Next (the VB6 side is in vb6/). + +Private Class NoEnum + Public Value As Long +End Class + +Private Class EnumNothing + [Enumerator] + Public Function NewEnum() As IUnknown + Set NewEnum = Nothing + End Function +End Class + +Private Class EnumRaises + [Enumerator] + Public Function NewEnum() As IUnknown + Err.Raise 5 + End Function +End Class + +Module Startup + + Private Calls As Long + + Private Function Source(ByVal Kind As Long) As Object + Calls = Calls + 1 + Select Case Kind + Case 0 + Dim ok As New Collection + ok.Add "item" + Set Source = ok + Case 1 + Set Source = Nothing + Case 2 + Set Source = New NoEnum + Case 3 + Set Source = New EnumNothing + Case 4 + Set Source = New EnumRaises + End Select + End Function + + Private Sub ResumeNext(ByVal Label As String, ByVal Kind As Long) + Dim v As Variant, body As Long, trace As String + Calls = 0 + On Error Resume Next + For Each v In Source(Kind) + body = body + 1 + trace = trace & " " & Err.Number + Err.Clear + If body = 6 Then Exit For + Next + trace = trace & " | after Next: " & Err.Number + On Error GoTo 0 + Debug.Print Label & ": header=" & Calls & " body=" & body & " errors:" & trace + End Sub + + Private Sub HandlerResumeNext(ByVal Label As String, ByVal Kind As Long) + Dim v As Variant, body As Long, hits As Long, trace As String + Calls = 0 + On Error GoTo Handler + For Each v In Source(Kind) + body = body + 1 + trace = trace & " body" + If body = 6 Then Exit For + Next + Done: + On Error GoTo 0 + Debug.Print Label & ": header=" & Calls & " body=" & body & " passes:" & trace + Exit Sub + Handler: + hits = hits + 1 + trace = trace & " error " & Err.Number + If hits = 6 Then Resume Done + Resume Next + End Sub + + Public Sub Main() + ResumeNext "Collection (control)", 0 + ResumeNext "Nothing", 1 + ResumeNext "Class with no enumerator", 2 + ResumeNext "Enumerator returns Nothing", 3 + ResumeNext "Enumerator raises error 5", 4 + HandlerResumeNext "Nothing, handler with Resume Next", 1 + HandlerResumeNext "Class with no enumerator, handler with Resume Next", 2 + HandlerResumeNext "Enumerator returns Nothing, handler with Resume Next", 3 + HandlerResumeNext "Enumerator raises error 5, handler with Resume Next", 4 + End Sub + +End Module diff --git a/bugs/for-each-resume-next-loops/vb6/EnumNothing.cls b/bugs/for-each-resume-next-loops/vb6/EnumNothing.cls new file mode 100644 index 000000000..076fad25e --- /dev/null +++ b/bugs/for-each-resume-next-loops/vb6/EnumNothing.cls @@ -0,0 +1,19 @@ +VERSION 1.0 CLASS +BEGIN + MultiUse = -1 'True + Persistable = 0 'NotPersistable + DataBindingBehavior = 0 'vbNone + DataSourceBehavior = 0 'vbNone + MTSTransactionMode = 0 'NotAnMTSObject +END +Attribute VB_Name = "EnumNothing" +Attribute VB_GlobalNameSpace = False +Attribute VB_Creatable = True +Attribute VB_PredeclaredId = False +Attribute VB_Exposed = False +Option Explicit + +Public Function NewEnum() As IUnknown +Attribute NewEnum.VB_UserMemId = -4 + Set NewEnum = Nothing +End Function diff --git a/bugs/for-each-resume-next-loops/vb6/EnumRaises.cls b/bugs/for-each-resume-next-loops/vb6/EnumRaises.cls new file mode 100644 index 000000000..8e82e404a --- /dev/null +++ b/bugs/for-each-resume-next-loops/vb6/EnumRaises.cls @@ -0,0 +1,19 @@ +VERSION 1.0 CLASS +BEGIN + MultiUse = -1 'True + Persistable = 0 'NotPersistable + DataBindingBehavior = 0 'vbNone + DataSourceBehavior = 0 'vbNone + MTSTransactionMode = 0 'NotAnMTSObject +END +Attribute VB_Name = "EnumRaises" +Attribute VB_GlobalNameSpace = False +Attribute VB_Creatable = True +Attribute VB_PredeclaredId = False +Attribute VB_Exposed = False +Option Explicit + +Public Function NewEnum() As IUnknown +Attribute NewEnum.VB_UserMemId = -4 + Err.Raise 5 +End Function diff --git a/bugs/for-each-resume-next-loops/vb6/Module1.bas b/bugs/for-each-resume-next-loops/vb6/Module1.bas new file mode 100644 index 000000000..caecb5eed --- /dev/null +++ b/bugs/for-each-resume-next-loops/vb6/Module1.bas @@ -0,0 +1,84 @@ +Attribute VB_Name = "Module1" +Option Explicit + +' A For Each whose enumerator cannot be had, under On Error Resume Next and under a handler +' that ends with Resume Next: the same cases as the twinBASIC side in src/. Each case counts +' how often the expression after In is evaluated (header=) and lists the error each pass of +' the body finds, and leaves the loop after six passes. +' Build and run it with: node scripts/bug_repro.mjs vb6 for-each-resume-next-loops + +Private Calls As Long + +Sub Main() + On Error GoTo Fail + Open App.Path & "\out.txt" For Output As #9 + ResumeNext "Collection (control)", 0 + ResumeNext "Nothing", 1 + ResumeNext "Class with no enumerator", 2 + ResumeNext "Enumerator returns Nothing", 3 + ResumeNext "Enumerator raises error 5", 4 + HandlerResumeNext "Nothing, handler with Resume Next", 1 + HandlerResumeNext "Class with no enumerator, handler with Resume Next", 2 + HandlerResumeNext "Enumerator returns Nothing, handler with Resume Next", 3 + HandlerResumeNext "Enumerator raises error 5, handler with Resume Next", 4 + Close #9 + Exit Sub +Fail: + On Error Resume Next + Print #9, "unhandled error " & Err.Number & ": " & Err.Description + Close #9 +End Sub + +Private Function Source(ByVal Kind As Long) As Object + Dim ok As Collection + Calls = Calls + 1 + Select Case Kind + Case 0 + Set ok = New Collection + ok.Add "item" + Set Source = ok + Case 1 + Set Source = Nothing + Case 2 + Set Source = New NoEnum + Case 3 + Set Source = New EnumNothing + Case 4 + Set Source = New EnumRaises + End Select +End Function + +Private Sub ResumeNext(ByVal Label As String, ByVal Kind As Long) + Dim v As Variant, body As Long, trace As String + Calls = 0 + On Error Resume Next + For Each v In Source(Kind) + body = body + 1 + trace = trace & " " & Err.Number + Err.Clear + If body = 6 Then Exit For + Next + trace = trace & " | after Next: " & Err.Number + On Error GoTo 0 + Print #9, Label & ": header=" & Calls & " body=" & body & " errors:" & trace +End Sub + +Private Sub HandlerResumeNext(ByVal Label As String, ByVal Kind As Long) + Dim v As Variant, body As Long, hits As Long, trace As String + Calls = 0 + On Error GoTo Handler + For Each v In Source(Kind) + body = body + 1 + trace = trace & " body" + If body = 6 Then Exit For + Next +Done: + On Error GoTo 0 + Print #9, Label & ": header=" & Calls & " body=" & body & " passes:" & trace + Exit Sub +Handler: + hits = hits + 1 + trace = trace & " error " & Err.Number + If hits = 6 Then Resume Done + Resume Next +End Sub diff --git a/bugs/for-each-resume-next-loops/vb6/NoEnum.cls b/bugs/for-each-resume-next-loops/vb6/NoEnum.cls new file mode 100644 index 000000000..34c751ec9 --- /dev/null +++ b/bugs/for-each-resume-next-loops/vb6/NoEnum.cls @@ -0,0 +1,16 @@ +VERSION 1.0 CLASS +BEGIN + MultiUse = -1 'True + Persistable = 0 'NotPersistable + DataBindingBehavior = 0 'vbNone + DataSourceBehavior = 0 'vbNone + MTSTransactionMode = 0 'NotAnMTSObject +END +Attribute VB_Name = "NoEnum" +Attribute VB_GlobalNameSpace = False +Attribute VB_Creatable = True +Attribute VB_PredeclaredId = False +Attribute VB_Exposed = False +Option Explicit + +Public Value As Long diff --git a/bugs/for-each-resume-next-loops/vb6/Probe.vbp b/bugs/for-each-resume-next-loops/vb6/Probe.vbp new file mode 100644 index 000000000..5732efdd5 --- /dev/null +++ b/bugs/for-each-resume-next-loops/vb6/Probe.vbp @@ -0,0 +1,11 @@ +Type=Exe +Reference=*\G{00020430-0000-0000-C000-000000000046}#2.0#0#C:\Windows\SysWOW64\stdole2.tlb#OLE Automation +Module=Module1; Module1.bas +Class=NoEnum; NoEnum.cls +Class=EnumNothing; EnumNothing.cls +Class=EnumRaises; EnumRaises.cls +Startup="Sub Main" +ExeName32="Probe.exe" +Name="Probe" +CompilationType=0 +OptimizationType=0 diff --git a/builder/README.md b/builder/README.md index a805001d6..e4d4b6992 100644 --- a/builder/README.md +++ b/builder/README.md @@ -27,7 +27,7 @@ CLI flags: | Flag | Effect | |---|---| | `--src ` | Source root (default `docs`). | -| `--dest ` | Online tree destination (default `/_site`). The offline tree lands at `-offline`, the PDF tree at `-pdf`. It may not be or contain ``, and inside `` it must be, or be inside, a folder directly under it whose name starts with `_site`, `_serve` or `_pdf`; the build refuses any other. | +| `--dest ` | Online tree destination (default `/_site`). The offline tree lands at `-offline`, the PDF tree at `-pdf`, each removed instead when `--no-offline` or `--no-pdf` skips it. Only the default `docs/_site` rewrites the committed baselines and the add-in's index copy; another `--dest` compares and reports. It may not be or contain ``, and inside `` it must be, or be inside, a folder directly under it whose name starts with `_site`, `_serve` or `_pdf`; the build refuses any other. | | `--baseurl ` | Overrides `_config.yml`'s `baseurl` (used by CI to inject the GitHub Pages base path on fork deployments). | | `--url ` | Overrides `_config.yml`'s `url` (used by CI so canonical URLs match the deployment origin). | | `--dry-run` | Skip every filesystem write. | diff --git a/builder/addin-index.mjs b/builder/addin-index.mjs index 7dddb9b57..6eace949c 100644 --- a/builder/addin-index.mjs +++ b/builder/addin-index.mjs @@ -6,9 +6,10 @@ // `git status` to be committed with the pages that changed it. // // The rules are the drift guards' (baseline.mjs): only a build of the -// documentation tree touches the copy, and neither CI nor `--serve` writes it; -// they say that it differs. A difference is never a failure: a copy one build -// behind gives the add-in an older index, not a broken one. +// documentation tree touches the copy, and neither CI, `--serve` nor a build +// into another `--dest` writes it; they say that it differs. A difference is +// never a failure: a copy one build behind gives the add-in an older index, +// not a broken one. // // The comparison is byte for byte. .gitattributes checks add-in/Resources/ // out as committed, so a CRLF checkout does not differ from the LF the build @@ -27,11 +28,19 @@ export const ADDIN_INDEX_REL = "add-in/Resources/SYMBOLS/symbols.json"; * @param {object} o * @param {string} o.src the source tree built, repo-relative with forward slashes * @param {string} o.json the index this build wrote, as written - * @param {boolean} o.write may the copy be written (not in CI, not under --serve) + * @param {boolean} o.write may the copy be written (baselineWrites: not in CI, + * not under --serve, not for another --dest) + * @param {string} [o.heldBack] why not, when the destination is the reason * @param {string} [o.file] the copy; the add-in's by default * @returns {Promise} what to print, "" when nothing changed */ -export async function syncAddinIndex({ src, json, write, file = path.join(REPO_ROOT, ADDIN_INDEX_REL) }) { +export async function syncAddinIndex({ + src, + json, + write, + heldBack = "", + file = path.join(REPO_ROOT, ADDIN_INDEX_REL), +}) { if (src !== GUARDED_SRC || !json) return ""; let current = null; try { @@ -40,7 +49,11 @@ export async function syncAddinIndex({ src, json, write, file = path.join(REPO_R if (err.code !== "ENOENT") throw err; } if (current === json) return ""; - if (!write) return `add-in index: ${ADDIN_INDEX_REL} differs from this build's index; a local build rewrites it\n`; + if (!write) { + const why = heldBack ? ` (${heldBack})` : ""; + const who = heldBack ? `a build into ${GUARDED_SRC}/_site` : "a local build"; + return `add-in index: ${ADDIN_INDEX_REL} differs from this build's index${why}; ${who} rewrites it\n`; + } await mkdir(path.dirname(file), { recursive: true }); await writeFile(file, json, "utf8"); return `add-in index ${current === null ? "created" : "updated"}: ${ADDIN_INDEX_REL} (commit it)\n`; diff --git a/builder/baseline.mjs b/builder/baseline.mjs index 6fb301639..f9b83ebc6 100644 --- a/builder/baseline.mjs +++ b/builder/baseline.mjs @@ -11,7 +11,7 @@ // the change. Each guard's module says why its figures are guarded, and gives // checkBaseline what to compare. // -// Two restrictions on the write, both deliberate: +// Three restrictions on the write, all deliberate, decided by baselineWrites: // // - **CI never writes.** A CI run that rewrote a baseline would bless the // loss it was asked to catch. It compares, and an absent file is a failure @@ -21,6 +21,13 @@ // `docs/`, so a page half-deleted in an editor would ratchet a baseline // down and a half-added one would ratchet it up. The comparison still // runs, so the console still says what happened. +// - **Only a build into the default tree writes**, `docs/_site` as build.bat +// builds it. A build into another `--dest` is someone's private copy: +// compare_trees builds two, and agents working side by side each build +// into a folder of their own. Each of those rewriting the committed file +// left every other checkout with a change to undo after every build. It +// still compares, and says what a build into the default tree would +// record. The help archive (help-archive-step.mjs) has the same rule. // // **A baseline describes one source tree, and the guard says which.** tbdocs // is not only run over `docs/`: scripts/check_links_diff.mjs spawns it over @@ -33,11 +40,37 @@ // never about it. import { readFile, writeFile } from "node:fs/promises"; +import path from "node:path"; +import { REPO_ROOT } from "../lib/repo-paths.mjs"; // Repo-relative, forward slashes. The documentation site is the only tree the // guards have figures for. export const GUARDED_SRC = "docs"; +// The online tree build.bat writes, tbdocs's default --dest for GUARDED_SRC. +export const GUARDED_DEST = path.join(REPO_ROOT, GUARDED_SRC, "_site"); + +/** + * May this build rewrite the committed files it compares with (the two + * baselines and the help add-in's copy of the index)? + * + * @param {object} o + * @param {string} o.destRoot the online tree this build writes, absolute + * @param {boolean} [o.ci] the build runs under CI ($CI set) + * @param {boolean} [o.serve] the build is a `--serve` rebuild + * @param {boolean} [o.dryRun] the build writes nothing + * @returns {{write: boolean, heldBack: string}} `heldBack` says why a change + * is not written when the destination is the reason, "" otherwise: CI and + * `--serve` compare without a word about it + */ +export function baselineWrites({ destRoot, ci = false, serve = false, dryRun = false }) { + if (ci || serve || dryRun) return { write: false, heldBack: "" }; + // Windows paths compare without case, which path.relative allows for. + if (path.relative(path.resolve(destRoot), GUARDED_DEST) === "") return { write: true, heldBack: "" }; + const rel = path.relative(REPO_ROOT, path.resolve(destRoot)).replaceAll(path.sep, "/"); + return { write: false, heldBack: `built into ${rel}` }; +} + async function readBaseline(file) { try { return JSON.parse(await readFile(file, "utf8")); @@ -86,13 +119,15 @@ function acceptCommands(name) { * @param {object} o.record what this build publishes, as the file holds it; * its `src` is this build's source root, repo-relative with forward * slashes, and anything but GUARDED_SRC is skipped - * @param {boolean} o.write may the file be rewritten? (false under CI, - * --serve and --dry-run) + * @param {boolean} o.write may the file be rewritten? (baselineWrites: false + * under CI, --serve and --dry-run, and for another --dest) + * @param {string} [o.heldBack] why it may not, said beside a gain that is not + * written; "" says nothing * @param {boolean} [o.force] write it whatever changed (--update--baseline) * @param {URL|string} o.file the file to read and write * @returns {Promise<{failed: boolean, text: string}>} */ -export async function checkBaseline(guard, { record, write, force = false, file }) { +export async function checkBaseline(guard, { record, write, heldBack = "", force = false, file }) { if (record.src !== GUARDED_SRC) return { failed: false, text: "" }; const baseline = await readBaseline(file); @@ -126,5 +161,11 @@ export async function checkBaseline(guard, { record, write, force = false, file text: `${guard.name} baseline raised: ${gained} (commit builder/${guard.name}-baseline.json)\n`, }; } + if (gained && heldBack) { + return { + failed: false, + text: `${guard.name} baseline not raised: ${gained} (${heldBack}; a build into ${GUARDED_SRC}/_site records it)\n`, + }; + } return { failed: false, text: "" }; } diff --git a/builder/page-baseline.json b/builder/page-baseline.json index 3d6ab60fc..c73ead275 100644 --- a/builder/page-baseline.json +++ b/builder/page-baseline.json @@ -1,5 +1,5 @@ { "src": "docs", - "pages": 1259, + "pages": 1282, "staticFiles": 1009 } diff --git a/builder/page-baseline.mjs b/builder/page-baseline.mjs index b2eb4d9f6..00913dc19 100644 --- a/builder/page-baseline.mjs +++ b/builder/page-baseline.mjs @@ -78,7 +78,9 @@ const PAGE_GUARD = { * @param {number} o.pages this build's page count * @param {number} o.staticFiles this build's static-file count * @param {boolean} o.write may the baseline be rewritten? (false under - * CI, --serve and --dry-run) + * CI, --serve and --dry-run, and for a --dest + * other than docs/_site: baselineWrites) + * @param {string} [o.heldBack] why not, said beside a rise left unwritten * @param {boolean} o.force write whatever the counts are, up or down * (--update-page-baseline) * @param {URL|string} [o.file] the baseline to read and write. Only @@ -87,6 +89,14 @@ const PAGE_GUARD = { * without touching the committed file. * @returns {Promise<{failed: boolean, text: string}>} */ -export async function checkPageBaseline({ src, pages, staticFiles, write, force = false, file = BASELINE_PATH }) { - return checkBaseline(PAGE_GUARD, { record: { src, pages, staticFiles }, write, force, file }); +export async function checkPageBaseline({ + src, + pages, + staticFiles, + write, + heldBack = "", + force = false, + file = BASELINE_PATH, +}) { + return checkBaseline(PAGE_GUARD, { record: { src, pages, staticFiles }, write, heldBack, force, file }); } diff --git a/builder/symbol-baseline.mjs b/builder/symbol-baseline.mjs index 535c74fc7..ecce01364 100644 --- a/builder/symbol-baseline.mjs +++ b/builder/symbol-baseline.mjs @@ -28,7 +28,8 @@ // The comparison, and the restrictions on the write, are builder/baseline.mjs's, // shared with the page guard: never under CI, which would bless the loss it // was asked to catch; never under --serve, whose rebuilds follow every -// half-typed heading; and only for the source tree the list describes. +// half-typed heading; never from a build into another --dest, someone's +// private copy; and only for the source tree the list describes. import { checkBaseline } from "./baseline.mjs"; @@ -76,13 +77,22 @@ const SYMBOL_GUARD = { * skipped * @param {Iterable} o.urls every URL in this build's index * @param {boolean} o.write may the list be rewritten? (false under CI, - * --serve and --dry-run) + * --serve and --dry-run, and for a --dest other + * than docs/_site: baselineWrites) + * @param {string} [o.heldBack] why not, said beside new URLs left unwritten * @param {boolean} [o.force] write it whatever changed (--update-symbol-baseline) * @param {URL|string} [o.file] the list to read and write; only the self-test * passes one * @returns {Promise<{failed: boolean, text: string}>} */ -export async function checkSymbolBaseline({ src, urls, write, force = false, file = SYMBOL_BASELINE_PATH }) { +export async function checkSymbolBaseline({ + src, + urls, + write, + heldBack = "", + force = false, + file = SYMBOL_BASELINE_PATH, +}) { const record = { src, urls: [...new Set(urls)].sort() }; - return checkBaseline(SYMBOL_GUARD, { record, write, force, file }); + return checkBaseline(SYMBOL_GUARD, { record, write, heldBack, force, file }); } diff --git a/builder/tbdocs.mjs b/builder/tbdocs.mjs index a59e8f125..705d646f1 100644 --- a/builder/tbdocs.mjs +++ b/builder/tbdocs.mjs @@ -76,6 +76,7 @@ import { loadHighlightTheme } from "./highlight-theme.mjs"; import { NAV_SCRIPT_REL, buildInitConfig, renderSidebar } from "./template.mjs"; import { writePhase, + destinationTrees, prepareDestinations, preparePageDirs, writeFileMkdirp, @@ -97,6 +98,7 @@ import { deriveTreeRels } from "./check-tree.mjs"; import { syncAddinIndex } from "./addin-index.mjs"; import { addinProjectOf, formatAddinProject, writeAddinProject } from "./addin-project.mjs"; import { helpArchiveStep, recheckHelpArchive } from "./help-archive-step.mjs"; +import { baselineWrites } from "./baseline.mjs"; import { checkPageBaseline } from "./page-baseline.mjs"; import { checkSymbolBaseline } from "./symbol-baseline.mjs"; import { deriveSymbolIndex, reportableGaps, serializeSymbolIndex, symbolPages, SYMBOL_INDEX_REL } from "./symbols.mjs"; @@ -181,6 +183,14 @@ function registerBarrier(scheduler, views, join, joinIdx, prefix, n) { setDepCount(views, joinIdx, n); } +// Does this run skip the offline or the PDF pass? --no-offline and --no-pdf +// (serve mode passes both), else _config.yml's also_build_offline and +// also_build_pdf. Every task that writes or prepares a tree asks here, so that +// none of them decides it differently: prepDest once made both trees for a run +// that skipped them. +const skipsOffline = (ctx, state) => ctx.opts.skipOffline ?? state.site.config.also_build_offline === false; +const skipsPdf = (ctx, state) => ctx.opts.skipPdf ?? state.site.config.also_build_pdf === false; + const TASKS = { // ── Seeds ───────────────────────────────────────────────────────────────── @@ -251,7 +261,7 @@ const TASKS = { await fs.mkdir(path.dirname(dest), { recursive: true }); await fs.writeFile(dest, online, "utf8"); - const skipOffline = ctx.opts.skipOffline ?? state.site.config.also_build_offline === false; + const skipOffline = skipsOffline(ctx, state); let offlineMisses = 0; if (!skipOffline) { const offlineState = { @@ -284,20 +294,22 @@ const TASKS = { }, }, - // Clean and recreate the trees the run owns. A build owns three -- _site/, - // _site-offline/ and _site-pdf/ -- and all three exist after it, whichever - // passes ran. Serve mode owns _serve/ alone: it never runs the offline or PDF - // pass, and preparing all three left an empty _serve-offline/ and _serve-pdf/ - // beside _serve/ after every rebuild. Deferred to after dispatch so the wipe + // Clean and recreate the trees the run writes: the online tree, and + // -offline and -pdf when their passes run. A build with + // --no-offline --no-pdf leaves the online tree alone; destinationTrees says + // what happens to a skipped sibling. Deferred to after dispatch so the wipe // doesn't contend with discover's source-file reads. Joined by write and // searchData. prepDest: { expected: ["dispatch"], runOnMain: true, - async execute(_, ctx) { - const r = ctx.destRoot; - const roots = ctx.opts.serve ? [r] : [r, r + "-offline", r + "-pdf"]; - await prepareDestinations(roots, ctx.opts.dryRun); + async execute(_, ctx, state) { + const { roots, gone } = destinationTrees(ctx.destRoot, { + skipOffline: skipsOffline(ctx, state), + skipPdf: skipsPdf(ctx, state), + serve: !!ctx.opts.serve, + }); + await prepareDestinations(roots, ctx.opts.dryRun, gone); return {}; }, submit() {}, @@ -310,7 +322,7 @@ const TASKS = { runOnMain: true, async execute(_, ctx, state) { if (ctx.opts.dryRun) return {}; - const skipOffline = ctx.opts.skipOffline ?? state.site.config.also_build_offline === false; + const skipOffline = skipsOffline(ctx, state); const offlineRoot = skipOffline ? null : ctx.destRoot + "-offline"; await preparePageDirs(state.pages, state.staticFiles, ctx.destRoot, offlineRoot); return {}; @@ -680,7 +692,7 @@ const TASKS = { NAV_SCRIPT_REL, ]); state.sitePaths = sitePaths; - const skipOffline = ctx.opts.skipOffline ?? state.site.config.also_build_offline === false; + const skipOffline = skipsOffline(ctx, state); // Everything deriveTreeRels needs is settled at this point: pages // from discover, stubs from deriveRedirects, staticFiles after dot @@ -1009,7 +1021,7 @@ const TASKS = { expected: ["flushJoin", "scss", "writeOffline"], runOnMain: true, async execute(_, ctx, state) { - const skipOffline = ctx.opts.skipOffline ?? state.site.config.also_build_offline === false; + const skipOffline = skipsOffline(ctx, state); return helpArchiveStep({ src: path.relative(REPO_ROOT, ctx.srcRoot).replaceAll(path.sep, "/"), offlineRoot: skipOffline ? null : `${ctx.destRoot}-offline`, @@ -1075,7 +1087,7 @@ const TASKS = { expected: ["writeAux", "writeAssets"], runOnMain: true, async execute({ writeAux: { redirectStats, sitemapStats, searchStats } }, ctx, state) { - const skipOffline = ctx.opts.skipOffline ?? state.site.config.also_build_offline === false; + const skipOffline = skipsOffline(ctx, state); if (ctx.opts.dryRun || skipOffline) return null; const auxStats = { redirects: redirectStats, sitemap: sitemapStats, search: searchStats }; return writeOffline(state.staticFiles, state.site, ctx.destRoot, { @@ -1107,7 +1119,7 @@ const TASKS = { expected: ["flushJoin", "renderJoin", "dot", "resolveBookChapters"], runOnMain: true, async execute(_, ctx, state) { - const skipPdf = ctx.opts.skipPdf ?? state.site.config.also_build_pdf === false; + const skipPdf = skipsPdf(ctx, state); if (ctx.opts.dryRun || skipPdf) return null; return writePdf(state.pages, state.staticFiles, state.site, ctx.destRoot, { tolerateMissingImages: ctx.opts.tolerateMissingImages, @@ -1659,12 +1671,19 @@ export async function runBuild(opts) { // build was invoked. tbdocs also runs over test/fixtures/check-src, which // has no baseline and must not be measured against the site's. const guardedSrc = path.relative(REPO_ROOT, path.resolve(opts.src ?? "docs")).replaceAll(path.sep, "/"); - const mayWrite = !process.env.CI && !opts.serve && !opts.dryRun; + // A build into another --dest compares and writes nothing -- see baseline.mjs. + const { write: mayWrite, heldBack } = baselineWrites({ + destRoot, + ci: !!process.env.CI, + serve: !!opts.serve, + dryRun: !!opts.dryRun, + }); const drift = await checkPageBaseline({ src: guardedSrc, pages: pages.length, staticFiles: staticFiles.length, write: mayWrite, + heldBack, force: !!opts.updatePageBaseline, }); if (drift.text) process.stdout.write(drift.text); @@ -1677,13 +1696,14 @@ export async function runBuild(opts) { src: guardedSrc, urls: symbolStats.urls, write: mayWrite, + heldBack, force: !!opts.updateSymbolBaseline, }); if (lost.text) process.stdout.write(lost.text); if (lost.failed) failBuild(); // The help add-in embeds a committed copy of the index -- see addin-index.mjs. - process.stdout.write(await syncAddinIndex({ src: guardedSrc, json: symbolStats.json, write: mayWrite })); + process.stdout.write(await syncAddinIndex({ src: guardedSrc, json: symbolStats.json, write: mayWrite, heldBack })); } // The help add-in's archive was written before the Gantt injection above, so diff --git a/builder/write.mjs b/builder/write.mjs index cc6314309..d9ec4daad 100644 --- a/builder/write.mjs +++ b/builder/write.mjs @@ -100,18 +100,36 @@ async function writeGeneratedAssets(assets, destRoot, limit, baseurl) { // ---------- §5.1 prepareDestinations ------------------------------------ -export async function prepareDestinations(roots, dryRun) { +// The trees a run prepares beside its online tree `destRoot`. `roots` are the +// trees it writes: -offline and -pdf only when their passes run. +// `gone` are the siblings whose pass it skips. A build removes them, so no +// tree from an earlier build survives beside this one and none is left empty. +// Serve mode skips both passes and owns its one tree, so it removes nothing. +export function destinationTrees(destRoot, { skipOffline, skipPdf, serve = false }) { + const roots = [destRoot]; + const gone = []; + (skipOffline ? gone : roots).push(`${destRoot}-offline`); + (skipPdf ? gone : roots).push(`${destRoot}-pdf`); + return { roots, gone: serve ? [] : gone }; +} + +// Empties each of `roots`, creating it if need be, and removes each of `gone`. +export async function prepareDestinations(roots, dryRun, gone = []) { if (dryRun) { for (const root of roots) console.log(`[dry-run] would clean ${root}`); + for (const root of gone) console.log(`[dry-run] would remove ${root}`); return; } + const all = [...roots, ...gone]; + for (const root of all) { + if (!isUnderProject(root)) { + throw new Error(`refusing to clean ${root}: not under the project tree`); + } + } await Promise.all( - roots.map(async (root) => { - if (!isUnderProject(root)) { - throw new Error(`refusing to clean ${root}: not under the project tree`); - } + all.map(async (root) => { await fs.rm(root, { recursive: true, force: true }); - await fs.mkdir(root, { recursive: true }); + if (roots.includes(root)) await fs.mkdir(root, { recursive: true }); }), ); } diff --git a/docs/Documentation/Builder.md b/docs/Documentation/Builder.md index eefbab8b5..5d89f18be 100644 --- a/docs/Documentation/Builder.md +++ b/docs/Documentation/Builder.md @@ -132,7 +132,7 @@ Modules grouped by role. [Pipeline Stages](Pipeline-Stages) covers each in depth | [`symbol-baseline.mjs`](https://github.com/twinbasic/documentation/blob/main/builder/symbol-baseline.mjs) | The symbol index's drift guard, and the committed `symbol-baseline.json`, every URL `tB/symbols.json` has published. A new URL rewrites the file, a lost one fails the build. | | [`addin-index.mjs`](https://github.com/twinbasic/documentation/blob/main/builder/addin-index.mjs) | The help add-in's committed copy of the index, `add-in/Resources/SYMBOLS/symbols.json`: a local build rewrites it when it differs from `tB/symbols.json`, and CI and `--serve` only say so. | | [`addin-project.mjs`](https://github.com/twinbasic/documentation/blob/main/builder/addin-project.mjs) | The help add-in's project file, a published download: packs the files git tracks in `add-in/`, with this build's symbol index, through `importProject` from `scripts/impexp.mjs` (the one `scripts/` module the builder may import). See [Build and Generation Tools](Tools-Build#the-help-add-ins-project-file). | -| [`baseline.mjs`](https://github.com/twinbasic/documentation/blob/main/builder/baseline.mjs) | The read, the write and the comparison the two drift guards share, and `GUARDED_SRC`, the one source tree they have figures for. | +| [`baseline.mjs`](https://github.com/twinbasic/documentation/blob/main/builder/baseline.mjs) | The read, the write and the comparison the two drift guards share; `GUARDED_SRC`, the one source tree they have figures for; and `baselineWrites`, which lets only a build into `docs/_site` rewrite a committed file. | | [`publish-policy.mjs`](https://github.com/twinbasic/documentation/blob/main/builder/publish-policy.mjs) | The allowlist of file types that may reach a published tree, enforced in `discover` (static-file inventory) and in `dispatch` (each tree's `deriveTreeRels` inventory). A finding aborts the build. See [Drift guards](#drift-guards-and-failure-modes). | **Dev mode and reporting** @@ -277,7 +277,7 @@ dispatch ┬→ render:0 ┬→ flush:0 ┐ searchData ``` -- `prepDest` (main) --- cleans and recreates the destination trees: all three for a build, `_serve/` alone in serve mode. Deferred until after `dispatch` so the wipe does not contend with `discover`'s reads. +- `prepDest` (main) --- cleans and recreates the destination trees the run writes: the online tree, and the offline and PDF trees when their passes run. A build removes a tree whose pass it skips; serve mode prepares `_serve/` alone. Deferred until after `dispatch` so the wipe does not contend with `discover`'s reads. - `prepPageDirs` (main) --- creates every page output directory, so `flush:i` needs no `mkdir`. - `warmInit` (worker, `on_demand` + `unique_per_worker` + `run_when_idle` + `survives_reset`) --- per-lane Shiki bootstrap. A worker with nothing else claimable runs it during the main-thread spine, every render worker needs it on its own lane, and in serve mode the per-lane done flag survives rebuilds, so later builds skip it. - `renderEnvInit` (worker, `on_demand` + `unique_per_worker`) --- per-lane render environment: unpacks the shared SAB, rebuilds the link-table Maps and instantiates the worker's own markdown-it. Every `render:i` lists it in `perWorkerDeps`, so the first render claim on a lane pulls it in. @@ -623,7 +623,7 @@ Some build-adjacent code lives at the repo root rather than under `builder/`: The build aborts or flips the exit code in these cases: -- **Page-count drift.** `runBuild()` ends by comparing this build's inventory with `builder/page-baseline.json`, a committed file holding the page and static-file counts of the last committed build. A rise rewrites it and says so; a fall fails the build. `discover()` returns {{tbdocs:pages}} pages today: every `.md` and `.html` under `docs/` with a parseable frontmatter block, after `_config.yml`'s `exclude:`. The baseline is a file, not a fixed minimum, because a minimum would miss a loss smaller than its margin, such as the 37 pages of `AppGlobalClassObject/_App/` that [Authoring](Authoring#what-may-live-in-docs) describes. See [the page-count drift guard](Building#the-page-count-drift-guard). How a single missing page is reported depends on how it went missing. A page that loses its frontmatter (a UTF-8 BOM in front of the `---`, or any line before it) is reclassified as a static file, and the publish-policy sweep below aborts the build naming the path, because `.md` is in neither extension set. A page dropped by an `exclude:` pattern is never seen; only the link check flags broken links into it, so a page nothing links to disappears silently. Nav integrity fires when a *parent* disappears and strands its children, not when a leaf does. +- **Page-count drift.** `runBuild()` ends by comparing this build's inventory with `builder/page-baseline.json`, a committed file holding the page and static-file counts of the last committed build. A rise rewrites it and says so (only in a build into `docs/_site`; another `--dest` reports it); a fall fails the build. `discover()` returns {{tbdocs:pages}} pages today: every `.md` and `.html` under `docs/` with a parseable frontmatter block, after `_config.yml`'s `exclude:`. The baseline is a file, not a fixed minimum, because a minimum would miss a loss smaller than its margin, such as the 37 pages of `AppGlobalClassObject/_App/` that [Authoring](Authoring#what-may-live-in-docs) describes. See [the page-count drift guard](Building#the-page-count-drift-guard). How a single missing page is reported depends on how it went missing. A page that loses its frontmatter (a UTF-8 BOM in front of the `---`, or any line before it) is reclassified as a static file, and the publish-policy sweep below aborts the build naming the path, because `.md` is in neither extension set. A page dropped by an `exclude:` pattern is never seen; only the link check flags broken links into it, so a page nothing links to disappears silently. Nav integrity fires when a *parent* disappears and strands its children, not when a leaf does. - **SAB structural validation.** `verifySchedulerSAB(TASKS, views, idMapping)` runs right after allocation. A misconfigured `expected`/`perWorkerDeps` list, a duplicate task name or a successor edge to an unknown task aborts the build before any task runs. - **DOT render failure.** A failed diagram keeps its previous SVG and the batch continues, so every broken diagram appears in one run; the orchestrator sets exit code 1 (`EXIT_FOUND`) from the failure count. - **SCSS compile failure.** The light/dark workers warn with the source location and continue with `failed: true`; the joiner sets exit code 1 (`EXIT_FOUND`). The existing `_site/` CSS stays. diff --git a/docs/Documentation/Building.md b/docs/Documentation/Building.md index 1aeb0bb43..34f8514a0 100644 --- a/docs/Documentation/Building.md +++ b/docs/Documentation/Building.md @@ -276,6 +276,11 @@ A tight constant would fail on every legitimate removal, and a gate that fails o - **CI**, recognised by the `CI` environment variable. A run there that rewrote the baseline would accept the drop it was asked to catch. A missing `page-baseline.json` is an error in CI, not a first run. - **`serve.bat`.** Its watcher rebuilds on every save, so a half-deleted page would lower the baseline. The comparison still runs and the console says what happened. - **`--dry-run`**, which writes no tree. +- **A build into another `--dest`.** Only a build into `docs/_site`, the tree `build.bat` writes, records a rise. A build into a folder of your own, so as to keep a second copy of the site or to build beside someone else, leaves the committed file alone and says what a build into `docs/_site` would record: + + page baseline not raised: pages 1259 -> 1267 (built into .claude/my-site; a build into docs/_site records it) + + A fall fails such a build as it fails any other. `--update-page-baseline` writes the file whatever the destination, because it is asked for. [`scripts/check_tree_fresh.mjs`](Tools-Site-Checks#check-tree-fresh) skips this file, or the build writing a raised baseline would make the tree it just produced report as stale. @@ -296,13 +301,13 @@ An installed add-in keeps its copy of the index, so a URL the index has given ou build.bat --update-symbol-baseline node builder/tbdocs.mjs --src docs --check-audit-index --update-symbol-baseline -The usual fix is the first: keep the new wording and pin the old id on the heading. New URLs need nothing --- a local build adds them to the list and says so, and you commit the file with the pages. The file is written under the same three restrictions as the page-count guard, and `check_tree_fresh.mjs` skips it. +The usual fix is the first: keep the new wording and pin the old id on the heading. New URLs need nothing --- a local build adds them to the list and says so, and you commit the file with the pages. The file is written under the same four restrictions as the page-count guard, and `check_tree_fresh.mjs` skips it. The help add-in in `add-in/` embeds a committed copy of the index, `add-in/Resources/SYMBOLS/symbols.json`. A local build rewrites that copy whenever its own index differs and says so: add-in index updated: add-in/Resources/SYMBOLS/symbols.json (commit it) -Commit it with the pages that changed it. CI and `--serve` only report that it differs. A copy one build behind is not a failure. The published project file is packed with the index that build wrote, so the download is current regardless. +Commit it with the pages that changed it. CI, `--serve` and a build into another `--dest` only report that it differs. A copy one build behind is not a failure. The published project file is packed with the index that build wrote, so the download is current regardless. A page in a package folder that gives no entry at all is reported by name after the summary. Its title names nothing the package declares, most often because the page's subject has an internal name, as the (Default) module's is `_HiddenModule`. [`symbols:` in its frontmatter](Authoring#symbols) says what it documents. diff --git a/docs/Documentation/Pipeline-Stages.md b/docs/Documentation/Pipeline-Stages.md index 5eb2c8441..d5abb9d74 100644 --- a/docs/Documentation/Pipeline-Stages.md +++ b/docs/Documentation/Pipeline-Stages.md @@ -288,7 +288,7 @@ dispatch.execute() → { chunks, sharedSAB } prepDest.expected = ["dispatch"] ``` -Cleans and recreates the trees the run owns. A build owns ``, `-offline` and `-pdf`, and all three exist afterwards whether or not the offline and PDF passes ran. Serve mode owns `` alone. It runs after `dispatch` so the wipe does not contend with `discover`'s reads on small machines. +Cleans and recreates the trees the run writes: ``, and `-offline` and `-pdf` when their passes run. A build removes a sibling whose pass it skips (`--no-offline`, `--no-pdf`, or `also_build_offline` / `also_build_pdf` set to `false`) and does not recreate it, so no tree from an earlier build survives beside the new one and none is left empty. Serve mode skips both passes and owns `` alone, so it removes nothing beside it. `destinationTrees` in `write.mjs` makes that decision from the same `skipsOffline` and `skipsPdf` every other task asks. It runs after `dispatch` so the wipe does not contend with `discover`'s reads on small machines. ### `prepPageDirs` (main) @@ -811,7 +811,8 @@ For **renderer rules**, order inverts. Both image plugins capture the current `m | Symbol | Signature | Description | |---|---|---| | `writePhase` | `(pages, staticFiles, { destRoot, dryRun?, generatedAssets?, baseurl?, skipPages? }) → Promise` | Writes the page set, vendored theme, project static files and generated CSS. `writeAssets` passes `skipPages: true`, since `flush:i` writes the pages. | -| `prepareDestinations` | `(roots, dryRun) → Promise` | Deletes and recreates each path in `roots`. `prepDest` passes `[destRoot, destRoot+"-offline", destRoot+"-pdf"]` for a build, and `[destRoot]` in serve mode. | +| `destinationTrees` | `(destRoot, { skipOffline, skipPdf, serve? }) → { roots, gone }` | The trees `prepDest` prepares. `roots` is `destRoot`, with `destRoot+"-offline"` and `destRoot+"-pdf"` when their passes run; `gone` is the siblings whose passes are skipped, empty in serve mode. Probed by `scripts/check_cli.mjs`. | +| `prepareDestinations` | `(roots, dryRun, gone?) → Promise` | Deletes and recreates each path in `roots`, and deletes each path in `gone`. Refuses any path outside the project before deleting anything. | | `preparePageDirs` | `(pages, staticFiles, destRoot, offlineRoot) → Promise` | Pre-creates every page output directory on both trees. Called by `prepPageDirs`. | | `WRITE_LIMIT` | `64` | Concurrency ceiling for `runLimited`. | | `isUnderProject` | `(destRoot) → boolean` | Guard against destructive `--dest` values. Used by `writeOffline` and `writePdf`. | @@ -871,20 +872,22 @@ For **renderer rules**, order inverts. Both image plugins capture the current `m | Symbol | Signature | Description | |---|---|---| | `GUARDED_SRC` | `string` | The repo-relative, forward-slashed source root the baselines describe: `"docs"`. `checkBaseline` skips any other source root; `scripts/check_page_baseline.mjs` and `scripts/check_symbol_index.mjs` pass it in their probes. | -| `checkBaseline` | `(guard, { record, write, force, file }) → Promise<{ failed, text }>` | The drift guard `checkPageBaseline` and `checkSymbolBaseline` share. `guard` names the file (`"page"` or `"symbol"`) and says what a loss and a gain are; `record` is what this build publishes, as the file holds it. Does nothing unless `record.src` is `GUARDED_SRC`. With `force` it writes `record` to `file`. Otherwise a missing file fails, or is created when `write` is set; a loss fails; and a gain rewrites `file` when `write` is set. The file is `record` as JSON, indented two spaces, with a closing newline. | +| `GUARDED_DEST` | `string` | The absolute path of `docs/_site`, the online tree `build.bat` writes. | +| `baselineWrites` | `({ destRoot, ci?, serve?, dryRun? }) → { write, heldBack }` | Whether this build may rewrite the committed files it compares with: the two baselines and the help add-in's copy of the index. `write` is set only for a build into `GUARDED_DEST` outside CI, `--serve` and `--dry-run`. `heldBack` names the destination (`built into .claude/my-site`) when it is the reason, and is `""` otherwise. Called by `runBuild`, and probed by `scripts/check_page_baseline.mjs`. | +| `checkBaseline` | `(guard, { record, write, heldBack, force, file }) → Promise<{ failed, text }>` | The drift guard `checkPageBaseline` and `checkSymbolBaseline` share. `guard` names the file (`"page"` or `"symbol"`) and says what a loss and a gain are; `record` is what this build publishes, as the file holds it. Does nothing unless `record.src` is `GUARDED_SRC`. With `force` it writes `record` to `file`. Otherwise a missing file fails, or is created when `write` is set; a loss fails; and a gain rewrites `file` when `write` is set, or is reported as not raised when `heldBack` says why. The file is `record` as JSON, indented two spaces, with a closing newline. | ### `symbol-baseline.mjs` | Symbol | Signature | Description | |---|---|---| -| `checkSymbolBaseline` | `({ src, urls, write, force, file }) → Promise<{ failed, text }>` | The symbol index's drift guard: fails on a URL `builder/symbol-baseline.json` has and `urls` does not, rewrites the file when `urls` adds one and `write` is set, and does nothing for a source root other than `docs`. | +| `checkSymbolBaseline` | `({ src, urls, write, heldBack, force, file }) → Promise<{ failed, text }>` | The symbol index's drift guard: fails on a URL `builder/symbol-baseline.json` has and `urls` does not, rewrites the file when `urls` adds one and `write` is set (or says it did not, when `heldBack` says why), and does nothing for a source root other than `docs`. | | `SYMBOL_BASELINE_PATH` | `URL` | `builder/symbol-baseline.json`. | ### `addin-index.mjs` | Symbol | Signature | Description | |---|---|---| -| `syncAddinIndex` | `({ src, json, write, file }) → Promise` | Compares `json`, the `tB/symbols.json` this build wrote, with the help add-in's committed copy, and rewrites the copy when they differ and `write` is set. Does nothing for a source root other than `GUARDED_SRC`, or when `json` is empty (`--dry-run`). Returns the line to print, `""` when the copy is current; a difference never fails the build. Called by `runBuild` after the symbol index's drift guard. | +| `syncAddinIndex` | `({ src, json, write, heldBack, file }) → Promise` | Compares `json`, the `tB/symbols.json` this build wrote, with the help add-in's committed copy, and rewrites the copy when they differ and `write` is set; otherwise it says that they differ, and names the destination when `heldBack` does. Does nothing for a source root other than `GUARDED_SRC`, or when `json` is empty (`--dry-run`). Returns the line to print, `""` when the copy is current; a difference never fails the build. Called by `runBuild` after the symbol index's drift guard. | | `ADDIN_INDEX_REL` | `string` | `"add-in/Resources/SYMBOLS/symbols.json"`, the default `file` under the repository root. | ### `addin-project.mjs` @@ -901,7 +904,7 @@ For **renderer rules**, order inverts. Both image plugins capture the current `m | Symbol | Signature | Description | |---|---|---| | `BASELINE_PATH` | `URL` | `builder/page-baseline.json`, the default `file` of `checkPageBaseline`. | -| `checkPageBaseline` | `({ src, pages, staticFiles, write, force, file }) → Promise<{ failed, text }>` | The page-count drift guard, which does nothing unless `src` is `GUARDED_SRC`. With `force` it writes this build's counts to `file`, up or down. Otherwise a missing baseline fails, or is created when `write` is set; a page or static-file count below the baseline fails; and a count above it rewrites `file` when `write` is set. Called by `runBuild` in `tbdocs.mjs`, and by the probes in `scripts/check_page_baseline.mjs`. | +| `checkPageBaseline` | `({ src, pages, staticFiles, write, heldBack, force, file }) → Promise<{ failed, text }>` | The page-count drift guard, which does nothing unless `src` is `GUARDED_SRC`. With `force` it writes this build's counts to `file`, up or down. Otherwise a missing baseline fails, or is created when `write` is set; a page or static-file count below the baseline fails; and a count above it rewrites `file` when `write` is set, or is reported as not raised when `heldBack` says why. Called by `runBuild` in `tbdocs.mjs`, and by the probes in `scripts/check_page_baseline.mjs`. | ### `offline.mjs` diff --git a/docs/Documentation/Tools-Build.md b/docs/Documentation/Tools-Build.md index 78ad5e24e..f74b58795 100644 --- a/docs/Documentation/Tools-Build.md +++ b/docs/Documentation/Tools-Build.md @@ -46,7 +46,7 @@ A flag that takes a value accepts it as the next argument or as `--flag=value`; | Flag | Effect | |---|---| | `--src ` | Source root. Default: `docs` relative to the working directory. | -| `--dest ` | Online-tree destination. Default: `/_site`. The offline tree goes to `-offline`, the PDF tree to `-pdf`. The build refuses a destination that is or contains ``. Inside ``, the destination must be, or be inside, a folder directly under it whose name starts with `_site`, `_serve` or `_pdf`; anywhere else there, the next build or `--serve`'s watcher would read the output back as source. | +| `--dest ` | Online-tree destination. Default: `/_site`. The offline tree goes to `-offline`, the PDF tree to `-pdf`; with `--no-offline` or `--no-pdf` that folder is removed rather than written, so no tree from an earlier build is left beside the new one. Only a build into `docs/_site` updates the committed files a build compares with --- `builder/page-baseline.json`, `builder/symbol-baseline.json` and the help add-in's copy of the symbol index --- and writes the [help archive](#the-help-archive). A build into another destination checks against them and says what a build into `docs/_site` would change, so builds into folders of their own can run side by side without changing the working copy (see [where the drift guard compares but does not write](Building#where-it-compares-but-does-not-write)). The build refuses a destination that is or contains ``. Inside ``, the destination must be, or be inside, a folder directly under it whose name starts with `_site`, `_serve` or `_pdf`; anywhere else there, the next build or `--serve`'s watcher would read the output back as source. | | `--baseurl ` | Overrides `_config.yml`'s `baseurl`. CI uses it to set the GitHub Pages base path on fork deployments. | | `--url ` | Overrides `_config.yml`'s `url`; an absolute `http` or `https` URL. CI uses it so canonical URLs match the deployment origin. | | `--dry-run` | Skip every filesystem write. | diff --git a/docs/Documentation/Tools-Toolchain-Tests.md b/docs/Documentation/Tools-Toolchain-Tests.md index 2785e7046..718ed09cb 100644 --- a/docs/Documentation/Tools-Toolchain-Tests.md +++ b/docs/Documentation/Tools-Toolchain-Tests.md @@ -329,13 +329,15 @@ Exit codes: **0** every test passed, **1** a test failed. node scripts/check_page_baseline.mjs -Verifies the [page-count drift guard](Building#the-page-count-drift-guard) still refuses what it exists to refuse. Eleven probes run against a scratch baseline file in the system temp directory, so `builder/page-baseline.json` is never touched. No browser, no built tree, well under a second. +Verifies the [page-count drift guard](Building#the-page-count-drift-guard) still refuses what it exists to refuse, and still writes only where it may. Seventeen probes run against a scratch baseline file in the system temp directory, so `builder/page-baseline.json` is never touched. No browser, no built tree, well under a second. The guard says nothing on a healthy tree, so a build whose guard has stopped working sounds the same. The first probe is a whole package lost to a blanket `exclude:` rule, which a fixed floor on the page count would not notice. Two probes look redundant and are not: - **A foreign source root must be ignored.** [`check_links_diff.mjs`](Tools-Site-Checks#check-links-diff) builds a three-page fixture tree, and a baseline keyed to nothing would report every other page missing. - **CI must refuse a missing baseline** instead of creating one, because a run that wrote the file would record whatever drop it had been asked to catch. +The last six ask who may write: a build into `docs/_site` may, and a build into another `--dest`, CI and `--serve` may not. A rise built into another destination leaves the file alone and says so, and a fall there still fails. + Exit codes: **0** every probe passed, **1** a probe failed, **2** the gate could not run: a refused command line, or a crash. ## check_book_coverage.mjs @@ -410,7 +412,7 @@ Verifies `lib/cli.mjs`, the module the tools read their command lines through, a The module's probes cover what `parseCli` returns and refuses, compared with a strict `node:util` `parseArgs` over the same argument lists. They also cover `numberOption`, `choiceOption`, `regexOption`, `urlOption`, `dateOption`, `refuseTogether`, `withUsageError` and `printHelpAndExit`. The first five read a value after the parse, and `refuseTogether` refuses options that exclude each other. -The parse is strict for every tool. `parseCli` refuses an unknown option, a boolean flag given a value, a value flag with no value, a positional beyond the count the tool declares, and an empty value unless the option allows one --- only `tbdocs`'s `--baseurl` does. The probes cover each refusal and the `--` that ends the options. They also cover the options `builder/command-line.mjs` returns for `tbdocs`, where `--no-check` makes the order of the flags matter. No recorded case can test that, since each of those command lines starts a build. +The parse is strict for every tool. `parseCli` refuses an unknown option, a boolean flag given a value, a value flag with no value, a positional beyond the count the tool declares, and an empty value unless the option allows one --- only `tbdocs`'s `--baseurl` does. The probes cover each refusal and the `--` that ends the options. They also cover the options `builder/command-line.mjs` returns for `tbdocs`, where `--no-check` makes the order of the flags matter, and the trees those options make the build prepare (`destinationTrees` in `builder/write.mjs`): with `--no-offline` and `--no-pdf`, the online tree alone, the other two removed. No recorded case can test that, since each of those command lines starts a build. The recorded cases are invocations that stop while the tool reads its command line, or at its first check of the project, folder, file or install the command line names. Each records its exit code and what it prints on each stream: the tool's own words for the error exactly, a crash's only by the line that names the problem, and the opening of a usage text printed after it. diff --git a/docs/Reference/Contributed.md b/docs/Reference/Contributed.md index 0fdd593cc..94eb22c19 100644 --- a/docs/Reference/Contributed.md +++ b/docs/Reference/Contributed.md @@ -18,10 +18,10 @@ Each package is versioned apart from twinBASIC, and its section says which versi - [**Generics**](Generics/) -- tuples, read-only objects that hold one to seven values of declared types - [**OLEGuids**](OLEGuids/) -- OLE and COM interfaces and structures in twinBASIC source, in place of `OLEGuids.tlb` - [**tbFileSysTools**](tbFileSysTools/) -- files, folders, drives and text streams, in place of the Scripting Runtime's **FileSystemObject** -- [**TBMANLIB_Dialog**](TBMANLIB_Dialog/) -- the **cDialog** class: the Open, Save and folder dialogs, with file filters and path helpers - [**TBMANLIB_ListBoxHScroll**](TBMANLIB_ListBoxHScroll/) -- a horizontal scroll bar for a **ListBox** - [**TBMANLIB_RES**](TBMANLIB_RES/) -- resources embedded in an executable, as bytes, strings, hex, Base64 or pictures - [**tbProjPacker**](tbProjPacker/) -- a `.twinproj` or `.twinpack` file unpacked into a folder of source files, and packed again +- [**tbRegExp**](tbRegExp/) -- regular expressions, in place of VBScript's **RegExp** object; a match is at most about 3,300 characters long - [**tsJson**](tsJson/) -- a JSON model with parsing, JSONPath queries, JSON Pointer, JSON Patch and JSON Schema validation - [**tsPwGen**](tsPwGen/) -- passwords, PINs and passphrases, with their entropy known before they are generated - [**TwinTimerLib**](TwinTimerLib/) -- a Win32 timer class that reports the elapsed time diff --git a/docs/Reference/Contributed/ArrayListLib/index.md b/docs/Reference/Contributed/ArrayListLib/index.md index 4a286e66a..e2a75afaa 100644 --- a/docs/Reference/Contributed/ArrayListLib/index.md +++ b/docs/Reference/Contributed/ArrayListLib/index.md @@ -78,11 +78,11 @@ The package declares the names **ArrayList**, **Comparer**, **Enumerator**, **Li These faults are those of the package as described in the note above. The ones marked as wrong results, memory damage or a hang are the ones a program is likely to meet. -- **For Each over a list with no items.** [**GetEnumerator**](ArrayList#getenumerator) returns nothing for a list or a range with no items, and the **For Each** raises error -2147467259 (`E_FAIL`, *Unspecified error*). Under `On Error Resume Next` the statement is repeated without end. The same holds for a list that **Clear** or the removal of its last item has emptied. +- **For Each over a list with no items.** [**GetEnumerator**](ArrayList#getenumerator) returns nothing for a list or a range with no items, and the **For Each** raises error -2147467259 (`E_FAIL`, *Unspecified error*). Under `On Error Resume Next` the loop never ends, a twinBASIC defect described on [**For Each...Next**](../../../Core/For-Each-Next). The same holds for a list that **Clear** or the removal of its last item has emptied. - **For Each over a variable of an interface type.** A **For Each** over a variable declared as **IList**, **ICollection**, **IEnumerable**, **IListRange** or **IArrayList** raises error -2147352567, *Exception occurred.* A variable declared as **ArrayList**, **ListRange**, **Object** or **Variant** works. A call of **GetEnumerator** through an interface variable with fewer than all four arguments does not compile (TB5050). - **GetEnumerator reuses its enumerators.** Each list keeps two enumerators and gives out the first one when it is not in use. The first one is in use from the start of a **For Each** until the loop ran to the end; leaving the loop with **Exit For** keeps it in use. When the list has not changed since the enumerator was bound, the arguments of **GetEnumerator** (*Index*, *GetCount* and *GetStep*) are ignored, so a second loop over the same list gives the part, the count and the direction of the first. A third loop nested inside two others reuses the second enumerator while it is in use, and gives wrong items. Assigning [**Capacity**](ArrayList#capacity) can move the items to another place in memory without changing the list's version number, so the next loop over the list can read the memory the items left. - **Reverse.** The loop runs for *i* from 0 up to and including half of the count, rounded down, and swaps the items at *i* and at the count - 1 - *i* from the start of the run. For an even count the last pass swaps the middle pair a second time, so those two items keep their order: a run of two items is not reversed at all. For a run of no items the loop still runs once, and swaps the item at the start of the run with the item-sized block before it (16 bytes in a 32-bit program, 24 in a 64-bit one). With the run at the start of the list, that block is the header of the memory block that holds the items. The next growth of the list raised error -2147024882 (*Not enough memory resources are available*) in a 32-bit program, and ended a 64-bit program with the exception 0xC0000374 (damage to the heap). -- **ToString.** The method goes through the items with **For Each** under `On Error Resume Next`. It never returns for a list with no items, for a nested list with no items, and for an item that is an object with no enumerator, such as an instance of a class of the program. +- **ToString.** The method goes through the items with **For Each** under `On Error Resume Next`. It never returns for a list with no items, for a nested list with no items, and for an item that is an object with no enumerator, such as an instance of a class of the program, because of the twinBASIC defect named in the first item of this list. - **Insert at the first position.** [**Insert**](ArrayList#insert) at **BaseIndex** on a list that has items needs room for 31 more items than the list holds. Without it, the method enlarges the array with `ReDim Preserve` and a lower bound of -30, which twinBASIC refuses, and raises error 9. A list with room for 31 more items does not fail. - **Insert and InsertRange.** *Index* is not checked against **Count**, and the number of items the method moves is **Count** - *Index*. An *Index* above **Count** + **BaseIndex** crashed the program in **Insert** with an access violation. - **CopyTo.** An array is refused with error 6 when `UBound(Target) - Index` is less than **Count**, so an array with exactly **Count** elements from *Index* is refused. The same check is in **ListRange**. diff --git a/docs/Reference/Contributed/TBMANLIB/Consts.md b/docs/Reference/Contributed/TBMANLIB/Consts.md new file mode 100644 index 000000000..292b1e8f0 --- /dev/null +++ b/docs/Reference/Contributed/TBMANLIB/Consts.md @@ -0,0 +1,72 @@ +--- +title: Consts +parent: TBMANLIB Package +permalink: /tB/Packages/Contributed/TBMANLIB/Consts +has_toc: false +--- + +# Consts module +{: .no_toc } + +Constants for HTTP requests: media types for the `Content-Type` header and the names of two header fields. [**cHttpClient**](cHttpClient) uses them; a program can use them to set the headers of a request. Eight constants, all **String**. + +```tb check_build check_run +Debug.Print Consts.TOOLS_HTTP_CONTENT_TYPE_JSON +Debug.Print Consts.TOOLS_HTTP_HEADER_FIELD_CONTENT_TYPE +' Output: +' application/json +' Content-Type +``` + +The same media types are also declared in [**ToolsHttp**](ToolsHttp) with other names (`C_CONTENT_TYPE_JSON` and so on), and [**cHttpClient.SetRequestContentType**](cHttpClient#setrequestcontenttype) chooses one by number. + +* TOC +{:toc} + +## Constants + +### TOOLS_HTTP_CONTENT_TYPE_FORM_MULTIPART +{: .no_toc } + +`multipart/form-data`, the media type of a form that uploads files. + +### TOOLS_HTTP_CONTENT_TYPE_FORM_URLENCODED +{: .no_toc } + +`application/x-www-form-urlencoded`, the media type of a form of `name=value` pairs. [**cHttpClient**](cHttpClient) uses it when a request has no `Content-Type` header and [**RequestContentType**](cHttpClient#requestcontenttype) is empty. + +### TOOLS_HTTP_CONTENT_TYPE_JSON +{: .no_toc } + +`application/json`. + +### TOOLS_HTTP_CONTENT_TYPE_STREAM +{: .no_toc } + +`application/octet-stream`, the media type of bytes of no known kind. + +### TOOLS_HTTP_CONTENT_TYPE_TEXT_HTML +{: .no_toc } + +`text/html`. + +### TOOLS_HTTP_CONTENT_TYPE_TEXT_PLAIN +{: .no_toc } + +`text/plain`. + +### TOOLS_HTTP_HEADER_FIELD_CONTENT_LENGHT +{: .no_toc } + +The text `Content-Lenght`. The name of the constant and its text both misspell `Content-Length`. [**cHttpClient**](cHttpClient) sends a header with this name, with the size of the body, on every request that has a body. Servers do not read it: WinHTTP adds the correct `Content-Length` header by itself, so a request carries both. + +### TOOLS_HTTP_HEADER_FIELD_CONTENT_TYPE +{: .no_toc } + +The text `Content-Type`. + +## See Also + +- [cHttpClient](cHttpClient) class -- sends the requests +- [ToolsHttp](ToolsHttp) module -- the same media types under other names +- [TBMANLIB](.) package -- overview diff --git a/docs/Reference/Contributed/TBMANLIB/TBMAN.md b/docs/Reference/Contributed/TBMANLIB/TBMAN.md index 3b9464048..42ae8ce00 100644 --- a/docs/Reference/Contributed/TBMANLIB/TBMAN.md +++ b/docs/Reference/Contributed/TBMANLIB/TBMAN.md @@ -8,7 +8,7 @@ has_toc: false # TBMAN module {: .no_toc } -The module that gives the package its name. It holds one ready-made instance of each of the package's classes, as public variables, and a **Version** property. A program can use them without a `New`, and every part of the program shares the same instance of each. +The module that gives the package its name. It holds one ready-made instance of eight of the package's classes, as public variables, and a **Version** property. A program can use them without a `New`, and every part of the program shares the same instance of each. ```tb check_build check_run TBMAN.Json.Clear @@ -38,17 +38,26 @@ Debug.Print TypeName(TBMANLIB.TBMAN.Json) ### Csv {: .no_toc } -A shared [**cCsv**](cCsv): in version 1.0.0.5, the reader and writer of INI files. +A shared [**cCsv**](cCsv), a table of text that can be read from and written to CSV files. Syntax: **TBMAN.Csv** **As cCsv** ### Ini {: .no_toc } -A shared **cIni**. The class has no members; see [Types without members](.#types-without-members). +A shared [**cIni**](cIni), which reads and writes INI files. Syntax: **TBMAN.Ini** **As cIni** +### HttpClient +{: .no_toc } + +A shared [**cHttpClient**](cHttpClient), which sends HTTP requests. + +Syntax: **TBMAN.HttpClient** **As cHttpClient** + +The request settings of a **cHttpClient**, such as its headers and its timeout, stay in the object, so every part of the program that uses this instance sees the settings of the others. A program that sends requests with different settings makes its own objects. + ### Json {: .no_toc } @@ -72,6 +81,13 @@ A shared [**cLogs**](cLogs), which writes log lines. Syntax: **TBMAN.Logs** **As cLogs** +### SysTray +{: .no_toc } + +A shared **cSysTray**. The class has no members; see [Types without members](.#types-without-members). + +Syntax: **TBMAN.SysTray** **As cSysTray** + ### TimerUse {: .no_toc } @@ -86,9 +102,9 @@ Returns a version number as text. Syntax: **TBMAN.Version** **As String** -The text is `v`, then `App.Major`, a period, `App.Minor`, a period and `App.Build`. The `App` is that of the program that uses the package, not that of the package: in a program whose version is 2.3.4.7 (major, minor, revision, build), the result is `v2.3.7`. The revision number is not in the text, and the version of the package, 1.0.0.5, is not in it either. +The text is `v`, then `App.Major`, a period, `App.Minor`, a period and `App.Build`. The `App` is that of the program that uses the package, not that of the package: in a program whose version is 2.3.4.7 (major, minor, revision, build), the result is `v2.3.7`. The revision number is not in the text, and the version of the package, 1.0.0.29, is not in it either. ## See Also -- [cJson](cJson), [cLogs](cLogs), [cCsv](cCsv), [cLayer](cLayer), [cTimeuse](cTimeuse) classes -- the classes of the instances +- [cJson](cJson), [cLogs](cLogs), [cCsv](cCsv), [cIni](cIni), [cHttpClient](cHttpClient), [cLayer](cLayer), [cTimeuse](cTimeuse) classes -- the classes of the instances - [TBMANLIB](.) package -- overview diff --git a/docs/Reference/Contributed/TBMANLIB/Tools.md b/docs/Reference/Contributed/TBMANLIB/Tools.md new file mode 100644 index 000000000..38c99d240 --- /dev/null +++ b/docs/Reference/Contributed/TBMANLIB/Tools.md @@ -0,0 +1,55 @@ +--- +title: Tools +parent: TBMANLIB Package +permalink: /tB/Packages/Contributed/TBMANLIB/Tools +has_toc: false +--- + +# Tools module +{: .no_toc } + +The module that holds the shared instances **ToolsStr** and **ToolsStream**. Its two public variables let a program write `ToolsStr.TrimEx(...)` and `ToolsStream.LoadFileAsText(...)` with no `New`, as it could when these two were modules of the package. The module has no procedures. + +```tb check_build check_run +Debug.Print TypeName(ToolsStr) +Debug.Print TypeName(Tools.ToolsStr) +Debug.Print ToolsStr Is Tools.ToolsStr +Debug.Print TypeName(ToolsStream) +' Output: +' cToolsStr +' cToolsStr +' True +' cToolsStream +``` + +The two names can be written alone, as in the first line of the example, because the members of the module are found without the name of the module. The names `TBMANLIB.Tools.ToolsStr` and `TBMANLIB.ToolsStr` also work, and are needed when another package the project references has a member of the same name. + +* TOC +{:toc} + +## Members + +### ToolsStr +{: .no_toc } + +The shared [**cToolsStr**](cToolsStr), the string functions. + +Syntax: **ToolsStr** **As cToolsStr** + +A field, declared `Public ToolsStr As New cToolsStr`, created the first time it is used. The class has no fields, so sharing the instance shares no state. + +### ToolsStream +{: .no_toc } + +The shared [**cToolsStream**](cToolsStream), the file reader and writer. + +Syntax: **ToolsStream** **As cToolsStream** + +A field, declared `Public ToolsStream As New cToolsStream`, created the first time it is used. The package's own classes use this instance, [**cJson**](cJson) among them, so the open stream that the text functions leave behind is shared by the program and the package; see the warning on the page of [**cToolsStream**](cToolsStream). + +## See Also + +- [cToolsStr](cToolsStr) class -- string functions +- [cToolsStream](cToolsStream) class -- whole files as text or bytes +- [TBMAN](TBMAN) module -- the shared instances of the other classes +- [TBMANLIB](.) package -- overview diff --git a/docs/Reference/Contributed/TBMANLIB/ToolsArray.md b/docs/Reference/Contributed/TBMANLIB/ToolsArray.md index b79e3378e..891f6fed8 100644 --- a/docs/Reference/Contributed/TBMANLIB/ToolsArray.md +++ b/docs/Reference/Contributed/TBMANLIB/ToolsArray.md @@ -350,7 +350,7 @@ Debug.Print UBound(ToolsArray.StringToByteArray("abc")) ## See Also -- [ToolsStr](ToolsStr) module -- **ToBytes**, **ToString** and **ToHex**, which convert between strings and byte arrays +- [cToolsStr](cToolsStr) class -- **ToBytes**, **ToString** and **ToHex**, which convert between strings and byte arrays - [ToolsUtf8](ToolsUtf8) module -- UTF-8 conversion - [Split](../../../Modules/Strings/Split) function -- splits a string - [TBMANLIB](.) package -- overview diff --git a/docs/Reference/Contributed/TBMANLIB/ToolsCN.md b/docs/Reference/Contributed/TBMANLIB/ToolsCN.md new file mode 100644 index 000000000..abd7a9584 --- /dev/null +++ b/docs/Reference/Contributed/TBMANLIB/ToolsCN.md @@ -0,0 +1,38 @@ +--- +title: ToolsCN +parent: TBMANLIB Package +permalink: /tB/Packages/Contributed/TBMANLIB/ToolsCN +has_toc: false +--- + +# ToolsCN module +{: .no_toc } + +Meant to convert Chinese characters to *pinyin*, the Latin spelling of their pronunciation. The module has one function, **GetPinYin**, and a table of 396 syllables. A comment at the head of the code, dated 30 December 2024, says that the author has *discarded this object for now*, and the function does not work in version 1.0.0.29. + +* TOC +{:toc} + +## Functions + +### GetPinYin +{: .no_toc } + +Converts Chinese characters to pinyin. + +Syntax: **ToolsCN.GetPinYin** ( *CnChar* ) **As String** + +*CnChar* +: *required* A **String**, the text to convert. + +For each character, the code takes the number that **Asc** returns for it. A number from 1 to 159 is an ASCII character and is copied. A number from -20319 to -10247 is the code of a Chinese character in the GBK code page, which **Asc** returns as a negative number on a computer whose code page is a Chinese one; the function looks up the syllable of the table with the greatest start code that does not exceed that number, and returns it with a capital first letter (`Zhong`). Any other character is dropped. The syllables are joined with no separator. + +An empty string returns an empty string. + +> [!NOTE] +> In version 1.0.0.29 the function raises error -2147467259, *Unspecified error*, for any text that holds a character with a code from 1 to 159, such as an English letter, a digit or a space. The line that copies such a character is written `G = Chr(Num)()`, with a pair of parentheses after the call to **Chr**, which returns a **Variant**, and that raises the error when it runs. On a computer whose code page is not a Chinese one, a Chinese character becomes `?`, code 63, so it raises the error too. Any other character is dropped with no error: `GetPinYin(ChrW(233))` returns an empty string. The same pair of parentheses after the call to **Asc** on the line before has no effect. The loop over the characters also uses the variable `i` for its own search through the table, so on a Chinese code page it would skip characters after a Chinese character. + +## See Also + +- [cToolsStr](cToolsStr) class -- string functions of the package +- [TBMANLIB](.) package -- overview diff --git a/docs/Reference/Contributed/TBMANLIB/ToolsDic.md b/docs/Reference/Contributed/TBMANLIB/ToolsDic.md index 4a860ffde..28a2638ea 100644 --- a/docs/Reference/Contributed/TBMANLIB/ToolsDic.md +++ b/docs/Reference/Contributed/TBMANLIB/ToolsDic.md @@ -8,7 +8,7 @@ has_toc: false # ToolsDic module {: .no_toc } -Helpers for **Scripting.Dictionary** objects: reading and writing the `name=value&name=value` text of web forms, setting a value two levels deep, merging one dictionary into another, and copying one. A dictionary is passed as a `Scripting.Dictionary` parameter, so a program that names the type in its own code references the Microsoft Scripting Runtime. +Helpers for **Scripting.Dictionary** objects: reading the `name=value&name=value` text of web forms, setting a value two levels deep, merging one dictionary into another, and copying one. A dictionary is passed as a `Scripting.Dictionary` parameter, so a program that names the type in its own code references the Microsoft Scripting Runtime. ```tb check_build check_run Dim Form As New Scripting.Dictionary @@ -130,39 +130,6 @@ Debug.Print A("z") ' 30 ``` -### ToWwwFormUrlencoded -{: .no_toc } - -Writes a dictionary as a `name=value&name=value` text. - -Syntax: **ToolsDic.ToWwwFormUrlencoded** ( *Dic* ) **As String** - -*Dic* -: *required* A **Scripting.Dictionary** of names and simple values. - -The function is meant to build the text in a JavaScript array made with [**ToolsJScript.NewArr**](ToolsJScript#newarr), and to turn the array into text with `&` in place of the commas. The values would not be percent-encoded, and a comma in a name or in a value would become `&`. - -> [!WARNING] -> In version 1.0.0.5 the function never returns a text. It adds the entries `a` with the value 1 and `bbb` with the value 2 to *Dic*, and then fails with error 91, *Object variable or With block variable not set*, because [**ToolsJScript.NewArr**](ToolsJScript#newarr) cannot create its array. The two entries stay in the dictionary. A second call with the same dictionary fails earlier, with error 457, because the keys `a` and `bbb` are already there. - -```tb check_build check_run -Dim Form As New Scripting.Dictionary -Form("name") = "Ann" - -On Error Resume Next -Debug.Print ToolsDic.ToWwwFormUrlencoded(Form) -Debug.Print Err.Number -Err.Clear -Debug.Print Form.Count - -Debug.Print ToolsDic.ToWwwFormUrlencoded(Form) -Debug.Print Err.Number -' Output: -' 91 -' 3 -' 457 -``` - ### TowLevelDicAssign {: .no_toc } @@ -197,6 +164,5 @@ Debug.Print D("a").Count ## See Also -- [ToolsJScript](ToolsJScript) module -- the JavaScript array that **ToWwwFormUrlencoded** uses - [cJson](cJson) class -- keeps a JSON document in dictionaries - [TBMANLIB](.) package -- overview diff --git a/docs/Reference/Contributed/TBMANLIB/ToolsFso.md b/docs/Reference/Contributed/TBMANLIB/ToolsFso.md index eb3459b9e..9e1d16eb5 100644 --- a/docs/Reference/Contributed/TBMANLIB/ToolsFso.md +++ b/docs/Reference/Contributed/TBMANLIB/ToolsFso.md @@ -126,6 +126,6 @@ A procedure of the author's own tests. It writes the names of the files and fold ## See Also -- [ToolsStream](ToolsStream) module -- reads and writes the files that this module prepares +- [cToolsStream](cToolsStream) class -- reads and writes the files that this module prepares - [cJson](cJson) class -- **SaveTo** completes the file name with this module - [TBMANLIB](.) package -- overview diff --git a/docs/Reference/Contributed/TBMANLIB/ToolsHttp.md b/docs/Reference/Contributed/TBMANLIB/ToolsHttp.md new file mode 100644 index 000000000..0cf173aef --- /dev/null +++ b/docs/Reference/Contributed/TBMANLIB/ToolsHttp.md @@ -0,0 +1,273 @@ +--- +title: ToolsHttp +parent: TBMANLIB Package +permalink: /tB/Packages/Contributed/TBMANLIB/ToolsHttp +has_toc: false +--- + +# ToolsHttp module +{: .no_toc } + +The helpers of HTTP requests: two enumerations that name the request methods and the content types, URL encoding and decoding, and functions that write and read the `name=value` text of forms and headers. [**cHttpClient**](cHttpClient) uses the enumerations and four of the functions; a program can call all of them. The module has 8 constants, 2 enumerations and 13 procedures. + +```tb check_build check_run +Dim Form As New Scripting.Dictionary + +Form.Add "user", "ann" +Form.Add "city", "New York" +Debug.Print ToolsHttp.MakeContent(Form) +Debug.Print ToolsHttp.UrlEncode("New York/a&b") +Debug.Print ToolsHttp.MapMethodName(ReqPut) +' Output: +' user=ann&city=New York +' New%20York/a%26b +' PUT +``` + +* TOC +{:toc} + +## Constants + +The eight constants repeat the ones in [**Consts**](Consts) under shorter names. All are **String**. + +### C_CONTENT_TYPE_FORM_MULTIPART +{: .no_toc } + +`multipart/form-data`. + +### C_CONTENT_TYPE_FORM_URLENCODED +{: .no_toc } + +`application/x-www-form-urlencoded`. + +### C_CONTENT_TYPE_JSON +{: .no_toc } + +`application/json`. + +### C_CONTENT_TYPE_STREAM +{: .no_toc } + +`application/octet-stream`. + +### C_CONTENT_TYPE_TEXT_HTML +{: .no_toc } + +`text/html`. + +### C_CONTENT_TYPE_TEXT_PLAIN +{: .no_toc } + +`text/plain`. + +### C_HEADER_FIELD_CONTENT_LENGHT +{: .no_toc } + +The text `Content-Lenght`, a misspelling of `Content-Length`; see [Consts](Consts#tools_http_header_field_content_lenght). + +### C_HEADER_FIELD_CONTENT_TYPE +{: .no_toc } + +The text `Content-Type`. + +## Enumerations + +### EnumRequestContentType +{: .no_toc } + +The content types that [**cHttpClient.SetRequestContentType**](cHttpClient#setrequestcontenttype) chooses between. + +| Member | Value | Content type | +|:-------|:------|:-------------| +| `JsonString` | 1 | `application/json` | +| `FormEncode` | 2 | `application/x-www-form-urlencoded` | +| `FormMultiPart` | 3 | `multipart/form-data` | +| `TextPlain` | 4 | `text/plain` | +| `TextHtml` | 5 | `text/html` | + +### EnumRequestMethod +{: .no_toc } + +The methods of a request, for [**cHttpClient.Fetch**](cHttpClient#fetch) and [**cHttpClient.Send**](cHttpClient#send). The value is the position of the name in the list `ANY`, `POST`, `GET`, `PUT`, `DELETE`, `OPTIONS` that [**MapMethod**](#mapmethod) and [**MapMethodName**](#mapmethodname) use. + +| Member | Value | Method | +|:-------|:------|:-------| +| `ReqPost` | 1 | `POST` | +| `ReqGet` | 2 | `GET` | +| `ReqPut` | 3 | `PUT` | +| `ReqDelete` | 4 | `DELETE` | +| `ReqOptions` | 5 | `OPTIONS` | + +## Functions + +### AddToQueryString +{: .no_toc } + +Adds text to the query of a URL. + +Syntax: **ToolsHttp.AddToQueryString** ( *Url* **,** *QS* ) **As String** + +*Url* +: *required* A **String**, the address. + +*QS* +: *required* A **String**, the text to add. + +Returns *Url* followed by `?ver=` and *QS*, or by `&ver=` and *QS* when *Url* already has a `?`. The text `ver=` is always inserted, so *QS* is the value of a parameter named `ver`; see the warning at [**cHttpClient.RequestDataQuery**](cHttpClient#requestdataquery), which calls the function. + +```tb check_build check_run +Debug.Print ToolsHttp.AddToQueryString("http://host/p", "a=1") +Debug.Print ToolsHttp.AddToQueryString("http://host/p?x=1", "a=1") +' Output: +' http://host/p?ver=a=1 +' http://host/p?x=1&ver=a=1 +``` + +### MakeContent +{: .no_toc } + +Writes a dictionary as form text. + +Syntax: **ToolsHttp.MakeContent** ( *Dic* ) **As String** + +*Dic* +: *required* A **Scripting.Dictionary**. + +Returns the keys and items as `key=value` pairs joined by `&`, in the order of the dictionary, or an empty string for an empty dictionary. Nothing is encoded. Compare [**ToolsDic.FromWwwFormUrlencoded**](ToolsDic#fromwwwformurlencoded), which does the reverse. + +### MapMethod +{: .no_toc } + +Returns the number of a method name. + +Syntax: **ToolsHttp.MapMethod** ( *Name* ) **As Long** + +*Name* is a **String**. The result is its position in `ANY`, `POST`, `GET`, `PUT`, `DELETE`, `OPTIONS`, counted from 0, which is the value of the matching **EnumRequestMethod** member (and 0 for `ANY`). The comparison respects letter case: `GET` is 2, and `get` and a name that is not in the list, such as `PATCH`, are -1. + +### MapMethodName +{: .no_toc } + +Returns the name of a method number. + +Syntax: **ToolsHttp.MapMethodName** ( *Index* ) **As String** + +*Index* is a **Long**, a value of **EnumRequestMethod**, or 0 for `ANY`. A number above 5, or below 0, raises error 9. + +### ParseContent +{: .no_toc } + +Reads form text into a dictionary. + +Syntax: **ToolsHttp.ParseContent** ( *Content* **,** *Obj* ) **As Boolean** + +*Content* +: *required* A **String** of `name=value` pairs joined by `&`. + +*Obj* +: *required* A **Scripting.Dictionary**. It receives the pairs; an earlier item of the same key is replaced and the other items stay. + +Each pair is cut at its first `=` and both parts are trimmed. A pair without `=` gets an empty value, and a pair whose name is empty is skipped. The text is not decoded: `%20` stays as it is. The function returns **False** always, whatever it did, because it never assigns its result; read the dictionary instead. + +```tb check_build check_run +Dim Form As New Scripting.Dictionary + +ToolsHttp.ParseContent "a=1&b= two &c&=x&d=1=2", Form +Debug.Print Form.Count +Debug.Print Form.Item("b") & "|" & Form.Item("c") & "|" & Form.Item("d") +' Output: +' 4 +' two||1 +``` + +### ParseKeyValue +{: .no_toc } + +Reads the headers of an answer into a dictionary. + +Syntax: **ToolsHttp.ParseKeyValue** ( *Content* **,** *Obj* ) **As Boolean** + +*Content* +: *required* A **String** of lines separated by `vbCrLf`, as WinHTTP returns the headers. + +*Obj* +: *required* A **Scripting.Dictionary**. It receives one item per line. + +Each line is cut at its first colon and both parts are trimmed, so the value of a line that has more colons is cut: `Date: Tue, 13 Oct 2026 18:00:00 GMT` is stored as `Tue, 13 Oct 2026 18`. A line without a colon becomes an item with the empty text, and an empty line (the last line of a header block is empty) becomes the item with the empty key. Like [**ParseContent**](#parsecontent), the function returns **False** always. [**cHttpClient**](cHttpClient) calls it to fill [**ResponseHeaders**](cHttpClient#responseheaders). + +```tb check_build check_run +Dim Head As New Scripting.Dictionary + +ToolsHttp.ParseKeyValue "Host: example.com" & vbCrLf & "Date: Tue, 13 Oct 2026 18:00:00 GMT" & vbCrLf, Head +Debug.Print Head.Item("Host") +Debug.Print Head.Item("Date") +Debug.Print Head.Count +' Output: +' example.com +' Tue, 13 Oct 2026 18 +' 3 +``` + +### UrlDecode +{: .no_toc } + +Decodes `%XX` escapes, assuming the Chinese code page GB2312. + +Syntax: **ToolsHttp.UrlDecode** ( *Url* ) **As String** + +*Url* is a **String**. A `%XX` below hex 80 becomes the character with that code. A `%XX` of hex 80 or above is combined with the `%XX` after it into one double-byte character, and the pair is converted with **Chr$**. The function does not turn `+` into a space. + +That conversion works only where the ANSI code page is a double-byte one, such as GBK. On a computer with a single-byte code page, such as 1252, any escape of hex 80 or above raises error 5, *Invalid procedure call or argument*, even a single one such as `%E9`. + +### UrlDecodeUtf8 +{: .no_toc } + +Meant to decode a URL written in UTF-8. + +Syntax: **ToolsHttp.UrlDecodeUtf8** ( *Url* ) **As String** + +> [!NOTE] +> The function raises error 13, *Type mismatch*, for every text that contains a `%`, and returns the text correctly only when it has none (a `+` becomes a space). The code compares a **Long** with an empty string at its first `%`. + +### UrlEncodUtf8 +{: .no_toc } + +Meant to encode a text as UTF-8 escapes. The name is spelled with one *e* less than the function name `UrlEncode`. + +Syntax: **ToolsHttp.UrlEncodUtf8** ( *szInput* ) **As String** + +*szInput* is a **String** (a **Variant**). A character below hex 80 is copied as it is, so a space, `&` and `=` stay: only the other characters are escaped. A character from hex 1000 up becomes three escapes, `%E4%B8%AD` for the character U+4E2D. The function returns an empty string for an empty text. + +> [!WARNING] +> A character from hex 80 to hex FFF, such as `é`, is written with only one `%`: `é` becomes `%C3A9` and U+0080 becomes `%C280`. The second byte has no `%` of its own, so the result is not a valid escape, and no decoder gets the character back. Characters from hex 1000 up, such as the Chinese ones, are right. + +```tb check_build check_run +Debug.Print ToolsHttp.UrlEncodUtf8("a b") +Debug.Print ToolsHttp.UrlEncodUtf8(ChrW(&H4E2D)) +Debug.Print ToolsHttp.UrlEncodUtf8("caf" & ChrW(233)) +' Output: +' a b +' %E4%B8%AD +' caf%C3A9 +``` + +### UrlEncode +{: .no_toc } + +Encodes a text with `%XX` escapes in the ANSI code page. + +Syntax: **ToolsHttp.UrlEncode** ( *strURL* ) **As String** + +*strURL* is a **String**, passed by reference. A letter of the English alphabet and the characters `-`, `,`, `.`, `/` and `0` to `9` are copied. Every other character becomes `%` and the hex code of its byte: `%20` for a space, `%5F` for `_`. A character with two bytes in the code page (a Chinese character in GBK) becomes two escapes. On a computer with a single-byte code page, a character such as `é` becomes one escape, `%E9`. + +### Test +{: .no_toc } + +A procedure of the author's tests: it applies **UrlDecodeUtf8** to an escaped path and writes the result to the Debug Console. It raises error 13 for the reason given at **UrlDecodeUtf8**. A program has no use for it. + +## See Also + +- [cHttpClient](cHttpClient) class -- sends requests with these helpers +- [Consts](Consts) module -- the content type and header names +- [ToolsDic](ToolsDic) module -- reads form text into a dictionary +- [TBMANLIB](.) package -- overview diff --git a/docs/Reference/Contributed/TBMANLIB/ToolsHttpMimeType.md b/docs/Reference/Contributed/TBMANLIB/ToolsHttpMimeType.md new file mode 100644 index 000000000..b15cfe945 --- /dev/null +++ b/docs/Reference/Contributed/TBMANLIB/ToolsHttpMimeType.md @@ -0,0 +1,152 @@ +--- +title: ToolsHttpMimeType +parent: TBMANLIB Package +permalink: /tB/Packages/Contributed/TBMANLIB/ToolsHttpMimeType +has_toc: false +--- + +# ToolsHttpMimeType module +{: .no_toc } + +Returns the media type (MIME type) for a file name. The module has one function, **MapMimeType**, with a built-in table of 99 file extensions. The code is taken from the *VbWebserver* project of Jason Peter Brown, which is published under the MIT licence (Copyright (c) 2017 Jason Peter Brown), and was changed by the author of TBMANLIB. + +```tb check_build check_run +Debug.Print ToolsHttpMimeType.MapMimeType("report.pdf") +Debug.Print ToolsHttpMimeType.MapMimeType("C:\site\index.html") +Debug.Print ToolsHttpMimeType.MapMimeType("archive.tar.zip") +Debug.Print ToolsHttpMimeType.MapMimeType("notes.unknown") +' Output: +' application/pdf +' text/html +' application/zip +' application/octet-stream +``` + +* TOC +{:toc} + +## Functions + +### MapMimeType +{: .no_toc } + +Returns the media type of a file name. + +Syntax: **ToolsHttpMimeType.MapMimeType** ( *FilePath* ) **As String** + +*FilePath* +: *required* A **String**, a file name or a path. The extension is the text after the last dot, so a name or a path with no dot is its own extension. The comparison respects letter case, and the table holds lower-case extensions: `report.PDF` is not found. A leading dot is fine, so `.html` and `html` give the same result. + +Returns the media type of the extension in the table below. For an extension that is not in the table it returns `application/octet-stream`. An empty string raises error 9, *Subscript out of range*. + +> [!NOTE] +> The extension `woff` is in the table with no media type, so it returns an empty string, not `application/x-font-woff`; `woff2` returns `application/x-font-woff`. The table also lacks `htm`, `mp3`, `ogg` and `wasm`, which return `application/octet-stream`, and `java` returns the text `text/x-java-source,java`. + +| Extension | Media type | +|:----------|:-----------| +| `7z` | `application/x-7z-compressed` | +| `aac` | `audio/x-aac` | +| `avi` | `video/x-msvideo` | +| `bmp` | `image/bmp` | +| `css` | `text/css` | +| `csv` | `text/csv` | +| `doc` | `application/msword` | +| `docx` | `application/vnd.openxmlformats-officedocument.wordprocessingml.document` | +| `dwf` | `model/vnd.dwf` | +| `dwg` | `image/vnd.dwg` | +| `dxf` | `image/vnd.dxf` | +| `eml` | `message/rfc822` | +| `exe` | `application/x-msdownload` | +| `f4v` | `video/x-f4v` | +| `flv` | `video/x-flv` | +| `gif` | `image/gif` | +| `html` | `text/html` | +| `ico` | `image/x-icon` | +| `ics` | `text/calendar` | +| `jar` | `application/java-archive` | +| `java` | `text/x-java-source,java` | +| `jpeg` | `image/jpeg` | +| `jpg` | `image/jpeg` | +| `jpgv` | `video/jpeg` | +| `jpm` | `video/jpm` | +| `js` | `application/javascript` | +| `json` | `application/json` | +| `kml` | `application/vnd.google-earth.kml+xml` | +| `kmz` | `application/vnd.google-earth.kmz` | +| `m3u` | `audio/x-mpegurl` | +| `m3u8` | `application/vnd.apple.mpegurl` | +| `mdb` | `application/x-msaccess` | +| `mid` | `audio/midi` | +| `mov` | `video/quicktime` | +| `mp4` | `video/mp4` | +| `mp4a` | `audio/mp4` | +| `mpeg` | `video/mpeg` | +| `mpg` | `video/mpeg` | +| `mpga` | `audio/mpeg` | +| `mpp` | `application/vnd.ms-project` | +| `odb` | `application/vnd.oasis.opendocument.database` | +| `odc` | `application/vnd.oasis.opendocument.chart` | +| `odf` | `application/vnd.oasis.opendocument.formula` | +| `odft` | `application/vnd.oasis.opendocument.formula-template` | +| `odg` | `application/vnd.oasis.opendocument.graphics` | +| `odi` | `application/vnd.oasis.opendocument.image` | +| `odm` | `application/vnd.oasis.opendocument.text-master` | +| `odp` | `application/vnd.oasis.opendocument.presentation` | +| `ods` | `application/vnd.oasis.opendocument.spreadsheet` | +| `odt` | `application/vnd.oasis.opendocument.text` | +| `oga` | `audio/ogg` | +| `ogv` | `video/ogg` | +| `ogx` | `application/ogg` | +| `onetoc` | `application/onenote` | +| `otf` | `application/x-font-otf` | +| `pdf` | `application/pdf` | +| `png` | `image/png` | +| `ppsx` | `application/vnd.openxmlformats-officedocument.presentationml.slideshow` | +| `ppt` | `application/vnd.ms-powerpoint` | +| `pptx` | `application/vnd.openxmlformats-officedocument.presentationml.presentation` | +| `psd` | `image/vnd.adobe.photoshop` | +| `pub` | `application/x-mspublisher` | +| `qt` | `video/quicktime` | +| `ram` | `audio/x-pn-realaudio` | +| `rar` | `application/x-rar-compressed` | +| `rm` | `application/vnd.rn-realmedia` | +| `rss` | `application/rss+xml` | +| `svg` | `image/svg+xml` | +| `swf` | `application/x-shockwave-flash` | +| `tar` | `application/x-tar` | +| `tif` | `image/tiff` | +| `tiff` | `image/tiff` | +| `torrent` | `application/x-bittorrent` | +| `tsv` | `text/tab-separated-values` | +| `ttf` | `application/x-font-ttf` | +| `txt` | `text/plain` | +| `vbml` | `text/html` | +| `vcf` | `text/x-vcard` | +| `vcs` | `text/x-vcalendar` | +| `vsd` | `application/vnd.visio` | +| `vsdx` | `application/vnd.visio2013` | +| `wav` | `audio/x-wav` | +| `weba` | `audio/webm` | +| `webm` | `video/webm` | +| `webp` | `image/webp` | +| `wm` | `video/x-ms-wm` | +| `wma` | `audio/x-ms-wma` | +| `wmf` | `application/x-msmetafile` | +| `wmv` | `video/x-ms-wmv` | +| `woff` | (empty) | +| `woff2` | `application/x-font-woff` | +| `xhtml` | `application/xhtml+xml` | +| `xls` | `application/vnd.ms-excel` | +| `xlsx` | `application/vnd.openxmlformats-officedocument.spreadsheetml.sheet` | +| `xml` | `application/xml` | +| `xps` | `application/vnd.ms-xpsdocument` | +| `xslt` | `application/xslt+xml` | +| `xul` | `application/vnd.mozilla.xul+xml` | +| `zip` | `application/zip` | + +## See Also + +- [Consts](Consts) module -- the common media types as constants +- [ToolsHttp](ToolsHttp) module -- the content type enumeration +- [cHttpClient](cHttpClient) class -- sends the requests +- [TBMANLIB](.) package -- overview diff --git a/docs/Reference/Contributed/TBMANLIB/ToolsJScript.md b/docs/Reference/Contributed/TBMANLIB/ToolsJScript.md deleted file mode 100644 index c8f27b103..000000000 --- a/docs/Reference/Contributed/TBMANLIB/ToolsJScript.md +++ /dev/null @@ -1,77 +0,0 @@ ---- -title: ToolsJScript -parent: TBMANLIB Package -permalink: /tB/Packages/Contributed/TBMANLIB/ToolsJScript -has_toc: false ---- - -# ToolsJScript module -{: .no_toc } - -Is meant to create JavaScript arrays and objects with the Microsoft Script Control, so that a program can hand them to code that expects one. The module has two functions. [**ToolsDic.ToWwwFormUrlencoded**](ToolsDic#towwwformurlencoded) is the one place in the package that uses it. - -> [!NOTE] -> In version 1.0.0.5 both functions raise error 91, *Object variable or With block variable not set*, in a 32-bit program. The module creates the Script Control with `Dim MSSC As New MSScriptControl.ScriptControl`. In BETA 1005 that statement creates no usable object, in the package and also in a program that references the Script Control itself: a variable declared with the type **ScriptControl** cannot hold the control, so `Set` raises error -2147467262, *No such interface supported*, and every member used on a `Dim ... As New` variable raises error 91. A variable declared **As Object**, or **As MSScriptControl.IScriptControl**, holds the control, so `CreateObject("MSScriptControl.ScriptControl")` works with either. VB6 creates the control with `As New`, with `Set` and with `CreateObject`, for a variable of any of these types. The same happens with the classes of other ActiveX controls, such as the Winsock and Common Dialog controls. -> -> The Script Control exists only as a 32-bit component. A project that embeds the package cannot be built for 64-bit programs at all; see [32-bit and 64-bit programs](.#32-bit-and-64-bit-programs). - -```tb check_build check_run -Dim Arr As Object -On Error Resume Next -Set Arr = ToolsJScript.NewArr() -Debug.Print Err.Number & " " & Err.Description -Err.Clear -Set Arr = ToolsJScript.NewObj() -Debug.Print Err.Number -' Output: -' 91 Object variable or With block variable not set -' 91 -``` - -What the module is meant to do is this. It creates one Script Control, shared by all callers, the first time one of its functions is called. It selects the language JScript and adds four methods to the built-in objects of the engine: `get_(x)` on every object and on every array, `has_(x)` on every object (**True** when the object has its own property named *x*), `Push(x)` on arrays, which calls the array's own `push`, and `Join(x)` on arrays, which calls `join`. The comments in the source say that `Push` and `Join` exist because the code editor changes the case of the names `push` and `join` that it knows, and that the two names with an underscore are slower than reading a property directly. The text of `Join` has no `return`, so `Join(x)` returns nothing; the array's own `join`, in lower case, returns the joined text. - -A program that needs a JavaScript array can create the control itself: - -```tb check_build check_run -Dim SC As Object -Dim Arr As Object -Set SC = CreateObject("MSScriptControl.ScriptControl") -SC.Language = "JScript" - -Set Arr = SC.Eval("[]") -Arr.push "x" -Arr.push "y" -Debug.Print Arr.length -Debug.Print Arr.join(",") -' Output: -' 2 -' x,y -``` - -## Functions - -### NewArr -{: .no_toc } - -Is meant to create an empty JavaScript array. - -Syntax: **ToolsJScript.NewArr** ( [ *Item* ] ) **As Object** - -*Item* -: *optional* Any number of values. They are accepted and ignored: the array would be empty whatever is passed. - -The function evaluates `[];` in the Script Control and returns the array. In version 1.0.0.5 it raises error 91 instead, as the note above says. - -### NewObj -{: .no_toc } - -Is meant to create an empty JavaScript object. - -Syntax: **ToolsJScript.NewObj** ( ) **As Object** - -The function evaluates `{};` and assigns the result to **NewArr**, the name of the other function, and not to its own result. In version 1.0.0.5 it raises error 91, as **NewArr** does. It would not work if the control existed: JScript reads `{};` as an empty block, not as an object, so a Script Control that evaluates `{};` returns no object, and the statement that uses the result fails with error 424, *Object required*. The text `({})` evaluates to an object. - -## See Also - -- [ToolsDic](ToolsDic) module -- **ToWwwFormUrlencoded** builds on **NewArr** -- [TBMANLIB](.) package -- overview diff --git a/docs/Reference/Contributed/TBMANLIB/ToolsJsonVba.md b/docs/Reference/Contributed/TBMANLIB/ToolsJsonVba.md index 7da5ccd89..3636cff08 100644 --- a/docs/Reference/Contributed/TBMANLIB/ToolsJsonVba.md +++ b/docs/Reference/Contributed/TBMANLIB/ToolsJsonVba.md @@ -116,5 +116,5 @@ Uses the time zone of the system. Raises error 10011 on failure. See [ParseUtc]( - [JsonConverter](../VBA_JSON/JsonConverter/) module -- the same code in the VBA_JSON package, with a full page for each function - [cJson](cJson) class -- a JSON document object built on this module -- [ToolsStr](ToolsStr) module -- **UnicodeEncode** and **UnicodeDecode** for the `\uXXXX` escapes +- [cToolsStr](cToolsStr) class -- **UnicodeEncode** and **UnicodeDecode** for the `\uXXXX` escapes - [TBMANLIB](.) package -- overview diff --git a/docs/Reference/Contributed/TBMANLIB/ToolsMath.md b/docs/Reference/Contributed/TBMANLIB/ToolsMath.md index 329bd5c0c..cf28e12ad 100644 --- a/docs/Reference/Contributed/TBMANLIB/ToolsMath.md +++ b/docs/Reference/Contributed/TBMANLIB/ToolsMath.md @@ -8,7 +8,7 @@ has_toc: false # ToolsMath module {: .no_toc } -Two number functions: **Ceil** rounds up to a chosen number of decimal places, and **GetRandRange** returns a random whole number in a range. The module needs no other component. [**ToolsStr.GetRandStr**](ToolsStr#getrandstr) uses **GetRandRange**. +Two number functions: **Ceil** rounds up to a chosen number of decimal places, and **GetRandRange** returns a random whole number in a range. The module needs no other component. [**ToolsStr.GetRandStr**](cToolsStr#getrandstr) uses **GetRandRange**. ```tb check_build check_run Debug.Print ToolsMath.Ceil(1.231, 2) @@ -58,6 +58,6 @@ Each call runs **Randomize** with no argument, which seeds the generator from th ## See Also -- [ToolsStr](ToolsStr) module -- **GetRandStr** and **GetRandByte** use the generator +- [cToolsStr](cToolsStr) class -- **GetRandStr** and **GetRandByte** use the generator - [Randomize](../../../Modules/Math/Randomize) statement and [Rnd](../../../Modules/Math/Rnd) function -- the generator that **GetRandRange** uses - [TBMANLIB](.) package -- overview diff --git a/docs/Reference/Contributed/TBMANLIB/ToolsStream.md b/docs/Reference/Contributed/TBMANLIB/ToolsStream.md deleted file mode 100644 index f2a546a68..000000000 --- a/docs/Reference/Contributed/TBMANLIB/ToolsStream.md +++ /dev/null @@ -1,204 +0,0 @@ ---- -title: ToolsStream -parent: TBMANLIB Package -permalink: /tB/Packages/Contributed/TBMANLIB/ToolsStream -has_toc: false ---- - -# ToolsStream module -{: .no_toc } - -Reads and writes whole files as text in a chosen character set, or as an array of bytes. The functions use the ADODB.Stream object of the ADO 2.8 library, which is part of Windows. [**cJson**](cJson) uses the text functions for its [**LoadFrom**](cJson#loadfrom) and [**SaveTo**](cJson#saveto). - -All four functions work through one stream object, [**Inst**](#inst), that every caller in the program shares. That has two consequences, which the entries for the functions describe: a text function that fails leaves the stream open, and a binary function leaves it set to the binary type. - -```tb check_build check_run -Dim Path As String -Path = Environ$("TEMP") & "\tbmanlib-docs-stream.txt" - -ToolsStream.SaveFileAsText Path, "line 1" & vbCrLf & "line 2" -Debug.Print ToolsStream.LoadFileAsText(Path) = "line 1" & vbCrLf & "line 2" -Kill Path -' Output: -' True -``` - -* TOC -{:toc} - -## Members - -### Inst -{: .no_toc } - -The shared **ADODB.Stream** object that all the functions of the module use. A field, declared `Public Inst As New ADODB.Stream`, created the first time it is used. - -A program can use it, for example to close a stream that a failed call left open: `ToolsStream.Inst.Close`. The program needs no reference to the ADO library for that. - -### LastError -{: .no_toc } - -A **String** field. [**LoadFileAsBinary**](#loadfileasbinary) and [**SaveFileAsBinary**](#savefileasbinary) set it to an empty string when they start, and to the text of the error when they fail. The two text functions neither set it nor clear it. - -### LoadFileAsBinary -{: .no_toc } - -Reads a whole file into a byte array. - -Syntax: **ToolsStream.LoadFileAsBinary** ( *Path* **,** *OutData* ) **As Boolean** - -*Path* -: *required* A **String**, the file to read. A name without a folder is looked for in the current folder of the program. - -*OutData* -: *required* A dynamic **Byte** array. It receives the bytes of the file. - -Returns **True** when the file was read, and **False** when an error occurred; the text of the error is then in [**LastError**](#lasterror). The function closes the stream if it is open, sets the stream to the binary type and leaves it so. - -The text in **LastError** is the description of the error, then `#`, then the description a second time. The number of the error is not in it. - -```tb check_build check_run -Dim Bytes() As Byte -Debug.Print ToolsStream.LoadFileAsBinary(Environ$("TEMP") & "\tbmanlib-docs-missing.bin", Bytes) -Debug.Print ToolsStream.LastError -If ToolsStream.Inst.State <> 0 Then ToolsStream.Inst.Close -ToolsStream.Inst.Type = 2 -' Output: -' False -' File could not be opened.#File could not be opened. -``` - -### LoadFileAsText -{: .no_toc } - -Reads a whole file as text and returns it. - -Syntax: **ToolsStream.LoadFileAsText** ( *FileName* [ **,** *CharSet* ] ) **As String** - -*FileName* -: *required* A **String**, the file to read. A name without a folder is looked for in the current folder of the program. - -*CharSet* -: *optional* A **String**, the name of the character set in which the file is stored, `UTF-8` by default. Any name that the ADODB.Stream accepts can be used. A byte order mark at the start of a UTF-8 file is not returned as a character. - -The function opens the stream, sets its character set, loads the file and reads all the text from it. It does not set the type of the stream. It handles no error: a failure is raised to the caller as the error of the stream. - -> [!WARNING] -> In version 1.0.0.5, a failed text call leaves the shared stream open, and a binary call leaves it set to the binary type. Both break the calls that follow. -> -> When the file cannot be opened, the call raises error 3002, *File could not be opened*, and the call that closes the stream is never reached. Every later call to **LoadFileAsText** or [**SaveFileAsText**](#savefileastext) then fails in its first step, which opens a stream that is already open, with error 3705, *Operation is not allowed when the object is open*. The calls work again after `ToolsStream.Inst.Close`, or after a binary function, which closes an open stream first. [**cJson.LoadFrom**](cJson#loadfrom) and [**cJson.SaveTo**](cJson#saveto) are affected in the same way. -> -> A binary function sets the stream to the binary type, and the text functions do not set it back. After a call to [**LoadFileAsBinary**](#loadfileasbinary) or [**SaveFileAsBinary**](#savefileasbinary), a text function fails with error 3219, *Operation is not allowed in this context*. Setting the type back to text with `ToolsStream.Inst.Type = 2`, on the closed stream, makes the text functions work again. - -```tb check_build check_run -Dim Path As String -Dim Text As String -Dim Bytes() As Byte - -Path = Environ$("TEMP") & "\tbmanlib-docs-stream2.txt" -ToolsStream.SaveFileAsText Path, "hello" - -On Error Resume Next -Text = ToolsStream.LoadFileAsText(Environ$("TEMP") & "\tbmanlib-docs-missing.txt") -Debug.Print Err.Number & " " & Err.Description -Err.Clear -Text = ToolsStream.LoadFileAsText(Path) -Debug.Print Err.Number & " " & Err.Description -Err.Clear -ToolsStream.Inst.Close -Debug.Print ToolsStream.LoadFileAsText(Path) - -Bytes = ToolsStr.ToBytes("abc") -Debug.Print ToolsStream.SaveFileAsBinary(Path, Bytes) -Text = ToolsStream.LoadFileAsText(Path) -Debug.Print Err.Number & " " & Err.Description -Err.Clear -If ToolsStream.Inst.State <> 0 Then ToolsStream.Inst.Close -ToolsStream.Inst.Type = 2 -Debug.Print ToolsStream.LoadFileAsText(Path) -Kill Path -' Output: -' 3002 File could not be opened. -' 3705 Operation is not allowed when the object is open. -' hello -' True -' 3219 Operation is not allowed in this context. -' abc -``` - -### SaveFileAsBinary -{: .no_toc } - -Writes a byte array to a file. - -Syntax: **ToolsStream.SaveFileAsBinary** ( *Path* **,** *OutData* ) **As Boolean** - -*Path* -: *required* A **String**, the file to write. An existing file is replaced. The folder must exist. - -*OutData* -: *required* A **Byte** array whose bytes are written. The parameter is called *OutData*, as in [**LoadFileAsBinary**](#loadfileasbinary), though here it is the input. - -Returns **True** when the file was written, and **False** when an error occurred; [**LastError**](#lasterror) then holds the text of the error, in the form described for **LoadFileAsBinary**. The stream is closed first if it was open, set to the binary type, and left so. - -```tb check_build check_run -Dim Path As String -Dim Bytes() As Byte -Dim Back() As Byte - -Path = Environ$("TEMP") & "\tbmanlib-docs-stream.bin" -Bytes = ToolsStr.ToBytes("abc") - -Debug.Print ToolsStream.SaveFileAsBinary(Path, Bytes) -Debug.Print ToolsStream.LoadFileAsBinary(Path, Back) -Debug.Print UBound(Back) -Kill Path - -' The stream is binary now; return it to text for the text functions. -ToolsStream.Inst.Type = 2 -' Output: -' True -' True -' 2 -``` - -### SaveFileAsText -{: .no_toc } - -Writes a string to a file as text. - -Syntax: **ToolsStream.SaveFileAsText** ( *FileName* **,** *Data* [ **,** *CharSet* ] ) **As Boolean** - -*FileName* -: *required* A **String**, the file to write. An existing file is replaced. The folder must exist. - -*Data* -: *required* A **String**, the text to write. It is passed by reference. The function adds no line break to it. - -*CharSet* -: *optional* A **String**, the name of the character set, `UTF-8` by default. - -The function opens the stream, sets its character set, writes the text and saves the file. A character set of `UTF-8` writes the byte order mark EF BB BF at the start of the file. The folder of the file has to exist: when it does not, the call raises error 3004, *Write to file failed*. - -> [!WARNING] -> The function always returns **False**. Its result is never set, even when the file was written. A program cannot use the result to see whether the call worked, and has to handle the error that the stream raises. - -```tb check_build check_run -Dim Path As String -Path = Environ$("TEMP") & "\tbmanlib-docs-stream3.txt" -Debug.Print ToolsStream.SaveFileAsText(Path, "hello") -Debug.Print Dir(Path) = "tbmanlib-docs-stream3.txt" -Kill Path -' Output: -' False -' True -``` - -A failure is raised to the caller and the stream stays open; see the warning for [**LoadFileAsText**](#loadfileastext). - -## See Also - -- [cJson](cJson) class -- uses **LoadFileAsText** and **SaveFileAsText** -- [ToolsUtf8](ToolsUtf8) module -- converts between UTF-8 bytes and strings, without a stream -- [ToolsFso](ToolsFso) module -- folders and path names -- [TBMANLIB](.) package -- overview diff --git a/docs/Reference/Contributed/TBMANLIB/ToolsUtf8.md b/docs/Reference/Contributed/TBMANLIB/ToolsUtf8.md index fcc4ef7ed..72996f43d 100644 --- a/docs/Reference/Contributed/TBMANLIB/ToolsUtf8.md +++ b/docs/Reference/Contributed/TBMANLIB/ToolsUtf8.md @@ -97,6 +97,6 @@ Debug.Print Err.Number ## See Also -- [ToolsStream](ToolsStream) module -- reads and writes files in a chosen character set, UTF-8 by default -- [ToolsStr](ToolsStr) module -- **ToBytes** and **ToString**, which convert with the ANSI code page +- [cToolsStream](cToolsStream) class -- reads and writes files in a chosen character set, UTF-8 by default +- [cToolsStr](cToolsStr) class -- **ToBytes** and **ToString**, which convert with the ANSI code page - [TBMANLIB](.) package -- overview diff --git a/docs/Reference/Contributed/TBMANLIB/cCsv.md b/docs/Reference/Contributed/TBMANLIB/cCsv.md index 0db00e483..3baea50c2 100644 --- a/docs/Reference/Contributed/TBMANLIB/cCsv.md +++ b/docs/Reference/Contributed/TBMANLIB/cCsv.md @@ -8,162 +8,434 @@ has_toc: false # cCsv class {: .no_toc } -In version 1.0.0.5, a **cCsv** is a reader and writer of INI files, not of CSV files. It keeps the settings of an INI file in a two-level **Scripting.Dictionary**, [**Root**](#root): the sections are the keys of **Root**, and each item is a dictionary of the keys and values of that section. [**LoadFrom**](#loadfrom) fills **Root** from a file and [**SaveTo**](#saveto) writes **Root** to a file. +A **cCsv** holds a table of text in memory and reads and writes it as a file of comma-separated values (CSV). The table is a dictionary of lines, [**Data**](#data), and each line is a dictionary of fields. [**NewLine**](#newline) and [**NewColumn**](#newcolumn) build the table, [**GetLine**](#getline) and [**value**](#value) read it, and [**LoadFrom**](#loadfrom) and [**SaveTo**](#saveto) read and write the file. The package keeps one ready-made instance, [**TBMAN.Csv**](TBMAN#csv). -The code is that of an INI tool: it adds the extension `.ini` to a file name, and its error messages name `iniTools`. The class reads and writes no comma-separated values. The package keeps one ready-made instance, [**TBMAN.Csv**](TBMAN#csv). +In version 1.0.0.5 the class of this name was a reader and writer of INI files. That code is now the class [**cIni**](cIni). -```tb check_build -Dim Cfg As New cCsv -Set Cfg.Root = CreateObject("Scripting.Dictionary") -Cfg.Root.Add "Window", CreateObject("Scripting.Dictionary") -Cfg.Root("Window")("Width") = "800" - -If Not Cfg.SaveTo(Environ$("TEMP") & "\tbmanlib-docs-settings.ini") Then - Debug.Print Cfg.LastError -End If +```tb check_build check_run +Dim T As New cCsv +Dim Path As String +Path = Environ$("TEMP") & "\tbmanlib-docs-table.csv" + +T.NewLine "id", "name", "note" +T.NewLine 1, "Ann", "x,y" +T.NewLine 2, "Bob" +Debug.Print T.SaveTo(Path) + +Dim R As New cCsv +R.LoadFrom Path +Debug.Print R.Data.Count +Debug.Print R.value(2, 3) +Debug.Print "[" & R.value(3, 3) & "]" +Kill Path +' Output: +' True +' 3 +' x,y +' [] ``` * TOC {:toc} +## How the table is held + +- [**Data**](#data) is a **Scripting.Dictionary**. Its keys are the line numbers, from 1, and each item is a **Scripting.Dictionary** that holds one line. +- A line has the column numbers, from 1, as keys and the fields as items. A field is a **String** when it was read from a file, and any value when it was added with **NewLine**. +- The first line is an ordinary line. By default the class treats it as the line of titles ([**IsFirstLineToHead**](#isfirstlinetohead)), but it is stored in **Data** like the others, and [**SaveTo**](#saveto) writes it first. +- [**HeadTitle**](#headtitle) and [**HeadTitleWidth**](#headtitlewidth) hold a title and a column width for each column. They are for a program that shows the table in a list control; the class stores them and does not draw anything. +- Every line has as many columns as the first line. A line that is added with fewer fields is filled with empty strings, and one with more loses the fields at the end. + ## The file format -The files are opened as ANSI text, in the code page of the system, not as Unicode or UTF-8. The text that **SaveTo** writes, and that **LoadFrom** reads, looks like this: +- **LoadFrom** reads a line at a time and splits it at the character in [**Delimiter**](#delimiter), `,` by default. **SaveTo** joins the fields with the same character and ends each line with a carriage return and line feed. +- A field may be enclosed in double quotes. Inside quotes the delimiter is part of the field, and a pair of double quotes stands for one. **LoadFrom** removes the enclosing quotes. **SaveTo** adds them to a field that holds the delimiter or a double quote, and to no other. +- **LoadFrom** removes the spaces at both ends of every field, also of a quoted one. **SaveTo** does not add or remove any. +- A field cannot hold a line break. **LoadFrom** reads one line at a time, so a quoted field that continues on the next line is read as two lines. **SaveTo** writes a line break inside a field as it is, and the file then has more lines than the table. +- The files are opened as ANSI text, in the code page of the system, not as Unicode or UTF-8, and **SaveTo** cannot write a character that the code page does not have (see the warning at **SaveTo**). +- A line that is empty or holds only spaces is skipped. Its number is still counted, so the numbers of the lines in **Data** have a gap there. -``` -[Window] -Width=800 +## Fields and properties -[Colors] -Back=white -``` +### CurrentLineDic +{: .no_toc } + +A **Scripting.Dictionary** field, declared `Public CurrentLineDic As New Scripting.Dictionary`. The line that was last added or fetched: [**NewLine**](#newline) and [**GetLine**](#getline) set it, [**SetCurrentLineData**](#setcurrentlinedata) changes it, and [**Reset**](#reset) empties it. It is the same dictionary object as the line in [**Data**](#data), so a change to one is a change to the other. After [**SetLine**](#setline) it is **Nothing**, and the next use creates an empty dictionary. + +### CurrentLineNumber +{: .no_toc } + +A **Long** field, 0 at the start. The number of the line in [**CurrentLineDic**](#currentlinedic). **NewLine** and **GetLine** set it. [**LoadFrom**](#loadfrom) does not. + +### Data +{: .no_toc } + +A **Scripting.Dictionary** field: the table. The keys are the line numbers and the items are the dictionaries of the lines; see *How the table is held*. [**LoadFrom**](#loadfrom) replaces it with a new dictionary, and [**Reset**](#reset) empties it. + +### Delimiter +{: .no_toc } + +A **String** field, `,` at the start and after a [**Reset**](#reset) that is not given *OnlyData*. The character that separates the fields; it should be one character, because a longer text never matches a character of the line when a file is read. + +### HasHeadTitle +{: .no_toc } + +A **Boolean** field, **True** at the start. Nothing in the class reads it. + +### HeadTitle +{: .no_toc } + +A **Scripting.Dictionary** field: the title of each column, with the column numbers as keys. [**NewLine**](#newline) and [**LoadFrom**](#loadfrom) fill it with the text `(无标题)`, a Chinese phrase that means *no title*, for each column that has none, and [**NewColumn**](#newcolumn) stores the title it is given. [**LoadFrom**](#loadfrom) does not take the titles from the first line of the file. + +### HeadTitleWidth +{: .no_toc } + +A **Scripting.Dictionary** field: the width of each column, with the column numbers as keys, in the units of the program's list control (twips, from the default). It is filled with [**HeadTitleWidthDefault**](#headtitlewidthdefault) for each column that has none, and [**SetHeadTitleWidth**](#setheadtitlewidth) and [**NewColumn**](#newcolumn) change it. + +### HeadTitleWidthAuto +{: .no_toc } + +A **Boolean** field, **False** at the start. Meant to say that the widths follow the text. Nothing in the class reads it. + +### HeadTitleWidthDefault +{: .no_toc } + +A **Long** field, 1500 at the start and after a **Reset** that is not given *OnlyData*. The width given to a column that has none. + +### HeadTitleWidthMax +{: .no_toc } -- A section starts with a name in square brackets on a line of its own. **SaveTo** writes a blank line after each section. -- Each key has a line of its own, in the form `key=value`. **LoadFrom** splits a line at its first `=`, removes the spaces around the key and around the value, and removes one `"` at the start and one at the end of the value, if they are there. -- A blank line and a line that starts with a semicolon are skipped. A line that starts with `#` is not a comment: it is read as a key if it holds an `=`, and skipped if it does not. -- A line that has no `=` and is not a section is skipped. -- **LoadFrom** puts the keys that come before the first section under a section whose name is the empty string. +A **Long** field, 8000 at the start and after a **Reset** that is not given *OnlyData*. [**SetHeadTitleWidth**](#setheadtitlewidth) lowers a width that is greater than this. -## Properties +### HeadTitleWidthWord +{: .no_toc } + +A **Long** field, 0 at the start. Meant to hold the width of one double-byte character. Nothing in the class reads it. + +### IsFirstLineToHead +{: .no_toc } + +A **Boolean** field, **True** at the start and after a **Reset** that is not given *OnlyData*. It decides what [**NewColumn**](#newcolumn) puts in the first line of the new column: the title when it is **True**, the content when it is **False**. ### LastError {: .no_toc } -A **String**. The text of the last error that [**LoadFrom**](#loadfrom) or [**SaveTo**](#saveto) caught, or an empty string after a call that worked. +A **String** field. The text of the last error that a method caught: [**GetLine**](#getline), [**SetLine**](#setline), [**SetCurrentLineData**](#setcurrentlinedata), [**SetHeadTitleWidth**](#setheadtitlewidth), [**value**](#value) and [**DeleteColumn**](#deletecolumn) set it to a Chinese text that means *line number does not exist* or *column number does not exist*, and [**LoadFrom**](#loadfrom) and [**SaveTo**](#saveto) set it to the error number, a `#` and the description of the error (and, for **LoadFrom**, a colon and the path). -After an error, the text is a Chinese label that names `iniTools.SaveTo` or `iniTools.LoadFrom` (not `cCsv`), then the error number, a `#` and the description. **LoadFrom** adds a colon and the path. When [**Root**](#root) has no entries, **SaveTo** sets the text to a Chinese sentence that means *there is no data to write*. +No call clears it, and **Reset** does not. A successful call leaves the text of an earlier failure in it. **LoadFrom** sets it, also when the file is read, to a Chinese text that means *the content is empty* for each line that is skipped because it is blank. -### Root +### value {: .no_toc } -The data, a **Scripting.Dictionary**: section names are the keys, and each item is a **Scripting.Dictionary** of the keys and values of the section. A field, declared `Public Root As Scripting.Dictionary` with no `New`, so it is **Nothing** until a program sets it: +Reads a field of the table. -```tb check_build -Dim Cfg As New cCsv -Set Cfg.Root = CreateObject("Scripting.Dictionary") -``` +Syntax: *object*.**value** ( *LineNumber* **,** *ColumnNumber* ) + +*LineNumber* +: *required* A **Long**, the key of the line in [**Data**](#data). + +*ColumnNumber* +: *required* A **Long**, the number of the column. -While **Root** is **Nothing**, both methods catch the error and set [**LastError**](#lasterror). After a file is read, a value is read as `Cfg.Root("Window")("Width")`. [**SaveTo**](#saveto) writes each value as a string. +Returns the field as a **Variant**. When the line or the column does not exist, the result is the integer 1 (the value of **vbNull**, which the source uses where it means a null) and [**LastError**](#lasterror) holds a Chinese text that says which of the two is missing. + +The class declares a **Set** accessor for the property and no **Let** accessor, and neither works. `T.value(2, 3) = "x"` and `Set T.value(2, 3) = Nothing` each compile and raise an error (-2147467259 and -2147352567), and the field is not changed. To change a field, use [**SetCurrentLineData**](#setcurrentlinedata) or write into the dictionary of the line. ## Methods +### DeleteColumn +{: .no_toc } + +Meant to remove a column from every line. + +Syntax: *object*.**DeleteColumn** ( [ *ColumnNumber* ] ) **As Boolean** + +*ColumnNumber* +: *optional* A **Long**, the column to remove. If it is left out, or 0, the last column of the current line is removed. + +The function needs line 1 to exist; when it does not, it returns **False** and sets [**LastError**](#lasterror). Otherwise it removes the column from every line, its title and its width, and returns **True**. The other columns keep their numbers. + +> [!WARNING] +> Only the last column can be removed without damage. A column in front of others leaves a gap in the column numbers, which the other methods do not expect. [**SaveTo**](#saveto) writes the columns for the numbers 1 to the new count of columns, so it writes an empty field in the place of the missing one and leaves out the last column: after removing column 2 of three, a line `a,b,c` is saved as `a,`. [**value**](#value) with the old numbers still finds the fields that remain. + +```tb check_build check_run +Dim T As New cCsv +Dim Path As String +Dim Text As String +Path = Environ$("TEMP") & "\tbmanlib-docs-delete.csv" + +T.NewLine "a", "b", "c" +T.NewLine "d", "e", "f" +T.DeleteColumn 2 +Debug.Print T.Data(1).Count & " " & T.Data(1).Exists(2) +T.SaveTo Path + +Open Path For Input As #1 +Do Until EOF(1) + Line Input #1, Text + Debug.Print "[" & Text & "]" +Loop +Close #1 +Kill Path +' Output: +' 2 False +' [a,] +' [d,] +``` + +### GetLine +{: .no_toc } + +Makes a line the current line. + +Syntax: *object*.**GetLine** ( *LineNumber* [ **,** *LineDic* ] ) **As Boolean** + +*LineNumber* +: *required* A **Long**, the key of the line in [**Data**](#data). + +*LineDic* +: *optional* A **Scripting.Dictionary** variable that is meant to receive the line. The variable that is passed is not changed, whether it is **Nothing** or holds a dictionary. Use [**CurrentLineDic**](#currentlinedic). + +Sets [**CurrentLineDic**](#currentlinedic) and [**CurrentLineNumber**](#currentlinenumber) and returns **True**. When the line does not exist, returns **False** and sets [**LastError**](#lasterror). + +```tb check_build check_run +Dim T As New cCsv +T.NewLine "id", "name" +T.NewLine 1, "Ann" +T.NewLine 2, "Bob" + +Debug.Print T.GetLine(2) +Debug.Print T.CurrentLineNumber & " " & T.CurrentLineDic(2) +Debug.Print T.GetLine(9) +Debug.Print T.CurrentLineNumber +' Output: +' True +' 2 Ann +' False +' 2 +``` + ### LoadFrom {: .no_toc } -Reads an INI file into [**Root**](#root). +Reads a CSV file into [**Data**](#data). -Syntax: *object*.**LoadFrom** ( *Path* ) **As cIni** +Syntax: *object*.**LoadFrom** ( *FilePath* [ **,** *IsFirstHead* ] ) **As cCsv** -*Path* -: *required* A **String**, the file to read. The extension `.ini` is added to a name that does not end with it, without regard to letter case, so `settings` and `settings.INI` name the same file. The path is used as written: a name without a folder is looked for in the current folder of the program. +*FilePath* +: *required* A **String**, the file to read. The path is used as written: a name without a folder is looked for in the current folder of the program. -The values that the file holds are added to the dictionaries of [**Root**](#root) and replace the values of the same keys. Nothing that **Root** held before is removed. **Root** has to hold a dictionary before the call. +*IsFirstHead* +: *optional* A **Boolean**, **True** by default. It is meant to say that the first line holds the titles. The function does not use it: the titles are never taken from the file. -The method is declared to return a [**cIni**](.#types-without-members), a class with no members, and its code assigns other values to its result: the first statement, `Set LoadFrom = Me`, assigns the **cCsv** itself, and the last, `LoadFrom = True`, assigns a **Boolean**. Neither is a **cIni**. The first assignment fails with the error *No such interface supported* (-2147467262), and because the error handler is switched on only after that statement, the error goes to the caller, with an empty source. The file is not read, [**LastError**](#lasterror) stays empty and **Root** is not changed. The call fails in this way every time. +Returns the object itself. The file is read as described in *The file format*. **Data** is replaced when the file has been opened, with one line for each line of the file that is not blank, numbered by its place in the file. Lines with more fields than the first line are cut to its count, and shorter ones are filled with empty strings. [**HeadTitle**](#headtitle) and [**HeadTitleWidth**](#headtitlewidth) get an entry for each column that has none. + +An error, such as a file that does not exist, is not raised. It is caught, [**LastError**](#lasterror) is set to its number, a `#`, the description, a colon and the path, and **Data** is not changed. > [!NOTE] -> **LoadFrom** cannot read a file in version 1.0.0.5. +> A blank line in the file leaves a gap in the line numbers of **Data**: for a file of three lines whose second is blank, the keys are 1 and 3, and the count is 2. [**NewLine**](#newline) numbers the new line with the count plus 1, which here is 3, a line that exists, and raises error 457, *This key is already associated with an element of this collection*. Lines can then not be added until the gap is gone. ```tb check_build check_run -Dim Cfg As New cCsv Dim Path As String -Path = Environ$("TEMP") & "\tbmanlib-docs-read.ini" +Dim T As New cCsv +Path = Environ$("TEMP") & "\tbmanlib-docs-load.csv" Open Path For Output As #1 -Print #1, "[Window]" -Print #1, "Width=800" +Print #1, "id,name,note" +Print #1, "1, Ann ,""x,y""" +Print #1, "" +Print #1, "2,""say """"hi"""""",z" Close #1 -Set Cfg.Root = CreateObject("Scripting.Dictionary") -On Error Resume Next -Cfg.LoadFrom Path -Debug.Print Err.Number -Debug.Print "[" & Cfg.LastError & "]" -Debug.Print Cfg.Root.Count -On Error GoTo 0 +T.LoadFrom Path +Debug.Print T.Data.Count & " " & T.Data.Exists(3) & " " & T.Data.Exists(4) +Debug.Print T.value(2, 2) & "|" & T.value(2, 3) +Debug.Print T.value(4, 2) Kill Path ' Output: -' -2147467262 +' 3 False True +' Ann|x,y +' say "hi" +``` + +### NewColumn +{: .no_toc } + +Adds a column to every line. + +Syntax: *object*.**NewColumn** ( *Title* [ **,** *Width* ] [ **,** *Content* ] ) **As Boolean** + +*Title* +: *required* A **String**, the title of the column. It is stored in [**HeadTitle**](#headtitle), and it is the field of the first line when [**IsFirstLineToHead**](#isfirstlinetohead) is **True**. + +*Width* +: *optional* A **Long**, the width. If it is 0 or left out, [**HeadTitleWidthDefault**](#headtitlewidthdefault) is used. + +*Content* +: *optional* A **String**, the field for every other line, an empty string by default. With **IsFirstLineToHead** set to **False**, it is the field of the first line as well. + +The new column gets the number after the last column of each line. A table with no lines gets no column, and no title. + +> [!WARNING] +> The function always returns **False**, also when it has added the column: the result is never set. A program cannot use it to see whether the call worked. + +```tb check_build check_run +Dim T As New cCsv +T.NewLine "id", "name" +T.NewLine 1, "Ann" +Debug.Print T.NewColumn("age", 700, "0") +Debug.Print T.value(1, 3) & " " & T.value(2, 3) +Debug.Print T.HeadTitle(3) & " " & T.HeadTitleWidth(3) +' Output: +' False +' age 0 +' age 700 +``` + +### NewLine +{: .no_toc } + +Adds a line to the end of the table. + +Syntax: *object*.**NewLine** ( [ *FieldsData* ] ) **As Boolean** + +*FieldsData* +: *optional* Any number of values, in a **ParamArray**: the fields of the line. A **Null** or **Empty** value becomes an empty string. As the only argument, a **Scripting.Dictionary** with the column numbers as keys is copied, with its items, and the copy becomes the line; later changes to the dictionary do not reach the table. + +The line gets the number after the count of the lines in [**Data**](#data). It is filled to the number of columns of the first line, or cut to it, and becomes the current line ([**CurrentLineDic**](#currentlinedic), [**CurrentLineNumber**](#currentlinenumber)). **HeadTitle** and **HeadTitleWidth** get an entry for each column that has none. Without arguments the line has one empty field, filled up to the width of the table. The first line of a table fixes the number of columns for all the others. + +Returns **True**. An error is raised when the number is taken; see the note at [**LoadFrom**](#loadfrom). + +```tb check_build check_run +Dim T As New cCsv +T.NewLine "id", "name", "note" +T.NewLine 1, "Ann", "x,y", "dropped" +T.NewLine 2 +Debug.Print T.Data(2).Count & " " & T.Data(3).Count +Debug.Print "[" & T.value(3, 2) & "]" +Debug.Print T.CurrentLineNumber +' Output: +' 3 3 ' [] -' 0 +' 3 ``` -### SaveTo +### Reset {: .no_toc } -Writes [**Root**](#root) to an INI file. +Empties the table. + +Syntax: *object*.**Reset** ( [ *OnlyData* ] ) + +*OnlyData* +: *optional* A **Boolean**, **False** by default. With **True**, only the table is emptied; the settings stay as they are. + +Empties [**Data**](#data), [**HeadTitle**](#headtitle), [**HeadTitleWidth**](#headtitlewidth) and [**CurrentLineDic**](#currentlinedic), and sets [**CurrentLineNumber**](#currentlinenumber) to 0. Without *OnlyData*, it also sets [**Delimiter**](#delimiter), [**HasHeadTitle**](#hasheadtitle), [**HeadTitleWidthDefault**](#headtitlewidthdefault), [**HeadTitleWidthMax**](#headtitlewidthmax) and [**IsFirstLineToHead**](#isfirstlinetohead) to their values at the start. [**LastError**](#lasterror) is not cleared. + +### SaveTo +{: .no_toc } -Syntax: *object*.**SaveTo** ( [ *Path* ] ) **As Boolean** +Writes the table to a CSV file. -*Path* -: *optional* A **String**, the file to write. The extension `.ini` is added to a name that does not end with it. A call without a path uses the name `.ini`. A file that exists is replaced. +Syntax: *object*.**SaveTo** ( *FilePath* ) **As Boolean** -Returns **True** when the file was written. It returns **False** when **Root** is **Nothing** or has no entries, or when an error occurred; [**LastError**](#lasterror) then says why. +*FilePath* +: *required* A **String**, the file to write. A file that exists is replaced. The folder must exist: the function does not create it. -The method first calls [**AutoMakeDir**](ToolsFso#automakedir) for *Path*, without telling it that the path names a file. **AutoMakeDir** then takes the whole path to be a folder and creates a folder with the name of the file, and the step that opens the file fails because that name is a folder. A file that exists is not affected by the call: no folder can be created with the name of a file, and the file is rewritten. +Writes the lines of [**Data**](#data) in the order of their keys, each as described in *The file format*, and returns **True**. When an error occurs, such as a folder that does not exist, it returns **False** and [**LastError**](#lasterror) holds its number, a `#` and the description. The titles in [**HeadTitle**](#headtitle) are not written. > [!WARNING] -> In version 1.0.0.5, a first save of a new file returns **False** and leaves a folder that has the name of the file, `Path.ini`. [**LastError**](#lasterror) then holds error 70, *Permission denied*, which is the error of the step that opens the file. A save over a file that exists works, and replaces the file. +> The file is created, or emptied if it exists, before the first field is written. When a field holds a character that the code page of the system does not have, such as `ChrW(&H4E2D)` on a system with the Western code page 1252, writing it fails with error 5, the function returns **False**, and the file is left with no content. The previous content of a file is lost. ```tb check_build check_run -Dim Cfg As New cCsv +Dim T As New cCsv Dim Path As String Dim Text As String +Path = Environ$("TEMP") & "\tbmanlib-docs-quote.csv" -Path = Environ$("TEMP") & "\tbmanlib-docs-save" -Set Cfg.Root = CreateObject("Scripting.Dictionary") -Cfg.Root.Add "Window", CreateObject("Scripting.Dictionary") -Cfg.Root("Window")("Width") = "800" +T.NewLine "plain", "a,b", "say ""hi""" +T.Delimiter = ";" +T.NewLine "x;y", "a,b", " pad" +T.SaveTo Path -' A new file: the save fails and leaves a folder named Path.ini. -Debug.Print Cfg.SaveTo(Path) -Debug.Print InStr(Cfg.LastError, "70#Permission denied") > 0 -Debug.Print (GetAttr(Path & ".ini") And vbDirectory) <> 0 -CreateObject("Scripting.FileSystemObject").DeleteFolder Path & ".ini" - -' A file that exists: the save works. -Open Path & ".ini" For Output As #1 -Close #1 -Debug.Print Cfg.SaveTo(Path) -Open Path & ".ini" For Input As #1 +Open Path For Input As #1 Do Until EOF(1) Line Input #1, Text - Debug.Print "[" & Text & "]" + Debug.Print Text Loop Close #1 -Kill Path & ".ini" +Kill Path ' Output: -' False -' True -' True -' True -' [[Window]] -' [Width=800] -' [] +' plain;a,b;"say ""hi""" +' "x;y";a,b; pad ``` +### SetCurrentLineData +{: .no_toc } + +Changes a field of the current line. + +Syntax: *object*.**SetCurrentLineData** ( *ColumnNumber* **,** *Dat* [ **,** *LineDic* ] ) **As Boolean** + +*ColumnNumber* +: *required* A **Long**, the column. + +*Dat* +: *required* A **Variant**, the new field. + +*LineDic* +: *optional* A **Scripting.Dictionary** variable. The parameter has no effect: the function works on [**CurrentLineDic**](#currentlinedic), and the variable that is passed is not changed. + +Returns **True** when the field was changed. It returns **False** and sets [**LastError**](#lasterror) when the current line has no such column, which includes the case that the table has no current line. + +### SetHeadTitleWidth +{: .no_toc } + +Sets the width of a column. + +Syntax: *object*.**SetHeadTitleWidth** ( *ColumnNumber* **,** *ColumnWidth* ) **As Boolean** + +*ColumnNumber* +: *required* A **Long**, the column. It has to have an entry in [**HeadTitleWidth**](#headtitlewidth). + +*ColumnWidth* +: *required* A **Long**, the width. A value above [**HeadTitleWidthMax**](#headtitlewidthmax) is replaced by it. + +Returns **True** when the width was stored. Returns **False** and sets [**LastError**](#lasterror) when the column has no entry. + +### SetHeadTitleWidthByDic +{: .no_toc } + +Sets the widths of several columns. + +Syntax: *object*.**SetHeadTitleWidthByDic** ( *Dic* ) **As Boolean** + +*Dic* +: *required* A **Scripting.Dictionary** with column numbers as keys and widths as items. + +For each key of *Dic* that has an entry in [**HeadTitleWidth**](#headtitlewidth), the width is stored; other keys are skipped. The widths are not limited by [**HeadTitleWidthMax**](#headtitlewidthmax). + +> [!WARNING] +> The function always returns **False**, also when it has stored the widths: the result is never set. + +### SetLine +{: .no_toc } + +Replaces a line of the table. + +Syntax: *object*.**SetLine** ( *LineNumber* **,** *LineDic* ) **As Boolean** + +*LineNumber* +: *required* A **Long**, the key of a line that exists in [**Data**](#data). + +*LineDic* +: *required* A **Scripting.Dictionary** with the column numbers as keys. + +Fills *LineDic* or cuts it to the number of columns of the first line, stores it in **Data** under *LineNumber* in place of the old line, and returns **True**. The dictionary itself is stored, not a copy, so later changes to it change the table. When the line does not exist, the function returns **False** and sets [**LastError**](#lasterror). [**CurrentLineDic**](#currentlinedic) is **Nothing** afterwards, and until the next [**GetLine**](#getline) or [**NewLine**](#newline) [**SetCurrentLineData**](#setcurrentlinedata) returns **False**. + ## See Also - [TBMAN](TBMAN) module -- the shared instance **TBMAN.Csv** -- [ToolsFso](ToolsFso) module -- **AutoMakeDir**, which the method calls +- [cIni](cIni) class -- reads and writes INI files; the code that this class held in version 1.0.0.5 +- [ToolsDic](ToolsDic) module -- **DeepCopy**, which **NewLine** uses for a dictionary - [TBMANLIB](.) package -- overview diff --git a/docs/Reference/Contributed/TBMANLIB/cHttpClient.md b/docs/Reference/Contributed/TBMANLIB/cHttpClient.md new file mode 100644 index 000000000..30cb719e4 --- /dev/null +++ b/docs/Reference/Contributed/TBMANLIB/cHttpClient.md @@ -0,0 +1,514 @@ +--- +title: cHttpClient +parent: TBMANLIB Package +permalink: /tB/Packages/Contributed/TBMANLIB/cHttpClient +has_toc: false +--- + +# cHttpClient class +{: .no_toc } + +A **cHttpClient** sends HTTP and HTTPS requests with the Windows component *WinHTTP* (`WinHttp.WinHttpRequest.5.1`) and reads the answer as text, as JSON, as bytes or as a file. A program fills in the settings (headers, form values, a JSON document, query values), calls one of the **Send** methods, and reads the result with one of the **Return** methods. The package keeps one ready-made instance, [**TBMAN.HttpClient**](TBMAN#httpclient). + +The settings are fields of the object, and most of them stay after a request: the headers and the content type are kept, and the form values, the JSON document and the query values are emptied. The **Send** methods return the object itself, so calls can be chained. + +```tb check_build +Dim Http As New cHttpClient +Dim Reply As cJson + +Http.RequestHeaders.Item("X-Api-Key") = "secret" +Http.SetRequestContentType JsonString +Http.RequestDataJson.Item("name") = "Ann" + +Http.SendPost "https://example.com/api/users" +Set Reply = Http.ReturnJson() +Debug.Print Reply.Item("id") +``` + +* TOC +{:toc} + +## How a request runs + +[**Fetch**](#fetch) does the work for every **Send** method. It opens the request, sets the headers, builds the body, sends it and, unless the request is asynchronous, waits for the answer. In order: + +1. It empties [**LastError**](#lasterror) and raises error 500 if the URL is empty. +2. If [**RequestDataQuery**](#requestdataquery) has items, it adds them to the URL. +3. If [**RequestHeaders**](#requestheaders) has no `Content-Type` item, it adds one with [**RequestContentType**](#requestcontenttype), which is set to `application/x-www-form-urlencoded` when it is empty. Every item of **RequestHeaders** whose text is not empty is then sent. +4. It sets the options of the request: all certificate errors are ignored, and redirects are followed or not as [**EnableRedirects**](#enableredirects) says. +5. If the call gave no body, it builds one: from [**RequestDataJson**](#requestdatajson) when the content type is exactly `application/json`, or from [**RequestDataForm**](#requestdataform) when it is exactly `application/x-www-form-urlencoded`. For any other content type, a body is sent only when the call gives one. +6. It encodes the body as UTF-8 when [**RequestChartSet**](#requestchartset) contains `utf`, and in the ANSI code page otherwise, sets the extra header `Content-Lenght` (see [Consts](Consts#tools_http_header_field_content_lenght)), and sends it. +7. Unless the request is asynchronous, it waits [**RequestTimeOut**](#requesttimeout) seconds (at least 5) for the answer, raises the status as an error when it is 400 to 599, and reads the response headers into [**ResponseHeaders**](#responseheaders). +8. It empties the form values, the JSON document and the query values, clears [**DebugStart**](#debugstart) and makes the next request synchronous. The headers are kept. + +A failed request does the last step too, sets **LastError** and raises the error again. The error is raised with the number of the cause: the HTTP status for a 4xx or 5xx answer, 900 for a timeout, or the number that WinHTTP reports (such as -2147012867, *a connection with the server could not be established*). + +Several error texts of the class are written in Chinese. The ones that are not a status or a WinHTTP text are these: + +| Number | Text means | Raised by | +|:-------|:-----------|:----------| +| 500 | the URL of the request is empty | **Fetch** | +| 900 | the request timed out, with the time in seconds | **Fetch**, **UploadFile** | +| 310 | more redirects than the maximum | **FollowRedirect** | +| 58 | the file exists | **DownloadFile**, **FinishDownloadFile** | +| 53 | the file does not exist | **UploadFile** | +| 1 | no download path was found, or the file could not be read | **FinishDownloadFile**, **UploadFile** | + +> [!WARNING] +> The class ignores every certificate error: an expired certificate, one from an unknown authority and one issued for another name are all accepted. An HTTPS request therefore encrypts the traffic, but does not prove who the server is, and a program that sends a password or a key with it can send it to an impostor. + +> [!NOTE] +> In BETA 1005 the compiler refuses an assignment written through a public dictionary field of an object, such as `Http.RequestHeaders("X-Key") = "1"`, with TB5090. Write the default member out: `Http.RequestHeaders.Item("X-Key") = "1"`. Reading `Http.RequestHeaders("X-Key")` is accepted. + +## Request settings + +### RequestContentType +{: .no_toc } + +A **String**, the media type of the body. It is empty in a new object. [**SetRequestContentType**](#setrequestcontenttype) sets it from a number, and the constants of [**Consts**](Consts) and [**ToolsHttp**](ToolsHttp) are the usual values. [**Fetch**](#fetch) reads it to choose the body and, when the headers have no `Content-Type`, to set that header. + +> [!WARNING] +> The `Content-Type` header is put into [**RequestHeaders**](#requestheaders) by the first request of an object and stays there. A later change of **RequestContentType** changes the body that is built, but not the header: after a form request, `SetRequestContentType JsonString` sends the JSON text with the header `application/x-www-form-urlencoded`. Set the item as well, `Http.RequestHeaders.Item("Content-Type") = "application/json"`, or use a new object. + +[**UploadFile**](#uploadfile) sets this field to its `multipart/form-data` value, with the boundary, and leaves it, so a request after an upload is sent with that content type unless the program sets it again. + +### RequestChartSet +{: .no_toc } + +A **String**, the character set of the request body. It is `utf-8` in a new object. If the name contains `utf`, in any letter case, the body is encoded as UTF-8; any other name encodes it in the ANSI code page of the computer, whatever the name is: `gbk` does not select GBK, and a character that the code page lacks becomes `?`. The field does not set a header. + +### RequestTimeOut +{: .no_toc } + +A **Long**, the seconds that a synchronous request waits for the answer. It is 5 in a new object. A value under 5 is raised to 5 by **Fetch**, and the field shows 5 afterwards. When the wait ends without an answer, the call raises error 900. **UploadFile** waits the same time, but a value under 5 becomes 60 there, so a new object, whose value is 5, waits 5 seconds for the answer to an upload. + +### RequestHeaders +{: .no_toc } + +A **Scripting.Dictionary** of the request headers: the keys are the header names, compared without regard to letter case, and the items are their text. It is created with the object, and it is the only setting that is not emptied after a request, so a header, a cookie or an API key stays on every request of the object. Write `Http.RequestHeaders.Item("X-Key") = "1"`; an item whose text is the empty string is not sent. To stop sending a header, remove the key or empty the dictionary with `RemoveAll`. + +### RequestDataForm +{: .no_toc } + +A **Scripting.Dictionary** of the values of a form: the keys are the field names. When the content type is `application/x-www-form-urlencoded` and the call gives no body, [**Fetch**](#fetch) sends `name=value` pairs joined by `&`, in the order they were added. It is emptied after each request. + +> [!WARNING] +> The names and values are written as they are, without URL encoding. A value with a space, `&`, `=` or `%` produces a body that a server reads as other fields: `Form.Item("city") = "A B&C"` is sent as `city=A B&C`. Such a value has to be escaped by the program before it is added; [**ToolsHttp.UrlEncode**](ToolsHttp#urlencode) does that for text in the ANSI code page. + +### RequestDataJson +{: .no_toc } + +A [**cJson**](cJson), the document that is sent when the content type is exactly `application/json` and the call gives no body. It is encoded with the characters themselves and not as `\uXXXX` escapes (the *FromUnicode* argument of [**cJson.Encode**](cJson#encode) is **True**), so the warning on that argument applies: a text value with a backslash followed by `u` raises error 13. The document is emptied after each request. + +If the document is empty, the request has no body and goes with `Content-Length: 0`. + +### RequestDataQuery +{: .no_toc } + +A **Scripting.Dictionary** of query values. When it has items, [**Fetch**](#fetch) adds them to the URL, after a `?` or, if the URL has one, a `&`. It is emptied after each request. + +> [!WARNING] +> The pairs are written after the fixed name `ver`: the URL gets `?ver=` followed by the pairs joined by `&`. With the items `a` = 1 and `b` = 2, the request goes to `?ver=a=1&b=2`: a server reads a parameter `ver` whose value is `a=1`, and a parameter `b`. The name of the first item is lost, and no item is sent as a parameter of that name. The values are not URL encoded either. Put the query values in the URL text instead, which the call sends as given. + +### RequestDataBody +{: .no_toc } + +A **Scripting.Dictionary**. No member reads it. + +### RequestData +{: .no_toc } + +An array of **Byte**. No member reads it. + +### EnableRedirects +{: .no_toc } + +A **Boolean**, **True** in a new object. When it is **True**, WinHTTP follows the redirects of the server (status 301, 302, 307 and so on) and the object holds the final answer. [**AllowRedirects**](#allowredirects) sets it. The value stays for every later request. + +### MaxRedirects +{: .no_toc } + +A **Long**, 10 in a new object. Only [**FollowRedirect**](#followredirect) reads it. WinHTTP follows redirects by itself, with a limit of its own, whatever this field says. + +## Response + +### ResponseHeaders +{: .no_toc } + +A **Scripting.Dictionary** of the headers of the last answer, keys compared without regard to letter case. It is filled by each successful synchronous request and by [**UploadFile**](#uploadfile). It is not emptied: the headers of an earlier answer stay, and the headers of a later answer overwrite those of the same name. An answer with an error status, and an asynchronous request, leave it unchanged. + +Each header line is split at its first colon and both parts are trimmed, so the value is cut at its own colon: + +| Line | Key | Value | +|:-----|:----|:------| +| `Content-Type: text/plain` | `Content-Type` | `text/plain` | +| `Date: Tue, 13 Oct 2026 18:00:00 GMT` | `Date` | `Tue, 13 Oct 2026 18` | +| `Location: http://host/path` | `Location` | `http` | + +The text after a blank line at the end becomes an item with the empty key and the empty value. A header that occurs twice (`Set-Cookie`) keeps its last value. + +### Cookies +{: .no_toc } + +A **Scripting.Dictionary**. No member fills it. WinHTTP keeps the cookies that the server sets and sends them with the later requests of the same object, in addition to an item `Cookie` of [**RequestHeaders**](#requestheaders). + +### ResponseRaw +{: .no_toc } + +A **Variant**. No member sets it. + +### LastError +{: .no_toc } + +A **String**, the text of the last error that **Fetch**, **DownloadFile**, **FinishDownloadFile**, **UploadFile** or **UploadFileSimple** caught. **Fetch** empties it at the start of a request. + +### DebugStart +{: .no_toc } + +A **Boolean**. When it is set to **True** before a request, [**DebugInfo**](#debuginfo) describes that request, and the field is cleared when the request ends. + +### DebugInfo +{: .no_toc } + +A [**cJson**](cJson). **Fetch** replaces it with an empty document at the start of every request. When [**DebugStart**](#debugstart) was set, it holds an object `Request` with `IsAsync`, `Method`, `Url`, `Body`, `Headers` (the request headers), `TimeOut` and `ChartSet`, and, for a synchronous request, an object `Response` with `Status`, `StatusText`, `Headers` and `Content`. A request that failed adds an object `Error` with `Number`, `Source` and `Description`. + +```tb check_build +Dim Http As New cHttpClient + +Http.DebugStart = True +Http.SendGet "https://example.com/" +Debug.Print Http.DebugInfo.Item("Response")("Status") +``` + +## Events + +### OnResponseFinished +{: .no_toc } + +An event with no arguments. The class declares it, and nothing raises it: a handler is never called. A program that sends an asynchronous request has to ask for the answer itself. + +## Methods + +### AllowRedirects +{: .no_toc } + +Sets [**EnableRedirects**](#enableredirects). + +Syntax: *object*.**AllowRedirects** ( *Bool* ) **As cHttpClient** + +*Bool* +: *required* A **Boolean**. **True** lets WinHTTP follow redirects. + +Returns the object itself. + +### Async +{: .no_toc } + +Makes the next request asynchronous. + +Syntax: *object*.**Async** ( *Bool* ) **As cHttpClient** + +*Bool* +: *required* A **Boolean**. + +Returns the object itself. The setting is for the next request only. An asynchronous [**Fetch**](#fetch) sends the request and returns at once: it does not wait, does not check the status and does not read [**ResponseHeaders**](#responseheaders). The answer is read later with a **Return** method, which raises an error (*the data necessary to complete this operation is not yet available*) until the answer is there. There is no status property and [**OnResponseFinished**](#onresponsefinished) is never raised, so the program finds out that the answer has come by trying. + +### DownloadFile +{: .no_toc } + +Downloads a file with a GET request. + +Syntax: *object*.**DownloadFile** ( *url* **,** *savePath* [ **,** *Overwrite* ] ) **As Boolean** + +*url* +: *required* A **String**, the address. + +*savePath* +: *required* A **String**, the file to write. + +*Overwrite* +: *optional* A **Boolean**, **True** by default. With **False**, a file that exists makes the call raise error 58. + +Sends the request with [**SendGet**](#sendget) (so a 4xx or 5xx status is raised as in **Fetch**), raises an error for a status other than 200, and writes the body to the file with [**cToolsStream.SaveFileAsBinary**](cToolsStream#savefileasbinary). It returns **True**. The test for **Overwrite** comes after the request, so the answer has been received when error 58 is raised; the file is not changed. + +> [!WARNING] +> The call returns **True** also when the file could not be written, for example for a folder that does not exist: **SaveFileAsBinary** returns **False** there, and **DownloadFile** does not look at it. Check that the file exists after the call. + +### DownloadFileAsync +{: .no_toc } + +Starts a download without waiting for it. + +Syntax: *object*.**DownloadFileAsync** ( *url* **,** *savePath* [ **,** *Overwrite* ] ) **As cHttpClient** + +The arguments are those of [**DownloadFile**](#downloadfile). The method sends an asynchronous GET request, then stores the path and the overwrite choice in [**RequestHeaders**](#requestheaders) as the items `X-Download-Path` and `X-Download-Overwrite`. [**FinishDownloadFile**](#finishdownloadfile) reads them and writes the file. + +The two items stay in **RequestHeaders**, so every later request of the object sends them to the server, with the local path in them. Remove them when the download is done: `Http.RequestHeaders.Remove "X-Download-Path"`, and the same for `X-Download-Overwrite`. + +### Fetch +{: .no_toc } + +Sends a request. The **Send** methods are shorter forms of this one; see [How a request runs](#how-a-request-runs). + +Syntax: *object*.**Fetch** ( *Method* **,** *url* [ **,** *Body* ] ) **As cHttpClient** + +*Method* +: *required* An **EnumRequestMethod** (declared in [**ToolsHttp**](ToolsHttp#enumrequestmethod)): **ReqPost**, **ReqGet**, **ReqPut**, **ReqDelete** or **ReqOptions**. + +*url* +: *required* A **String**, the address. An empty string raises error 500. + +*Body* +: *optional* A **String**, the body. If it is given, it is sent as it is and the form values and the JSON document are not used (they are still emptied after the request). An empty string counts as not given. + +Returns the object itself. + +### FinishDownloadFile +{: .no_toc } + +Writes the file of a download that [**DownloadFileAsync**](#downloadfileasync) started. + +Syntax: *object*.**FinishDownloadFile** ( ) **As Boolean** + +Reads the answer, raises an error for a status other than 200 and, if the file exists and the overwrite choice was **False**, error 58. It then writes the file and returns **True**. While the answer has not arrived, the call raises the WinHTTP error *the data necessary to complete this operation is not yet available* (-2147483638); a program calls it again after a short wait. If the item `X-Download-Path` is not in [**RequestHeaders**](#requestheaders), it raises error 1. Like [**DownloadFile**](#downloadfile), it returns **True** also when the file could not be written. + +### FollowRedirect +{: .no_toc } + +Meant to send the request again to the address of a redirect. + +Syntax: *object*.**FollowRedirect** ( [ *RedirectMethod* ] ) **As cHttpClient** + +*RedirectMethod* +: *optional* An **EnumRequestMethod**, the method for a 307 or 308 answer. If it is left out, a GET request is sent. + +Reads the address with [**GetRedirectUrl**](#getredirecturl) and sends a GET request to it (for 307 and 308, the given method). Each call counts, and error 310 is raised when the count exceeds [**MaxRedirects**](#maxredirects); [**ResetRedirectCount**](#resetredirectcount) sets the count to 0. The method needs [**EnableRedirects**](#enableredirects) set to **False** with [**AllowRedirects**](#allowredirects), or no redirect answer is left to follow. It returns the object itself, and does nothing if the last answer had no `Location` header. + +> [!NOTE] +> The method cannot send a request in BETA 1005. The address comes from [**ResponseHeaders**](#responseheaders), which cuts a value at its first colon, so `http://host/path` is read as `http`, and WinHTTP raises error -2147012890, *the URL does not use a recognized protocol*. A `Location` header with a relative address, such as `/path`, has no colon and is read whole, but WinHTTP refuses it as well. A program that has to follow a redirect leaves **EnableRedirects** at **True**, so that WinHTTP follows it. + +### GetRedirectUrl +{: .no_toc } + +Returns the `Location` header of the last answer. + +Syntax: *object*.**GetRedirectUrl** ( ) **As String** + +Returns the item `Location` of [**ResponseHeaders**](#responseheaders), or an empty string when there is none. The value is cut at its first colon (see [ResponseHeaders](#responseheaders)), so an absolute address comes back as `http` or `https`. + +### MapRequestContentType +{: .no_toc } + +The same as [**SetRequestContentType**](#setrequestcontenttype). + +Syntax: *object*.**MapRequestContentType** ( *ReqType* [ **,** *ContentType* ] ) **As String** + +### ResetRedirectCount +{: .no_toc } + +Sets the count of [**FollowRedirect**](#followredirect) to 0. + +Syntax: *object*.**ResetRedirectCount** ( ) **As cHttpClient** + +Returns the object itself. + +### ReturnBody +{: .no_toc } + +Returns the body of the last answer as bytes. + +Syntax: *object*.**ReturnBody** ( ) **As Byte()** + +Raises the WinHTTP error -2147012795, *this method cannot be called until the Send method has been called*, when the object has sent no request. + +### ReturnJson +{: .no_toc } + +Reads the body of the last answer as a JSON document. + +Syntax: *object*.**ReturnJson** ( [ *IsUtf8* ] [ **,** *IsConvert* ] ) **As cJson** + +*IsUtf8* +: *optional* A **Boolean**, **True** by default. + +*IsConvert* +: *optional* A **Boolean**. + +Takes the text from [**ReturnText**](#returntext) with the same arguments and decodes it with [**cJson.Decode**](cJson#decode), in a new **cJson**. A text that is not JSON raises error 10001 from the parser. + +### ReturnStream +{: .no_toc } + +Returns the answer as a stream. + +Syntax: *object*.**ReturnStream** ( ) **As Variant** + +Returns the `ResponseStream` of the WinHTTP object, an `IStream`. + +### ReturnText +{: .no_toc } + +Returns the body of the last answer as text. + +Syntax: *object*.**ReturnText** ( [ *IsUtf8* ] [ **,** *IsConvert* ] ) **As String** + +*IsUtf8* +: *optional* A **Boolean**, **True** by default. With **True**, the bytes of the body are decoded as UTF-8 with [**ToolsUtf8.Decode**](ToolsUtf8#decode). The character set that the answer's headers name is not looked at. + +*IsConvert* +: *optional* A **Boolean**. Used only when *IsUtf8* is **False**. With **True**, every byte of the body becomes one character (`StrConv(bytes, vbUnicode)`), which is the form to use for a text that is neither UTF-8 nor correctly named. With **False**, the text is WinHTTP's `ResponseText`, which it decodes with the character set of the `Content-Type` header, or UTF-8. + +### Send +{: .no_toc } + +The same as [**Fetch**](#fetch). + +Syntax: *object*.**Send** ( *Method* **,** *Url* [ **,** *Body* ] ) **As cHttpClient** + +### SendDelete +{: .no_toc } + +Sends a DELETE request. + +Syntax: *object*.**SendDelete** ( *url* [ **,** *Body* ] ) **As cHttpClient** + +The same as **Fetch** with **ReqDelete**. + +### SendGet +{: .no_toc } + +Sends a GET request. + +Syntax: *object*.**SendGet** ( *url* [ **,** *Body* ] ) **As cHttpClient** + +The same as **Fetch** with **ReqGet**. + +### SendOptions +{: .no_toc } + +Sends an OPTIONS request. + +Syntax: *object*.**SendOptions** ( *url* [ **,** *Body* ] ) **As cHttpClient** + +The same as **Fetch** with **ReqOptions**. + +### SendPost +{: .no_toc } + +Sends a POST request. + +Syntax: *object*.**SendPost** ( *url* [ **,** *Body* ] ) **As cHttpClient** + +The same as **Fetch** with **ReqPost**. + +```tb check_build +Dim Http As New cHttpClient + +Http.RequestDataForm.Item("user") = "ann" +Http.RequestDataForm.Item("language") = "en" +Http.SendPost "https://example.com/login" + +Debug.Print Http.ReturnText() +``` + +### SendPut +{: .no_toc } + +Sends a PUT request. + +Syntax: *object*.**SendPut** ( *url* [ **,** *Body* ] ) **As cHttpClient** + +The same as **Fetch** with **ReqPut**. + +### SetCookies +{: .no_toc } + +Sets the `Cookie` header. + +Syntax: *object*.**SetCookies** ( *Value* ) **As cHttpClient** + +*Value* +: *required* A **String**, the text of the header, such as `a=1; b=2`. Nothing happens when it is empty. + +Stores the text as the item `Cookie` of [**RequestHeaders**](#requestheaders), where it stays for every later request. It returns the object itself. WinHTTP adds the cookies it has received from the server to the header it sends, so a cookie can appear twice. + +### SetRequestContentType +{: .no_toc } + +Sets [**RequestContentType**](#requestcontenttype) from a number. + +Syntax: *object*.**SetRequestContentType** ( *ReqType* [ **,** *ContentType* ] ) **As String** + +*ReqType* +: *required* An **EnumRequestContentType** (declared in [**ToolsHttp**](ToolsHttp#enumrequestcontenttype)): **JsonString** (`application/json`), **FormEncode** (`application/x-www-form-urlencoded`), **FormMultiPart** (`multipart/form-data`), **TextPlain** (`text/plain`) or **TextHtml** (`text/html`). A value of 0 sets the empty string, and a value above 5 raises error 9. + +*ContentType* +: *optional* A **String**. Not used. + +Returns the new text. Read the warning at [**RequestContentType**](#requestcontenttype): the call after a first request does not change the header. + +### ShowPage +{: .no_toc } + +Opens an address in the program that Windows associates with it. + +Syntax: *object*.**ShowPage** ( *url* ) + +*url* +: *required* A **String**, the address. + +Calls **ShellExecute** with the verb `open` and the address, so a web address opens in the default browser. It sends no request itself. + +### UploadFile +{: .no_toc } + +Sends a file in a POST request of the type `multipart/form-data`. + +Syntax: *object*.**UploadFile** ( *url* **,** *filePath* [ **,** *fieldName* ] [ **,** *additionalFormData* ] ) **As cHttpClient** + +*url* +: *required* A **String**, the address. + +*filePath* +: *required* A **String**, the file to send. It is read whole into memory. If it does not exist, the call raises error 53. + +*fieldName* +: *optional* A **String**, the name of the form field that holds the file. The default is `file`. + +*additionalFormData* +: *optional* A **Scripting.Dictionary** of more form fields, as names and text values. + +The body has a part with the file and a part for each item of *additionalFormData*. The file is always sent with the type `application/octet-stream` and the file name of the path. The boundary is `----WebKitFormBoundary` followed by a random number. The call waits [**RequestTimeOut**](#requesttimeout) seconds for the answer and raises the status of a 400 or higher as an error, and error 900 for a timeout. It reads [**ResponseHeaders**](#responseheaders) and returns the object itself. + +The call sends neither the items of **RequestHeaders** nor the extra header of [**Fetch**](#fetch), and it follows redirects as **EnableRedirects** says. It leaves two things behind: **RequestContentType** keeps the multipart value, and **RequestHeaders** gets an item `Content-Length` with the size of the body. A later request therefore has the multipart content type, as the warning at [**RequestContentType**](#requestcontenttype) says. The `Content-Length` item does not reach the server with a later GET request. + +> [!NOTE] +> A call that fails with a status or a timeout raises the right number but a general text: *Application-defined or object-defined error* for a status, *Automation error* for error 900. The real text, such as `上传失败: 404#Not Found` (*the upload failed*), is in [**LastError**](#lasterror). + +```tb check_build +Dim Http As New cHttpClient +Dim Extra As New Scripting.Dictionary + +Extra.Add "owner", "ann" +Http.UploadFile "https://example.com/upload", "C:\data\report.pdf", "document", Extra +Debug.Print Http.ReturnText() +``` + +### UploadFileSimple +{: .no_toc } + +Sends a file and returns whether it worked. + +Syntax: *object*.**UploadFileSimple** ( *url* **,** *filePath* ) **As Boolean** + +Calls [**UploadFile**](#uploadfile) with the default field name, with error handling turned off, and returns **True** when it raised no error. After an error it sets [**LastError**](#lasterror) to the text of the error that was raised, which is the general text described at **UploadFile** for a status, and not the text of the failure. + +## See Also + +- [Consts](Consts) module -- the media types and header names +- [ToolsHttp](ToolsHttp) module -- the enumerations, and URL and form helpers +- [ToolsHttpMimeType](ToolsHttpMimeType) module -- the media type for a file name +- [cJson](cJson) class -- the JSON document of requests and answers +- [cToolsStream](cToolsStream) class -- writes the downloaded file +- [TBMAN](TBMAN) module -- the shared instance **TBMAN.HttpClient** +- [TBMANLIB](.) package -- overview diff --git a/docs/Reference/Contributed/TBMANLIB/cIni.md b/docs/Reference/Contributed/TBMANLIB/cIni.md new file mode 100644 index 000000000..58f3284d5 --- /dev/null +++ b/docs/Reference/Contributed/TBMANLIB/cIni.md @@ -0,0 +1,184 @@ +--- +title: cIni +parent: TBMANLIB Package +permalink: /tB/Packages/Contributed/TBMANLIB/cIni +has_toc: false +--- + +# cIni class +{: .no_toc } + +A **cIni** reads and writes INI files. It keeps the settings of an INI file in a two-level **Scripting.Dictionary**, [**Root**](#root): the sections are the keys of **Root**, and each item is a dictionary of the keys and values of that section. [**LoadFrom**](#loadfrom) fills **Root** from a file and [**SaveTo**](#saveto) writes **Root** to a file. The package keeps one ready-made instance, [**TBMAN.Ini**](TBMAN#ini). + +The error messages of the class name `iniTools`, the name the code had before it was a class of the package. + +```tb check_build check_run +Dim Cfg As New cIni +Dim Path As String +Path = Environ$("TEMP") & "\tbmanlib-docs-settings.ini" + +Cfg.Section("Window")("Width") = "800" +Cfg.Section("Window")("Height") = "600" +Debug.Print Cfg.SaveTo(Path) + +Dim Back As New cIni +Back.LoadFrom Path +Debug.Print Back.Root("Window")("Width") + 1 +Kill Path +' Output: +' True +' 801 +``` + +* TOC +{:toc} + +## The file format + +The files are opened as ANSI text, in the code page of the system, not as Unicode or UTF-8. The text that **SaveTo** writes, and that **LoadFrom** reads, looks like this: + +``` +[Window] +Width=800 + +[Colors] +Back=white +``` + +- A section starts with a name in square brackets on a line of its own. **SaveTo** writes a blank line after each section. +- Each key has a line of its own, in the form `key=value`. **LoadFrom** splits a line at its first `=`, removes the spaces around the key and around the value, and removes one `"` at the start and one at the end of the value, if they are there. +- A blank line and a line that starts with a semicolon are skipped. A line that starts with `#` is not a comment: it is read as a key if it holds an `=`, and skipped if it does not. +- A line that has no `=` and is not a section is skipped. +- **LoadFrom** puts the keys that come before the first section under a section whose name is the empty string. + +## Properties + +### LastError +{: .no_toc } + +A **String**. The text of the last error that [**LoadFrom**](#loadfrom) or [**SaveTo**](#saveto) caught, or an empty string after a call that worked. + +After an error, the text is a Chinese label that names `iniTools.SaveTo` or `iniTools.LoadFrom` (not `cIni`), then the error number, a `#` and the description. **LoadFrom** adds a colon and the path. When [**Root**](#root) has no entries, **SaveTo** sets the text to a Chinese sentence that means *there is no data to write*. + +### Root +{: .no_toc } + +Returns the data, a **Scripting.Dictionary**: section names are the keys, and each item is a **Scripting.Dictionary** of the keys and values of the section. It is the default member of the class, so `Cfg("Window")("Width")` is `Cfg.Root("Window")("Width")`. The dictionary is created with the object, and is the same object for as long as the **cIni** lives; a program cannot replace it, but can empty it with `Cfg.Root.RemoveAll`. + +After a file is read, a value is read as `Cfg.Root("Window")("Width")`. [**SaveTo**](#saveto) writes each value as a string. + +### Section +{: .no_toc } + +Returns the dictionary of one section, and creates the section when it is not there. + +Syntax: *object*.**Section** ( *Key* ) **As Scripting.Dictionary** + +*Key* +: *required* A **String**, the name of the section. Names are compared with letter case. + +The dictionary is the one that [**Root**](#root) holds for the name, so a value assigned to it is stored in **Root**: `Cfg.Section("Window")("Width") = "800"`. A name that is not in **Root** is added to it, with an empty dictionary, by the call itself, even when the program only reads. + +```tb check_build check_run +Dim Cfg As New cIni +Debug.Print Cfg.Root.Count +Cfg.Section("Window")("Width") = "800" +Debug.Print Cfg.Root.Count +Debug.Print Cfg.Section("Colors").Count +Debug.Print Cfg.Root.Count +' Output: +' 0 +' 1 +' 0 +' 2 +``` + +## Methods + +### LoadFrom +{: .no_toc } + +Reads an INI file into [**Root**](#root). + +Syntax: *object*.**LoadFrom** ( *Path* ) **As cIni** + +*Path* +: *required* A **String**, the file to read. The extension `.ini` is added to a name that does not end with it, without regard to letter case, so `settings` and `settings.INI` name the same file. The path is used as written: a name without a folder is looked for in the current folder of the program. + +Returns the object itself, so a call can be followed by a member: `Cfg.LoadFrom(Path).Section("Window")`. + +The call empties **Root** first, so afterwards it holds only what the file holds. If the file cannot be opened, **Root** has been emptied, the call raises no error, and [**LastError**](#lasterror) says why. + +```tb check_build check_run +Dim Cfg As New cIni +Dim Path As String +Path = Environ$("TEMP") & "\tbmanlib-docs-read.ini" + +Open Path For Output As #1 +Print #1, "top=1" +Print #1, "; a comment" +Print #1, "[Window]" +Print #1, "Title = ""Main""" +Print #1, "Width=800" +Close #1 + +Cfg.LoadFrom Path +Debug.Print Cfg.Root.Count +Debug.Print Cfg.Root("")("top") +Debug.Print Cfg("Window")("Title") +Kill Path + +Cfg.LoadFrom Path +Debug.Print Cfg.Root.Count +Debug.Print InStr(Cfg.LastError, "iniTools.LoadFrom") > 0 +' Output: +' 2 +' 1 +' Main +' 0 +' True +``` + +### SaveTo +{: .no_toc } + +Writes [**Root**](#root) to an INI file. + +Syntax: *object*.**SaveTo** ( [ *Path* ] ) **As Boolean** + +*Path* +: *optional* A **String**, the file to write. The extension `.ini` is added to a name that does not end with it. A call without a path uses the name `.ini`. A file that exists is replaced. The folders of the path are created if they are missing. + +Returns **True** when the file was written. It returns **False** when **Root** has no entries, or when an error occurred; [**LastError**](#lasterror) then says why. + +```tb check_build check_run +Dim Cfg As New cIni +Dim Path As String +Dim Text As String + +Path = Environ$("TEMP") & "\tbmanlib-docs-save" +Debug.Print Cfg.SaveTo(Path) + +Cfg.Section("Window")("Width") = "800" +Debug.Print Cfg.SaveTo(Path) +Open Path & ".ini" For Input As #1 +Do Until EOF(1) + Line Input #1, Text + Debug.Print "[" & Text & "]" +Loop +Close #1 +Kill Path & ".ini" +' Output: +' False +' True +' [[Window]] +' [Width=800] +' [] +``` + +## See Also + +- [TBMAN](TBMAN) module -- the shared instance **TBMAN.Ini** +- [ToolsFso](ToolsFso) module -- **AutoMakeDir**, which the method calls +- [cCsv](cCsv) class -- reads and writes comma-separated values +- [TBMANLIB](.) package -- overview diff --git a/docs/Reference/Contributed/TBMANLIB/cJson.md b/docs/Reference/Contributed/TBMANLIB/cJson.md index f2ec5f600..b59040c82 100644 --- a/docs/Reference/Contributed/TBMANLIB/cJson.md +++ b/docs/Reference/Contributed/TBMANLIB/cJson.md @@ -76,32 +76,29 @@ Debug.Print "[" & J.Item("missing") & "]" ### Items {: .no_toc } -Reads the item at a position in [**RootItems**](#rootitems). +Reads an item of [**RootItems**](#rootitems), or adds one to it. -Syntax: *object*.**Items** ( *Index* ) +Syntax: *object*.**Items** ( *Index* ) [ **=** *value* ] *Index* : *required* A **Long**, the position of the item, counted from 1. -Returns a **Variant**, an object for a nested object or array. If *Index* is greater than the number of items, the result is **Empty**. A position of 0 or less is not checked, and the **Collection** raises its own error. +Reading returns a **Variant**, an object for a nested object or array. If *Index* is greater than the number of items, the result is **Empty**. A position of 0 or less is not checked, and the **Collection** raises its own error. -The class declares a **Set** accessor for **Items**, which is meant to replace the item at a position with an object, and declares the accessor that would assign a value other than an object under the name `uuItems`, not `Items`. Neither works. `J.Items(1) = 5`, `Set J.Items(1) = New Collection` and `J.uuItems(1) = 5` all compile, and each raises the error *Unspecified error* (-2147467259) and leaves the array as it was. +Assigning a value, with or without **Set**, adds it as a new last item of **RootItems**, whatever *Index* is: the item at *Index* is not replaced, and an *Index* beyond the end does not leave a gap. A value that is an object, such as a **Scripting.Dictionary** or another **Collection**, is added as a nested object or array. ```tb check_build check_run Dim J As New cJson J.Decode "[1,2]" -On Error Resume Next J.Items(1) = 5 -Debug.Print Err.Number -Err.Clear +Debug.Print J.Encode() +J.Items(9) = "x" Set J.Items(1) = New Collection -Debug.Print Err.Number Debug.Print J.Encode() Debug.Print J.Items(2) ' Output: -' -2147467259 -' -2147467259 -' [1,2] +' [1,2,5] +' [1,2,5,"x",[]] ' 2 ``` @@ -278,10 +275,10 @@ Syntax: *object*.**LoadFrom** ( *Path* [ **,** *CharSet* ] ) **As cJson** *CharSet* : *optional* A **String**, the name of the character set of the file, `UTF-8` by default. It is passed to an ADODB.Stream, so it can be any name that the stream accepts. A byte order mark at the start of a UTF-8 file is not part of the text. -The file is read by [**ToolsStream.LoadFileAsText**](ToolsStream#loadfileastext) and the text goes to [**Decode**](#decode), so its rules and errors apply. Returns the object itself. +The file is read by [**ToolsStream.LoadFileAsText**](cToolsStream#loadfileastext) and the text goes to [**Decode**](#decode), so its rules and errors apply. Returns the object itself. -> [!IMPORTANT] -> A file that cannot be opened raises the error of the ADODB.Stream and leaves the shared stream of [**ToolsStream**](ToolsStream) open. Every later call to **LoadFrom** or **SaveTo** then fails until the stream is closed with `ToolsStream.Inst.Close`. See [**LoadFileAsText**](ToolsStream#loadfileastext). +> [!WARNING] +> **ToolsStream.LoadFileAsText** leaves the shared stream open, and raises error 3705, *Operation is not allowed when the object is open*, when it finds it open. A **LoadFrom** therefore fails after any earlier **LoadFrom** or [**SaveTo**](#saveto) of the program, and after a file that could not be opened. See [**cToolsStream**](cToolsStream). `ToolsStream.Inst.Close` after each call makes the next one work. ### NewItem {: .no_toc } @@ -349,7 +346,7 @@ Syntax: *object*.**SaveTo** ( *FileName* [ **,** *CharSet* ] [ **,** *Whitespace : *required* A **String**, the file to write. A name that does not contain `:\` is taken to be relative and gets the folder of the program (`App.Path`) and a backslash in front. Forward slashes become backslashes, and the folders of the path are created if they are missing. See [**AutoCompleteFullPath**](ToolsFso#autocompletefullpath). A path with a drive that is written with forward slashes, such as `C:/data/a.json`, has no `:\` and is treated as relative. *CharSet* -: *optional* A **String**, the name of the character set, `UTF-8` by default. The file is written by [**ToolsStream.SaveFileAsText**](ToolsStream#savefileastext), which replaces a file that exists. +: *optional* A **String**, the name of the character set, `UTF-8` by default. The file is written by [**ToolsStream.SaveFileAsText**](cToolsStream#savefileastext), which replaces a file that exists. *Whitespace* : *optional* A **Variant**; as for [**Encode**](#encode). @@ -359,6 +356,9 @@ Syntax: *object*.**SaveTo** ( *FileName* [ **,** *CharSet* ] [ **,** *Whitespace The text is made by [**Encode**](#encode) of the object's [**Root**](#root), so the rules of that method apply, among them that an empty document writes the text of an earlier call. The function returns the text that it wrote to the file. A file written with the character set `UTF-8` begins with the byte order mark EF BB BF. +> [!WARNING] +> **ToolsStream.SaveFileAsText** leaves the shared stream open and, when it finds it open, writes over the text that is still in it. After a first **SaveTo**, a second one with a shorter document, in the same or another file, writes the new text with the end of the old text after it, which is not valid JSON: saving `{"a":"long long"}` and then `{"b":1}` leaves `{"b":1}ong long"}` in the second file. No error is raised. See [**cToolsStream**](cToolsStream). `ToolsStream.Inst.Close` after each call avoids it. + ```tb check_build check_run Dim Path As String Path = Environ$("TEMP") & "\tbmanlib-docs\sample.json" @@ -367,8 +367,10 @@ Dim J As New cJson Dim K As New cJson J.Item("word") = "caf" & ChrW(233) Debug.Print J.SaveTo(Path) +ToolsStream.Inst.Close K.LoadFrom Path +ToolsStream.Inst.Close Debug.Print AscW(Right$(K.Item("word"), 1)) Kill Path @@ -391,7 +393,7 @@ If [**RootItem**](#rootitem) has entries, the result is an array of one element - [TBMAN](TBMAN) module -- the shared instance **TBMAN.Json** - [ToolsJsonVba](ToolsJsonVba) module -- the parser and writer behind **Decode** and **Encode** -- [ToolsStream](ToolsStream) module -- reads and writes the files for **LoadFrom** and **SaveTo** -- [ToolsStr](ToolsStr) module -- the `\uXXXX` conversion of **Encode** +- [cToolsStream](cToolsStream) class -- reads and writes the files for **LoadFrom** and **SaveTo** +- [cToolsStr](cToolsStr) class -- the `\uXXXX` conversion of **Encode** - [JsonConverter](../VBA_JSON/JsonConverter/) module -- the same parser and writer, in the VBA_JSON package - [TBMANLIB](.) package -- overview diff --git a/docs/Reference/Contributed/TBMANLIB/cLayer.md b/docs/Reference/Contributed/TBMANLIB/cLayer.md index 1f8c3d2fc..83f6557ca 100644 --- a/docs/Reference/Contributed/TBMANLIB/cLayer.md +++ b/docs/Reference/Contributed/TBMANLIB/cLayer.md @@ -8,7 +8,7 @@ has_toc: false # cLayer class {: .no_toc } -A **cLayer** is meant to hold the user-interface helpers of the package. In version 1.0.0.5 it has one member, [**Toast**](#toast), which shows a message box. The package keeps one ready-made instance, [**TBMAN.Layer**](TBMAN#layer). +A **cLayer** is meant to hold the user-interface helpers of the package. In version 1.0.0.29 it has one member, [**Toast**](#toast), which shows a message box. The package keeps one ready-made instance, [**TBMAN.Layer**](TBMAN#layer). ```tb check_build Dim Layer As New cLayer diff --git a/docs/Reference/Contributed/TBMANLIB/cSubClass.md b/docs/Reference/Contributed/TBMANLIB/cSubClass.md new file mode 100644 index 000000000..09b77bb3a --- /dev/null +++ b/docs/Reference/Contributed/TBMANLIB/cSubClass.md @@ -0,0 +1,94 @@ +--- +title: cSubClass +parent: TBMANLIB Package +permalink: /tB/Packages/Contributed/TBMANLIB/cSubClass +has_toc: false +--- + +# cSubClass class +{: .no_toc } + +A **cSubClass** subclasses a window: it replaces the window procedure of a window with one of the package's, so that a program sees every message sent to the window in the **WindowProc** event, and can stop a message from reaching the window. The window is given by its handle, so it can be a form, a control, or a window of another part of the same program. The window must belong to the program: Windows allows a window procedure to be replaced only from the process that owns the window. + +The class works in 32-bit programs only. It declares the window procedure and its arguments as **Long**, and the compiler refuses the package's call to **SetWindowLong** in a build for 64-bit programs; see [32-bit and 64-bit programs](.#32-bit-and-64-bit-programs). + +```tb check_build +Private WithEvents Watcher As cSubClass + +Private Sub Attach(ByVal hWnd As Long) + Set Watcher = New cSubClass + Watcher.Hook hWnd +End Sub + +Private Sub Watcher_WindowProc(Msg As Long, wParam As Long, lParam As Long, CancelNextCall As Boolean) + Const WM_SETTEXT As Long = &HC + + If Msg = WM_SETTEXT Then CancelNextCall = True +End Sub +``` + +* TOC +{:toc} + +## Fields + +### mHWnd +{: .no_toc } + +A **Long**, the handle of the hooked window, or 0 before [**Hook**](#hook) is called. A program reads it; it should not set it. + +## Methods + +### Callback +{: .no_toc } + +The routine that the package's window procedure calls for each message. A program does not call it. + +Syntax: *object*.**Callback** ( *Msg* **,** *wParam* **,** *lParam* ) **As Long** + +Raises [**WindowProc**](#windowproc) and returns 0 when the handler set *CancelNextCall*, and otherwise the address of the window's original window procedure, which the package then calls. An error in a handler is ignored. + +### Hook +{: .no_toc } + +Starts to subclass a window. + +Syntax: *object*.**Hook** ( *hWnd* ) + +*hWnd* +: *required* A **Long**, the handle of a window of the program. + +Stores the handle, saves the address of the window's procedure, installs the package's procedure, and adds the object to a collection of the internal module **mCallBackSubClass** under the handle. A call on an object that already hooks a window does nothing. Hooking a window that another **cSubClass** object has hooked raises error 457, *this key is already associated with an element of this collection*. In the IDE, the call writes a line to the Debug Console. + +The collection holds a reference to the object, so the object is not destroyed while the window is hooked, and the window keeps its replacement procedure until [**UnHook**](#unhook) is called. A program calls **UnHook** before the window is destroyed and before it ends. + +### UnHook +{: .no_toc } + +Restores the window's own procedure. + +Syntax: *object*.**UnHook** + +Puts back the procedure that **Hook** saved and removes the object from the collection. A second call, with no hook in place, raises error 5. + +## Events + +### WindowProc +{: .no_toc } + +Raised for each message that the hooked window receives. + +Syntax: **Event** *object*_**WindowProc** ( *Msg* **,** *wParam* **,** *lParam* **,** *CancelNextCall* ) + +*Msg*, *wParam*, *lParam* +: **Long** values passed by reference: the message number and its two arguments. + +*CancelNextCall* +: A **Boolean**, passed by reference, **False** on entry. When the handler sets it to **True**, the message does not reach the window's own procedure, and the window procedure returns 0. Otherwise the window handles the message after the event. + +A handler is written for a variable declared with **WithEvents**. The event is raised synchronously, in the thread that sends the message, before the window sees it. A handler cannot change the value that the window procedure returns, or the arguments that the window procedure receives (the arguments are passed by reference but the original procedure is called with the originals). + +## See Also + +- [cTimer](cTimer) class -- another class built on a Windows callback +- [TBMANLIB](.) package -- overview diff --git a/docs/Reference/Contributed/TBMANLIB/cTimer.md b/docs/Reference/Contributed/TBMANLIB/cTimer.md new file mode 100644 index 000000000..e80c87b5b --- /dev/null +++ b/docs/Reference/Contributed/TBMANLIB/cTimer.md @@ -0,0 +1,132 @@ +--- +title: cTimer +parent: TBMANLIB Package +permalink: /tB/Packages/Contributed/TBMANLIB/cTimer +has_toc: false +--- + +# cTimer class +{: .no_toc } + +A **cTimer** is a timer that raises its **Timer** event at a set interval. It is built on the Windows function **SetTimer**, with no window, and does not need a form. The code is by David Zimmer (2003), who allows its use in commercial and non-commercial programs. A **cTimer** can be used alone, as here, or as one of the numbered timers of a [**cTimers**](cTimers). + +```tb check_build +Private WithEvents Tick As cTimer +Private Count As Long + +Private Sub StartTicking() + Set Tick = New cTimer + Tick.Interval = 500 + Tick.Enabled = True +End Sub + +Private Sub Tick_Timer() + Count = Count + 1 + If Count = 3 Then Tick.Enabled = False +End Sub +``` + +The event is raised from the message loop of the program, so it is not raised while the program is busy in a long loop without **DoEvents**. + +* TOC +{:toc} + +## Properties + +### Enabled +{: .no_toc } + +A **Boolean**: whether the timer is running. It is **False** for a new object. + +Setting it to **True** starts the timer, or starts it again from zero when it is running. Setting it to **False** stops it. A start with [**Interval**](#interval) of 0 or less does nothing: **Enabled** stays **False**. + +```tb check_build check_run +Dim T As New cTimer + +Debug.Print T.Interval & " " & T.Enabled +T.Interval = 0 +T.Enabled = True +Debug.Print T.Enabled +T.Interval = 1000 +T.Enabled = True +Debug.Print T.Enabled +T.Enabled = False +Debug.Print T.Enabled +' Output: +' 2000 False +' False +' True +' False +``` + +### Interval +{: .no_toc } + +A **Long**, the time between two events in milliseconds. It is 2000 for a new object. It is an ordinary field: a new value does not reach a running timer until the timer is started again with [**Reset**](#reset), [**Enable**](#enable) or [**EnableTimer**](#enabletimer). + +### Tag +{: .no_toc } + +A **Variant** for the program's own use. The class does not read it. + +## Methods + +### Disable +{: .no_toc } + +Stops the timer. + +Syntax: *object*.**Disable** + +The same as setting [**Enabled**](#enabled) to **False**. + +### Enable +{: .no_toc } + +Starts the timer, with the current [**Interval**](#interval). A running timer is stopped and started again. + +Syntax: *object*.**Enable** + +### EnableTimer +{: .no_toc } + +Sets the interval and starts the timer. + +Syntax: *object*.**EnableTimer** ( *mInterval* ) + +*mInterval* +: *required* A **Long**, the new [**Interval**](#interval) in milliseconds. + +### Reset +{: .no_toc } + +Stops the timer and starts it again, so that the next event comes after one full interval. + +Syntax: *object*.**Reset** + +The timer runs after the call, also when it was not running before it. + +## Events + +### Timer +{: .no_toc } + +Raised each time the interval has passed. + +Syntax: **Event** *object*_**Timer** ( ) + +A handler is written for a variable declared with **WithEvents**. A timer that belongs to a [**cTimers**](cTimers) raises the **Timer** event of that object instead, with its number, and not its own. + +## The registry of timers + +The class keeps every running timer in a collection of the module **mTimer**, by the number that Windows gives the timer, and the callback that Windows calls, **mTimer.TimerProc**, finds the object there and raises its event. The module belongs to the package's internal code and has no page; a program does not call it. + +> [!NOTE] +> The collection holds a reference to each running timer. A **cTimer** that is enabled is therefore not destroyed when the program releases its last reference: it stays registered and goes on running until [**Enabled**](#enabled) is set to **False**. A timer that was never enabled is destroyed as usual, and the timer of a destroyed object is stopped. + +## See Also + +- [cTimers](cTimers) class -- a numbered group of these timers +- [cTimeuse](cTimeuse) class -- a stopwatch +- [TwinTimerLib](../TwinTimerLib/) package -- another timer class +- [TBMANLIB](.) package -- overview diff --git a/docs/Reference/Contributed/TBMANLIB/cTimers.md b/docs/Reference/Contributed/TBMANLIB/cTimers.md new file mode 100644 index 000000000..d4659445d --- /dev/null +++ b/docs/Reference/Contributed/TBMANLIB/cTimers.md @@ -0,0 +1,110 @@ +--- +title: cTimers +parent: TBMANLIB Package +permalink: /tB/Packages/Contributed/TBMANLIB/cTimers +has_toc: false +--- + +# cTimers class +{: .no_toc } + +A **cTimers** is a numbered group of [**cTimer**](cTimer) objects that share one event. Each timer has its own interval and is switched on and off on its own, but all of them raise the single **Timer** event of the group, with the number of the timer. The code is by David Zimmer (2003). + +```tb check_build check_run +Dim Group As New cTimers + +Debug.Print Group.Add() & " " & Group.Add() & " " & Group.Count +Group(2).Interval = 250 +Debug.Print Group(2).Interval & " " & Group(1).Interval +Group.BulkLoad 3 +Debug.Print Group.Count +' Output: +' 1 2 2 +' 250 2000 +' 6 +``` + +* TOC +{:toc} + +## Properties + +### Count +{: .no_toc } + +An **Integer**, the number of timers in the group. + +### Item +{: .no_toc } + +Returns one timer. + +Syntax: *object*.**Item** ( *Index* ) **As cTimer** + +*Index* +: *required* An **Integer**, from 1 to [**Count**](#count). + +It is the default member, so `Group(2)` is `Group.Item(2)`. A number above **Count** raises error -2147221504 (`vbObjectError`), with the text *Index does not Exist*, and 0 or a negative number raises error 9. + +## Methods + +### Add +{: .no_toc } + +Adds a timer to the group. + +Syntax: *object*.**Add** ( ) **As Integer** + +Returns the number of the new timer, counted from 1. The timer is off, with an [**Interval**](cTimer#interval) of 2000; start it with `Group(n).Enabled = True`. + +### BulkLoad +{: .no_toc } + +Adds several timers at once. + +Syntax: *object*.**BulkLoad** ( *num* ) **As Variant** + +*num* +: *required* An **Integer** from 1 to 199. + +Adds *num* + 1 timers, not *num*: a call with 3 adds 4. Any other value raises error -2147221504 with the text *No more than 200 times allowed per class* (the text means *timers*). The function returns nothing useful. + +## Events + +### Timer +{: .no_toc } + +Raised when the interval of one of the enabled timers has passed. + +Syntax: **Event** *object*_**Timer** ( *Index* ) + +*Index* +: An **Integer**, the number of the timer that is raising the event. + +A handler is written for a variable declared with **WithEvents**. The timers of the group do not raise their own **Timer** events. + +```tb check_build +Private WithEvents Group As cTimers + +Private Sub StartTimers() + Set Group = New cTimers + Group.BulkLoad 2 + Group(1).Interval = 500 + Group(1).Enabled = True + Group(2).Interval = 1500 + Group(2).Enabled = True +End Sub + +Private Sub Group_Timer(ByVal Index As Integer) + Debug.Print "timer " & Index +End Sub +``` + +> [!NOTE] +> A **cTimers** registers itself in a collection of the package's internal module **mTimer**, which keeps a reference to it. The group is therefore not destroyed when the program releases its last reference, and its enabled timers go on running. Set [**Enabled**](cTimer#enabled) of each timer to **False** first. + +## See Also + +- [cTimer](cTimer) class -- one timer +- [cTimeuse](cTimeuse) class -- a stopwatch +- [TBMANLIB](.) package -- overview diff --git a/docs/Reference/Contributed/TBMANLIB/ToolsStr.md b/docs/Reference/Contributed/TBMANLIB/cToolsStr.md similarity index 86% rename from docs/Reference/Contributed/TBMANLIB/ToolsStr.md rename to docs/Reference/Contributed/TBMANLIB/cToolsStr.md index 463a12431..9c9ee2197 100644 --- a/docs/Reference/Contributed/TBMANLIB/ToolsStr.md +++ b/docs/Reference/Contributed/TBMANLIB/cToolsStr.md @@ -1,14 +1,16 @@ --- -title: ToolsStr +title: cToolsStr parent: TBMANLIB Package -permalink: /tB/Packages/Contributed/TBMANLIB/ToolsStr +permalink: /tB/Packages/Contributed/TBMANLIB/cToolsStr has_toc: false --- -# ToolsStr module +# cToolsStr class {: .no_toc } -String functions: trimming and testing, searching and cutting out a part, the conversions to `%XX` and `\uXXXX` escapes, conversion between strings and bytes, and random text and identifiers. Twenty-six functions, in five groups. Most use nothing but the language; [**GetGUID**](#getguid) uses the Windows object `Scriptlet.TypeLib`. +String functions: trimming and testing, searching and cutting out a part, the conversions to `%XX` and `\uXXXX` escapes, the splitting of text into lines, conversion between strings and bytes, and random text and identifiers. Twenty-eight functions, in six groups. Most use nothing but the language; [**GetGUID**](#getguid) uses the Windows object `Scriptlet.TypeLib`. + +The class has no fields. The package keeps one instance of it, named **ToolsStr**, in the module [**Tools**](Tools), so a program writes `ToolsStr.TrimEx(...)` without a `New`, and the syntax lines below use that name. In the package's earlier versions **ToolsStr** was a module, and the calls are the same. A program that wants an object of its own writes `Dim S As New cToolsStr`. ```tb check_build check_run Debug.Print ToolsStr.TrimEx(vbTab & " a b " & vbCrLf) @@ -433,6 +435,62 @@ Syntax: **ToolsStr.UnicodeEncode** ( *inputString* [ **,** *PreFix* ] ) **As Str Characters with a code from 0 to 127 stay as they are, the control characters included. Every other character becomes *PreFix* and the four hexadecimal digits of its code, with letters in upper case. A character that is made of two UTF-16 units, such as an emoji, becomes two such groups. +## Lines + +### JoinLinesFromCollection +{: .no_toc } + +Joins the items of a collection into one text, one item to a line. + +Syntax: **ToolsStr.JoinLinesFromCollection** ( *Coll* ) **As String** + +*Coll* +: *required* A **Collection**, passed by reference and not changed. The items are taken in order from 1 and converted to strings. + +Returns the items with a carriage return and line feed (**vbCrLf**) between them, and none after the last. A collection with no items gives an empty string. + +```tb check_build check_run +Dim Lines As New Collection +Lines.Add "one" +Lines.Add "two" +Lines.Add 3 +Debug.Print Replace(ToolsStr.JoinLinesFromCollection(Lines), vbCrLf, "|") +Debug.Print "[" & ToolsStr.JoinLinesFromCollection(New Collection) & "]" +' Output: +' one|two|3 +' [] +``` + +### SplitLinesToCollection +{: .no_toc } + +Splits a text into lines. + +Syntax: **ToolsStr.SplitLinesToCollection** ( *Text* ) **As Collection** + +*Text* +: *required* A **String**. + +Returns a new **Collection** that holds one string for each line, numbered from 1. Lines end at a carriage return and line feed (**vbCrLf**) only. A line feed or a carriage return alone does not end a line: the lines of a text from another system stay in one item. An empty text gives an empty collection. A text that ends with **vbCrLf** gives an empty last item. + +> [!WARNING] +> The loop stops when its position is equal to the length of the text, which is the position of the last character. When the last line has exactly one character, the loop stops before it and the line is lost: `"a" & vbCrLf & "b"` gives one item, `a`, and the text `"a"` gives none. A last line of two or more characters is kept. + +```tb check_build check_run +Dim Lines As Collection +Set Lines = ToolsStr.SplitLinesToCollection("one" & vbCrLf & "two" & vbCrLf & "three") +Debug.Print Lines.Count & " " & Lines(1) & " " & Lines(3) + +Set Lines = ToolsStr.SplitLinesToCollection("a" & vbCrLf & "b") +Debug.Print Lines.Count +Set Lines = ToolsStr.SplitLinesToCollection("a" & vbCrLf & "bc" & vbCrLf) +Debug.Print Lines.Count & " [" & Lines(3) & "]" +' Output: +' 3 one three +' 1 +' 3 [] +``` + ## Bytes ### LenBytes @@ -579,6 +637,8 @@ Debug.Print Len(ToolsStr.GetRandStr(0)) ## See Also +- [Tools](Tools) module -- the shared instance **ToolsStr** +- [cToolsStream](cToolsStream) class -- reads and writes whole files - [ToolsArray](ToolsArray) module -- searching and cutting byte arrays - [ToolsUtf8](ToolsUtf8) module -- UTF-8 conversion of strings and bytes - [ToolsMath](ToolsMath) module -- the random numbers behind **GetRandStr** diff --git a/docs/Reference/Contributed/TBMANLIB/cToolsStream.md b/docs/Reference/Contributed/TBMANLIB/cToolsStream.md new file mode 100644 index 000000000..1ce79ac0c --- /dev/null +++ b/docs/Reference/Contributed/TBMANLIB/cToolsStream.md @@ -0,0 +1,275 @@ +--- +title: cToolsStream +parent: TBMANLIB Package +permalink: /tB/Packages/Contributed/TBMANLIB/cToolsStream +has_toc: false +--- + +# cToolsStream class +{: .no_toc } + +Reads and writes whole files as text in a chosen character set, or as an array of bytes, and can hold the text as a collection of lines. The class uses the ADODB.Stream object of the ADO 2.8 library, which is part of Windows. [**cJson**](cJson) uses it for its [**LoadFrom**](cJson#loadfrom) and [**SaveTo**](cJson#saveto), and [**cHttpClient**](cHttpClient) for its file transfers. + +The package keeps one instance of the class, named **ToolsStream**, in the module [**Tools**](Tools), so a program writes `ToolsStream.LoadFileAsText(...)` without a `New`, and the syntax lines below use that name. In the package's earlier versions **ToolsStream** was a module, and the calls are the same. A program that wants a stream of its own writes `Dim S As New cToolsStream`. + +All the functions of an object work through one stream object, [**Inst**](#inst). The text functions leave it open, which the next text call does not expect; see the warning below. + +```tb check_build check_run +Dim Path As String +Path = Environ$("TEMP") & "\tbmanlib-docs-stream.txt" + +Dim S As New cToolsStream +S.SaveFileAsText Path, "line 1" & vbCrLf & "line 2" +S.Inst.Close +Debug.Print S.LoadFileAsText(Path) = "line 1" & vbCrLf & "line 2" +Kill Path +' Output: +' True +``` + +> [!WARNING] +> In version 1.0.0.29, [**LoadFileAsText**](#loadfileastext) and [**SaveFileAsText**](#savefileastext) leave the stream open, and neither closes it first. That breaks the next call of either on the same object, and the shared **ToolsStream** is such an object for the whole run of the program: +> +> - A **LoadFileAsText** that finds the stream open raises error 3705, *Operation is not allowed when the object is open*, before it reads anything. It finds it open after any earlier text call, a failed one included. +> - A **SaveFileAsText** that finds the stream open writes its text over the text the stream still holds, from the start, and saves the whole stream. When the new text is shorter than the old, the end of the old text stays in the file: after saving `0123456789` and then `abc`, the file holds `abc3456789`. No error is raised. +> - [**LoadFileAsBinary**](#loadfileasbinary) and [**SaveFileAsBinary**](#savefileasbinary) close an open stream first and close it at the end, so a text call works after them. +> +> [**cJson.SaveTo**](cJson#saveto) and [**cJson.LoadFrom**](cJson#loadfrom), which use the shared stream, are affected: a second **SaveTo** with a shorter document writes a JSON text with the end of the earlier one after it, and a **LoadFrom** after any earlier **SaveTo** or **LoadFrom** fails. The calls work when the stream is closed after each text call with `ToolsStream.Inst.Close`, or when each call uses a new **cToolsStream**, whose stream is closed when the object is released. + +```tb check_build check_run +Dim Path As String +Path = Environ$("TEMP") & "\tbmanlib-docs-stream2.txt" + +Dim S As New cToolsStream +Dim Text As String +S.SaveFileAsText Path, "0123456789" +S.SaveFileAsText Path, "abc" +S.Inst.Close +Debug.Print S.LoadFileAsText(Path) + +On Error Resume Next +Text = S.LoadFileAsText(Path) +Debug.Print Err.Number & " " & Err.Description +On Error GoTo 0 +S.Inst.Close +Debug.Print S.LoadFileAsText(Path) +Kill Path +' Output: +' abc3456789 +' 3705 Operation is not allowed when the object is open. +' abc3456789 +``` + +* TOC +{:toc} + +## Members + +### Inst +{: .no_toc } + +The **ADODB.Stream** object that the functions of the object use. A field, declared `Public Inst As New ADODB.Stream`, created the first time it is used. + +A program can use it, for example to close a stream that a text call left open: `ToolsStream.Inst.Close`. The program needs no reference to the ADO library for that. When the object is released, its stream is closed if it is open. + +### LastError +{: .no_toc } + +A **String** field. [**LoadFileAsBinary**](#loadfileasbinary) and [**SaveFileAsBinary**](#savefileasbinary) set it to an empty string when they start, and to the text of the error when they fail. The functions for text neither set it nor clear it. + +### LineData +{: .no_toc } + +A **Collection** field, declared `Public LineData As New Collection`. In line mode ([**UseLine**](#useline)), [**LoadFileAsText**](#loadfileastext) replaces it with the lines of the file, numbered from 1, and [**ReadLine**](#readline) reads from it. It is empty until then. + +### LineNumber +{: .no_toc } + +A **Long** field, 0 at the start. The number of the line that [**ReadLine**](#readline) and [**WriteLine**](#writeline) use next. **LoadFileAsText** sets it to 1 in line mode, and **ReadLine** adds 1 after each line. A program can assign to it. + +### LoadFileAsBinary +{: .no_toc } + +Reads a whole file into a byte array. + +Syntax: **ToolsStream.LoadFileAsBinary** ( *Path* **,** *OutData* ) **As Boolean** + +*Path* +: *required* A **String**, the file to read. A name without a folder is looked for in the current folder of the program. + +*OutData* +: *required* A dynamic **Byte** array. It receives the bytes of the file. + +Returns **True** when the file was read, and **False** when an error occurred; the text of the error is then in [**LastError**](#lasterror). The function closes the stream if it is open, sets it to the binary type, reads the file and closes the stream. + +The text in **LastError** is the description of the error, then `#`, then the description a second time. The number of the error is not in it. + +```tb check_build check_run +Dim S As New cToolsStream +Dim Bytes() As Byte +Debug.Print S.LoadFileAsBinary(Environ$("TEMP") & "\tbmanlib-docs-missing.bin", Bytes) +Debug.Print S.LastError +' Output: +' False +' File could not be opened.#File could not be opened. +``` + +### LoadFileAsText +{: .no_toc } + +Reads a whole file as text and returns it. + +Syntax: **ToolsStream.LoadFileAsText** ( *FileName* [ **,** *CharSet* ] ) **As String** + +*FileName* +: *required* A **String**, the file to read. A name without a folder is looked for in the current folder of the program. + +*CharSet* +: *optional* A **String**, the name of the character set in which the file is stored, `UTF-8` by default. Any name that the ADODB.Stream accepts can be used. A byte order mark at the start of a UTF-8 file is not returned as a character. + +The function sets the type of the stream to text, opens it, sets its character set, loads the file and reads all the text from it. It leaves the stream open, with its position at the start. It handles no error: a failure, such as error 3002, *File could not be opened*, for a file that does not exist, is raised to the caller as the error of the stream, and leaves the stream open. + +In line mode, the text is also split into lines with [**SplitLinesToCollection**](cToolsStr#splitlinestocollection), which is stored in [**LineData**](#linedata), and [**LineNumber**](#linenumber) is set to 1. The function still returns the whole text. The rules of the split apply, among them that a last line of one character is lost. + +See the warning at the top of the page for the calls that follow. + +### ReadLine +{: .no_toc } + +Returns a line of [**LineData**](#linedata). + +Syntax: **ToolsStream.ReadLine** ( [ *Line* ] ) **As String** + +*Line* +: *optional* A **Long**. When it is greater than 0, it is stored in [**LineNumber**](#linenumber) first, and that line is read. + +Returns the item of **LineData** that **LineNumber** names, and adds 1 to **LineNumber**. A number that is not the number of an item raises error 9, *Subscript out of range*. When line mode is off, the function raises error 1 with a Chinese message that means *line reading and writing is not switched on*. + +```tb check_build check_run +Dim Path As String +Path = Environ$("TEMP") & "\tbmanlib-docs-lines.txt" + +Dim S As New cToolsStream +S.UseLineMode True +S.SaveFileAsText Path, "first" & vbCrLf & "second" & vbCrLf & "third" +S.Inst.Close +S.LoadFileAsText Path +S.Inst.Close + +Debug.Print S.ReadLine() & " " & S.ReadLine() & " " & S.LineNumber +Debug.Print S.ReadLine(1) +Kill Path +' Output: +' first second 3 +' first +``` + +### SaveFileAsBinary +{: .no_toc } + +Writes a byte array to a file. + +Syntax: **ToolsStream.SaveFileAsBinary** ( *Path* **,** *OutData* ) **As Boolean** + +*Path* +: *required* A **String**, the file to write. An existing file is replaced. The folder must exist. + +*OutData* +: *required* A **Byte** array whose bytes are written. The parameter is called *OutData*, as in [**LoadFileAsBinary**](#loadfileasbinary), though here it is the input. + +Returns **True** when the file was written, and **False** when an error occurred; [**LastError**](#lasterror) then holds the text of the error, in the form described for **LoadFileAsBinary**. The stream is closed first if it was open, set to the binary type, and closed again at the end. + +```tb check_build check_run +Dim Path As String +Dim Bytes() As Byte +Dim Back() As Byte +Dim S As New cToolsStream + +Path = Environ$("TEMP") & "\tbmanlib-docs-stream.bin" +Bytes = ToolsStr.ToBytes("abc") + +Debug.Print S.SaveFileAsBinary(Path, Bytes) +Debug.Print S.LoadFileAsBinary(Path, Back) +Debug.Print UBound(Back) +Kill Path +' Output: +' True +' True +' 2 +``` + +### SaveFileAsText +{: .no_toc } + +Writes a string to a file as text. + +Syntax: **ToolsStream.SaveFileAsText** ( *FileName* [ **,** *Data* ] [ **,** *CharSet* ] ) **As Boolean** + +*FileName* +: *required* A **String**, the file to write. An existing file is replaced. The folder must exist: when it does not, the call raises error 3004, *Write to file failed*. + +*Data* +: *optional* A **String**, the text to write. It is declared optional, and meant to default to the lines of [**LineData**](#linedata) in line mode, or to the text the stream already holds. Neither default works: a **String** that is left out is a null string, not a missing argument, so the call tries to write a null string and raises error 3001, *Arguments are of the wrong type, are out of acceptable range, or are in conflict with one another*. Pass the text, an empty string if need be. + +*CharSet* +: *optional* A **String**, the name of the character set, `UTF-8` by default. A character set of `UTF-8` writes the byte order mark EF BB BF at the start of the file. + +The function opens the stream if it is closed, sets its character set, writes the text into it and saves the file. It leaves the stream open. The function adds no line break to the text. See the warning at the top of the page for what happens when the stream is already open. + +> [!WARNING] +> The function always returns **False**. Its result is never set, even when the file was written. A program cannot use the result to see whether the call worked, and has to handle the error that the stream raises. + +```tb check_build check_run +Dim Path As String +Dim S As New cToolsStream +Path = Environ$("TEMP") & "\tbmanlib-docs-stream3.txt" +Debug.Print S.SaveFileAsText(Path, "hello") +Debug.Print Dir(Path) = "tbmanlib-docs-stream3.txt" +Kill Path +' Output: +' False +' True +``` + +### UseLine +{: .no_toc } + +A **Boolean** field, **False** at the start. **True** when line mode is on. Set it with [**UseLineMode**](#uselinemode). + +### UseLineMode +{: .no_toc } + +Switches line mode on or off, and returns the object. + +Syntax: *object*.**UseLineMode** ( *v* ) **As cToolsStream** + +*v* +: *required* A **Boolean**. **True** turns line mode on. + +The function stores *v* in [**UseLine**](#useline) and returns the object itself, so a call can be chained: `S.UseLineMode(True).LoadFileAsText(Path)`. + +### WriteLine +{: .no_toc } + +Meant to store a line in [**LineData**](#linedata). + +Syntax: **ToolsStream.WriteLine** ( *Text* [ **,** *Line* ] ) **As Boolean** + +*Text* +: *required* A **String**, the line. + +*Line* +: *optional* A **Long**. When it is greater than 0, it is stored in [**LineNumber**](#linenumber) first. + +> [!NOTE] +> The function raises error 13 every time line mode is on. It adds the line to the collection with `LineData.Add Text, LineNumber`, and a collection takes a **String** as the key of an item, not a **Long**. Items can be added to **LineData** directly with its own **Add**. When line mode is off, the function raises error 1 with a Chinese message that means *line reading and writing is not switched on*. + +## See Also + +- [Tools](Tools) module -- the shared instance **ToolsStream** +- [cJson](cJson) class -- uses **LoadFileAsText** and **SaveFileAsText** +- [cHttpClient](cHttpClient) class -- downloads and uploads files with the binary functions +- [cToolsStr](cToolsStr) class -- the split into lines and the conversion of bytes +- [ToolsUtf8](ToolsUtf8) module -- converts between UTF-8 bytes and strings, without a stream +- [ToolsFso](ToolsFso) module -- folders and path names +- [TBMANLIB](.) package -- overview diff --git a/docs/Reference/Contributed/TBMANLIB/index.md b/docs/Reference/Contributed/TBMANLIB/index.md index e34e15cd5..a2551f960 100644 --- a/docs/Reference/Contributed/TBMANLIB/index.md +++ b/docs/Reference/Contributed/TBMANLIB/index.md @@ -3,19 +3,24 @@ title: TBMANLIB Package parent: Experimental Packages has_toc: false exclude_from_docs: - - cIni - - cTimer - ToolsJsonUcs -indexed_from: 1.0.0.5 + - Tests + - mTimer + - mCallBackSubClass + - ForDialog + - ToolsDialog + - cSysTray + - ToolsJScript +indexed_from: 1.0.0.29 permalink: /tB/Packages/Contributed/TBMANLIB/ --- # TBMANLIB Package {: .no_toc } -**Author:** woeoio. **Version:** 1.0.0.5. **On GitHub:** [woeoio/tbman](https://github.com/woeoio/tbman), the repository of the TBMAN toolkit. Its first commit, of 7 December 2024, the day this version was published, holds the code of this package. The repository has been developed since, and its current code differs from version 1.0.0.5. +**Author:** woeoio. **Version:** 1.0.0.29. **On GitHub:** [woeoio/tbman](https://github.com/woeoio/tbman), the repository of the TBMAN toolkit. This section documents version 1.0.0.29, published on TWINSERV on 9 February 2026 under the MIT licence. The repository has been developed since, and its current code may differ from that version. -The **TBMANLIB** contributed package is the library of **TBMAN**, a toolkit for twinBASIC programmers that grew out of the author's VB6 library *vbman*. It is a collection of small helpers rather than a library with one theme: a JSON document class and parser, a log writer, a stopwatch, an INI file class, a message-box helper, and modules of functions for strings, arrays and bytes, UTF-8, dictionaries, files and folders, numbers and JavaScript objects. The package's own description on TWINSERV is the single word *TBMAN*. +The **TBMANLIB** contributed package is the library of **TBMAN**, a toolkit for twinBASIC programmers that grew out of the author's VB6 library *vbman*. It is a collection of small helpers rather than a library with one theme: a JSON document class and parser, an HTTP client, a log writer, a stopwatch, timers, an INI file class and a CSV file class, a message-box helper, a window subclasser, and modules of functions for strings, arrays and bytes, UTF-8, dictionaries, files and folders and numbers. The package's own description on TWINSERV is a tagline, *a rapid development toolkit for twinBASIC*. ```tb check_build check_run Dim J As New cJson @@ -41,80 +46,100 @@ Debug.Print J.Encode() - **JSON.** [**cJson**](cJson) holds a JSON document: it reads a text or a file, gives access to its objects and arrays, builds new ones, and writes them as text or to a file. The parser and writer are the code of VBA-JSON, in [**ToolsJsonVba**](ToolsJsonVba). - **Logs.** [**cLogs**](cLogs) writes log lines to files in a folder tree by day or month, and can send each line to a list box, to a debugging tool or to a web page, with four levels. -- **Time.** [**cTimeuse**](cTimeuse) is a stopwatch with a millisecond count. -- **Messages.** [**cLayer**](cLayer) shows a message box. -- **INI files.** [**cCsv**](cCsv), in spite of its name, reads and writes INI files. -- **Text and bytes.** [**ToolsStr**](ToolsStr) trims, cuts and searches strings, makes `%XX` and `\uXXXX` escapes, converts between strings and bytes, and makes random strings and GUIDs. [**ToolsArray**](ToolsArray) finds, slices and splits byte arrays. [**ToolsUtf8**](ToolsUtf8) converts between UTF-8 bytes and strings. [**ToolsMath**](ToolsMath) rounds up and picks random numbers. -- **Files and folders.** [**ToolsFso**](ToolsFso) completes paths and creates folders. [**ToolsStream**](ToolsStream) reads and writes whole files as text in a chosen character set or as bytes. -- **Dictionaries and JavaScript.** [**ToolsDic**](ToolsDic) reads and writes form text, merges and copies `Scripting.Dictionary` objects. [**ToolsJScript**](ToolsJScript) creates JavaScript arrays and objects with the Microsoft Script Control. -- **Ready-made instances.** The module [**TBMAN**](TBMAN) has one shared instance of each class, so a program can write `TBMAN.Json` or `TBMAN.Logs` without `New`. +- **HTTP.** [**cHttpClient**](cHttpClient) sends requests with WinHTTP and reads the answer as text, JSON, bytes or a file, and uploads and downloads files. [**ToolsHttp**](ToolsHttp) holds its enumerations and helpers for URLs and forms, [**ToolsHttpMimeType**](ToolsHttpMimeType) gives the media type of a file name, and [**Consts**](Consts) holds media-type constants. +- **Time.** [**cTimeuse**](cTimeuse) is a stopwatch with a millisecond count. [**cTimer**](cTimer) raises an event at an interval, and [**cTimers**](cTimers) is a numbered group of timers with one event. +- **Messages.** [**cLayer**](cLayer) shows a message box. [**cSubClass**](cSubClass) lets a program see the messages that a window receives. +- **INI and CSV files.** [**cIni**](cIni) reads and writes INI files into a two-level dictionary. [**cCsv**](cCsv) holds a table of text and reads and writes CSV files. +- **Text and bytes.** [**cToolsStr**](cToolsStr) trims, cuts and searches strings, makes `%XX` and `\uXXXX` escapes, splits text into lines, converts between strings and bytes, and makes random strings and GUIDs. [**ToolsArray**](ToolsArray) finds, slices and splits byte arrays. [**ToolsUtf8**](ToolsUtf8) converts between UTF-8 bytes and strings. [**ToolsMath**](ToolsMath) rounds up and picks random numbers. [**ToolsCN**](ToolsCN) is meant to convert Chinese characters to pinyin. +- **Files and folders.** [**ToolsFso**](ToolsFso) completes paths and creates folders. [**cToolsStream**](cToolsStream) reads and writes whole files as text in a chosen character set or as bytes. +- **Dictionaries.** [**ToolsDic**](ToolsDic) reads form text, merges and copies `Scripting.Dictionary` objects. +- **Ready-made instances.** The module [**TBMAN**](TBMAN) has one shared instance of each main class, so a program can write `TBMAN.Json` or `TBMAN.Logs` without `New`. The module [**Tools**](Tools) has the instances **ToolsStr** and **ToolsStream** of the two classes of that name. -The classes are created with `New`, as in `Dim J As New cJson`. The members of a module are called with the name of the module in front, as in `ToolsStr.TrimEx(...)`. The names of the package's own members keep the author's spelling, including the misspellings, such as **DebugPring**, **LogFileNameByUer** and **TowLevelDicAssign**; this documentation uses them as they are. +The classes are created with `New`, as in `Dim J As New cJson`. The members of a module are called with the name of the module in front, as in `ToolsArray.FindByteArray(...)`; the string and stream functions are called through the instances of the module [**Tools**](Tools), as in `ToolsStr.TrimEx(...)`. The names of the package's own members keep the author's spelling, including the misspellings, such as **DebugPring**, **LogFileNameByUer** and **TowLevelDicAssign**; this documentation uses them as they are. ## Components the package uses -The package is written on top of components that are not part of twinBASIC. Its references are the Microsoft Scripting Runtime, the Microsoft ActiveX Data Objects 2.8 library and the Microsoft Script Control 1.0, and some members create other objects by name. +The package is written on top of components that are not part of twinBASIC. Its references are the Microsoft Scripting Runtime, the Microsoft ActiveX Data Objects 2.8 library and the Microsoft WinHTTP Services 5.1 library, besides the VB and Common Controls compatibility packages of twinBASIC. Some members create other objects by name. | Component | Used by | |:----------|:--------| -| Scripting Runtime: `Scripting.Dictionary` | **cJson** (`RootItem`), **cCsv** (`Root`), **ToolsDic**, **ToolsJsonVba** | -| Scripting Runtime: `Scripting.FileSystemObject` | **cCsv**, **ToolsFso** | -| ActiveX Data Objects: `ADODB.Stream` | **ToolsStream**, so **cJson.LoadFrom** and **cJson.SaveTo** | -| Microsoft Script Control: `MSScriptControl.ScriptControl` | **ToolsJScript**, so **ToolsDic.ToWwwFormUrlencoded** | +| Scripting Runtime: `Scripting.Dictionary` | **cJson** (`RootItem`), **cIni** and **cCsv** (their data), **cHttpClient** (headers and form values), **ToolsDic**, **ToolsJsonVba** | +| Scripting Runtime: `Scripting.FileSystemObject` | **cCsv**, **cIni**, **ToolsFso** | +| ActiveX Data Objects: `ADODB.Stream` | **cToolsStream**, so **cJson.LoadFrom**, **cJson.SaveTo** and the file members of **cHttpClient** | +| WinHTTP: `WinHttp.WinHttpRequest.5.1` | **cHttpClient** | | `WinHttp.WinHttpRequest.5.1`, created by name | **cLogs.WebView** | -| `Scriptlet.TypeLib`, created by name | **ToolsStr.GetGUID** | +| `Scriptlet.TypeLib`, created by name | **cToolsStr.GetGUID** | | `imagehlp.dll` (`MakeSureDirectoryPathExists`) | **cLogs.Save**, **ToolsFso.AutoMakeDir** | | `kernel32.dll` (`GetTickCount`, `OutputDebugStringA`, `RtlMoveMemory`, `MultiByteToWideChar`, `WideCharToMultiByte`) | **cTimeuse**, **cLogs.DbgView**, **ToolsArray**, **ToolsUtf8** | +| `user32.dll` (`SetTimer`, `KillTimer`, `GetWindowLong`, `SetWindowLong`, `CallWindowProc`, `ShellExecute` from `shell32.dll`) | **cTimer**, **cSubClass**, **cHttpClient.ShowPage** | The references of a package are not visible to the project that uses it. A program that names a type of these libraries in its own code, such as `Dim D As New Scripting.Dictionary`, adds the library to its own references; in a project that references only TBMANLIB the compiler refuses the name with TB5079, *Unrecognized datatype symbol 'Scripting'*. A program can call members that return those objects without naming the type, for example `J.Item("user")("name")`, and can create an object by name with **CreateObject**. ### 32-bit and 64-bit programs -The Microsoft Script Control exists only as a 32-bit component, and the package's reference to it has a path for 32-bit programs and none for 64-bit programs. +The package is built for 32-bit programs. The window procedure that **cSubClass** installs is passed to **SetWindowLong** as a **Long**, which a 64-bit program cannot hold. > [!WARNING] -> In BETA 1005, a project that embeds TBMANLIB and is built for 64-bit programs (**Win64**) crashes the compiler, again after each restart, until the IDE falls back to Safe Mode, even when the program uses nothing of the package. The cause is a defect of twinBASIC, not of the package: any project that references the Microsoft Script Control, directly or through a package, crashes the compiler in a 64-bit build, whatever path the reference gives for 64-bit programs. Other 32-bit-only controls, such as the Common Dialog control, do not. The package can be used in 32-bit programs only. +> In BETA 1005, a project that embeds TBMANLIB and is built for 64-bit programs (**Win64**) does not compile. The compiler reports TB5001 in `cSubClass.twin`, *Validation of call to 'SetWindowLong' failed. Argument for 'dwNewLong': cannot coerce type 'Callback' to 'Long'*, even when the program uses nothing of the package. The package can be used in 32-bit programs only. -The Script Control is used by **ToolsJScript** and, through it, by **ToolsDic.ToWwwFormUrlencoded**. Those members do not work in 32-bit programs either; see [ToolsJScript](ToolsJScript). The other components are available to 32-bit programs. +The other components are available to 32-bit programs. -## Defects of version 1.0.0.5 +## Known problems in version 1.0.0.29 Part of the code is unfinished, and the source holds test code and notes to the author. Where a member does not do what its name or comment says, the page of the member says so. The ones that matter most are these: -- [**cCsv.SaveTo**](cCsv#saveto) does not write a new file, and [**cCsv.LoadFrom**](cCsv#loadfrom) raises an error before it reads anything. -- [**ToolsDic.ToWwwFormUrlencoded**](ToolsDic#towwwformurlencoded) adds two entries to the dictionary it is given and then fails, and [**ToolsJScript.NewArr**](ToolsJScript#newarr) and **NewObj** raise error 91. -- [**cJson.Items**](cJson#items) cannot be assigned to, and [**cJson.Decode**](cJson#decode) rejects an array that follows a line break. -- [**ToolsStream.SaveFileAsText**](ToolsStream#savefileastext) always returns **False**, and a failed text call leaves the shared stream open, so that the next calls fail. -- [**ToolsStr.GetLastChar**](ToolsStr#getlastchar) returns the first character, [**UniVbCrLf**](ToolsStr#univbcrlf) doubles a line feed, and [**IsEmptyEx**](ToolsStr#isemptyex) trims the variable it is given. +- [**cToolsStream**](cToolsStream), and with it [**cJson.LoadFrom**](cJson#loadfrom) and [**cJson.SaveTo**](cJson#saveto), leave the shared stream open after a text call, so that the next text call fails or writes over the old text. [**SaveFileAsText**](cToolsStream#savefileastext) always returns **False**, and [**WriteLine**](cToolsStream#writeline) always raises error 13. +- [**cCsv**](cCsv): the functions that add columns and widths return **False** always, the `value` property cannot be set, removing a column other than the last one corrupts the saved file, and a character that the system code page lacks leaves an empty file. +- [**cHttpClient**](cHttpClient) accepts every certificate, [**RequestDataQuery**](cHttpClient#requestdataquery) loses the name of its first item, header values are cut at their first colon, so [**FollowRedirect**](cHttpClient#followredirect) cannot work, and its content type header does not follow a later change of the content type. +- [**ToolsHttp.UrlDecodeUtf8**](ToolsHttp#urldecodeutf8) raises error 13 for any text with a `%`, [**UrlEncodUtf8**](ToolsHttp#urlencodutf8) writes the characters from hex 80 to hex FFF wrongly, and [**ToolsCN.GetPinYin**](ToolsCN#getpinyin) raises an error for any text with an English letter, a digit or a space in it. +- [**cJson.Decode**](cJson#decode) rejects an array that follows a line break. +- [**cToolsStr.GetLastChar**](cToolsStr#getlastchar) returns the first character, [**UniVbCrLf**](cToolsStr#univbcrlf) doubles a line feed, [**IsEmptyEx**](cToolsStr#isemptyex) trims the variable it is given, and [**SplitLinesToCollection**](cToolsStr#splitlinestocollection) loses a last line of one character. - [**ToolsUtf8.DecodeToByteArray**](ToolsUtf8#decodetobytearray) returns half of the bytes. - [**ToolsArray.SliceByteArray**](ToolsArray#slicebytearray) returns a wrong result when the start and the end are the same. - [**cLogs.Save**](cLogs#save) sends the other outputs at the level **Info**, and the levels **Debugger** and **Custom** raise an error. +- [**cTimer**](cTimer) and [**cTimers**](cTimers) objects that are enabled are not destroyed when the program releases them, and go on running. ## Classes -- [cCsv](cCsv) -- reads and writes INI files into a two-level dictionary. +- [cCsv](cCsv) -- holds a table of text and reads and writes CSV files. +- [cHttpClient](cHttpClient) -- sends HTTP and HTTPS requests with WinHTTP, and uploads and downloads files. +- [cIni](cIni) -- reads and writes INI files into a two-level dictionary. - [cJson](cJson) -- holds a JSON document: reads and writes text and files, and builds objects and arrays. - [cLayer](cLayer) -- shows a message box. - [cLogs](cLogs) -- writes log lines to files and to other outputs, with levels. +- [cSubClass](cSubClass) -- sees the messages that a window receives, and can stop them. +- [cTimer](cTimer) -- raises an event at an interval. +- [cTimers](cTimers) -- a numbered group of timers with one event. - [cTimeuse](cTimeuse) -- a stopwatch in milliseconds. +- [cToolsStr](cToolsStr) -- trims, searches, escapes and converts strings; random text and GUIDs. +- [cToolsStream](cToolsStream) -- reads and writes whole files as text or bytes. ## Modules +- [Consts](Consts) -- media types and header names of HTTP requests. - [TBMAN](TBMAN) -- shared instances of the classes, and a version text. +- [Tools](Tools) -- the shared instances of **cToolsStr** and **cToolsStream**. - [ToolsArray](ToolsArray) -- finds, slices and splits arrays of bytes and longs. +- [ToolsCN](ToolsCN) -- meant to convert Chinese characters to pinyin. - [ToolsDic](ToolsDic) -- form text, merging and copying of dictionaries. - [ToolsFso](ToolsFso) -- completes paths, tidies slashes and creates folders. -- [ToolsJScript](ToolsJScript) -- creates JavaScript arrays and objects with the Script Control. +- [ToolsHttp](ToolsHttp) -- request methods and content types, URL encoding, form and header text. +- [ToolsHttpMimeType](ToolsHttpMimeType) -- the media type of a file name. - [ToolsJsonVba](ToolsJsonVba) -- the VBA-JSON parser and writer, and the VBA-UTC date functions. - [ToolsMath](ToolsMath) -- rounds up, and picks random whole numbers. -- [ToolsStr](ToolsStr) -- trims, searches, escapes and converts strings; random text and GUIDs. -- [ToolsStream](ToolsStream) -- reads and writes whole files as text or bytes. - [ToolsUtf8](ToolsUtf8) -- converts between UTF-8 bytes and strings. ## Types without members {: #types-without-members } -Three types of the package have no members, and have no page: **cIni**, **cTimer** and **ToolsJsonUcs**. Each holds only the comment *add your member fields and procedures here*, or, for **ToolsJsonUcs**, *add your procedures here*. **cIni** is the type of [**TBMAN.Ini**](TBMAN#ini) and the declared result of [**cCsv.LoadFrom**](cCsv#loadfrom). +Some types of the package have no members, or none that a program is meant to use, and have no page: + +- **ToolsJsonUcs** holds only the comment *add your procedures here*. +- **cSysTray** holds only the comment *add your member fields and procedures here*. It is the type of [**TBMAN.SysTray**](TBMAN#systray). +- **ForDialog** is an empty form. **ToolsDialog** has one public member, a **String** named **LastError**, and the rest of its code is commented out. +- **ToolsJScript** has all its code commented out; it once created JavaScript objects with the Script Control, which the package no longer references. +- **Tests** is the author's demonstration code: **UploadAndDownloadDemo** and **TestHttpClient** upload and download files from a public test server on the internet, with paths on drive C: that a computer is unlikely to have. A program has no use for them. +- **mTimer** and **mCallBackSubClass** are the internal modules behind [**cTimer**](cTimer) and [**cSubClass**](cSubClass). Their public members, the callbacks that Windows calls and the collections that register the objects, are not meant to be called by a program. ## See Also diff --git a/docs/Reference/Contributed/TBMANLIB_Dialog/cDialog/FileName.md b/docs/Reference/Contributed/TBMANLIB_Dialog/cDialog/FileName.md index b17b752f6..3fa02e412 100644 --- a/docs/Reference/Contributed/TBMANLIB_Dialog/cDialog/FileName.md +++ b/docs/Reference/Contributed/TBMANLIB_Dialog/cDialog/FileName.md @@ -22,7 +22,7 @@ Syntax: *object*.**FileName** [ = *string* ] **Before a dialog.** [**ShowOpen**](ShowOpen) and [**ShowSave**](ShowSave) copy the text into the buffer that Windows uses for the `lpstrFile` member of `OPENFILENAME`, which initialises the File name box. The Save As dialog shows an empty box for an empty **FileName** and `x.txt` for `x.txt`. [**ShowSave**](ShowSave) pads the buffer with spaces, and the dialog does not show the padding. -**After a dialog.** A **ShowSave** or **ShowOpen** that the user confirms stores the path it returns in **FileName**. For a [**MultiSelect**](MultiSelect) selection of several files that is the folder, not a file name. A cancelled dialog leaves **FileName** as it was, and [**ShowBrowseForFolder**](ShowBrowseForFolder) neither reads nor changes it. +**After a dialog.** A **ShowSave** or **ShowOpen** that the user confirms stores the path it returns in **FileName**. For a [**MultiSelect**](MultiSelect) selection of several files, which **ShowOpen** returns as an array, **FileName** becomes an empty string. A cancelled dialog leaves **FileName** as it was, and [**ShowBrowseForFolder**](ShowBrowseForFolder) neither reads nor changes it. > [!NOTE] > Because a confirmed dialog stores the full path, the next **ShowOpen** or **ShowSave** passes that path as the initial file name. Set **FileName** to an empty string, or call [**Reset**](Reset), to start without it. diff --git a/docs/Reference/Contributed/TBMANLIB_Dialog/cDialog/MultiSelect.md b/docs/Reference/Contributed/TBMANLIB_Dialog/cDialog/MultiSelect.md index 4fee9d05b..4b001e25f 100644 --- a/docs/Reference/Contributed/TBMANLIB_Dialog/cDialog/MultiSelect.md +++ b/docs/Reference/Contributed/TBMANLIB_Dialog/cDialog/MultiSelect.md @@ -24,12 +24,9 @@ Only [**ShowOpen**](ShowOpen) uses the property. [**ShowSave**](ShowSave) and [* With **MultiSelect** **True**, **ShowOpen** gives Windows a buffer of 32,768 characters, in place of 260. The class also always sets `OFN_EXPLORER`, so Windows fills the buffer with the folder, then the name of each selected file, each ended by a null character, and one more null character at the end. When the user selects one file, the buffer holds its full path, with no separator. -> [!WARNING] -> When the user selects two or more files, **ShowOpen** returns a **String** that holds only the folder of the files. It does not return an array and it does not return the file names, and [**FileName**](FileName) becomes the folder too. No error is raised, so the result looks like a valid answer and the names of the files are lost. One selected file gives its full path, as a **String**. +When the user selects one file, **ShowOpen** returns its full path as a **String**. When the user selects two or more, it returns an array of **String** values, one full path for each file, in the order in which the names stand in the File name box. The array is 0-based. [**FileName**](FileName) is an empty string after a selection of several files. A program can tell the two results apart with **IsArray**, and [**SelectFiles**](SelectFiles) hides the difference by returning a **Collection** in both cases. -The source of **ShowOpen** has a branch that splits the names and returns an array of full paths. It never runs: the code cuts the buffer at its first null character, which is the end of the folder, before it looks for the separators between the names. A program can tell the two results apart, because the result for several files is the path of a folder. - -[**SelectAndOpenFile**](SelectAndOpenFile) passes the folder to [**OpenFile**](OpenFile), which opens it in File Explorer. +[**SelectAndOpenFile**](SelectAndOpenFile) opens each file of an array. ## Example @@ -41,28 +38,23 @@ dlg.MultiSelect = True Debug.Print dlg.MultiSelect ' True ``` -This example lets the user select several files, and reports when only the folder came back. +This example lets the user select several image files and prints each path. ```tb check_build Dim dlg As New cDialog -Dim result As String +Dim file As Variant dlg.MultiSelect = True dlg.SetImageFilters -result = dlg.ShowOpen() - -If Len(result) = 0 Then - Debug.Print "Cancelled" -ElseIf GetAttr(result) And vbDirectory Then - Debug.Print "Several files were selected; only the folder came back: " & result -Else - Debug.Print "One file: " & result -End If +For Each file In dlg.SelectFiles() + Debug.Print file +Next ``` ## See Also - [ShowOpen](ShowOpen) method -- shows the Open dialog and returns the result +- [SelectFiles](SelectFiles) method -- returns the selected files as a collection - [SelectAndOpenFile](SelectAndOpenFile) method -- opens the file that the dialog returns - [FileName](FileName) property -- holds the result - [cDialog](.) class -- overview diff --git a/docs/Reference/Contributed/TBMANLIB_Dialog/cDialog/NewDialogStyle.md b/docs/Reference/Contributed/TBMANLIB_Dialog/cDialog/NewDialogStyle.md index 8d94e8ccf..bd0bef880 100644 --- a/docs/Reference/Contributed/TBMANLIB_Dialog/cDialog/NewDialogStyle.md +++ b/docs/Reference/Contributed/TBMANLIB_Dialog/cDialog/NewDialogStyle.md @@ -28,8 +28,6 @@ With **False**, **ShowBrowseForFolder** sets only `BIF_RETURNONLYFSDIRS`, which The Windows documentation of `SHBrowseForFolder` requires COM to be initialised before the call, with apartment threading if the program calls `CoInitializeEx`, and says that the call fails when `BIF_NEWDIALOGSTYLE` is passed and COM is initialised as multithreaded. -In a 64-bit program **ShowBrowseForFolder** ends the program with an access violation whatever **NewDialogStyle** holds, as the warning on its page describes. - ## Example ```tb check_build check_run diff --git a/docs/Reference/Contributed/TBMANLIB_Dialog/cDialog/OpenFile.md b/docs/Reference/Contributed/TBMANLIB_Dialog/cDialog/OpenFile.md index dae4e4fca..db563ba25 100644 --- a/docs/Reference/Contributed/TBMANLIB_Dialog/cDialog/OpenFile.md +++ b/docs/Reference/Contributed/TBMANLIB_Dialog/cDialog/OpenFile.md @@ -10,18 +10,18 @@ has_toc: false Runs a shell verb, by default `open`, on a file or a program. -Syntax: *object*.**OpenFile** ( *strFilePath* [, *strOperation* ] ) **As Long** +Syntax: *object*.**OpenFile** ( *strFilePath* [, *strOperation* ] ) **As LongPtr** *object* : *required* An object expression that evaluates to a **cDialog**. *strFilePath* -: *required* A **String**, the file to act on or the program to start. Any value that [ShellExecute](https://learn.microsoft.com/en-us/windows/win32/api/shellapi/nf-shellapi-shellexecutea) accepts as its file argument is allowed: a document, a program, a folder or a web address. +: *required* A **String**, the file to act on or the program to start. Any value that [ShellExecute](https://learn.microsoft.com/en-us/windows/win32/api/shellapi/nf-shellapi-shellexecutew) accepts as its file argument is allowed: a document, a program, a folder or a web address. *strOperation* : *optional* A **String**, the verb to run, such as `open`, `edit`, `explore`, `print` or `properties`. The default is `open`. The verbs that exist depend on the type of the file. -Returns a **Long**, the value that `ShellExecute` returned. A value above 32 means that the call succeeded. A smaller value is an error code. +Returns a **LongPtr** (a **Long** in a 32-bit program, a **LongLong** in a 64-bit one), the value that `ShellExecute` returned. A value above 32 means that the call succeeded. A smaller value is an error code. | Value | Meaning | |---|---| @@ -37,7 +37,7 @@ The Windows documentation of `ShellExecute` lists the other values. ## Remarks -**OpenFile** calls `ShellExecuteA`, the ANSI form of `ShellExecute`, with no owner window, no parameters and no working folder, and shows the program's window normally (`SW_SHOWNORMAL`). The method raises no error: the return value is the only report. A path that does not exist returns 2 and shows no message. +**OpenFile** calls `ShellExecuteW`, the Unicode form of `ShellExecute`, so a path with any character is passed intact. It passes no owner window, no parameters and no working folder, and shows the program's window normally (`SW_SHOWNORMAL`). The method raises no error: the return value is the only report. A path that does not exist returns 2 and shows no message. For a program, the verb `open` starts the program. For a document, it starts the program that Windows associates with the document. For a folder, it opens the folder in File Explorer. @@ -50,7 +50,7 @@ This example opens a document and reports an error code. ```tb check_build Dim dlg As New cDialog -Dim result As Long +Dim result As LongPtr result = dlg.OpenFile("C:\Reports\summary.pdf") If result <= 32 Then diff --git a/docs/Reference/Contributed/TBMANLIB_Dialog/cDialog/PathCombine.md b/docs/Reference/Contributed/TBMANLIB_Dialog/cDialog/PathCombine.md new file mode 100644 index 000000000..3f0c93ae4 --- /dev/null +++ b/docs/Reference/Contributed/TBMANLIB_Dialog/cDialog/PathCombine.md @@ -0,0 +1,61 @@ +--- +title: PathCombine +parent: cDialog +permalink: /tB/Packages/Contributed/TBMANLIB_Dialog/cDialog/PathCombine +has_toc: false +--- + +# PathCombine +{: .no_toc } + +Joins two path parts with one backslash. + +Syntax: *object*.**PathCombine** ( *strPath1*, *strPath2* ) **As String** + +*object* +: *required* An object expression that evaluates to a **cDialog**. + +*strPath1* +: *required* A **String**, the folder or the first part of the path. + +*strPath2* +: *required* A **String**, the file name or the rest of the path. + +## Remarks + +**PathCombine** returns *strPath1* and *strPath2* with exactly one backslash between them, so it does not matter whether *strPath1* ends with a backslash or *strPath2* starts with one. The method follows these steps, in order: + +1. An empty *strPath1* returns *strPath2* as it is, and an empty *strPath2* returns *strPath1* as it is. Two empty strings give an empty string. +2. A *strPath2* that is already absolute is returned alone, and *strPath1* is dropped. It is absolute when it starts with a drive letter, a colon and a backslash (`D:\b`), or with two backslashes (`\\server\share`). +3. One trailing backslash of *strPath1* and one leading backslash of *strPath2* are removed. +4. The two parts are joined with a backslash. + +| *strPath1* | *strPath2* | Result | +|---|---|---| +| `C:\a` | `b` | `C:\a\b` | +| `C:\a\` | `\b` | `C:\a\b` | +| `C:` | `b` | `C:\b` | +| `C:\a` | `D:\b` | `D:\b` | +| `C:\a` | `\\srv\x` | `\\srv\x` | +| `C:\a` | `D:b` | `C:\a\D:b` | +| `C:\a` | `..\b` | `C:\a\..\b` | +| `C:\a\\` | `b` | `C:\a\\b` | + +The method works on text only. It does not check that a folder or a file exists, and it does not resolve `..` or `.`. A forward slash is an ordinary character: `a/` and `b` give `a/\b`, and `http://x/y` after `C:\a` gives `C:\a\http://x/y`. Only one backslash is removed from each side, so a doubled backslash stays. A *strPath2* with a drive letter but no backslash after the colon (`D:b`) is not absolute and is joined like any other name. [**cDialogEx**](../cDialogEx#pathcombine) has the same method. + +## Example + +```tb check_build check_run +Dim dlg As New cDialog +Debug.Print dlg.PathCombine("C:\Reports", "summary.txt") ' C:\Reports\summary.txt +Debug.Print dlg.PathCombine("C:\Reports\", "\summary.txt") ' C:\Reports\summary.txt +Debug.Print dlg.PathCombine("C:\Reports", "D:\other.txt") ' D:\other.txt +Debug.Print dlg.PathCombine("", "summary.txt") ' summary.txt +Debug.Print dlg.PathCombine("C:\Reports", "") ' C:\Reports +``` + +## See Also + +- [GetFilePath](GetFilePath) method -- the part before the last backslash +- [GetFileName](GetFileName) method -- the part after the last backslash +- [cDialog](.) class -- overview diff --git a/docs/Reference/Contributed/TBMANLIB_Dialog/cDialog/SelectAndOpenFile.md b/docs/Reference/Contributed/TBMANLIB_Dialog/cDialog/SelectAndOpenFile.md index 96c760444..559f09787 100644 --- a/docs/Reference/Contributed/TBMANLIB_Dialog/cDialog/SelectAndOpenFile.md +++ b/docs/Reference/Contributed/TBMANLIB_Dialog/cDialog/SelectAndOpenFile.md @@ -10,12 +10,12 @@ has_toc: false Shows the Open dialog and opens the file or files that the user chose. -Syntax: *object*.**SelectAndOpenFile** ( ) **As Long** +Syntax: *object*.**SelectAndOpenFile** ( ) **As LongPtr** *object* : *required* An object expression that evaluates to a **cDialog**. -Returns a **Long**: +Returns a **LongPtr** (a **Long** in a 32-bit program, a **LongLong** in a 64-bit one): - 0 when the user cancelled the dialog or the dialog failed. - Otherwise the value that [**OpenFile**](OpenFile) returned for the path. A value above 32 means that the file was opened. @@ -24,10 +24,7 @@ Returns a **Long**: **SelectAndOpenFile** calls [**ShowOpen**](ShowOpen) with the current properties of the object, so every option that applies to the Open dialog ([**Filter**](Filter), [**InitialDir**](InitialDir), [**MultiSelect**](MultiSelect) and the others) applies here. It then calls **OpenFile** with the verb `open` for the path. The file is run as **OpenFile** describes: a program starts, and a document opens in its associated program. -> [!WARNING] -> With [**MultiSelect**](MultiSelect) **True** and several files selected, **ShowOpen** returns only the folder of the files, so **SelectAndOpenFile** passes the folder to **OpenFile**, which opens the folder in File Explorer. None of the selected files is opened, and no error is raised. - -The source also has a branch for an array result, which opens each element and returns 1. **ShowOpen** never returns an array, so the branch never runs. +With [**MultiSelect**](MultiSelect) **True** and two or more files selected, **ShowOpen** returns an array, and **SelectAndOpenFile** calls **OpenFile** for each element in turn. It then returns 1, whatever the individual calls returned, so the return value does not report a file that failed to open. For a single file it returns what **OpenFile** returned. ## Example @@ -35,7 +32,7 @@ This example lets the user pick any file and opens it. ```tb check_build Dim dlg As New cDialog -Dim result As Long +Dim result As LongPtr dlg.DialogTitle = "Open a document" dlg.SetDocumentFilters diff --git a/docs/Reference/Contributed/TBMANLIB_Dialog/cDialog/SelectFiles.md b/docs/Reference/Contributed/TBMANLIB_Dialog/cDialog/SelectFiles.md new file mode 100644 index 000000000..81f3c176c --- /dev/null +++ b/docs/Reference/Contributed/TBMANLIB_Dialog/cDialog/SelectFiles.md @@ -0,0 +1,53 @@ +--- +title: SelectFiles +parent: cDialog +permalink: /tB/Packages/Contributed/TBMANLIB_Dialog/cDialog/SelectFiles +has_toc: false +--- + +# SelectFiles +{: .no_toc } + +Shows the Open dialog and returns the chosen files as a **Collection** of full paths. + +Syntax: *object*.**SelectFiles** ( ) **As Collection** + +*object* +: *required* An object expression that evaluates to a **cDialog**. + +Returns a **Collection** of **String** values, one full path for each chosen file, in the order in which [**ShowOpen**](ShowOpen) returns them. The collection is empty when the user cancels the dialog, and its **Count** is 1 when one file is chosen. It is never **Nothing**. + +## Remarks + +**SelectFiles** calls **ShowOpen** with the current properties of the object, so every option of the Open dialog applies, [**MultiSelect**](MultiSelect) first of all. With **MultiSelect** **False** the collection holds one path at most. With **MultiSelect** **True** and one file selected it holds one path, and with two or more files selected it holds one path for each. The method saves the program from testing whether **ShowOpen** returned a **String** or an array. An empty result and a failure of the dialog look the same, as they do for **ShowOpen**. + +[**FileName**](FileName) is set as **ShowOpen** sets it: to the path when one file was chosen, and to an empty string after several. + +## Example + +This example lets the user select several text files and prints their names. + +```tb check_build +Dim dlg As New cDialog +Dim file As Variant + +dlg.MultiSelect = True +dlg.SetCommonFilters + +Dim files As Collection +Set files = dlg.SelectFiles() +If files.Count = 0 Then + Debug.Print "Cancelled" +Else + For Each file In files + Debug.Print dlg.GetFileName(CStr(file)) + Next +End If +``` + +## See Also + +- [ShowOpen](ShowOpen) method -- shows the Open dialog and returns a string or an array +- [MultiSelect](MultiSelect) property -- lets the user select several files +- [SelectAndOpenFile](SelectAndOpenFile) method -- shows the dialog and opens the files +- [cDialog](.) class -- overview diff --git a/docs/Reference/Contributed/TBMANLIB_Dialog/cDialog/ShowBrowseForFolder.md b/docs/Reference/Contributed/TBMANLIB_Dialog/cDialog/ShowBrowseForFolder.md index 380be5bfc..af05b2699 100644 --- a/docs/Reference/Contributed/TBMANLIB_Dialog/cDialog/ShowBrowseForFolder.md +++ b/docs/Reference/Contributed/TBMANLIB_Dialog/cDialog/ShowBrowseForFolder.md @@ -17,12 +17,9 @@ Syntax: *object*.**ShowBrowseForFolder** ( ) **As String** Returns a **String**, the path of the chosen folder, or an empty string when the user cancelled. -> [!WARNING] -> **ShowBrowseForFolder** works in 32-bit programs only. In a 64-bit program it ends the program with an access violation (exception 0xC0000005 in KERNELBASE.dll) before any dialog appears, whatever [**NewDialogStyle**](NewDialogStyle) holds, and unsaved data is lost. The class declares the handles and pointers of `BROWSEINFO`, and the item list that `SHBrowseForFolder` returns, as **Long**, which is the wrong size in a 64-bit program. - ## Remarks -**ShowBrowseForFolder** calls `SHBrowseForFolder` with a `BROWSEINFO` structure, then turns the item that the user chose into a path with `SHGetPathFromIDListA` and frees the item with `CoTaskMemFree`. Two properties apply: +**ShowBrowseForFolder** calls `SHBrowseForFolderW` with a `BROWSEINFO` structure, then turns the item that the user chose into a path with `SHGetPathFromIDListW` and frees the item with `CoTaskMemFree`. Two properties apply: | Property | What **ShowBrowseForFolder** passes | |---|---| @@ -36,7 +33,7 @@ The dialog does not start in the folder that [**InitialDir**](InitialDir) holds. > [!NOTE] > When the user confirms a folder, **ShowBrowseForFolder** stores the path in [**InitialDir**](InitialDir). A [**ShowOpen**](ShowOpen) or [**ShowSave**](ShowSave) after it passes that folder to Windows as its starting folder. A path typed into the edit box of the new-style dialog and confirmed is returned and stored in the same way. A cancelled dialog returns an empty string and leaves **InitialDir** as it was. [**FileName**](FileName) is not used or changed. -The class reads the path with the ANSI function `SHGetPathFromIDListA`, into a buffer of 260 characters. An empty string results when the user cancels, and also when Windows cannot turn the chosen item into a path. The method raises no error. +The class uses the Unicode functions and reads the path into a buffer of 260 characters, so a folder name with characters that the ANSI code page lacks is returned intact. The method works in 32-bit and in 64-bit programs. An empty string results when the user cancels, and also when Windows cannot turn the chosen item into a path. The method raises no error. ## Example diff --git a/docs/Reference/Contributed/TBMANLIB_Dialog/cDialog/ShowOpen.md b/docs/Reference/Contributed/TBMANLIB_Dialog/cDialog/ShowOpen.md index 9328a4e9b..3cad8e9b8 100644 --- a/docs/Reference/Contributed/TBMANLIB_Dialog/cDialog/ShowOpen.md +++ b/docs/Reference/Contributed/TBMANLIB_Dialog/cDialog/ShowOpen.md @@ -15,7 +15,7 @@ Syntax: *object*.**ShowOpen** ( ) **As Variant** *object* : *required* An object expression that evaluates to a **cDialog**. -Returns a **Variant** that always holds a **String**. When the user confirms the dialog, it holds the full path of the chosen file. When the user cancels, it holds an empty string. With [**MultiSelect**](MultiSelect) **True** and several files selected, it holds only a folder, as [below](#several-files) describes. +Returns a **Variant**. When the user confirms the dialog, it holds the full path of the chosen file as a **String**. When the user cancels, it holds an empty string. With [**MultiSelect**](MultiSelect) **True** and two or more files selected, it holds a 0-based array of **String** values, one full path for each file, as [below](#several-files) describes. ## Remarks @@ -41,14 +41,11 @@ A flag is set when its property is **True**. The method always sets `OFN_LONGNAM An empty string means that the user cancelled, or that Windows reported a failure, such as a selected path that does not fit into the buffer, and no value tells which of the two happened. The buffer holds 260 characters, or 32,768 when [**MultiSelect**](MultiSelect) is **True** or [**FileName**](FileName) is not empty. Neither case raises an error. -A confirmed **ShowOpen** also stores the path it returns in [**FileName**](FileName), and leaves [**InitialDir**](InitialDir) as it was. A cancelled dialog changes neither. The next call therefore passes the previous path as the initial file name. +A confirmed **ShowOpen** that returns a **String** also stores it in [**FileName**](FileName), and leaves [**InitialDir**](InitialDir) as it was. A cancelled dialog changes neither. The next call therefore passes the previous path as the initial file name. ### Several files -> [!WARNING] -> With [**MultiSelect**](MultiSelect) **True** and two or more files selected, **ShowOpen** returns a **String** that holds only the folder of the files, never an array, and [**FileName**](FileName) becomes the folder. The names of the files are lost, and no error is raised. One selected file gives its full path. - -Windows returns the selected names in one buffer, and the class cuts that buffer at its first null character, which ends the folder, before it looks for the separators between the names. The code that splits the names and returns an array of full paths therefore never runs. See [**MultiSelect**](MultiSelect) for a way to detect the result. +With [**MultiSelect**](MultiSelect) **True**, Windows returns the folder and then the name of each selected file in one buffer. **ShowOpen** joins the folder to each name with a backslash and returns the full paths as an array of **String** values, in the order in which the names stand in the File name box, and sets [**FileName**](FileName) to an empty string. One selected file gives its full path as a **String**, as without **MultiSelect**. Test the result with **IsArray**, or call [**SelectFiles**](SelectFiles), which returns a **Collection** in both cases. ## Example @@ -76,6 +73,7 @@ End If - [ShowSave](ShowSave) method -- shows the Save As dialog - [ShowBrowseForFolder](ShowBrowseForFolder) method -- shows the Browse For Folder dialog - [SelectAndOpenFile](SelectAndOpenFile) method -- shows this dialog and opens the file +- [SelectFiles](SelectFiles) method -- shows this dialog and returns the files as a collection - [MultiSelect](MultiSelect) property -- lets the user select several files - [FileName](FileName) property -- holds the path afterwards - [cDialog](.) class -- overview diff --git a/docs/Reference/Contributed/TBMANLIB_Dialog/cDialog/index.md b/docs/Reference/Contributed/TBMANLIB_Dialog/cDialog/index.md index f9ca44696..d4ce2a091 100644 --- a/docs/Reference/Contributed/TBMANLIB_Dialog/cDialog/index.md +++ b/docs/Reference/Contributed/TBMANLIB_Dialog/cDialog/index.md @@ -10,7 +10,7 @@ has_toc: false Shows the Windows Open, Save As and Browse For Folder dialogs, builds the file-type list of the first two, takes file paths apart, and opens a file in the program that Windows associates with it. -Properties set the options of a dialog before it is shown. [**ShowOpen**](ShowOpen), [**ShowSave**](ShowSave) and [**ShowBrowseForFolder**](ShowBrowseForFolder) show the dialogs and return the path that the user chose. A cancelled dialog returns an empty string. **ShowOpen** and **ShowSave** also store the path in [**FileName**](FileName). +Properties set the options of a dialog before it is shown. **cDialog** works in 32-bit and in 64-bit programs. [**ShowOpen**](ShowOpen), [**ShowSave**](ShowSave) and [**ShowBrowseForFolder**](ShowBrowseForFolder) show the dialogs and return the path that the user chose. A cancelled dialog returns an empty string. **ShowOpen** and **ShowSave** also store the path in [**FileName**](FileName); several files chosen with [**MultiSelect**](MultiSelect) come back as an array, and **FileName** is then empty. ```tb check_build Dim dlg As New cDialog @@ -29,13 +29,7 @@ End If The options are the members and flags of the Win32 structures that the dialogs take: [OPENFILENAME](https://learn.microsoft.com/en-us/windows/win32/api/commdlg/ns-commdlg-openfilenamew) for **ShowOpen** (`GetOpenFileNameW`) and **ShowSave** (`GetSaveFileNameW`), and [BROWSEINFO](https://learn.microsoft.com/en-us/windows/win32/api/shlobj_core/ns-shlobj_core-browseinfow) for **ShowBrowseForFolder** (`SHBrowseForFolder`). Each property page names the flag it sets and the dialogs in which it takes effect, and the table [below](#which-dialog-uses-which-property) lists them all. > [!NOTE] -> The property names follow VB6's **CommonDialog** control, but **cDialog** is a class with a smaller set of members. It has no **Flags** property (each option is a **Boolean** property of its own), no **FilterIndex** (the first filter is always selected, and the filter that the user chose is not reported), no **CancelError** (a cancelled dialog returns an empty string), no **FileTitle** or **MaxFileSize**, and no owner window. **InitialDir** is the control's **InitDir**. **ShowOpen** returns the file name, as well as storing it in **FileName**, and there is no Color, Font or Print dialog. The [**CommonDialog**](../../VBComDlg/CommonDialog) class of the VBComDlg package has **Flags**, **FilterIndex**, **CancelError**, **FileTitle**, **MaxFileSize** and those dialogs. - -> [!WARNING] -> **ShowBrowseForFolder** works in 32-bit programs only. In a 64-bit program it ends the program with an access violation before any dialog appears, because the class declares the handles, pointers and item list of that dialog as **Long**. - -> [!WARNING] -> With [**MultiSelect**](MultiSelect) **True** and several files selected, **ShowOpen** returns only the folder of the files, as a **String**, and the names of the files are lost. No error is raised. +> The property names follow VB6's **CommonDialog** control, but **cDialog** is a class with a smaller set of members. It has no **Flags** property (each option is a **Boolean** property of its own), no **FilterIndex** (the first filter is always selected, and the filter that the user chose is not reported), no **CancelError** (a cancelled dialog returns an empty string), no **FileTitle** or **MaxFileSize**, and no owner window. **InitialDir** is the control's **InitDir**. **ShowOpen** returns the file name, as well as storing it in **FileName**, and there is no Color, Font or Print dialog in this class (the package has a class for each of them, listed on its [landing page](../)). The [**cDialogEx**](../cDialogEx) class of this package has a **FilterIndex** and an owner window. The [**CommonDialog**](../../VBComDlg/CommonDialog) class of the VBComDlg package has **Flags**, **FilterIndex**, **CancelError**, **FileTitle**, **MaxFileSize** and those dialogs. ## Defaults @@ -114,8 +108,10 @@ Some settings cannot be changed. - [GetFileNameWithoutExt](GetFileNameWithoutExt) -- returns the file name of a path without its extension - [GetFilePath](GetFilePath) -- returns the part of a path before its last backslash - [OpenFile](OpenFile) -- runs a shell verb, by default `open`, on a file or a program +- [PathCombine](PathCombine) -- joins two path parts with one backslash - [Reset](Reset) -- restores every property to its starting value - [SelectAndOpenFile](SelectAndOpenFile) -- shows the Open dialog and opens the chosen file or files +- [SelectFiles](SelectFiles) -- shows the Open dialog and returns the chosen files as a collection - [SetCodeFilters](SetCodeFilters) -- replaces the filter with one for source code files - [SetCommonFilters](SetCommonFilters) -- replaces the filter with Text Files and All Files - [SetDocumentFilters](SetDocumentFilters) -- replaces the filter with one for office documents @@ -127,4 +123,5 @@ Some settings cannot be changed. ## See Also - [TBMANLIB_Dialog](../) package -- overview +- [cDialogEx](../cDialogEx) class -- the same dialogs in the style of Windows Vista and later - [CommonDialog](../../VBComDlg/CommonDialog) class -- the fuller alternative, with flags, a filter index, cancel errors and the Color, Font and Print dialogs diff --git a/docs/Reference/Contributed/TBMANLIB_Dialog/cDialogColor.md b/docs/Reference/Contributed/TBMANLIB_Dialog/cDialogColor.md new file mode 100644 index 000000000..43e6bc4fa --- /dev/null +++ b/docs/Reference/Contributed/TBMANLIB_Dialog/cDialogColor.md @@ -0,0 +1,408 @@ +--- +title: cDialogColor +parent: TBMANLIB_Dialog Package +permalink: /tB/Packages/Contributed/TBMANLIB_Dialog/cDialogColor +has_toc: false +--- + +# cDialogColor class +{: .no_toc } + +A **cDialogColor** shows the Windows Color dialog, in which the user picks a color from the basic colors, from 16 custom colors or, in the expanded dialog, by typing its red, green and blue parts. It holds the chosen color as a **Long**, in the same layout as the **RGB** function and the `vb` color constants (`&H00BBGGRR`), and converts it to and from the HTML form `#RRGGBB`. + +**Properties:** [**AnyColor**](#anycolor), [**Blue**](#blue), [**Color**](#color), [**FullOpen**](#fullopen), [**Green**](#green), [**OwnerHwnd**](#ownerhwnd), [**PreventFullOpen**](#preventfullopen), [**Red**](#red), [**SolidColor**](#solidcolor). +**Methods:** [**FromHtmlColor**](#fromhtmlcolor), [**GetCustomColor**](#getcustomcolor), [**Reset**](#reset), [**SetCustomColor**](#setcustomcolor), [**SetRgb**](#setrgb), [**ShowDialog**](#showdialog), [**ToHtmlColor**](#tohtmlcolor), [**ToRgbString**](#torgbstring). + +Create an object with `Dim dlg As New cDialogColor`. The class is declared `[COMCreatable(False)]`, so **CreateObject** cannot make one, and **New** can. + +```tb check_build +Dim dlg As New cDialogColor + +dlg.Color = RGB(255, 128, 0) +dlg.FullOpen = True +If dlg.ShowDialog() Then + Debug.Print dlg.ToHtmlColor() + Debug.Print dlg.ToRgbString() +Else + Debug.Print "Cancelled" +End If +``` + +## How the dialog is shown + +[**ShowDialog**](#showdialog) calls `ChooseColorW` with the current [**Color**](#color) as the starting color (flag `CC_RGBINIT`, always set) and the 16 custom colors of the object. The properties [**FullOpen**](#fullopen), [**PreventFullOpen**](#preventfullopen), [**SolidColor**](#solidcolor) and [**AnyColor**](#anycolor) set the flags `CC_FULLOPEN`, `CC_PREVENTFULLOPEN`, `CC_SOLIDCOLOR` and `CC_ANYCOLOR` when they are **True**. The class sets no hook or template, no help button and no owner other than [**OwnerHwnd**](#ownerhwnd). The dialog is modal and has the title *Color*. + +When the user chooses **OK**, [**ShowDialog**](#showdialog) stores the color in [**Color**](#color) and returns **True**. When the user chooses **Cancel**, or closes the dialog, it returns **False** and leaves **Color** as it was. No error is raised in either case, and no value tells a cancel from a failure of the call. + +The color is a plain number. A system color constant such as `vbButtonFace` (`&H8000000F`) is not turned into the color it names: it is passed to the dialog as it is, and [**Red**](#red), [**Green**](#green) and [**Blue**](#blue) do not read a value with the high bit set in a meaningful way (see [**Color**](#color)). + +### Custom colors + +The object keeps 16 custom colors, numbered 0 to 15, for the 16 custom boxes of the dialog. They start as the colors of **QBColor**(0) to **QBColor**(15) and last as long as the object does, so a second dialog of the same object shows the custom colors that the first one left. Nothing is saved to the registry or to a file. + +> [!NOTE] +> The **Add to Custom Colors** button of the expanded dialog writes into the custom colors of the object at once. Choosing **Cancel** afterwards leaves [**Color**](#color) unchanged but keeps the added custom color. [**Reset**](#reset) restores the 16 starting colors. + +## Properties + +### AnyColor +{: .no_toc } + +Whether the dialog shows all the colors of the basic set. + +Syntax: *object*.**AnyColor** [ **=** *value* ] + +A **Boolean**, **True** by default. When it is **True**, [**ShowDialog**](#showdialog) sets `CC_ANYCOLOR`. Windows decides what the flag changes on the screen in use. + +```tb check_build +Dim dlg As New cDialogColor + +dlg.AnyColor = False +dlg.SolidColor = True +Debug.Print dlg.AnyColor +``` + +### Blue +{: .no_toc } + +The blue part of the color. + +Syntax: *object*.**Blue** [ **=** *value* ] + +A **Long** from 0 to 255. Setting a value below 0 stores 0, and above 255 stores 255, without an error. Reading returns the third byte of [**Color**](#color). Setting **Blue** changes the other two parts not at all. + +```tb check_build check_run +Dim dlg As New cDialogColor + +dlg.Color = RGB(10, 20, 30) +dlg.Blue = 300 +Debug.Print dlg.Blue +Debug.Print dlg.ToRgbString() +' Output: +' 255 +' RGB(10, 20, 255) +``` + +### Color +{: .no_toc } + +The selected color. + +Syntax: *object*.**Color** [ **=** *value* ] + +A **Long** in the layout `&H00BBGGRR`: red in the lowest byte, then green, then blue. Its default is `vbWhite` (`&HFFFFFF`). It is the color that the dialog shows when it opens, and the color that the user chose after a [**ShowDialog**](#showdialog) that returned **True**. + +Any **Long** is accepted and stored as it is. For a negative value, such as a system color constant or -1, [**Red**](#red) reads the lowest byte, but [**Green**](#green) and [**Blue**](#blue) divide the negative number, which rounds toward zero, before they take a byte, and so read other bytes than the ones of the color: -1 reads as red 255, green 0, blue 0, and `&H80000005` as red 5, green 1, blue 1. + +```tb check_build check_run +Dim dlg As New cDialogColor + +dlg.Color = vbWhite +Debug.Print Hex$(dlg.Color) +dlg.Color = RGB(10, 20, 30) +Debug.Print Hex$(dlg.Color) +Debug.Print dlg.Red +Debug.Print dlg.Green +Debug.Print dlg.Blue +' Output: +' FFFFFF +' 1E140A +' 10 +' 20 +' 30 +``` + +### FullOpen +{: .no_toc } + +Whether the dialog opens with the part for defining custom colors already shown. + +Syntax: *object*.**FullOpen** [ **=** *value* ] + +A **Boolean**, **False** by default. When it is **True**, [**ShowDialog**](#showdialog) sets `CC_FULLOPEN`: the dialog opens expanded, with the *Red*, *Green* and *Blue* boxes filled with the parts of [**Color**](#color) and enabled, and the *Define Custom Colors* button disabled. When it is **False**, those boxes are empty and disabled until the user chooses the button. + +With **FullOpen** and [**PreventFullOpen**](#preventfullopen) both **True**, the dialog opens expanded and the button stays disabled. + +```tb check_build +Dim dlg As New cDialogColor + +dlg.FullOpen = True +dlg.Color = RGB(10, 20, 30) +Debug.Print dlg.ShowDialog() +``` + +### Green +{: .no_toc } + +The green part of the color. + +Syntax: *object*.**Green** [ **=** *value* ] + +A **Long** from 0 to 255, clamped as [**Blue**](#blue) is. Reading returns the second byte of [**Color**](#color). + +```tb check_build check_run +Dim dlg As New cDialogColor + +dlg.Color = RGB(10, 20, 30) +dlg.Green = -4 +Debug.Print dlg.Green +Debug.Print dlg.ToHtmlColor() +' Output: +' 0 +' #0A001E +``` + +### OwnerHwnd +{: .no_toc } + +The window that owns the dialog. + +Syntax: *object*.**OwnerHwnd** [ **=** *value* ] + +A **LongPtr** window handle, 0 by default, which gives the dialog no owner. [**ShowDialog**](#showdialog) passes it as the owner window of the call. + +```tb check_build +Private Sub PickColor(ByVal ownerWindow As LongPtr) + Dim dlg As New cDialogColor + + dlg.OwnerHwnd = ownerWindow ' the handle of a window of the program + Debug.Print dlg.OwnerHwnd + Debug.Print dlg.ShowDialog() +End Sub +``` + +### PreventFullOpen +{: .no_toc } + +Whether the user is kept from defining custom colors. + +Syntax: *object*.**PreventFullOpen** [ **=** *value* ] + +A **Boolean**, **False** by default. When it is **True**, [**ShowDialog**](#showdialog) sets `CC_PREVENTFULLOPEN`, which disables the *Define Custom Colors* button. A dialog that is not expanded then stays small. With [**FullOpen**](#fullopen) also **True** it opens expanded, and the user can still type values. + +```tb check_build +Dim dlg As New cDialogColor + +dlg.PreventFullOpen = True +Debug.Print dlg.ShowDialog() +``` + +### Red +{: .no_toc } + +The red part of the color. + +Syntax: *object*.**Red** [ **=** *value* ] + +A **Long** from 0 to 255, clamped as [**Blue**](#blue) is. Reading returns the lowest byte of [**Color**](#color). + +```tb check_build check_run +Dim dlg As New cDialogColor + +dlg.Red = 200 +dlg.Green = 100 +dlg.Blue = 50 +Debug.Print Hex$(dlg.Color) +' Output: +' 3264C8 +``` + +### SolidColor +{: .no_toc } + +Whether the dialog shows only solid colors. + +Syntax: *object*.**SolidColor** [ **=** *value* ] + +A **Boolean**, **False** by default. When it is **True**, [**ShowDialog**](#showdialog) sets `CC_SOLIDCOLOR`. Windows decides what the flag changes on the screen in use. + +```tb check_build +Dim dlg As New cDialogColor + +dlg.SolidColor = True +Debug.Print dlg.SolidColor +``` + +## Methods + +### FromHtmlColor +{: .no_toc } + +Sets the color from text of the form `#RRGGBB`. + +Syntax: *object*.**FromHtmlColor** ( *htmlColor* ) + +*htmlColor* +: *required* A **String** of six hexadecimal digits, with or without a leading `#`. Spaces before and after are ignored, and the letters may be of either case. + +The method sets [**Red**](#red), [**Green**](#green) and [**Blue**](#blue) from the three pairs of digits. It raises no error for text it cannot read, and leaves the color as it was when the text, without its `#`, is not exactly 6 characters long: `#abc`, an empty string, a seventh digit and `&HFF0000` are all ignored. A pair that is not hexadecimal ends the method at that pair, with the parts before it already set: `#12345G` sets red to `&H12` and green to `&H34` and leaves blue as it was. + +```tb check_build check_run +Dim dlg As New cDialogColor + +dlg.FromHtmlColor "#00FF7F" +Debug.Print dlg.ToRgbString() +dlg.FromHtmlColor " ff8000 " +Debug.Print dlg.ToHtmlColor() +dlg.FromHtmlColor "#abc" ' ignored +Debug.Print dlg.ToHtmlColor() +' Output: +' RGB(0, 255, 127) +' #FF8000 +' #FF8000 +``` + +### GetCustomColor +{: .no_toc } + +Returns one of the 16 custom colors. + +Syntax: *object*.**GetCustomColor** ( *index* ) **As Long** + +*index* +: *required* A **Long** from 0 to 15. + +Returns the custom color as a **Long** in the layout of [**Color**](#color). An *index* outside 0 to 15 gives 0, which is black, without an error. + +```tb check_build check_run +Dim dlg As New cDialogColor + +Debug.Print Hex$(dlg.GetCustomColor(1)) +Debug.Print Hex$(dlg.GetCustomColor(15)) +Debug.Print dlg.GetCustomColor(16) +' Output: +' 800000 +' FFFFFF +' 0 +``` + +### Reset +{: .no_toc } + +Restores every property and the 16 custom colors to their starting values. + +Syntax: *object*.**Reset** + +[**Color**](#color) becomes white, [**FullOpen**](#fullopen), [**PreventFullOpen**](#preventfullopen) and [**SolidColor**](#solidcolor) **False**, [**AnyColor**](#anycolor) **True**, [**OwnerHwnd**](#ownerhwnd) 0, and the custom colors the colors of **QBColor**(0) to **QBColor**(15). + +```tb check_build check_run +Dim dlg As New cDialogColor + +dlg.Color = vbRed +dlg.SetCustomColor 0, vbRed +dlg.Reset +Debug.Print Hex$(dlg.Color) +Debug.Print Hex$(dlg.GetCustomColor(0)) +' Output: +' FFFFFF +' 0 +``` + +### SetCustomColor +{: .no_toc } + +Sets one of the 16 custom colors. + +Syntax: *object*.**SetCustomColor** ( *index*, *color* ) + +*index* +: *required* A **Long** from 0 to 15. Any other value is ignored, without an error. + +*color* +: *required* A **Long** in the layout of [**Color**](#color). + +The next [**ShowDialog**](#showdialog) shows the color in the custom box of that number. + +```tb check_build check_run +Dim dlg As New cDialogColor + +dlg.SetCustomColor 3, RGB(17, 34, 51) +dlg.SetCustomColor 99, vbRed ' ignored +Debug.Print Hex$(dlg.GetCustomColor(3)) +' Output: +' 332211 +``` + +### SetRgb +{: .no_toc } + +Sets the color from its three parts. + +Syntax: *object*.**SetRgb** ( *red*, *green*, *blue* ) + +*red*, *green*, *blue* +: *required* A **Long** for each part. A value outside 0 to 255 is clamped, as the properties [**Red**](#red), [**Green**](#green) and [**Blue**](#blue) clamp it. + +```tb check_build check_run +Dim dlg As New cDialogColor + +dlg.SetRgb 255, 128, 0 +Debug.Print dlg.ToHtmlColor() +dlg.SetRgb 300, -5, 7 +Debug.Print dlg.ToRgbString() +' Output: +' #FF8000 +' RGB(255, 0, 7) +``` + +### ShowDialog +{: .no_toc } + +Shows the Color dialog. + +Syntax: *object*.**ShowDialog** ( ) **As Boolean** + +Returns **True** when the user chose **OK**, and then [**Color**](#color) holds the chosen color. Returns **False** when the user cancelled, and **Color** is unchanged. See [How the dialog is shown](#how-the-dialog-is-shown). + +```tb check_build +Dim dlg As New cDialogColor + +dlg.Color = vbBlue +dlg.SetCustomColor 0, RGB(255, 128, 0) +If dlg.ShowDialog() Then + Debug.Print "Chosen: " & dlg.ToHtmlColor() +End If +``` + +### ToHtmlColor +{: .no_toc } + +Returns the color as text of the form `#RRGGBB`. + +Syntax: *object*.**ToHtmlColor** ( ) **As String** + +The result has seven characters: a `#` and two upper-case hexadecimal digits each for red, green and blue. + +```tb check_build check_run +Dim dlg As New cDialogColor + +dlg.Color = RGB(160, 176, 192) +Debug.Print dlg.ToHtmlColor() +' Output: +' #A0B0C0 +``` + +### ToRgbString +{: .no_toc } + +Returns the color as text of the form `RGB(r, g, b)`. + +Syntax: *object*.**ToRgbString** ( ) **As String** + +The three parts are decimal numbers, separated by a comma and a space. + +```tb check_build check_run +Dim dlg As New cDialogColor + +dlg.Color = RGB(160, 176, 192) +Debug.Print dlg.ToRgbString() +' Output: +' RGB(160, 176, 192) +``` + +## See Also + +- [cDialogFont](cDialogFont) class -- shows the Font dialog +- [cDialogPrint](cDialogPrint) class -- shows the Print dialog +- [cDialog](cDialog/) class -- shows the Open, Save As and Browse For Folder dialogs +- [TBMANLIB_Dialog](.) package -- overview diff --git a/docs/Reference/Contributed/TBMANLIB_Dialog/cDialogCountdownMsgBox.md b/docs/Reference/Contributed/TBMANLIB_Dialog/cDialogCountdownMsgBox.md new file mode 100644 index 000000000..574a4385a --- /dev/null +++ b/docs/Reference/Contributed/TBMANLIB_Dialog/cDialogCountdownMsgBox.md @@ -0,0 +1,595 @@ +--- +title: cDialogCountdownMsgBox +parent: TBMANLIB_Dialog Package +permalink: /tB/Packages/Contributed/TBMANLIB_Dialog/cDialogCountdownMsgBox +has_toc: false +--- + +# cDialogCountdownMsgBox class +{: .no_toc } + +A **cDialogCountdownMsgBox** shows a Windows message box that closes by itself after a number of seconds, and reports which button ended it. The box is the one that the **MessageBoxTimeoutW** function of `user32.dll` shows; the class has no window of its own. + +A **cDialogCountdownMsgBox** is made with **New**: `Dim box As New cDialogCountdownMsgBox`. It cannot be made with **CreateObject**. It is not a countdown that runs on screen: the box shows the number of seconds as text that never changes, and Windows closes it when the time is up. + +The properties [**Title**](#title), [**Message**](#message), [**CountdownSeconds**](#countdownseconds), [**Buttons**](#buttons), [**Icon**](#icon) and [**DefaultButton**](#defaultbutton) describe the box, [**ShowDialog**](#showdialog) shows it, and [**ShowInfo**](#showinfo), [**ShowWarning**](#showwarning), [**ShowError**](#showerror), [**ShowQuestion**](#showquestion), [**ShowConfirm**](#showconfirm) and [**ShowCustom**](#showcustom) set the first five and show it in one call. [**Result**](#result), [**IsTimedOut**](#istimedout) and [**GetResultDescription**](#getresultdescription) report how it ended. + +```tb check_build +Dim box As New cDialogCountdownMsgBox +Dim answer As DialogResult + +answer = box.ShowQuestion("Save the changes before closing?", "Close", 20) +Select Case answer + Case DialogResult.drYes: Debug.Print "Save" + Case DialogResult.drNo: Debug.Print "Do not save" + Case DialogResult.drTimeout: Debug.Print "No answer in 20 seconds" +End Select +``` + +> [!WARNING] +> In version 1.1.0.0 a box with only an OK button that closes because its time ran out reports [**drOK**](#dialogresult), not [**drTimeout**](#dialogresult), and [**IsTimedOut**](#istimedout) is **False**. This affects the default box and [**ShowInfo**](#showinfo), [**ShowWarning**](#showwarning) and [**ShowError**](#showerror). A box with any other set of buttons reports **drTimeout** when it times out. No error is raised, so a program cannot tell a press of OK from the end of the time with these members alone. + +```tb check_build +Dim box As New cDialogCountdownMsgBox +Dim started As Single + +started = Timer +box.ShowInfo "Backup finished.", "Backup", 5 + +' With OK only, the result cannot show a timeout: compare the time. +If Timer - started >= 5 Then + Debug.Print "Closed by the countdown" +Else + Debug.Print "OK was pressed" +End If +``` + +## Properties + +### AutoClose +{: .no_toc } + +Whether the box closes when the time is up. + +Syntax: *object*.**AutoClose** [ **=** *value* ] + +*value* +: A **Boolean**. **True** (the default) closes the box after [**CountdownSeconds**](#countdownseconds) seconds. **False** leaves it open until the user presses a button; it then shows no countdown text, whatever [**ShowProgress**](#showprogress) says. + +```tb check_build check_run +Dim box As New cDialogCountdownMsgBox + +Debug.Print box.AutoClose ' True +box.AutoClose = False +Debug.Print box.AutoClose ' False +``` + +### Buttons +{: .no_toc } + +The buttons of the box. + +Syntax: *object*.**Buttons** [ **=** *value* ] + +*value* +: A **Long**, usually a [**MessageBoxButtons**](#messageboxbuttons) member. The default is **MB_OK**. + +The value is combined with [**Icon**](#icon) and [**DefaultButton**](#defaultbutton) by **Or** and passed to Windows as the style of the box. [**ShowInfo**](#showinfo), [**ShowWarning**](#showwarning), [**ShowError**](#showerror), [**ShowQuestion**](#showquestion), [**ShowConfirm**](#showconfirm) and [**ShowCustom**](#showcustom) set it. + +The enumeration has the six sets of buttons that [**Result**](#result) can report. A button that [**DialogResult**](#dialogresult) has no member for, such as Try Again and Continue of the style 6 (Cancel, Try Again, Continue), is not mapped: [**ShowDialog**](#showdialog) then leaves [**Result**](#result) as the previous call left it, or **drTimeout** in a new object, and returns that. + +```tb check_build check_run +Dim box As New cDialogCountdownMsgBox + +Debug.Print box.Buttons = MB_OK ' True +box.Buttons = MB_YESNOCANCEL +Debug.Print box.Buttons ' 3 +``` + +### CountdownSeconds +{: .no_toc } + +The time after which the box closes, in seconds. + +Syntax: *object*.**CountdownSeconds** [ **=** *value* ] + +*value* +: A **Long**. The default is 10. + +Setting it also sets [**RemainingSeconds**](#remainingseconds). The shortened forms [**ShowInfo**](#showinfo) and the others take the time as an argument, store it here, and do not touch [**RemainingSeconds**](#remainingseconds). + +The countdown text that the box shows (see [**ShowProgress**](#showprogress)) names this value in whole seconds, also for 1 and 0: `in 1 seconds...`. A value of 0 or less does not close the box: it stays open until a button is pressed, and its text says `in 0 seconds...`. + +> [!NOTE] +> The class multiplies the value by 1000 to get milliseconds. A value above 2,147,483 (about 24.8 days) overflows, and [**ShowDialog**](#showdialog) handles the error 6 itself: it shows a plain message box with the message and an OK button, with no icon, countdown text or other buttons, and returns **drOK**. The call raises nothing, but **Err.Number** is 6 afterwards. + +```tb check_build check_run +Dim box As New cDialogCountdownMsgBox + +Debug.Print box.CountdownSeconds ' 10 +box.CountdownSeconds = 45 +Debug.Print box.CountdownSeconds ' 45 +Debug.Print box.RemainingSeconds ' 45 +``` + +### DefaultButton +{: .no_toc } + +The button that Enter presses. + +Syntax: *object*.**DefaultButton** [ **=** *value* ] + +*value* +: A **Long**, usually a [**MessageBoxDefaultButton**](#messageboxdefaultbutton) member. The default is **MB_DEFBUTTON1**, the first button. + +With **MB_YESNO** and **MB_DEFBUTTON2**, Enter ends the box with **drNo**. The [**Show\***](#showdialog) shortened forms do not change this property. + +```tb check_build check_run +Dim box As New cDialogCountdownMsgBox + +Debug.Print box.DefaultButton = MB_DEFBUTTON1 ' True +box.Buttons = MB_YESNO +box.DefaultButton = MB_DEFBUTTON2 +Debug.Print box.DefaultButton ' 256 +``` + +### Icon +{: .no_toc } + +The icon of the box. + +Syntax: *object*.**Icon** [ **=** *value* ] + +*value* +: A **Long**, usually a [**MessageBoxIcons**](#messageboxicons) member. The default is **MB_ICONINFORMATION**. + +The [**Show\***](#showdialog) shortened forms set it; each form names its icon. The enumeration has no member for a box without an icon. + +```tb check_build check_run +Dim box As New cDialogCountdownMsgBox + +Debug.Print box.Icon = MB_ICONINFORMATION ' True +box.Icon = MB_ICONSTOP +Debug.Print box.Icon ' 16 +``` + +### Message +{: .no_toc } + +The text of the box. + +Syntax: *object*.**Message** [ **=** *value* ] + +*value* +: A **String**. The default is an empty string. + +The box shows this text; when the countdown text is on (see [**ShowProgress**](#showprogress)), it follows after a blank line. + +```tb check_build check_run +Dim box As New cDialogCountdownMsgBox + +Debug.Print Len(box.Message) ' 0 +box.Message = "Do you want to continue?" +Debug.Print box.Message ' Do you want to continue? +``` + +### OwnerHwnd +{: .no_toc } + +The window that owns the box. + +Syntax: *object*.**OwnerHwnd** [ **=** *value* ] + +*value* +: A **LongPtr**, the handle of a window. The default is 0, no owner. + +With an owner, the box is shown over it and the owner cannot be used while the box is open. [**Reset**](#reset) sets it back to 0. + +```tb check_build +Sub AskFromForm(ByVal parentWindow As LongPtr) + Dim box As New cDialogCountdownMsgBox + + box.OwnerHwnd = parentWindow + Debug.Print box.ShowConfirm("Discard the draft?", "Draft", 15) +End Sub +``` + +### RemainingSeconds +{: .no_toc } + +The seconds left, read-only. + +Syntax: *object*.**RemainingSeconds** **As Long** + +Returns the value that [**CountdownSeconds**](#countdownseconds) was last set to by the property. It does not count down, neither while the box is open nor afterwards, so it is of no use for showing the time left. + +```tb check_build check_run +Dim box As New cDialogCountdownMsgBox + +box.CountdownSeconds = 30 +Debug.Print box.RemainingSeconds ' 30 +``` + +### Result +{: .no_toc } + +How the last box ended, read-only. + +Syntax: *object*.**Result** **As** [**DialogResult**](#dialogresult) + +The button that ended the last call of [**ShowDialog**](#showdialog), or **drTimeout**. A new object, and one after [**Reset**](#reset), holds **drTimeout** before any box has been shown. + +```tb check_build check_run +Dim box As New cDialogCountdownMsgBox + +Debug.Print box.Result = DialogResult.drTimeout ' True +Debug.Print box.Result ' 32000 +``` + +### ShowProgress +{: .no_toc } + +Whether the box tells the number of seconds. + +Syntax: *object*.**ShowProgress** [ **=** *value* ] + +*value* +: A **Boolean**. The default is **True**. + +When this and [**AutoClose**](#autoclose) are **True**, the box adds a blank line and `This message will close automatically in N seconds...` to [**Message**](#message), where N is [**CountdownSeconds**](#countdownseconds). The text is fixed: it does not count down and the box has no progress bar. When **False**, only the message is shown; the box still closes when the time is up. + +```tb check_build check_run +Dim box As New cDialogCountdownMsgBox + +Debug.Print box.ShowProgress ' True +box.ShowProgress = False +Debug.Print box.ShowProgress ' False +``` + +### Title +{: .no_toc } + +The title of the box. + +Syntax: *object*.**Title** [ **=** *value* ] + +*value* +: A **String**. The default is `Message`. + +```tb check_build check_run +Dim box As New cDialogCountdownMsgBox + +Debug.Print box.Title ' Message +box.Title = "Backup" +Debug.Print box.Title ' Backup +``` + +## Methods + +### GetResultDescription +{: .no_toc } + +Names the result in words. + +Syntax: *object*.**GetResultDescription** ( ) **As String** + +Returns `OK`, `Cancel`, `Abort`, `Retry`, `Ignore`, `Yes`, `No` or `Timeout (Auto-close)` for the current [**Result**](#result). In a new object it returns `Timeout (Auto-close)`. + +```tb check_build check_run +Dim box As New cDialogCountdownMsgBox + +Debug.Print box.GetResultDescription() ' Timeout (Auto-close) +``` + +### IsTimedOut +{: .no_toc } + +Whether the last box was closed by the countdown. + +Syntax: *object*.**IsTimedOut** ( ) **As Boolean** + +Returns **True** when [**Result**](#result) is **drTimeout**. That is also the case in a new object before any box has been shown. A box with only an OK button that times out gives **False**, as the warning [above](#cdialogcountdownmsgbox-class) describes. + +```tb check_build check_run +Dim box As New cDialogCountdownMsgBox + +Debug.Print box.IsTimedOut() ' True +``` + +### Reset +{: .no_toc } + +Restores every property to its starting value. + +Syntax: *object*.**Reset** + +Sets [**Title**](#title) to `Message`, [**Message**](#message) to an empty string, [**CountdownSeconds**](#countdownseconds) and [**RemainingSeconds**](#remainingseconds) to 10, [**Buttons**](#buttons) to **MB_OK**, [**Icon**](#icon) to **MB_ICONINFORMATION**, [**DefaultButton**](#defaultbutton) to **MB_DEFBUTTON1**, [**ShowProgress**](#showprogress) and [**AutoClose**](#autoclose) to **True**, [**OwnerHwnd**](#ownerhwnd) to 0 and [**Result**](#result) to **drTimeout**. + +```tb check_build check_run +Dim box As New cDialogCountdownMsgBox + +box.Title = "Backup" +box.CountdownSeconds = 3 +box.AutoClose = False +box.Reset +Debug.Print box.Title ' Message +Debug.Print box.CountdownSeconds ' 10 +Debug.Print box.AutoClose ' True +``` + +### ShowConfirm +{: .no_toc } + +Shows a box with Yes, No and Cancel. + +Syntax: *object*.**ShowConfirm** ( *message* [ , *title* [ , *seconds* ] ] ) **As** [**DialogResult**](#dialogresult) + +*message* +: *required* The text of the box. + +*title* +: *optional* The title. The default is `Confirm`. + +*seconds* +: *optional* The time before the box closes. The default is 30. + +Sets [**Message**](#message), [**Title**](#title), [**CountdownSeconds**](#countdownseconds), [**Buttons**](#buttons) (**MB_YESNOCANCEL**) and [**Icon**](#icon) (**MB_ICONQUESTION**), then calls [**ShowDialog**](#showdialog). The other properties keep their values. The result is **drYes**, **drNo**, **drCancel** or **drTimeout**; Esc and the close button of the box give **drCancel**. + +```tb check_build +Dim box As New cDialogCountdownMsgBox + +Select Case box.ShowConfirm("Overwrite the file?", "Save") + Case DialogResult.drYes: Debug.Print "Overwrite" + Case DialogResult.drNo: Debug.Print "Choose another name" + Case DialogResult.drCancel: Debug.Print "Cancelled" + Case DialogResult.drTimeout: Debug.Print "No answer" +End Select +``` + +### ShowCustom +{: .no_toc } + +Shows a box with the buttons and the icon that are passed. + +Syntax: *object*.**ShowCustom** ( *message* , *title* , *buttons* , *icon* [ , *seconds* ] ) **As** [**DialogResult**](#dialogresult) + +*message* +: *required* The text of the box. + +*title* +: *required* The title. + +*buttons* +: *required* A **Long**, a [**MessageBoxButtons**](#messageboxbuttons) member. + +*icon* +: *required* A **Long**, a [**MessageBoxIcons**](#messageboxicons) member. + +*seconds* +: *optional* The time before the box closes. The default is 10. + +Sets the five properties that correspond to the arguments, then calls [**ShowDialog**](#showdialog). The arguments are numbers, not captions: the class does not offer button captions of its own. + +```tb check_build +Dim box As New cDialogCountdownMsgBox +Dim answer As DialogResult + +answer = box.ShowCustom("Try the upload again?", "Upload", MB_RETRYCANCEL, MB_ICONEXCLAMATION, 20) +Debug.Print box.GetResultDescription() +``` + +### ShowDialog +{: .no_toc } + +Shows the box that the properties describe. + +Syntax: *object*.**ShowDialog** ( ) **As** [**DialogResult**](#dialogresult) + +Shows the box and waits until a button is pressed, the box is closed, or the time is up. The call combines [**Buttons**](#buttons), [**Icon**](#icon) and [**DefaultButton**](#defaultbutton) into the style, takes the time from [**CountdownSeconds**](#countdownseconds) when [**AutoClose**](#autoclose) is **True** (otherwise there is no limit), adds the countdown text when [**ShowProgress**](#showprogress) is **True**, and stores the answer in [**Result**](#result), which it also returns. + +The box is modal. Enter presses [**DefaultButton**](#defaultbutton). Esc and the close button end a box that has only OK (the result is **drOK**) or has a Cancel button (**drCancel**); a box with Yes and No only ignores both. + +See [**Buttons**](#buttons) for a button that is not mapped and [**CountdownSeconds**](#countdownseconds) for a time that is too long, and the warning at the top of the page for the timeout of a box with OK only. + +```tb check_build +Dim box As New cDialogCountdownMsgBox + +box.Title = "Session" +box.Message = "Your session is about to end. Stay signed in?" +box.Buttons = MB_YESNO +box.Icon = MB_ICONQUESTION +box.DefaultButton = MB_DEFBUTTON1 +box.CountdownSeconds = 30 + +If box.ShowDialog() = DialogResult.drYes Then + Debug.Print "Stay signed in" +Else + Debug.Print "Sign out" +End If +``` + +### ShowError +{: .no_toc } + +Shows an error box with an OK button. + +Syntax: *object*.**ShowError** ( *message* [ , *title* [ , *seconds* ] ] ) **As** [**DialogResult**](#dialogresult) + +*message* +: *required* The text of the box. + +*title* +: *optional* The title. The default is `Error`. + +*seconds* +: *optional* The time before the box closes. The default is 20. + +Sets [**Buttons**](#buttons) to **MB_OK** and [**Icon**](#icon) to **MB_ICONSTOP** and the other three properties as [**ShowConfirm**](#showconfirm) does, then calls [**ShowDialog**](#showdialog). The result is **drOK** whether OK was pressed or the time ran out. + +```tb check_build +Dim box As New cDialogCountdownMsgBox + +box.ShowError "The file could not be saved.", "Save" +``` + +### ShowInfo +{: .no_toc } + +Shows an information box with an OK button. + +Syntax: *object*.**ShowInfo** ( *message* [ , *title* [ , *seconds* ] ] ) **As** [**DialogResult**](#dialogresult) + +*message* +: *required* The text of the box. + +*title* +: *optional* The title. The default is `Information`. + +*seconds* +: *optional* The time before the box closes. The default is 10. + +Sets [**Buttons**](#buttons) to **MB_OK** and [**Icon**](#icon) to **MB_ICONINFORMATION** and the other three properties as [**ShowConfirm**](#showconfirm) does, then calls [**ShowDialog**](#showdialog). The result is **drOK** whether OK was pressed or the time ran out. + +```tb check_build +Dim box As New cDialogCountdownMsgBox + +box.ShowInfo "The report was exported.", "Export", 8 +``` + +### ShowQuestion +{: .no_toc } + +Shows a box with Yes and No. + +Syntax: *object*.**ShowQuestion** ( *message* [ , *title* [ , *seconds* ] ] ) **As** [**DialogResult**](#dialogresult) + +*message* +: *required* The text of the box. + +*title* +: *optional* The title. The default is `Question`. + +*seconds* +: *optional* The time before the box closes. The default is 30. + +Sets [**Buttons**](#buttons) to **MB_YESNO** and [**Icon**](#icon) to **MB_ICONQUESTION** and the other three properties as [**ShowConfirm**](#showconfirm) does, then calls [**ShowDialog**](#showdialog). The result is **drYes**, **drNo** or **drTimeout**. Esc and the close button of the box do nothing. + +```tb check_build +Dim box As New cDialogCountdownMsgBox + +If box.ShowQuestion("Restart now?", "Update", 30) = DialogResult.drYes Then + Debug.Print "Restarting" +ElseIf box.IsTimedOut() Then + Debug.Print "No answer; restart later" +End If +``` + +### ShowWarning +{: .no_toc } + +Shows a warning box with an OK button. + +Syntax: *object*.**ShowWarning** ( *message* [ , *title* [ , *seconds* ] ] ) **As** [**DialogResult**](#dialogresult) + +*message* +: *required* The text of the box. + +*title* +: *optional* The title. The default is `Warning`. + +*seconds* +: *optional* The time before the box closes. The default is 15. + +Sets [**Buttons**](#buttons) to **MB_OK** and [**Icon**](#icon) to **MB_ICONEXCLAMATION** and the other three properties as [**ShowConfirm**](#showconfirm) does, then calls [**ShowDialog**](#showdialog). The result is **drOK** whether OK was pressed or the time ran out. + +```tb check_build +Dim box As New cDialogCountdownMsgBox + +box.ShowWarning "The disk is almost full.", "Disk" +``` + +## Enumerations + +The four enumerations are declared in the class and are used by their member names, such as `MB_YESNO` and `DialogResult.drYes`. + +### DialogResult +{: .no_toc } + +The result of a box. + +| Member | Value | Meaning | +|---|---|---| +| **drOK** | 1 | OK. Also the result of a box with only OK that timed out | +| **drCancel** | 2 | Cancel, Esc or the close button | +| **drAbort** | 3 | Abort | +| **drRetry** | 4 | Retry | +| **drIgnore** | 5 | Ignore | +| **drYes** | 6 | Yes | +| **drNo** | 7 | No | +| **drTimeout** | 32000 | The time ran out | + +### MessageBoxButtons +{: .no_toc } + +The sets of buttons for [**Buttons**](#buttons). + +| Member | Value | Buttons | +|---|---|---| +| **MB_OK** | 0 | OK | +| **MB_OKCANCEL** | 1 | OK, Cancel | +| **MB_ABORTRETRYIGNORE** | 2 | Abort, Retry, Ignore | +| **MB_YESNOCANCEL** | 3 | Yes, No, Cancel | +| **MB_YESNO** | 4 | Yes, No | +| **MB_RETRYCANCEL** | 5 | Retry, Cancel | + +### MessageBoxIcons +{: .no_toc } + +The icons for [**Icon**](#icon). + +| Member | Value | Icon | +|---|---|---| +| **MB_ICONSTOP** | 16 | A red circle with a cross | +| **MB_ICONQUESTION** | 32 | A question mark | +| **MB_ICONEXCLAMATION** | 48 | An exclamation mark | +| **MB_ICONINFORMATION** | 64 | The letter i | + +### MessageBoxDefaultButton +{: .no_toc } + +The default buttons for [**DefaultButton**](#defaultbutton). + +| Member | Value | Default button | +|---|---|---| +| **MB_DEFBUTTON1** | 0 | The first button | +| **MB_DEFBUTTON2** | 256 | The second button | +| **MB_DEFBUTTON3** | 512 | The third button | + +## Example + +This example asks a question that has a safe answer when nobody replies, and treats a timeout as the answer No. + +```tb check_build +Dim box As New cDialogCountdownMsgBox +Dim answer As DialogResult + +box.Title = "Unsaved changes" +box.Message = "Close the document without saving?" +box.Buttons = MB_YESNO +box.Icon = MB_ICONEXCLAMATION +box.DefaultButton = MB_DEFBUTTON2 +box.CountdownSeconds = 30 + +answer = box.ShowDialog() +If answer = DialogResult.drYes Then + Debug.Print "Closing without saving" +Else + Debug.Print "Keeping the document: " & box.GetResultDescription() +End If +``` + +## See Also + +- [cDialogInputBox](cDialogInputBox) class -- asks for one line of text +- [cDialogProgress](cDialogProgress) class -- tracks the progress of a task +- [TBMANLIB_Dialog](.) package -- overview diff --git a/docs/Reference/Contributed/TBMANLIB_Dialog/cDialogDateTime.md b/docs/Reference/Contributed/TBMANLIB_Dialog/cDialogDateTime.md new file mode 100644 index 000000000..1981fa3e2 --- /dev/null +++ b/docs/Reference/Contributed/TBMANLIB_Dialog/cDialogDateTime.md @@ -0,0 +1,797 @@ +--- +title: cDialogDateTime +parent: TBMANLIB_Dialog Package +permalink: /tB/Packages/Contributed/TBMANLIB_Dialog/cDialogDateTime +has_toc: false +--- + +# cDialogDateTime class +{: .no_toc } + +A **cDialogDateTime** holds a date and a time, asks the user for new values in a box with one line of text, and formats and calculates with them. The box is the **InputBox** of the run-time library: the class has no calendar or clock control, and the user types the date and the time. + +A **cDialogDateTime** is made with **New**: `Dim picker As New cDialogDateTime`. It cannot be made with **CreateObject**. A new object holds the date and time of the computer's clock. + +[**ShowDialog**](#showdialog) opens the box and reads what the user typed, [**ShowDatePicker**](#showdatepicker) and [**ShowTimePicker**](#showtimepicker) ask for a date alone or a time alone. The parts are read and written through [**Year**](#year), [**Month**](#month), [**Day**](#day), [**Hour**](#hour), [**Minute**](#minute) and [**Second**](#second), or as one value through [**DateValue**](#datevalue), [**TimeValue**](#timevalue) and [**DateTimeValue**](#datetimevalue). [**FormatDate**](#formatdate), [**FormatTime**](#formattime) and [**FormatDateTime**](#formatdatetime) return text, and [**AddDays**](#adddays), [**AddHours**](#addhours), [**SetNow**](#setnow) and [**SetToday**](#settoday) change the value. + +```tb check_build +Dim picker As New cDialogDateTime + +picker.Title = "Appointment" +picker.Prompt = "When should the meeting start?" +picker.DateTimeValue = DateSerial(2026, 3, 15) + TimeSerial(9, 0, 0) + +If picker.ShowDialog() Then + Debug.Print picker.FormatDateTime() +Else + Debug.Print "Cancelled" +End If +``` + +## What the box shows and accepts + +The box has the title [**Title**](#title) and the text [**Prompt**](#prompt), a blank line, a line that names the format (`Enter date and time:`, `Enter date (MM/DD/YYYY):` or `Enter time (HH:MM):`, with `HH:MM AM/PM` when [**Is24HourFormat**](#is24hourformat) is **False**), a blank line and `Year range:` with [**MinYear**](#minyear) and [**MaxYear**](#maxyear). The edit box holds the current value as [**FormatDateTime**](#formatdatetime) gives it, such as `03/15/2026 14:05`. [**OwnerHwnd**](#ownerhwnd) has no effect, because the box has no owner. + +The class reads the text like this: + +- The text is trimmed and cut at its first space. What is before the space is the date, what is after it is the time. +- Without a space, the whole text is the time in the time mode, the date in the date mode, and in the date-and-time mode a date when it contains a `/` or a `-`, otherwise a time. +- A date is month, day and year in that order, separated by `/` or `-`, whatever the settings of Windows are. The month must be 1 to 12, the day must exist in the month, and the year must lie in the range [**MinYear**](#minyear) to [**MaxYear**](#maxyear). +- A time is hour, minute and optionally second, separated by `:`, with the hour 0 to 23, the minute and the second 0 to 59. Seconds are read whether [**ShowSeconds**](#showseconds) is set or not. `AM` or `PM` after the time is read whether [**Is24HourFormat**](#is24hourformat) is set or not: 12 AM is hour 0, and 1 to 11 PM are 13 to 23. +- A part that is not typed keeps its value, so `11/12/2028` changes only the date, and `23:59` only the hour and minute. + +The result of [**ShowDialog**](#showdialog) is **False** when the user cancelled or typed a value that fails these rules, which also shows a second box, `Invalid date/time format. Please use:` followed by the text that the edit box offered. The result is **True** when the user pressed OK, and that includes an empty entry and text without a recognisable part (`hello` or `1/2`): such text changes nothing. + +> [!WARNING] +> In version 1.1.0.0, when the text fails the check, the parts that the class has already read stay in the object. After `13/45/2026`, [**Month**](#month) is 13 and [**Day**](#day) is 45; after `02/30/2026`, **Day** is 30 in February; after `25:61`, [**Hour**](#hour) is 25 and [**Minute**](#minute) is 61; after `04/05/2027 25:00` the date is 5 April 2027 and the hour is 25. [**ShowDialog**](#showdialog) returns **False** and [**Cancelled**](#cancelled) is **True**, yet the object does not hold the value it held before. The setters of the parts, which refuse such values, are bypassed. A program that carries on with the object after a **False** result reads wrong values. Copy [**DateTimeValue**](#datetimevalue) before the call and restore it when the call returns **False**. + +> [!WARNING] +> In version 1.1.0.0 a time with `AM` or `PM` and no date in front of it is not read correctly. `9:30 PM` is cut at its space into the date `9:30` and the time `PM` and rejected, so the offered text of the time mode with [**Is24HourFormat**](#is24hourformat) **False** (`02:05 PM`) is rejected when the user presses OK without changing it. `9:30pm`, with no space, is accepted and read as 21:03, because the class removes three characters from the end of the text and the third is a digit of the minutes. With a date in front, `03/15/2026 9:30 PM` is read correctly. Typing the time with a 24-hour clock (`21:30`) works in every mode. + +```tb check_build +Dim picker As New cDialogDateTime +Dim before As Date + +before = picker.DateTimeValue +If Not picker.ShowDialog() Then + ' The object may hold parts that were read before the entry was refused. + picker.DateTimeValue = before +End If +``` + +## Properties + +### Cancelled +{: .no_toc } + +Whether the last box was cancelled, read-only. + +Syntax: *object*.**Cancelled** **As Boolean** + +**False** in a new object and after a box that the user closed with OK. **True** after Cancel, and after an entry that was refused, as [above](#what-the-box-shows-and-accepts) describes. [**Reset**](#reset) sets it to **False**. + +```tb check_build check_run +Dim picker As New cDialogDateTime + +Debug.Print picker.Cancelled ' False +``` + +### CustomFormat +{: .no_toc } + +The pattern that [**FormatDate**](#formatdate) uses for **Custom**. + +Syntax: *object*.**CustomFormat** [ **=** *value* ] + +*value* +: A **String**. The default is an empty string. + +Only [**FormatDate**](#formatdate) with the argument **Custom** reads it. [**FormatDateTime**](#formatdatetime) and the box ignore it. + +```tb check_build check_run +Dim picker As New cDialogDateTime + +picker.CustomFormat = "DD.MM.YYYY" +Debug.Print picker.CustomFormat ' DD.MM.YYYY +``` + +### DateTimeValue +{: .no_toc } + +The date and the time together. + +Syntax: *object*.**DateTimeValue** [ **=** *value* ] + +*value* +: A **Date**. + +Reading returns [**DateValue**](#datevalue) plus [**TimeValue**](#timevalue). Setting sets all six parts. The value is not checked against [**MinYear**](#minyear) and [**MaxYear**](#maxyear). + +```tb check_build check_run +Dim picker As New cDialogDateTime + +picker.DateTimeValue = DateSerial(2026, 3, 15) + TimeSerial(14, 5, 9) +Debug.Print Format$(picker.DateTimeValue, "yyyy-mm-dd hh:nn:ss") ' 2026-03-15 14:05:09 +Debug.Print picker.Year ' 2026 +Debug.Print picker.Second ' 9 +``` + +### DateValue +{: .no_toc } + +The date. + +Syntax: *object*.**DateValue** [ **=** *value* ] + +*value* +: A **Date**. Only its date part is used. + +Reading returns the date at midnight, built from [**Year**](#year), [**Month**](#month) and [**Day**](#day). Setting sets the three parts, without checking them against [**MinYear**](#minyear) and [**MaxYear**](#maxyear). + +```tb check_build check_run +Dim picker As New cDialogDateTime + +picker.DateValue = DateSerial(2027, 7, 4) +Debug.Print picker.Month ' 7 +Debug.Print Format$(picker.DateValue, "yyyy-mm-dd") ' 2027-07-04 +``` + +### Day +{: .no_toc } + +The day of the month. + +Syntax: *object*.**Day** [ **=** *value* ] + +*value* +: A **Long** from 1 to the number of days of the current [**Month**](#month) and [**Year**](#year). + +A value outside that range is ignored, without an error. To enter a date, set [**Year**](#year) and [**Month**](#month) first, or use [**DateValue**](#datevalue). + +```tb check_build check_run +Dim picker As New cDialogDateTime + +picker.DateValue = DateSerial(2026, 2, 10) +picker.Day = 30 ' no such day in February 2026: ignored +Debug.Print picker.Day ' 10 +picker.Day = 28 +Debug.Print picker.Day ' 28 +``` + +### Hour +{: .no_toc } + +The hour, in the 24-hour clock. + +Syntax: *object*.**Hour** [ **=** *value* ] + +*value* +: A **Long** from 0 to 23. Another value is ignored, without an error. + +```tb check_build check_run +Dim picker As New cDialogDateTime + +picker.Hour = 18 +Debug.Print picker.Hour ' 18 +picker.Hour = 24 ' ignored +Debug.Print picker.Hour ' 18 +``` + +### Is24HourFormat +{: .no_toc } + +Whether the offered time is written in the 24-hour clock. + +Syntax: *object*.**Is24HourFormat** [ **=** *value* ] + +*value* +: A **Boolean**. The default is **True**. + +With **False**, [**FormatTime**](#formattime) and the text that the box offers write `02:05 PM` for 14:05, and the time mode names `HH:MM AM/PM`. The box reads either form of a time, as [above](#what-the-box-shows-and-accepts) describes. + +```tb check_build check_run +Dim picker As New cDialogDateTime + +picker.Hour = 14 +picker.Minute = 5 +Debug.Print picker.FormatTime() ' 14:05 +picker.Is24HourFormat = False +Debug.Print picker.FormatTime() ' 02:05 PM +``` + +### MaxYear +{: .no_toc } + +The last year that the box accepts. + +Syntax: *object*.**MaxYear** [ **=** *value* ] + +*value* +: A **Long**. The default is 2100. + +Limits the year typed in the box and the value of [**Year**](#year). The box names the range. [**DateValue**](#datevalue) and [**DateTimeValue**](#datetimevalue) do not check it. + +```tb check_build check_run +Dim picker As New cDialogDateTime + +Debug.Print picker.MaxYear ' 2100 +picker.MaxYear = 2050 +picker.Year = 2060 ' above MaxYear: ignored +Debug.Print picker.Year = 2060 ' False +``` + +### MinYear +{: .no_toc } + +The first year that the box accepts. + +Syntax: *object*.**MinYear** [ **=** *value* ] + +*value* +: A **Long**. The default is 1900. + +Limits the year typed in the box and the value of [**Year**](#year), as [**MaxYear**](#maxyear) does for the last year. + +```tb check_build check_run +Dim picker As New cDialogDateTime + +Debug.Print picker.MinYear ' 1900 +picker.Year = 1850 ' below MinYear: ignored +Debug.Print picker.Year = 1850 ' False +``` + +### Minute +{: .no_toc } + +The minute. + +Syntax: *object*.**Minute** [ **=** *value* ] + +*value* +: A **Long** from 0 to 59. Another value is ignored, without an error. + +```tb check_build check_run +Dim picker As New cDialogDateTime + +picker.Minute = 45 +Debug.Print picker.Minute ' 45 +picker.Minute = 60 ' ignored +Debug.Print picker.Minute ' 45 +``` + +### Mode +{: .no_toc } + +What the box asks for. + +Syntax: *object*.**Mode** [ **=** *value* ] + +*value* +: A [**PickerMode**](#pickermode) member. The default is **DateTime**. + +[**ShowDatePicker**](#showdatepicker) and [**ShowTimePicker**](#showtimepicker) set it to **DateOnly** and **TimeOnly**, and it stays so for the following calls. It also decides what [**FormatDateTime**](#formatdatetime) returns. + +```tb check_build check_run +Dim picker As New cDialogDateTime + +Debug.Print picker.Mode = PickerMode.DateTime ' True +picker.Mode = PickerMode.TimeOnly +Debug.Print picker.Mode ' 2 +``` + +### Month +{: .no_toc } + +The month. + +Syntax: *object*.**Month** [ **=** *value* ] + +*value* +: A **Long** from 1 to 12. Another value is ignored, without an error. + +When the current [**Day**](#day) does not exist in the new month, it is lowered to the last day of the month. + +```tb check_build check_run +Dim picker As New cDialogDateTime + +picker.DateValue = DateSerial(2026, 1, 31) +picker.Month = 2 +Debug.Print picker.Day ' 28 +picker.Month = 13 ' ignored +Debug.Print picker.Month ' 2 +``` + +### OwnerHwnd +{: .no_toc } + +A window handle that has no effect. + +Syntax: *object*.**OwnerHwnd** [ **=** *value* ] + +*value* +: A **LongPtr**. The default is 0. + +The value is stored, but the box does not use it: it has no owner, and [**Reset**](#reset) does not change it. + +```tb check_build check_run +Dim picker As New cDialogDateTime + +Debug.Print picker.OwnerHwnd ' 0 +``` + +### Prompt +{: .no_toc } + +The first text of the box. + +Syntax: *object*.**Prompt** [ **=** *value* ] + +*value* +: A **String**. The default is `Please select a date and time:`. + +The class adds the line that names the format and the year range below this text. + +```tb check_build check_run +Dim picker As New cDialogDateTime + +Debug.Print picker.Prompt ' Please select a date and time: +``` + +### Second +{: .no_toc } + +The second. + +Syntax: *object*.**Second** [ **=** *value* ] + +*value* +: A **Long** from 0 to 59. Another value is ignored, without an error. + +```tb check_build check_run +Dim picker As New cDialogDateTime + +picker.Second = 30 +Debug.Print picker.Second ' 30 +picker.Second = 75 ' ignored +Debug.Print picker.Second ' 30 +``` + +### ShowSeconds +{: .no_toc } + +Whether the offered time has seconds. + +Syntax: *object*.**ShowSeconds** [ **=** *value* ] + +*value* +: A **Boolean**. The default is **False**. + +With **True**, [**FormatTime**](#formattime) and the text that the box offers write the seconds, `14:05:09` or `02:05:09 PM`. The box reads seconds whatever this property is. + +```tb check_build check_run +Dim picker As New cDialogDateTime + +picker.TimeValue = TimeSerial(14, 5, 9) +Debug.Print picker.FormatTime() ' 14:05 +picker.ShowSeconds = True +Debug.Print picker.FormatTime() ' 14:05:09 +``` + +### TimeValue +{: .no_toc } + +The time of day. + +Syntax: *object*.**TimeValue** [ **=** *value* ] + +*value* +: A **Date**. Only its time part is used. + +Reading returns the time on 30 December 1899, built from [**Hour**](#hour), [**Minute**](#minute) and [**Second**](#second). Setting sets the three parts. + +```tb check_build check_run +Dim picker As New cDialogDateTime + +picker.TimeValue = TimeSerial(8, 30, 15) +Debug.Print picker.Hour ' 8 +Debug.Print Format$(picker.TimeValue, "hh:nn:ss") ' 08:30:15 +``` + +### Title +{: .no_toc } + +The title of the box. + +Syntax: *object*.**Title** [ **=** *value* ] + +*value* +: A **String**. The default is `Select Date and Time`. + +The box that reports a refused entry has the same title. [**ShowDatePicker**](#showdatepicker) and [**ShowTimePicker**](#showtimepicker) set it. + +```tb check_build check_run +Dim picker As New cDialogDateTime + +Debug.Print picker.Title ' Select Date and Time +``` + +### Year +{: .no_toc } + +The year. + +Syntax: *object*.**Year** [ **=** *value* ] + +*value* +: A **Long** from [**MinYear**](#minyear) to [**MaxYear**](#maxyear). Another value is ignored, without an error. + +```tb check_build check_run +Dim picker As New cDialogDateTime + +picker.Year = 2030 +Debug.Print picker.Year ' 2030 +picker.Year = 2200 ' above MaxYear: ignored +Debug.Print picker.Year ' 2030 +``` + +## Methods + +### AddDays +{: .no_toc } + +Moves the date by a number of days. + +Syntax: *object*.**AddDays** ( *days* ) + +*days* +: *required* A **Long**, negative to move back. + +The time of day does not change. A result after 31 December 9999 raises error 13 (*Type mismatch*). + +```tb check_build check_run +Dim picker As New cDialogDateTime + +picker.DateValue = DateSerial(2026, 3, 15) +picker.AddDays 20 +Debug.Print Format$(picker.DateValue, "yyyy-mm-dd") ' 2026-04-04 +picker.AddDays -35 +Debug.Print Format$(picker.DateValue, "yyyy-mm-dd") ' 2026-02-28 +``` + +### AddHours +{: .no_toc } + +Moves the time by a number of hours, carrying over into the date. + +Syntax: *object*.**AddHours** ( *hours* ) + +*hours* +: *required* A **Long**, negative to move back. + +Adds the hours to the time of day, and moves the date forward or back by the days that this passes. + +> [!NOTE] +> The class works in seconds in a **Long**, so a value of about 596,000 hours (68 years) or more, in either direction, raises error 6 (*Overflow*). The date and time are left as they were. + +```tb check_build check_run +Dim picker As New cDialogDateTime + +picker.DateTimeValue = DateSerial(2026, 3, 15) + TimeSerial(14, 5, 9) +picker.AddHours 10 +Debug.Print Format$(picker.DateTimeValue, "yyyy-mm-dd hh:nn:ss") ' 2026-03-16 00:05:09 +picker.AddHours -15 +Debug.Print Format$(picker.DateTimeValue, "yyyy-mm-dd hh:nn:ss") ' 2026-03-15 09:05:09 +``` + +### FormatDate +{: .no_toc } + +Returns the date as text. + +Syntax: *object*.**FormatDate** ( [ *formatType* ] ) **As String** + +*formatType* +: *optional* A [**DateFormat**](#dateformat) member. The default is **ShortDate**. + +**ShortDate** gives `03/15/2026`, with month, day and year in that order. **LongDate** gives `Sunday, March 15, 2026`, in English whatever the language of Windows is. **Custom** gives the text of [**CustomFormat**](#customformat) in which these patterns are replaced, in this order: `YYYY` (four digits), `YY` (two digits), `MMMM` (the month's name), `MMM` (its first three letters), `MM` (two digits), `M`, `DD` (two digits) and `D`. With an empty [**CustomFormat**](#customformat), **Custom** gives the short date. + +> [!WARNING] +> In version 1.1.0.0 the patterns are replaced one after the other in the text that is being built, so the letters `M` and `D` of a month's name, which `MMMM` and `MMM` have just put in, are replaced as well. **March** and **May** lose their first letter to the number of the month (`3arch`, `5ay`) and **December** its first letter to the number of the day (`15ecember`). No error is raised. The same happens to any other `Y`, `M` or `D` in the format, which is not kept as a literal character. Formats with `MMMM` or `MMM` are right only for the other months. + +```tb check_build check_run +Dim picker As New cDialogDateTime + +picker.DateValue = DateSerial(2026, 3, 15) +Debug.Print picker.FormatDate() ' 03/15/2026 +Debug.Print picker.FormatDate(DateFormat.LongDate) ' Sunday, March 15, 2026 + +picker.CustomFormat = "YYYY-MM-DD" +Debug.Print picker.FormatDate(DateFormat.Custom) ' 2026-03-15 +picker.CustomFormat = "D.M.YY" +Debug.Print picker.FormatDate(DateFormat.Custom) ' 15.3.26 +picker.CustomFormat = "MMMM D, YYYY" +Debug.Print picker.FormatDate(DateFormat.Custom) ' 3arch 15, 2026 +``` + +### FormatDateTime +{: .no_toc } + +Returns the value as the box offers it. + +Syntax: *object*.**FormatDateTime** ( ) **As String** + +Depends on [**Mode**](#mode): **DateOnly** gives [**FormatDate**](#formatdate), **TimeOnly** gives [**FormatTime**](#formattime), and **DateTime** gives the two with a space between, such as `03/15/2026 14:05`. It always uses the short date, never the long or the custom one. + +```tb check_build check_run +Dim picker As New cDialogDateTime + +picker.DateTimeValue = DateSerial(2026, 3, 15) + TimeSerial(14, 5, 9) +Debug.Print picker.FormatDateTime() ' 03/15/2026 14:05 +picker.Mode = PickerMode.DateOnly +Debug.Print picker.FormatDateTime() ' 03/15/2026 +picker.Mode = PickerMode.TimeOnly +Debug.Print picker.FormatDateTime() ' 14:05 +``` + +### FormatTime +{: .no_toc } + +Returns the time as text. + +Syntax: *object*.**FormatTime** ( ) **As String** + +Returns `HH:MM` in the 24-hour clock, or `hh:MM AM` or `hh:MM PM` when [**Is24HourFormat**](#is24hourformat) is **False**, with `:SS` after the minutes when [**ShowSeconds**](#showseconds) is **True**. Midnight is `12:00 AM`. + +```tb check_build check_run +Dim picker As New cDialogDateTime + +picker.TimeValue = TimeSerial(14, 5, 9) +picker.Is24HourFormat = False +picker.ShowSeconds = True +Debug.Print picker.FormatTime() ' 02:05:09 PM +picker.TimeValue = TimeSerial(0, 30, 0) +Debug.Print picker.FormatTime() ' 12:30:00 AM +``` + +### GetDayOfWeek +{: .no_toc } + +Returns the weekday of the date. + +Syntax: *object*.**GetDayOfWeek** ( ) **As Long** + +Returns 0 for Sunday to 6 for Saturday. + +```tb check_build check_run +Dim picker As New cDialogDateTime + +picker.DateValue = DateSerial(2026, 3, 15) +Debug.Print picker.GetDayOfWeek() ' 0 +``` + +### GetDayOfWeekName +{: .no_toc } + +Returns the name of the weekday of the date. + +Syntax: *object*.**GetDayOfWeekName** ( ) **As String** + +Returns `Sunday` to `Saturday`, in English. + +```tb check_build check_run +Dim picker As New cDialogDateTime + +picker.DateValue = DateSerial(2026, 3, 16) +Debug.Print picker.GetDayOfWeekName() ' Monday +``` + +### GetDaysInMonth +{: .no_toc } + +Returns the number of days in a month. + +Syntax: *object*.**GetDaysInMonth** ( *year* , *month* ) **As Long** + +*year* +: *required* A **Long**. It matters for February: leap years have 29 days. + +*month* +: *required* A **Long** from 1 to 12. For another value the result is 0. + +The method does not use the date of the object. + +```tb check_build check_run +Dim picker As New cDialogDateTime + +Debug.Print picker.GetDaysInMonth(2024, 2) ' 29 +Debug.Print picker.GetDaysInMonth(1900, 2) ' 28 +Debug.Print picker.GetDaysInMonth(2026, 4) ' 30 +Debug.Print picker.GetDaysInMonth(2026, 13) ' 0 +``` + +### GetMonthName +{: .no_toc } + +Returns the English name of a month. + +Syntax: *object*.**GetMonthName** ( [ *month* ] ) **As String** + +*month* +: *optional* A **Long** from 1 to 12. Omitted or 0, the month of the object is used. For another value the result is an empty string. + +```tb check_build check_run +Dim picker As New cDialogDateTime + +picker.DateValue = DateSerial(2026, 3, 15) +Debug.Print picker.GetMonthName() ' March +Debug.Print picker.GetMonthName(12) ' December +Debug.Print Len(picker.GetMonthName(13)) ' 0 +``` + +### Reset +{: .no_toc } + +Restores the starting values. + +Syntax: *object*.**Reset** + +Sets the date and time to the clock of the computer, [**Title**](#title) and [**Prompt**](#prompt) to their defaults, [**Mode**](#mode) to **DateTime**, [**MinYear**](#minyear) and [**MaxYear**](#maxyear) to 1900 and 2100, [**ShowSeconds**](#showseconds) to **False**, [**Is24HourFormat**](#is24hourformat) to **True**, [**CustomFormat**](#customformat) to an empty string and [**Cancelled**](#cancelled) to **False**. [**OwnerHwnd**](#ownerhwnd) is left as it is. + +```tb check_build check_run +Dim picker As New cDialogDateTime + +picker.Title = "Meeting" +picker.Mode = PickerMode.DateOnly +picker.Reset +Debug.Print picker.Title ' Select Date and Time +Debug.Print picker.Mode = PickerMode.DateTime ' True +``` + +### SetNow +{: .no_toc } + +Sets the value to the clock of the computer. + +Syntax: *object*.**SetNow** + +Sets the six parts to the local date and time, with the seconds. + +```tb check_build check_run +Dim picker As New cDialogDateTime + +picker.Year = 2000 +picker.SetNow +Debug.Print picker.Year = Year(Date) ' True +``` + +### SetToday +{: .no_toc } + +Sets the value to today at midnight. + +Syntax: *object*.**SetToday** + +Sets the date to today's and the hour, minute and second to 0. + +```tb check_build check_run +Dim picker As New cDialogDateTime + +picker.SetToday +Debug.Print picker.Hour + picker.Minute + picker.Second ' 0 +Debug.Print picker.Day = Day(Date) ' True +``` + +### ShowDatePicker +{: .no_toc } + +Asks for a date. + +Syntax: *object*.**ShowDatePicker** ( [ *title* ] ) **As Boolean** + +*title* +: *optional* The title of the box. The default is `Select Date`. + +Sets [**Title**](#title) and sets [**Mode**](#mode) to **DateOnly**, then calls [**ShowDialog**](#showdialog) and returns its result. Both properties keep the new values afterwards. + +```tb check_build +Dim picker As New cDialogDateTime + +picker.DateValue = DateSerial(2026, 3, 15) +If picker.ShowDatePicker("Birthday") Then + Debug.Print picker.FormatDate(DateFormat.LongDate) +End If +``` + +### ShowDialog +{: .no_toc } + +Asks for a date and a time, or the part that [**Mode**](#mode) names. + +Syntax: *object*.**ShowDialog** ( ) **As Boolean** + +Opens the box that [above](#what-the-box-shows-and-accepts) describes, waits until it is closed, and reads the text. Returns **True** when the user pressed OK, **False** when the user cancelled or the text was refused; [**Cancelled**](#cancelled) is the opposite. The warnings in that section apply. + +```tb check_build +Dim picker As New cDialogDateTime + +picker.Title = "Start of the shift" +picker.Is24HourFormat = False +picker.DateTimeValue = DateSerial(2026, 3, 15) + TimeSerial(8, 0, 0) + +If picker.ShowDialog() Then + Debug.Print "Shift starts " & picker.FormatDateTime() +End If +``` + +### ShowTimePicker +{: .no_toc } + +Asks for a time. + +Syntax: *object*.**ShowTimePicker** ( [ *title* ] ) **As Boolean** + +*title* +: *optional* The title of the box. The default is `Select Time`. + +Sets [**Title**](#title) and sets [**Mode**](#mode) to **TimeOnly**, then calls [**ShowDialog**](#showdialog) and returns its result. With [**Is24HourFormat**](#is24hourformat) **False**, the warning [above](#what-the-box-shows-and-accepts) about `AM` and `PM` applies to every entry. + +```tb check_build +Dim picker As New cDialogDateTime + +picker.TimeValue = TimeSerial(7, 30, 0) +If picker.ShowTimePicker("Alarm") Then + Debug.Print picker.FormatTime() +End If +``` + +## Enumerations + +### PickerMode +{: .no_toc } + +What the box asks for, for [**Mode**](#mode). + +| Member | Value | Meaning | +|---|---|---| +| **DateTime** | 0 | A date and a time | +| **DateOnly** | 1 | A date | +| **TimeOnly** | 2 | A time | + +### DateFormat +{: .no_toc } + +The forms of [**FormatDate**](#formatdate). + +| Member | Value | Meaning | +|---|---|---| +| **ShortDate** | 0 | `MM/DD/YYYY` | +| **LongDate** | 1 | `Sunday, March 15, 2026` | +| **Custom** | 2 | The pattern in [**CustomFormat**](#customformat) | + +## Example + +This example asks for the start of a meeting, keeps the previous value when the entry is refused, and prints the end two hours later. + +```tb check_build +Dim picker As New cDialogDateTime +Dim before As Date + +picker.Title = "Meeting" +picker.Prompt = "When does the meeting start?" +picker.DateTimeValue = DateSerial(2026, 3, 15) + TimeSerial(10, 0, 0) +before = picker.DateTimeValue + +If picker.ShowDialog() Then + picker.AddHours 2 + Debug.Print "Ends " & picker.FormatDateTime() +Else + picker.DateTimeValue = before + Debug.Print "Kept " & picker.FormatDateTime() +End If +``` + +## See Also + +- [cDialogInputBox](cDialogInputBox) class -- asks for one line of text +- [cDialogCountdownMsgBox](cDialogCountdownMsgBox) class -- shows a message box that closes by itself +- [TBMANLIB_Dialog](.) package -- overview diff --git a/docs/Reference/Contributed/TBMANLIB_Dialog/cDialogEx.md b/docs/Reference/Contributed/TBMANLIB_Dialog/cDialogEx.md new file mode 100644 index 000000000..c72ee856c --- /dev/null +++ b/docs/Reference/Contributed/TBMANLIB_Dialog/cDialogEx.md @@ -0,0 +1,443 @@ +--- +title: cDialogEx +parent: TBMANLIB_Dialog Package +permalink: /tB/Packages/Contributed/TBMANLIB_Dialog/cDialogEx +has_toc: false +--- + +# cDialogEx class +{: .no_toc } + +Shows the Open, Save As and folder-picker dialogs of Windows Vista and later (the item dialogs, `IFileOpenDialog` and `IFileSaveDialog`), with a file-type index, an owner window, a title-bar icon and a folder-picking mode. Its members follow those of [**cDialog**](cDialog/), and it adds [**FilterIndex**](#filterindex), [**OwnerHwnd**](#ownerhwnd), [**PickFolders**](#pickfolders), [**GetPresetFilter**](#getpresetfilter), [**SetSystemIcon**](#setsystemicon), [**SetCustomIcon**](#setcustomicon) and [**ClearIcon**](#clearicon). + +A **cDialogEx** is created with **New**. The properties are set before a dialog is shown, and [**ShowOpen**](#showopen), [**ShowSave**](#showsave) and [**ShowBrowseForFolder**](#showbrowseforfolder) show it. Creating an object calls `CoInitialize` for the thread, and destroying it calls `CoUninitialize`. + +**Properties:** [CreatePrompt](#createprompt), [DefaultExt](#defaultext), [DialogTitle](#dialogtitle), [FileMustExist](#filemustexist), [FileName](#filename), [Filter](#filter), [FilterIndex](#filterindex), [InitialDir](#initialdir), [MultiSelect](#multiselect), [NoChangeDir](#nochangedir), [OverwritePrompt](#overwriteprompt), [OwnerHwnd](#ownerhwnd), [PathMustExist](#pathmustexist), [PickFolders](#pickfolders). + +**Methods:** [AddFilter](#addfilter), [ClearFilter](#clearfilter), [ClearIcon](#clearicon), [GetFileExtension](#getfileextension), [GetFileName](#getfilename), [GetFileNameWithoutExt](#getfilenamewithoutext), [GetFilePath](#getfilepath), [GetPresetFilter](#getpresetfilter), [OpenFile](#openfile), [PathCombine](#pathcombine), [Reset](#reset), [SelectAndOpenFile](#selectandopenfile), [SelectFiles](#selectfiles), [SetCodeFilters](#setcodefilters), [SetCommonFilters](#setcommonfilters), [SetCustomIcon](#setcustomicon), [SetDocumentFilters](#setdocumentfilters), [SetImageFilters](#setimagefilters), [SetSystemIcon](#setsystemicon), [ShowBrowseForFolder](#showbrowseforfolder), [ShowOpen](#showopen), [ShowSave](#showsave). + +**Enumeration:** [SystemIconId](#systemiconid). + +```tb check_build +Dim dlg As New cDialogEx +Dim result As String + +dlg.DialogTitle = "Open a text file" +dlg.Filter = "Text Files (*.txt)|*.txt|All Files (*.*)|*.*|" +result = dlg.ShowOpen() + +If Len(result) > 0 Then Debug.Print "Chosen: " & result +``` + +> [!WARNING] +> In version 1.1.0.0 the three dialogs return the **name** of the chosen file or folder, not its full path: [**ShowOpen**](#showopen), [**ShowSave**](#showsave), [**ShowBrowseForFolder**](#showbrowseforfolder) and [**SelectFiles**](#selectfiles) give `a.txt` for `C:\Reports\a.txt`, and `other` for the folder `C:\Reports\other`. A program that opens or saves the result opens or saves a file in its own current folder, and no error is raised. [**FileName**](#filename) holds the same short name afterwards. The class asks Windows for the name that is relative to the parent folder, because the constant `SIGDN_FILESYSPATH` of the source has the value `&H80058001` and the file-system path is `&H80058000`. The folder in which the user chose is not reported, so the result cannot be used to open or save the file. Use [**cDialog**](cDialog/), which returns full paths. + +> [!WARNING] +> In version 1.1.0.0 [**InitialDir**](#initialdir) has no effect: the dialogs always open in the *Documents* folder, and no error is raised. The class makes the folder item with `SHCreateItemFromParsingName` and a GUID for `IShellItem` that has a wrong byte (`&H37` where the interface identifier has `&H7B`), so Windows answers that it has no such interface and the class skips `SetDefaultFolder`. + +> [!WARNING] +> In version 1.1.0.0 a [**Filter**](#filter) is ignored unless it holds an even number of pipe characters (`|`). A filter of N pairs written `Name|Pattern|Name|Pattern` holds 2N minus 1, an odd number, so the dialog shows no file-type list and accepts every file, and no error is raised. A pipe after the last pattern makes the count even: `Text (*.txt)|*.txt|All (*.*)|*.*|` shows two entries. [**AddFilter**](#addfilter) and the four **Set...Filters** methods build a filter without the closing pipe, and so does the text of [**GetPresetFilter**](#getpresetfilter), so none of them shows a list until the text gets its pipe: add `dlg.Filter = dlg.Filter & "|"` after them. + +## Which dialog uses which property + +*yes* means that the class passes the value to the dialog. [**ShowOpen**](#showopen) with [**PickFolders**](#pickfolders) **True** and [**ShowBrowseForFolder**](#showbrowseforfolder) show the same folder dialog. + +| Property | ShowOpen | ShowSave | Folder dialog | +|---|---|---|---| +| [**DialogTitle**](#dialogtitle) | yes | yes | yes | +| [**InitialDir**](#initialdir) | yes (no effect) | yes (no effect) | yes (no effect) | +| [**FileName**](#filename) | read and written | read and written | written, not read | +| [**DefaultExt**](#defaultext) | ignored | yes | ignored | +| [**Filter**](#filter), [**FilterIndex**](#filterindex) | yes | yes | ignored | +| [**OverwritePrompt**](#overwriteprompt) | yes | yes | ignored | +| [**PathMustExist**](#pathmustexist) | yes | yes | always set | +| [**FileMustExist**](#filemustexist), [**CreatePrompt**](#createprompt), [**MultiSelect**](#multiselect) | yes | ignored | ignored | +| [**NoChangeDir**](#nochangedir) | yes | yes | yes | +| [**OwnerHwnd**](#ownerhwnd) | yes | yes | yes | +| [**PickFolders**](#pickfolders) | selects the folder dialog | ignored | ignored | +| the icon ([**SetSystemIcon**](#setsystemicon), [**SetCustomIcon**](#setcustomicon)) | yes | yes | yes | + +Every method catches its errors: it writes a line that starts with the name of the method to the debug console (`Debug.Print`) and returns an empty string. A dialog that Windows cannot create gives an empty string in the same way, so an empty result means a cancel or a failure. + +## Differences from cDialog + +- A new **cDialogEx** has every Boolean property **False**. A new [**cDialog**](cDialog/#defaults) has [**OverwritePrompt**](#overwriteprompt), [**PathMustExist**](#pathmustexist) and [**FileMustExist**](#filemustexist) set to **True**. +- The dialogs are the item dialogs of Windows Vista and later: the folder dialog is the file dialog in a folder-picking mode, with a *Select Folder* button, and not the tree of `SHBrowseForFolder`. There is no [**NewDialogStyle**](cDialog/NewDialogStyle), [**HideReadOnly**](cDialog/HideReadOnly) or [**ReadOnly**](cDialog/ReadOnly) property. +- A [**ShowOpen**](#showopen) with [**MultiSelect**](#multiselect) puts the first file in [**FileName**](#filename) when two or more files are chosen. **cDialog** empties it. +- With the defaults the Open dialog accepts the name of a file that does not exist, but a name in a folder that does not exist raises a message box and the dialog stays open, whatever [**PathMustExist**](#pathmustexist) holds. +- [**Filter**](#filter) needs the closing pipe, as the warning above says. + +## Properties + +### CreatePrompt +{: .no_toc } + +Returns or sets whether the Open dialog asks before it accepts the name of a file that does not exist. + +Syntax: *object*.**CreatePrompt** [ = *boolean* ] + +A **Boolean**. The default is **False**. With **True**, the Open dialog sets `FOS_CREATEPROMPT`: a name that matches no file raises a box titled *Open*, with the text *This file doesn't exist. Create the file?* and the buttons *Yes* and *No*. Windows shows the box twice in a row. *Yes* closes the dialog and returns the name; *No* leaves the dialog open. The class does not create the file. [**ShowSave**](#showsave) ignores the property. + +### DefaultExt +{: .no_toc } + +Returns or sets the extension that the Save As dialog adds to a name typed without one. + +Syntax: *object*.**DefaultExt** [ = *string* ] + +A **String**, an extension without a period (`txt`). The default is an empty string, which adds nothing. Only [**ShowSave**](#showsave) uses it, through `SetDefaultExtension`. A name typed without an extension gets the extension after a period (`out` becomes `out.txt`), and a name with its own extension, such as `out.dat`, is kept. A value with a period (`.txt`) is passed as it stands, and the result is `out..txt`. + +### DialogTitle +{: .no_toc } + +Returns or sets the title of the dialog. + +Syntax: *object*.**DialogTitle** [ = *string* ] + +A **String**. The default is an empty string, and the dialog then shows its own title: *Open* for [**ShowOpen**](#showopen), *Save As* for [**ShowSave**](#showsave), and for the folder dialog (**ShowBrowseForFolder**, or **ShowOpen** with [**PickFolders**](#pickfolders)) the five characters U+9009 U+62E9 U+6587 U+4EF6 U+5939, which mean *Select Folder* in Chinese. A non-empty value replaces the title in all three. + +### FileMustExist +{: .no_toc } + +Returns or sets whether the Open dialog accepts only the name of a file that exists. + +Syntax: *object*.**FileMustExist** [ = *boolean* ] + +A **Boolean**. The default is **False**, so the Open dialog accepts a name that matches no file and returns it. With **True** it sets `FOS_FILEMUSTEXIST`: a name that matches no file raises a box with the text *File not found. Check the file name and try again.*, and the dialog stays open. [**ShowSave**](#showsave) ignores the property. + +### FileName +{: .no_toc } + +Returns or sets the file name that the dialogs show when they open, and holds the name that the last dialog returned. + +Syntax: *object*.**FileName** [ = *string* ] + +A **String**. The default is an empty string. [**ShowOpen**](#showopen) and [**ShowSave**](#showsave) write a non-empty value into the File name box when the dialog opens. A confirmed dialog stores its result here: the name of the file (see the first warning above), the first name of several files from [**ShowOpen**](#showopen), or the name of the folder. A cancelled dialog leaves **FileName** as it was. The folder dialog does not read it. Because a confirmed dialog stores its result, the next dialog opens with that name in the box; set **FileName** to an empty string, or call [**Reset**](#reset), to start without it. + +### Filter +{: .no_toc } + +Returns or sets the file-type list of the Open and Save As dialogs. + +Syntax: *object*.**Filter** [ = *string* ] + +A **String** of pairs of a description and a pattern, separated by pipe characters: `Text (*.txt)|*.txt|All (*.*)|*.*|`. Several patterns of one pair are separated by semicolons. The default is an empty string, which shows no list. The text is split at each pipe, so it must hold an even number of pipes, which means that it must end with a pipe; see the warning at the top of the page. A text with an odd number of pipes is ignored, and a text with an even number but an incomplete last pair keeps what it can: `Text (*.txt)|*.txt|All` shows one entry. The folder dialog ignores the property. Descriptions are shown as written, so add the pattern to the description if the dialog is to show it. + +### FilterIndex +{: .no_toc } + +Returns or sets which entry of the file-type list the dialog selects when it opens. + +Syntax: *object*.**FilterIndex** [ = *long* ] + +A **Long**, 1 for the first entry. The default is 0, which selects the first entry. A value above the number of entries selects the last one, and a negative value selects the first. The dialog sets the index only when [**Filter**](#filter) is in effect (see the warning above). The property is not updated when the user chooses another entry, so the program cannot find out which entry the user chose. + +### InitialDir +{: .no_toc } + +Returns or sets the folder in which the dialogs should open. + +Syntax: *object*.**InitialDir** [ = *string* ] + +A **String**. The default is an empty string. In version 1.1.0.0 the value has no effect; see the warning at the top of the page. The class never changes it. + +### MultiSelect +{: .no_toc } + +Returns or sets whether the Open dialog lets the user select more than one file. + +Syntax: *object*.**MultiSelect** [ = *boolean* ] + +A **Boolean**. The default is **False**. With **True** the dialog allows several files, and [**ShowOpen**](#showopen) returns a **String** for one file and an array of **String** values for two or more. [**ShowSave**](#showsave) and the folder dialog ignore the property. + +### NoChangeDir +{: .no_toc } + +Returns or sets whether the dialogs leave the current folder of the program alone. + +Syntax: *object*.**NoChangeDir** [ = *boolean* ] + +A **Boolean**. The default is **False**. With **True** the class sets `FOS_NOCHANGEDIR` for all three dialogs. The current folder of the program stayed the same after each dialog with the property **False** and **True**. + +### OverwritePrompt +{: .no_toc } + +Returns or sets whether the dialog asks before it accepts the name of an existing file. + +Syntax: *object*.**OverwritePrompt** [ = *boolean* ] + +A **Boolean**. The default is **False**, so the Save As dialog accepts the name of an existing file without asking. With **True** it sets `FOS_OVERWRITEPROMPT`: the dialog raises a box titled *Confirm Save As* with the buttons *Yes* and *No*. *Yes* returns the name and *No* leaves the dialog open. [**ShowOpen**](#showopen) passes the flag too, and the folder dialog does not. + +### OwnerHwnd +{: .no_toc } + +Returns or sets the window that owns the dialogs. + +Syntax: *object*.**OwnerHwnd** [ = *longptr* ] + +A **LongPtr**, the handle of a window of the program. The default is 0, a dialog with no owner. The dialog is owned by that window. A handle that is not a window (12345, for example) makes **Show** fail: the method returns an empty string at once, shows no dialog and raises no error. + +### PathMustExist +{: .no_toc } + +Returns or sets whether the Open and Save As dialogs accept only paths that exist. + +Syntax: *object*.**PathMustExist** [ = *boolean* ] + +A **Boolean**. The default is **False**. With **True** it sets `FOS_PATHMUSTEXIST` for [**ShowOpen**](#showopen) and [**ShowSave**](#showsave), and a name in a folder that does not exist raises a box with the text *Path does not exist. Check the path and try again.* The Open dialog shows the same box with the property **False**. The folder dialog always sets the flag. + +### PickFolders +{: .no_toc } + +Returns or sets whether [**ShowOpen**](#showopen) picks a folder. + +Syntax: *object*.**PickFolders** [ = *boolean* ] + +A **Boolean**. The default is **False**. With **True**, **ShowOpen** shows the folder dialog and returns the folder, as [**ShowBrowseForFolder**](#showbrowseforfolder) does. [**MultiSelect**](#multiselect) is ignored in that mode. + +## Methods + +### AddFilter +{: .no_toc } + +Appends a description and a pattern to the filter. + +Syntax: *object*.**AddFilter** ( *strDescription*, *strExtension* ) + +Both arguments are **String** values. The method joins the new pair to [**Filter**](#filter) with a pipe, and adds no pipe at the end; see the warning at the top of the page. + +### ClearFilter +{: .no_toc } + +Empties the filter. + +Syntax: *object*.**ClearFilter** + +### ClearIcon +{: .no_toc } + +Removes the icon that [**SetSystemIcon**](#setsystemicon) or [**SetCustomIcon**](#setcustomicon) set. + +Syntax: *object*.**ClearIcon** + +The dialogs then show the icon that Windows chooses. The class destroys an icon that **SetSystemIcon** loaded, and **Reset** calls this method. + +### GetFileExtension +{: .no_toc } + +Returns the part of a path from its last period. + +Syntax: *object*.**GetFileExtension** ( *strFullPath* ) **As String** + +The same as [**cDialog.GetFileExtension**](cDialog/GetFileExtension). + +### GetFileName +{: .no_toc } + +Returns the part of a path after its last backslash. + +Syntax: *object*.**GetFileName** ( *strFullPath* ) **As String** + +The same as [**cDialog.GetFileName**](cDialog/GetFileName). + +### GetFileNameWithoutExt +{: .no_toc } + +Returns the file name of a path without its extension. + +Syntax: *object*.**GetFileNameWithoutExt** ( *strFullPath* ) **As String** + +The same as [**cDialog.GetFileNameWithoutExt**](cDialog/GetFileNameWithoutExt). + +### GetFilePath +{: .no_toc } + +Returns the part of a path before its last backslash. + +Syntax: *object*.**GetFilePath** ( *strFullPath* ) **As String** + +The same as [**cDialog.GetFilePath**](cDialog/GetFilePath). + +### GetPresetFilter +{: .no_toc } + +Returns the text of a filter for a kind of file. + +Syntax: *object*.**GetPresetFilter** ( *filterName* ) **As String** + +*filterName* is a **String** that the method compares without regard to case and after removing spaces at both ends. The result is a pair, with no closing pipe, and an empty string for any other name. The method does not change [**Filter**](#filter). + +| *filterName* | Result | +|---|---| +| `all` | `All Files|*.*` | +| `text` | `Text Files|*.txt` | +| `images` or `image` | `Image Files|*.bmp;*.jpg;*.jpeg;*.png;*.gif` | +| `documents` or `document` | `Documents|*.doc;*.docx;*.pdf;*.txt;*.rtf` | +| `executables` or `executable` | `Executables|*.exe;*.bat;*.cmd;*.ps1` | + +```tb check_build check_run +Dim dlg As New cDialogEx +Debug.Print dlg.GetPresetFilter("Images") ' Image Files|*.bmp;*.jpg;*.jpeg;*.png;*.gif +Debug.Print "[" & dlg.GetPresetFilter("code") & "]" ' [] + +dlg.Filter = dlg.GetPresetFilter("text") & "|" & dlg.GetPresetFilter("all") & "|" +Debug.Print dlg.Filter ' Text Files|*.txt|All Files|*.*| +``` + +### OpenFile +{: .no_toc } + +Runs a shell verb, by default `open`, on a file or a program. + +Syntax: *object*.**OpenFile** ( *strFilePath* [, *strOperation* ] ) **As LongPtr** + +The same as [**cDialog.OpenFile**](cDialog/OpenFile). A path that does not exist returns 2. + +### PathCombine +{: .no_toc } + +Joins two path parts with one backslash. + +Syntax: *object*.**PathCombine** ( *strPath1*, *strPath2* ) **As String** + +The same as [**cDialog.PathCombine**](cDialog/PathCombine). + +### Reset +{: .no_toc } + +Restores every property to its starting value. + +Syntax: *object*.**Reset** + +[**DialogTitle**](#dialogtitle), [**InitialDir**](#initialdir), [**FileName**](#filename), [**DefaultExt**](#defaultext) and [**Filter**](#filter) become empty strings, [**FilterIndex**](#filterindex) and [**OwnerHwnd**](#ownerhwnd) become 0, and every Boolean property becomes **False**. The icon is cleared. + +### SelectAndOpenFile +{: .no_toc } + +Shows the Open dialog and opens the file or files that the user chose. + +Syntax: *object*.**SelectAndOpenFile** ( ) **As LongPtr** + +The same as [**cDialog.SelectAndOpenFile**](cDialog/SelectAndOpenFile): 0 for a cancel, the value of [**OpenFile**](#openfile) for one file, and 1 after an array of files. With the name-only result described in the warning at the top of the page, the file that is opened is the one with that name in the current folder of the program. + +### SelectFiles +{: .no_toc } + +Shows the Open dialog and returns the chosen files as a **Collection**. + +Syntax: *object*.**SelectFiles** ( ) **As Collection** + +The same as [**cDialog.SelectFiles**](cDialog/SelectFiles): one **String** for each file, an empty collection after a cancel. With [**PickFolders**](#pickfolders) **True** it holds the chosen folder. + +### SetCodeFilters +{: .no_toc } + +Replaces the filter with one for source code files. + +Syntax: *object*.**SetCodeFilters** + +The same pairs as [**cDialog.SetCodeFilters**](cDialog/SetCodeFilters). Like the three methods below, it builds a filter with no closing pipe, so the dialog shows no list until one is added; see the warning at the top of the page. + +### SetCommonFilters +{: .no_toc } + +Replaces the filter with Text Files and All Files. + +Syntax: *object*.**SetCommonFilters** + +The same pairs as [**cDialog.SetCommonFilters**](cDialog/SetCommonFilters). + +### SetCustomIcon +{: .no_toc } + +Sets an icon from a handle. + +Syntax: *object*.**SetCustomIcon** ( *iconHandle* ) + +*iconHandle* is a **LongPtr**, the handle of an icon (**HICON**). The dialogs show the icon in the title bar and do not destroy it: the program owns the handle and must destroy it. The icon is applied when the dialog opens, and the dialog accepts files as before. + +### SetDocumentFilters +{: .no_toc } + +Replaces the filter with one for office documents. + +Syntax: *object*.**SetDocumentFilters** + +The same pairs as [**cDialog.SetDocumentFilters**](cDialog/SetDocumentFilters). + +### SetImageFilters +{: .no_toc } + +Replaces the filter with one for image files. + +Syntax: *object*.**SetImageFilters** + +The same pairs as [**cDialog.SetImageFilters**](cDialog/SetImageFilters). + +### SetSystemIcon +{: .no_toc } + +Sets one of the icons of Windows. + +Syntax: *object*.**SetSystemIcon** ( *iconId* ) + +*iconId* is a member of [**SystemIconId**](#systemiconid). The class loads the icon with `SHGetStockIconInfo`, large if it can and small otherwise, shows it in the title bar of the dialogs, and destroys it when it is replaced, cleared or the object ends. **SIID_INVALID** and an icon that cannot be loaded leave the dialog without an icon. + +### ShowBrowseForFolder +{: .no_toc } + +Shows the folder dialog and returns the folder that the user chose. + +Syntax: *object*.**ShowBrowseForFolder** ( ) **As String** + +The dialog is the Open dialog in `FOS_PICKFOLDERS` mode with `FOS_FORCEFILESYSTEM` and `FOS_PATHMUSTEXIST`, and with `FOS_NOCHANGEDIR` when [**NoChangeDir**](#nochangedir) is **True**. The title is [**DialogTitle**](#dialogtitle). The button is *Select Folder*. The result is a **String**: the **name** of the folder (see the first warning), and for a folder with no file-system name, such as the *Documents* library when the user confirms with nothing typed, the text `::{D3162B92-9365-467A-956B-92703ACA08AF}`. A name typed into the box that is a file raises a box and the dialog stays open. A cancel returns an empty string. The method stores the result in [**FileName**](#filename) and leaves [**InitialDir**](#initialdir) as it was. + +### ShowOpen +{: .no_toc } + +Shows the Open dialog and returns the chosen file. + +Syntax: *object*.**ShowOpen** ( ) **As Variant** + +Returns a **Variant**: a **String** for one file, an empty string for a cancel, and, with [**MultiSelect**](#multiselect) **True** and two or more files chosen, an array of **String** values (0-based, in the order in which the names were typed). Each value is a name; see the first warning at the top of the page. With [**PickFolders**](#pickfolders) **True** it shows the folder dialog. A confirmed result goes into [**FileName**](#filename). Test the result with **IsArray**, or call [**SelectFiles**](#selectfiles). + +### ShowSave +{: .no_toc } + +Shows the Save As dialog and returns the file name that the user chose. + +Syntax: *object*.**ShowSave** ( ) **As String** + +Returns the name of the file (see the first warning at the top of the page) or an empty string for a cancel. The dialog starts with [**FileName**](#filename) in the File name box, and adds [**DefaultExt**](#defaultext) to a name typed without an extension. It asks before it replaces a file when [**OverwritePrompt**](#overwriteprompt) is **True**. The result goes into **FileName**. + +## SystemIconId + +The icons of [**SetSystemIcon**](#setsystemicon). The members can be written without the class name. + +| Member | Value | Icon | +|---|---|---| +| **SIID_INVALID** | -1 | none | +| **SIID_DOCUMENT** | 0 | a document | +| **SIID_FOLDER** | 3 | a folder | +| **SIID_FOLDEROPEN** | 5 | an open folder | +| **SIID_DISK** | 6 | a disk | +| **SIID_DRIVEFIXED** | 8 | a fixed drive | +| **SIID_DRIVECD** | 10 | a CD drive | +| **SIID_DRIVEHD** | 11 | a hard drive | +| **SIID_SERVER** | 15 | a server | +| **SIID_SHARE** | 28 | a shared folder | +| **SIID_STAROFFILE** | 30 | a file with a star | + +```tb check_build +Dim dlg As New cDialogEx +dlg.SetSystemIcon SIID_FOLDER +dlg.DialogTitle = "Choose a project folder" +dlg.PickFolders = True +Debug.Print dlg.ShowOpen() +dlg.ClearIcon +``` + +## See Also + +- [cDialog](cDialog/) class -- the same dialogs through the Win32 structures, with full paths +- [TBMANLIB_Dialog](.) package -- overview diff --git a/docs/Reference/Contributed/TBMANLIB_Dialog/cDialogFindReplace.md b/docs/Reference/Contributed/TBMANLIB_Dialog/cDialogFindReplace.md new file mode 100644 index 000000000..3e1925f35 --- /dev/null +++ b/docs/Reference/Contributed/TBMANLIB_Dialog/cDialogFindReplace.md @@ -0,0 +1,489 @@ +--- +title: cDialogFindReplace +parent: TBMANLIB_Dialog Package +permalink: /tB/Packages/Contributed/TBMANLIB_Dialog/cDialogFindReplace +has_toc: false +--- + +# cDialogFindReplace class +{: .no_toc } + +A **cDialogFindReplace** shows the Windows Find dialog or the Find and Replace dialog. The dialog is modeless: [**ShowFind**](#showfind) and [**ShowReplace**](#showreplace) return at once, and the dialog stays open beside the program's own window. The class holds the text, the options and the last action of the user as properties, and builds the line of text that describes the search. + +> [!WARNING] +> In version 1.1.0.0 the program never learns what the user did in the dialog. [**ShowFind**](#showfind) and [**ShowReplace**](#showreplace) keep the structure that Windows works on in a local variable, which no longer exists when the method returns, although Windows keeps its address for as long as the dialog is open. The owner window then receives no message for **Find Next**, **Replace**, **Replace All** or **Cancel**, [**LastAction**](#lastaction) stays **None**, and [**hWnd**](#hwnd) and [**IsVisible**](#isvisible) still show a dialog after it was closed. In a 32-bit program, **ShowReplace** can also end the program with an access violation while the dialog is open, before anything is pressed, because Windows reads memory that has been reused. See [How the dialog reports](#how-the-dialog-reports). A program that needs the dialog calls `FindTextW` or `ReplaceTextW` itself and keeps the `FINDREPLACE` structure in a module-level variable. + +**Properties:** [**AllowMatchCase**](#allowmatchcase), [**AllowUpDown**](#allowupdown), [**AllowWholeWord**](#allowwholeword), [**Direction**](#direction), [**FindWhat**](#findwhat), [**hWnd**](#hwnd), [**LastAction**](#lastaction), [**LastError**](#lasterror), [**MatchCase**](#matchcase), [**MessageId**](#messageid), [**Mode**](#mode), [**OwnerHwnd**](#ownerhwnd), [**ReplaceWith**](#replacewith), [**WholeWord**](#wholeword). +**Methods:** [**IsVisible**](#isvisible), [**Reset**](#reset), [**ShowFind**](#showfind), [**ShowReplace**](#showreplace), [**ToDescription**](#todescription), [**UpdateFromFlags**](#updatefromflags). +**Enumerations:** [**DialogMode**](#dialogmode), [**FindAction**](#findaction), [**SearchDirection**](#searchdirection). + +Create an object with `Dim dlg As New cDialogFindReplace`. The class is declared `[COMCreatable(False)]`, so **CreateObject** cannot make one, and **New** can. + +```tb check_build +Private Sub OpenFind(ByVal ownerWindow As LongPtr) + Dim dlg As New cDialogFindReplace + + dlg.OwnerHwnd = ownerWindow ' the window that receives the messages + dlg.FindWhat = "twin" + dlg.MatchCase = True + If dlg.ShowFind() Then + Debug.Print "Open, message " & dlg.MessageId + Else + Debug.Print "Not shown, error " & dlg.LastError + End If +End Sub +``` + +## How the dialog reports + +The dialog is not modal, so there is no result to return. Windows reports the user's actions by **sending a registered window message** to the owner window, with the address of a `FINDREPLACE` structure as `lParam`. The number of the message is [**MessageId**](#messageid), the same for every object. In the structure, `Flags` tells what happened: `FR_FINDNEXT` (&H8), `FR_REPLACE` (&H10), `FR_REPLACEALL` (&H20) or `FR_DIALOGTERM` (&H40, the dialog closed), together with the options `FR_DOWN` (&H1), `FR_WHOLEWORD` (&H2) and `FR_MATCHCASE` (&H4). The program's window procedure reads `Flags`, at byte 12 of the structure in a 32-bit program and byte 24 in a 64-bit one, and passes it to [**UpdateFromFlags**](#updatefromflags), which sets [**LastAction**](#lastaction), [**MatchCase**](#matchcase), [**WholeWord**](#wholeword), [**Direction**](#direction) and reads [**FindWhat**](#findwhat) and [**ReplaceWith**](#replacewith) back from the text the user typed. + +The class has no event, callback or message loop of its own. To receive the message, the owner window needs a window procedure that the program controls, for example by subclassing the window, and the message loop of the program must run while the dialog is open. + +```tb check_build +Private Declare PtrSafe Sub FrCopyMemory Lib "kernel32" Alias "RtlMoveMemory" (ByVal Destination As LongPtr, ByVal Source As LongPtr, ByVal Length As LongPtr) + +' Called from the owner window's procedure when the message number equals dlg.MessageId. +Public Sub FrHandleFindMessage(ByVal dlg As cDialogFindReplace, ByVal lParam As LongPtr) + Dim flags As Long + + #If Win64 Then + FrCopyMemory VarPtr(flags), lParam + 24, 4 + #Else + FrCopyMemory VarPtr(flags), lParam + 12, 4 + #End If + dlg.UpdateFromFlags flags + Select Case dlg.LastAction + Case FindAction.FindNext + Debug.Print "Find: " & dlg.FindWhat + Case FindAction.ReplaceAll + Debug.Print "Replace all: " & dlg.FindWhat & " -> " & dlg.ReplaceWith + Case FindAction.DialogClosed + Debug.Print "Closed" + End Select +End Sub +``` + +### The structure the class keeps in a local variable + +Each of [**ShowFind**](#showfind) and [**ShowReplace**](#showreplace) declares its `FINDREPLACE` as a local variable and passes it to `FindTextW` or `ReplaceTextW`. Windows does not copy it: it keeps the pointer for the whole life of the dialog and reads and writes the structure each time the user presses a button. When the method returns, the memory belongs to whatever uses the stack next. In a 32-bit and in a 64-bit program alike, the dialog opens, accepts text and closes, and the owner window gets no message at all, so a program that waits for [**LastAction**](#lastaction) never leaves the wait. + +The fault is in the code of the class and not in Windows: the same call made with the structure in a module-level variable delivers a message for **Find Next** and one for **Cancel**, with the typed text in the buffer. + +## Properties + +### AllowMatchCase +{: .no_toc } + +Whether the user may change the *Match case* option. + +Syntax: *object*.**AllowMatchCase** [ **=** *value* ] + +A **Boolean**, **True** by default. When it is **False**, [**ShowFind**](#showfind) and [**ShowReplace**](#showreplace) set `FR_NOMATCHCASE`, and the *Match case* check box is shown disabled. + +```tb check_build +Dim dlg As New cDialogFindReplace + +dlg.AllowMatchCase = False +dlg.MatchCase = True ' fixed for the user +Debug.Print dlg.AllowMatchCase +``` + +### AllowUpDown +{: .no_toc } + +Whether the user may change the search direction. + +Syntax: *object*.**AllowUpDown** [ **=** *value* ] + +A **Boolean**, **True** by default. When it is **False**, the methods set `FR_NOUPDOWN`, and the *Up* and *Down* buttons are shown disabled. + +```tb check_build +Dim dlg As New cDialogFindReplace + +dlg.AllowUpDown = False +dlg.Direction = SearchDirection.Down +Debug.Print dlg.AllowUpDown +``` + +### AllowWholeWord +{: .no_toc } + +Whether the user may change the *Match whole word only* option. + +Syntax: *object*.**AllowWholeWord** [ **=** *value* ] + +A **Boolean**, **True** by default. When it is **False**, the methods set `FR_NOWHOLEWORD`, and the check box is shown disabled. + +```tb check_build +Dim dlg As New cDialogFindReplace + +dlg.AllowWholeWord = False +Debug.Print dlg.AllowWholeWord +``` + +### Direction +{: .no_toc } + +The direction of the search. + +Syntax: *object*.**Direction** [ **=** *value* ] + +A [**SearchDirection**](#searchdirection), **Down** by default. When it is **Down**, the methods set `FR_DOWN` and the dialog opens with *Down* selected, otherwise with *Up*. [**UpdateFromFlags**](#updatefromflags) sets it from `FR_DOWN`. + +```tb check_build +Dim dlg As New cDialogFindReplace + +dlg.Direction = SearchDirection.Up +Debug.Print dlg.Direction = SearchDirection.Up +``` + +### FindWhat +{: .no_toc } + +The text to find. + +Syntax: *object*.**FindWhat** [ **=** *value* ] + +A **String**, empty by default. [**ShowFind**](#showfind) and [**ShowReplace**](#showreplace) put it in the *Find what* box. The box holds 256 characters, and the methods raise error 5 (*Invalid procedure call or argument*) for a longer text, before they ask Windows for the dialog. [**UpdateFromFlags**](#updatefromflags) replaces it with the text of the box. + +```tb check_build check_run +Dim dlg As New cDialogFindReplace + +dlg.FindWhat = String$(300, "a") +On Error Resume Next +Debug.Print dlg.ShowFind() +Debug.Print Err.Number +' Output: +' 5 +``` + +### hWnd +{: .no_toc } + +The window handle of the dialog. + +Syntax: *object*.**hWnd** + +A read-only **LongPtr**, 0 by default and when no dialog was created. [**ShowFind**](#showfind) and [**ShowReplace**](#showreplace) set it to the handle that Windows returns. [**UpdateFromFlags**](#updatefromflags) sets it to 0 when the flags tell that the dialog closed, and [**Reset**](#reset) sets it to 0 without closing the dialog. + +```tb check_build +Dim dlg As New cDialogFindReplace + +Debug.Print dlg.hWnd +``` + +### LastAction +{: .no_toc } + +What the user did last, as [**UpdateFromFlags**](#updatefromflags) read it. + +Syntax: *object*.**LastAction** + +A read-only [**FindAction**](#findaction), **None** by default. Only [**UpdateFromFlags**](#updatefromflags) and [**Reset**](#reset) change it. + +```tb check_build +Dim dlg As New cDialogFindReplace + +If dlg.LastAction = FindAction.None Then Debug.Print "No action yet" +``` + +### LastError +{: .no_toc } + +The error code of the last call that could not create a dialog. + +Syntax: *object*.**LastError** + +A read-only **Long**, 0 by default. After a [**ShowFind**](#showfind) or [**ShowReplace**](#showreplace) that returned **False** it holds the value of `CommDlgExtendedError`; after one that returned **True** it is 0. With [**OwnerHwnd**](#ownerhwnd) 0, Windows refuses the dialog and the value is `&HFFFF`. + +```tb check_build check_run +Dim dlg As New cDialogFindReplace + +Debug.Print dlg.ShowFind() +Debug.Print Hex$(dlg.LastError) +' Output: +' False +' FFFF +``` + +### MatchCase +{: .no_toc } + +Whether the search distinguishes upper-case from lower-case letters. + +Syntax: *object*.**MatchCase** [ **=** *value* ] + +A **Boolean**, **False** by default. The methods set `FR_MATCHCASE` for **True** and the *Match case* check box starts selected. [**UpdateFromFlags**](#updatefromflags) sets it from `FR_MATCHCASE`. + +```tb check_build +Dim dlg As New cDialogFindReplace + +dlg.MatchCase = True +Debug.Print dlg.ToDescription() +``` + +### MessageId +{: .no_toc } + +The number of the window message that the dialog sends to the owner window. + +Syntax: *object*.**MessageId** + +A read-only **Long**: the value of `RegisterWindowMessage("commdlg_FindReplace")`, a number that Windows assigns once for the whole session, from `&HC000` up. Every object gets the same number. The class reads it when it is created. + +```tb check_build +Dim dlg As New cDialogFindReplace + +Debug.Print dlg.MessageId +``` + +### Mode +{: .no_toc } + +Which dialog the object last showed, and how [**ToDescription**](#todescription) describes the search. + +Syntax: *object*.**Mode** [ **=** *value* ] + +A [**DialogMode**](#dialogmode), **FindMode** by default. [**ShowFind**](#showfind) sets **FindMode** and [**ShowReplace**](#showreplace) sets **ReplaceMode**, also when Windows refuses the dialog. Assigning a value changes only the text that **ToDescription** returns; the dialog that opens is the one of the method that is called. + +```tb check_build +Dim dlg As New cDialogFindReplace + +dlg.Mode = DialogMode.ReplaceMode +dlg.ReplaceWith = "x" +Debug.Print dlg.ToDescription() +``` + +### OwnerHwnd +{: .no_toc } + +The window that owns the dialog and receives its messages. + +Syntax: *object*.**OwnerHwnd** [ **=** *value* ] + +A **LongPtr** window handle, 0 by default. Windows requires an owner: with 0, [**ShowFind**](#showfind) and [**ShowReplace**](#showreplace) return **False** and no dialog opens. [**Reset**](#reset) does not change it. + +```tb check_build +Private Sub SetOwner(ByVal ownerWindow As LongPtr) + Dim dlg As New cDialogFindReplace + + dlg.OwnerHwnd = ownerWindow ' the handle of a window of the program + Debug.Print dlg.OwnerHwnd +End Sub +``` + +### ReplaceWith +{: .no_toc } + +The replacement text. + +Syntax: *object*.**ReplaceWith** [ **=** *value* ] + +A **String**, empty by default. [**ShowReplace**](#showreplace) puts it in the *Replace with* box, which holds 256 characters, and raises error 5 for a longer text. [**ShowFind**](#showfind) has no such box and does not use it. [**UpdateFromFlags**](#updatefromflags) replaces it with the text of the box; after a **ShowFind** the box does not exist, and the text becomes empty. + +```tb check_build +Dim dlg As New cDialogFindReplace + +dlg.FindWhat = "old" +dlg.ReplaceWith = "new" +Debug.Print dlg.ReplaceWith +``` + +### WholeWord +{: .no_toc } + +Whether the search matches whole words only. + +Syntax: *object*.**WholeWord** [ **=** *value* ] + +A **Boolean**, **False** by default. The methods set `FR_WHOLEWORD` for **True** and the check box starts selected. [**UpdateFromFlags**](#updatefromflags) sets it from `FR_WHOLEWORD`. + +```tb check_build +Dim dlg As New cDialogFindReplace + +dlg.WholeWord = True +Debug.Print dlg.ToDescription() +``` + +## Methods + +### IsVisible +{: .no_toc } + +Tells whether the object has a dialog. + +Syntax: *object*.**IsVisible** ( ) **As Boolean** + +Returns **True** when [**hWnd**](#hwnd) is not 0. It does not ask Windows whether the window is shown or still exists, so it stays **True** after the user closed the dialog, until [**UpdateFromFlags**](#updatefromflags) reads a closing message or [**Reset**](#reset) runs. + +```tb check_build check_run +Dim dlg As New cDialogFindReplace + +Debug.Print dlg.IsVisible() +' Output: +' False +``` + +### Reset +{: .no_toc } + +Restores the search settings to their starting values. + +Syntax: *object*.**Reset** + +[**FindWhat**](#findwhat) and [**ReplaceWith**](#replacewith) become empty, [**MatchCase**](#matchcase) and [**WholeWord**](#wholeword) **False**, [**Direction**](#direction) **Down**, [**Mode**](#mode) **FindMode**, the three **Allow** properties **True**, [**hWnd**](#hwnd) 0, [**LastAction**](#lastaction) **None** and [**LastError**](#lasterror) 0. It does not close a dialog that is open, and it does not change [**OwnerHwnd**](#ownerhwnd) or the text the dialog edits, so a later [**UpdateFromFlags**](#updatefromflags) can bring that text back. + +```tb check_build check_run +Dim dlg As New cDialogFindReplace + +dlg.FindWhat = "alpha" +dlg.MatchCase = True +dlg.Direction = SearchDirection.Up +dlg.Reset +Debug.Print dlg.ToDescription() +' Output: +' Find: "", Down +``` + +### ShowFind +{: .no_toc } + +Shows the Find dialog. + +Syntax: *object*.**ShowFind** ( ) **As Boolean** + +Calls `FindTextW` and returns at once. Returns **True** when Windows created the dialog, and [**hWnd**](#hwnd) holds its handle. Returns **False** when it did not, and [**LastError**](#lasterror) holds the reason; a missing [**OwnerHwnd**](#ownerhwnd) is the usual one. The dialog opens with [**FindWhat**](#findwhat), [**MatchCase**](#matchcase), [**WholeWord**](#wholeword) and [**Direction**](#direction) in its controls and has the title *Find*. [**Mode**](#mode) becomes **FindMode**. Read the warning at the head of the page: the owner window receives no message from the dialog. + +```tb check_build +Private Sub FindIn(ByVal ownerWindow As LongPtr) + Dim dlg As New cDialogFindReplace + + dlg.OwnerHwnd = ownerWindow + dlg.FindWhat = "twinBASIC" + dlg.Direction = SearchDirection.Down + Debug.Print dlg.ShowFind() +End Sub +``` + +### ShowReplace +{: .no_toc } + +Shows the Replace dialog. + +Syntax: *object*.**ShowReplace** ( ) **As Boolean** + +As [**ShowFind**](#showfind), with `ReplaceTextW`, a title *Replace*, the *Replace with* box filled from [**ReplaceWith**](#replacewith), and the buttons **Replace** and **Replace All** beside **Find Next**. [**Mode**](#mode) becomes **ReplaceMode**. + +```tb check_build +Private Sub ReplaceIn(ByVal ownerWindow As LongPtr) + Dim dlg As New cDialogFindReplace + + dlg.OwnerHwnd = ownerWindow + dlg.FindWhat = "old" + dlg.ReplaceWith = "new" + Debug.Print dlg.ShowReplace() +End Sub +``` + +### ToDescription +{: .no_toc } + +Returns the search as a line of text. + +Syntax: *object*.**ToDescription** ( ) **As String** + +`Find: "text"`, then, when [**Mode**](#mode) is **ReplaceMode**, `, Replace: "text"`, then `, Match Case` and `, Whole Word` for each that is **True**, and last `, Down` or `, Up`. + +```tb check_build check_run +Dim dlg As New cDialogFindReplace + +dlg.FindWhat = "alpha" +dlg.MatchCase = True +dlg.Direction = SearchDirection.Up +Debug.Print dlg.ToDescription() +dlg.Mode = DialogMode.ReplaceMode +dlg.ReplaceWith = "beta" +dlg.WholeWord = True +Debug.Print dlg.ToDescription() +' Output: +' Find: "alpha", Match Case, Up +' Find: "alpha", Replace: "beta", Match Case, Whole Word, Up +``` + +### UpdateFromFlags +{: .no_toc } + +Reads the flags that the dialog sent, and updates the properties. + +Syntax: *object*.**UpdateFromFlags** ( *flags* ) + +*flags* +: *required* A **Long**: the `Flags` member of the `FINDREPLACE` structure at `lParam` of the registered message. See [How the dialog reports](#how-the-dialog-reports). + +The method sets [**MatchCase**](#matchcase) from `FR_MATCHCASE`, [**WholeWord**](#wholeword) from `FR_WHOLEWORD` and [**Direction**](#direction) from `FR_DOWN`, and sets [**LastAction**](#lastaction) from the first of these that is set, in this order: `FR_DIALOGTERM` gives **DialogClosed** (and sets [**hWnd**](#hwnd) to 0), then `FR_FINDNEXT` **FindNext**, `FR_REPLACE` **Replace**, `FR_REPLACEALL` **ReplaceAll**; with none of them it is **None**. Then it reads [**FindWhat**](#findwhat) and [**ReplaceWith**](#replacewith) from the two text buffers of the object. + +Call it only from the handler of the message. Called at another time, it sets the options from whatever flags it is given and replaces both texts with the content of the buffers. The buffers are empty before the first dialog, so [**FindWhat**](#findwhat) and [**ReplaceWith**](#replacewith) then become empty strings. The buffer of the replacement text is filled only by [**ShowReplace**](#showreplace), so after a [**ShowFind**](#showfind) alone **ReplaceWith** becomes empty. + +```tb check_build check_run +Dim dlg As New cDialogFindReplace + +dlg.FindWhat = "alpha" +dlg.UpdateFromFlags &H8 Or &H1 Or &H4 ' FR_FINDNEXT, FR_DOWN, FR_MATCHCASE +Debug.Print dlg.LastAction = FindAction.FindNext +Debug.Print dlg.MatchCase +Debug.Print "[" & dlg.FindWhat & "]" +dlg.UpdateFromFlags &H40 ' FR_DIALOGTERM +Debug.Print dlg.LastAction = FindAction.DialogClosed +Debug.Print dlg.Direction = SearchDirection.Up +' Output: +' True +' True +' [] +' True +' True +``` + +## Enumerations + +The enumerations are declared inside the class. A program names their members with the name of the enumeration in front, as in the samples, such as `FindAction.FindNext`. The name of the class cannot be put in front: `cDialogFindReplace.FindAction.FindNext` is refused with TB5079, *Unrecognized symbol 'cDialogFindReplace'*. + +### DialogMode +{: .no_toc } + +Which dialog an object shows, the type of [**Mode**](#mode). + +| Member | Value | Meaning | +|:-------|------:|:--------| +| **FindMode** | 0 | The Find dialog. | +| **ReplaceMode** | 1 | The Replace dialog. | + +### FindAction +{: .no_toc } + +What the user did in the dialog, the type of [**LastAction**](#lastaction). + +| Member | Value | Meaning | +|:-------|------:|:--------| +| **None** | 0 | Nothing yet. | +| **FindNext** | 1 | The user chose **Find Next**. | +| **Replace** | 2 | The user chose **Replace**. | +| **ReplaceAll** | 3 | The user chose **Replace All**. | +| **DialogClosed** | 4 | The dialog was closed. | + +### SearchDirection +{: .no_toc } + +The direction of a search, the type of [**Direction**](#direction). + +| Member | Value | Meaning | +|:-------|------:|:--------| +| **Up** | 0 | Toward the start of the text. | +| **Down** | 1 | Toward the end of the text. | + +## See Also + +- [cDialogPrint](cDialogPrint) class -- shows the Print dialog +- [cDialogPageSetup](cDialogPageSetup) class -- shows the Page Setup dialog +- [cDialog](cDialog/) class -- shows the Open, Save As and Browse For Folder dialogs +- [TBMANLIB_Dialog](.) package -- overview diff --git a/docs/Reference/Contributed/TBMANLIB_Dialog/cDialogFont.md b/docs/Reference/Contributed/TBMANLIB_Dialog/cDialogFont.md new file mode 100644 index 000000000..d629c201b --- /dev/null +++ b/docs/Reference/Contributed/TBMANLIB_Dialog/cDialogFont.md @@ -0,0 +1,494 @@ +--- +title: cDialogFont +parent: TBMANLIB_Dialog Package +permalink: /tB/Packages/Contributed/TBMANLIB_Dialog/cDialogFont +has_toc: false +--- + +# cDialogFont class +{: .no_toc } + +A **cDialogFont** shows the Windows Font dialog, in which the user picks a face, a style, a size and, optionally, a color and the effects underline and strikeout. It holds the choice as properties, and describes it as text: a short description and a line of CSS. + +**Properties:** [**Bold**](#bold), [**Color**](#color), [**FaceName**](#facename), [**FixedPitchOnly**](#fixedpitchonly), [**ForceFontExist**](#forcefontexist), [**Italic**](#italic), [**MaxSize**](#maxsize), [**MinSize**](#minsize), [**OwnerHwnd**](#ownerhwnd), [**ScreenFontsOnly**](#screenfontsonly), [**ShowEffects**](#showeffects), [**Size**](#size), [**StrikeOut**](#strikeout), [**Style**](#style), [**Underline**](#underline). +**Methods:** [**Reset**](#reset), [**ShowDialog**](#showdialog), [**ToCssFont**](#tocssfont), [**ToDescription**](#todescription). +**Enumerations:** [**FontFamily**](#fontfamily), [**FontPitch**](#fontpitch), [**FontStyle**](#fontstyle), [**FontWeight**](#fontweight). + +Create an object with `Dim dlg As New cDialogFont`. The class is declared `[COMCreatable(False)]`, so **CreateObject** cannot make one, and **New** can. + +```tb check_build +Dim dlg As New cDialogFont + +dlg.FaceName = "Consolas" +dlg.Size = 11 +If dlg.ShowDialog() Then + Debug.Print dlg.ToDescription() + Debug.Print dlg.ToCssFont() +Else + Debug.Print "Cancelled" +End If +``` + +## How the dialog is shown + +[**ShowDialog**](#showdialog) calls `ChooseFontW` with a `LOGFONT` made from the properties and the flag `CF_INITTOLOGFONTSTRUCT`, which is always set. The size is passed in points, as the structure holds it in tenths of a point, and as a height in pixels at 96 dots per inch (`-Size * 96 / 72`). Four properties set a flag when they are **True**: [**ScreenFontsOnly**](#screenfontsonly) (`CF_SCREENFONTS`), [**ShowEffects**](#showeffects) (`CF_EFFECTS`), [**ForceFontExist**](#forcefontexist) (`CF_FORCEFONTEXIST`) and [**FixedPitchOnly**](#fixedpitchonly) (`CF_FIXEDPITCHONLY`, together with a fixed pitch in the `LOGFONT`). `CF_LIMITSIZE` is set when [**MinSize**](#minsize) or [**MaxSize**](#maxsize) is above 0. The class sets no hook, template, printer device context or help button. The dialog is modal and has the title *Font*. + +When the user chooses **OK**, [**ShowDialog**](#showdialog) stores the face, the size, the bold and italic parts of the style, the underline and strikeout effects and the color in the properties, and returns **True**. When the user chooses **Cancel**, or closes the dialog, it returns **False** and leaves every property as it was. No error is raised in either case. + +The face list holds every font that Windows lists for the screen, including the vertical variants, whose names begin with `@`: the class sets no flag that hides them. The list of styles, the script box and the colors depend on the face and on Windows, and the class reads neither the style name nor the script that the user chose, only the weight and the italic bit. + +> [!WARNING] +> **ShowDialog** also returns **False**, without showing a dialog, when the call to Windows fails. A **MinSize** above 0 with **MaxSize** left at 0, or a **MinSize** above **MaxSize**, makes it fail. The class does not read the error code, so the program cannot tell such a failure from a **Cancel**. Set both [**MinSize**](#minsize) and [**MaxSize**](#maxsize), or neither. + +## Properties + +### Bold +{: .no_toc } + +Whether the font is bold. + +Syntax: *object*.**Bold** [ **=** *value* ] + +A **Boolean**, **False** by default. It is bit 1 of [**Style**](#style): setting it sets or clears that bit and leaves bit 2 (italic) alone. After a [**ShowDialog**](#showdialog) that returned **True** it is **True** when the weight of the chosen font is 700 or more, so a style such as *Semibold* (600) gives **False**. + +```tb check_build check_run +Dim dlg As New cDialogFont + +dlg.Bold = True +dlg.Italic = True +dlg.Bold = False +Debug.Print dlg.Style +Debug.Print dlg.Italic +' Output: +' 2 +' True +``` + +### Color +{: .no_toc } + +The color of the text. + +Syntax: *object*.**Color** [ **=** *value* ] + +A **Long** in the layout of the **RGB** function (`&H00BBGGRR`), black by default. It is the color the dialog shows when [**ShowEffects**](#showeffects) is **True**, and after **OK** it holds one of the 16 colors of the dialog's color box, such as `&H8000` for *Green*. Any **Long** is accepted and stored as it is. + +```tb check_build +Dim dlg As New cDialogFont + +dlg.Color = RGB(0, 0, 128) +dlg.ShowEffects = True +Debug.Print Hex$(dlg.Color) +``` + +### FaceName +{: .no_toc } + +The name of the font face. + +Syntax: *object*.**FaceName** [ **=** *value* ] + +A **String**, `Segoe UI` by default. The dialog opens with that face selected. A name that is not an installed face leaves the face box empty, and then the property [**ForceFontExist**](#forcefontexist) decides what **OK** does. + +```tb check_build +Dim dlg As New cDialogFont + +dlg.FaceName = "Courier New" +Debug.Print dlg.FaceName +``` + +### FixedPitchOnly +{: .no_toc } + +Whether the dialog lists only fixed-pitch fonts. + +Syntax: *object*.**FixedPitchOnly** [ **=** *value* ] + +A **Boolean**, **False** by default. When it is **True**, the dialog lists only fonts whose characters have one width, such as `Consolas` and `Courier New`. Set [**FaceName**](#facename) to one of them, or the face box opens without a selection. + +```tb check_build +Dim dlg As New cDialogFont + +dlg.FixedPitchOnly = True +dlg.FaceName = "Consolas" +Debug.Print dlg.ShowDialog() +``` + +### ForceFontExist +{: .no_toc } + +Whether the dialog accepts only the name of an installed face. + +Syntax: *object*.**ForceFontExist** [ **=** *value* ] + +A **Boolean**, **True** by default. When it is **True** and the face box holds a name that is not in the list, **OK** shows a message, *There is no font with that name. Choose a font from the list of fonts.*, and the dialog stays open; **Cancel** then returns **False** and keeps [**FaceName**](#facename). When it is **False**, **OK** closes the dialog. + +> [!NOTE] +> With **ForceFontExist** **False** and a [**FaceName**](#facename) that is not installed, **OK** returns **True** and sets **FaceName** to an empty string. + +```tb check_build +Dim dlg As New cDialogFont + +dlg.ForceFontExist = False +dlg.FaceName = "A Face Of My Own" +If dlg.ShowDialog() Then + Debug.Print "[" & dlg.FaceName & "]" +End If +``` + +### Italic +{: .no_toc } + +Whether the font is italic. + +Syntax: *object*.**Italic** [ **=** *value* ] + +A **Boolean**, **False** by default. It is bit 2 of [**Style**](#style), set and cleared as [**Bold**](#bold) sets and clears bit 1. + +```tb check_build check_run +Dim dlg As New cDialogFont + +dlg.Italic = True +Debug.Print dlg.Style +Debug.Print dlg.Bold +' Output: +' 2 +' False +``` + +### MaxSize +{: .no_toc } + +The largest size, in points, that the dialog accepts. + +Syntax: *object*.**MaxSize** [ **=** *value* ] + +A **Long**, 0 by default, which means no limit. Above 0, [**ShowDialog**](#showdialog) sets `CF_LIMITSIZE` and passes the value as the largest size. The size list then ends at the largest standard size not above it, and a size that the user types above it is refused with a message and the dialog stays open. The message, for the limits 10 and 14, is *Size must be between 10 and 14 points.* + +A **MaxSize** alone is valid. A [**MinSize**](#minsize) above it makes the call fail: see the warning on [**ShowDialog**](#showdialog). + +```tb check_build +Dim dlg As New cDialogFont + +dlg.MinSize = 10 +dlg.MaxSize = 14 +dlg.Size = 12 +Debug.Print dlg.ShowDialog() +``` + +### MinSize +{: .no_toc } + +The smallest size, in points, that the dialog accepts. + +Syntax: *object*.**MinSize** [ **=** *value* ] + +A **Long**, 0 by default, which means no limit. Above 0, [**ShowDialog**](#showdialog) sets `CF_LIMITSIZE` and passes the value as the smallest size. The size list then starts at the smallest standard size not below it. + +> [!WARNING] +> A **MinSize** above 0 needs a [**MaxSize**](#maxsize) of at least the same value. With **MaxSize** left at 0, [**ShowDialog**](#showdialog) returns **False** at once, without a dialog, as if the user had cancelled. + +```tb check_build +Dim dlg As New cDialogFont + +dlg.MinSize = 8 +dlg.MaxSize = 72 +Debug.Print dlg.MinSize +``` + +### OwnerHwnd +{: .no_toc } + +The window that owns the dialog. + +Syntax: *object*.**OwnerHwnd** [ **=** *value* ] + +A **LongPtr** window handle, 0 by default, which gives the dialog no owner. [**ShowDialog**](#showdialog) passes it as the owner window of the call. + +```tb check_build +Private Sub PickFont(ByVal ownerWindow As LongPtr) + Dim dlg As New cDialogFont + + dlg.OwnerHwnd = ownerWindow ' the handle of a window of the program + Debug.Print dlg.OwnerHwnd + Debug.Print dlg.ShowDialog() +End Sub +``` + +### ScreenFontsOnly +{: .no_toc } + +Whether the dialog lists the fonts of the screen. + +Syntax: *object*.**ScreenFontsOnly** [ **=** *value* ] + +A **Boolean**, **True** by default. When it is **True**, [**ShowDialog**](#showdialog) sets `CF_SCREENFONTS`. The class never sets the flag for printer fonts, and the documentation of `ChooseFont` asks for one of the two, so leave the property **True**. On Windows 10 a **False** still shows the same dialog with the same fonts. + +```tb check_build +Dim dlg As New cDialogFont + +dlg.ScreenFontsOnly = True +Debug.Print dlg.ScreenFontsOnly +``` + +### ShowEffects +{: .no_toc } + +Whether the dialog offers the effects strikeout and underline, and the color. + +Syntax: *object*.**ShowEffects** [ **=** *value* ] + +A **Boolean**, **True** by default. When it is **True**, [**ShowDialog**](#showdialog) sets `CF_EFFECTS` and the dialog shows the *Strikeout* and *Underline* check boxes and the *Color* box. When it is **False** they are hidden, and [**Underline**](#underline), [**StrikeOut**](#strikeout) and [**Color**](#color) keep the values they had. + +```tb check_build +Dim dlg As New cDialogFont + +dlg.ShowEffects = False +dlg.Underline = True +If dlg.ShowDialog() Then Debug.Print dlg.Underline ' still True +``` + +### Size +{: .no_toc } + +The size of the font, in points. + +Syntax: *object*.**Size** [ **=** *value* ] + +A **Double**, 9 by default. A value of 0 or less is ignored, without an error, and the size is not changed. After **OK** it holds the size the dialog returns, which is a number of tenths of a point divided by 10. + +```tb check_build check_run +Dim dlg As New cDialogFont + +dlg.Size = 12 +dlg.Size = 0 ' ignored +dlg.Size = -3 ' ignored +Debug.Print dlg.Size +' Output: +' 12 +``` + +### StrikeOut +{: .no_toc } + +Whether the text is struck out. + +Syntax: *object*.**StrikeOut** [ **=** *value* ] + +A **Boolean**, **False** by default. It is the strikeout effect of the dialog. [**ToDescription**](#todescription) names it, and [**ToCssFont**](#tocssfont) leaves it out. + +```tb check_build +Dim dlg As New cDialogFont + +dlg.StrikeOut = True +Debug.Print dlg.ToDescription() +``` + +### Style +{: .no_toc } + +The bold and italic parts of the style, as one number. + +Syntax: *object*.**Style** [ **=** *value* ] + +A [**FontStyle**](#fontstyle). Bit 1 is [**Bold**](#bold) and bit 2 is [**Italic**](#italic). The property holds the number that was assigned, whatever it is: `4` is accepted and gives **Bold** and **Italic** both **False**. + +```tb check_build check_run +Dim dlg As New cDialogFont + +dlg.Style = FontStyle.StyleBoldItalic +Debug.Print dlg.Bold +Debug.Print dlg.Italic +dlg.Style = 4 +Debug.Print dlg.Bold +' Output: +' True +' True +' False +``` + +### Underline +{: .no_toc } + +Whether the text is underlined. + +Syntax: *object*.**Underline** [ **=** *value* ] + +A **Boolean**, **False** by default. It is the underline effect of the dialog. [**ToCssFont**](#tocssfont) writes it as `text-decoration`. + +```tb check_build +Dim dlg As New cDialogFont + +dlg.Underline = True +Debug.Print dlg.ToCssFont() +``` + +## Methods + +### Reset +{: .no_toc } + +Restores every property to its starting value. + +Syntax: *object*.**Reset** + +[**FaceName**](#facename) becomes `Segoe UI`, [**Size**](#size) 9, [**Style**](#style) regular, [**Color**](#color) black, [**Underline**](#underline), [**StrikeOut**](#strikeout) and [**FixedPitchOnly**](#fixedpitchonly) **False**, [**ForceFontExist**](#forcefontexist), [**ShowEffects**](#showeffects) and [**ScreenFontsOnly**](#screenfontsonly) **True**, and [**OwnerHwnd**](#ownerhwnd), [**MinSize**](#minsize) and [**MaxSize**](#maxsize) 0. + +```tb check_build check_run +Dim dlg As New cDialogFont + +dlg.FaceName = "Courier New" +dlg.Size = 14 +dlg.Bold = True +dlg.Reset +Debug.Print dlg.ToDescription() +' Output: +' Segoe UI 9pt +``` + +### ShowDialog +{: .no_toc } + +Shows the Font dialog. + +Syntax: *object*.**ShowDialog** ( ) **As Boolean** + +Returns **True** when the user chose **OK**, and then the properties hold the choice. Returns **False** when the user cancelled, and the properties are unchanged. Any error inside the method is trapped and also gives **False**. See [How the dialog is shown](#how-the-dialog-is-shown), and the warning there: **False** is also the answer to a call that Windows refused. + +```tb check_build +Dim dlg As New cDialogFont + +dlg.FaceName = "Courier New" +dlg.Size = 10 +If dlg.ShowDialog() Then + Debug.Print dlg.ToDescription() +End If +``` + +### ToCssFont +{: .no_toc } + +Returns the font as a line of CSS. + +Syntax: *object*.**ToCssFont** ( ) **As String** + +The result has the form + +`font-family: 'Segoe UI'; font-size: 9pt; font-weight: normal; font-style: normal; text-decoration: none;` + +with the face name between single quotes, the size in points, `bold` or `normal`, `italic` or `normal`, and `underline` or `none`. It does not write [**StrikeOut**](#strikeout) or [**Color**](#color). A single quote in the face name is written as it is, without an escape, and ends the quoted name early. A fractional size is written with the decimal separator of the regional settings of the computer, so the CSS is wrong where that is a comma. + +```tb check_build check_run +Dim dlg As New cDialogFont + +dlg.FaceName = "Courier New" +dlg.Size = 14 +dlg.Bold = True +dlg.Italic = True +dlg.Underline = True +Debug.Print dlg.ToCssFont() +' Output: +' font-family: 'Courier New'; font-size: 14pt; font-weight: bold; font-style: italic; text-decoration: underline; +``` + +### ToDescription +{: .no_toc } + +Returns the font as a short line of text. + +Syntax: *object*.**ToDescription** ( ) **As String** + +The face name, a space, the size and `pt`, then ` Bold`, ` Italic`, ` Underline` and ` Strikeout` for each that is set, in that order. The color is not part of it. A fractional size is written with the decimal separator of the regional settings of the computer. + +```tb check_build check_run +Dim dlg As New cDialogFont + +Debug.Print dlg.ToDescription() +dlg.FaceName = "Courier New" +dlg.Size = 14 +dlg.Style = FontStyle.StyleBoldItalic +dlg.Underline = True +dlg.StrikeOut = True +Debug.Print dlg.ToDescription() +' Output: +' Segoe UI 9pt +' Courier New 14pt Bold Italic Underline Strikeout +``` + +## Enumerations + +The enumerations are declared inside the class. A program names their members with the name of the enumeration in front, as in the samples, or by the member alone, such as `StyleBold`. The name of the class cannot be put in front: `cDialogFont.FontStyle.StyleBold` is refused with TB5079, *Unrecognized symbol 'cDialogFont'*. Only **FontStyle** is used by a member, the property [**Style**](#style). The other three hold Windows constants for the font structure and are not taken or returned by any member. + +### FontFamily +{: .no_toc } + +The font families of Windows. + +| Member | Value | +|:-------|------:| +| **FF_DONTCARE** | 0 | +| **FF_ROMAN** | 16 | +| **FF_SWISS** | 32 | +| **FF_MODERN** | 48 | +| **FF_SCRIPT** | 64 | +| **FF_DECORATIVE** | 80 | + +### FontPitch +{: .no_toc } + +The pitch of a font. + +| Member | Value | +|:-------|------:| +| **DEFAULT_PITCH** | 0 | +| **FIXED_PITCH** | 1 | +| **VARIABLE_PITCH** | 2 | + +### FontStyle +{: .no_toc } + +The bold and italic parts of a style, the type of [**Style**](#style). + +| Member | Value | Meaning | +|:-------|------:|:--------| +| **StyleRegular** | 0 | Neither bold nor italic. | +| **StyleBold** | 1 | Bold. | +| **StyleItalic** | 2 | Italic. | +| **StyleBoldItalic** | 3 | Bold and italic. | + +### FontWeight +{: .no_toc } + +The weights of a font, as Windows numbers them. + +| Member | Value | +|:-------|------:| +| **FW_DONTCARE** | 0 | +| **FW_THIN** | 100 | +| **FW_EXTRALIGHT** | 200 | +| **FW_LIGHT** | 300 | +| **FW_NORMAL** | 400 | +| **FW_MEDIUM** | 500 | +| **FW_SEMIBOLD** | 600 | +| **FW_BOLD** | 700 | +| **FW_EXTRABOLD** | 800 | +| **FW_HEAVY** | 900 | + +```tb check_build check_run +Debug.Print FontWeight.FW_BOLD +Debug.Print FontFamily.FF_SWISS +Debug.Print FontPitch.FIXED_PITCH +Debug.Print FontStyle.StyleBoldItalic +' Output: +' 700 +' 32 +' 1 +' 3 +``` + +## See Also + +- [cDialogColor](cDialogColor) class -- shows the Color dialog +- [cDialogPrint](cDialogPrint) class -- shows the Print dialog +- [cDialog](cDialog/) class -- shows the Open, Save As and Browse For Folder dialogs +- [TBMANLIB_Dialog](.) package -- overview diff --git a/docs/Reference/Contributed/TBMANLIB_Dialog/cDialogInputBox.md b/docs/Reference/Contributed/TBMANLIB_Dialog/cDialogInputBox.md new file mode 100644 index 000000000..4ecca0f0b --- /dev/null +++ b/docs/Reference/Contributed/TBMANLIB_Dialog/cDialogInputBox.md @@ -0,0 +1,626 @@ +--- +title: cDialogInputBox +parent: TBMANLIB_Dialog Package +permalink: /tB/Packages/Contributed/TBMANLIB_Dialog/cDialogInputBox +has_toc: false +--- + +# cDialogInputBox class +{: .no_toc } + +A **cDialogInputBox** asks the user for one line of text, checks it against rules and asks again when the text fails, up to three times. The box is the **InputBox** of the run-time library, and the messages about a failed check are ordinary message boxes: the class has no window of its own. + +A **cDialogInputBox** is made with **New**: `Dim box As New cDialogInputBox`. It cannot be made with **CreateObject**. + +The properties [**Title**](#title), [**Prompt**](#prompt), [**DefaultValue**](#defaultvalue) and [**Placeholder**](#placeholder) decide what the box shows, and [**Validation**](#validation), [**MinLength**](#minlength), [**MaxLength**](#maxlength) and [**AllowEmpty**](#allowempty) what it accepts. [**ShowDialog**](#showdialog) shows the box and puts the accepted text into [**Value**](#value). [**ShowSimple**](#showsimple), [**ShowInteger**](#showinteger), [**ShowNumeric**](#shownumeric), [**ShowEmail**](#showemail), [**ShowPhone**](#showphone) and [**ShowURL**](#showurl) set the rules for a kind of text and return the value in one call. + +```tb check_build +Dim box As New cDialogInputBox + +box.Title = "New user" +box.Prompt = "Name of the user?" +box.MinLength = 3 +box.MaxLength = 20 +box.AllowEmpty = False + +If box.ShowDialog() Then + Debug.Print "Hello, " & box.Value +Else + Debug.Print "No name given" +End If +``` + +## How the box works + +The text of the box is [**Prompt**](#prompt), followed by a blank line and `(Hint: ` and [**Placeholder**](#placeholder) and `)` when a placeholder is set, followed by a blank line and a line on the length when [**MinLength**](#minlength) or [**MaxLength**](#maxlength) is above 0: `Length: 3 to 8 characters`, `Length: At least 3 characters` or `Length: Maximum 8 characters`. The edit box holds [**DefaultValue**](#defaultvalue). + +[**ShowDialog**](#showdialog) then works like this: + +1. When the user cancels, [**Cancelled**](#cancelled) is set to **True**, [**Value**](#value) to an empty string, and the result is **False**. +2. Otherwise [**ValidateInput**](#validateinput) checks the text. A text that passes becomes [**Value**](#value), and the result is **True**. +3. A text that fails is reported in a message box that shows the message of [**ValidateInput**](#validateinput) under the title of the box, and the box is shown again with [**DefaultValue**](#defaultvalue) in the edit box, not with the text that failed. +4. The third failure shows the message with `Maximum attempts reached.` after a blank line. The result is **False**, [**Cancelled**](#cancelled) is **True**, and [**Value**](#value) keeps what it held before the call. + +An empty string passes when [**AllowEmpty**](#allowempty) is **True** (the default) and no [**MinLength**](#minlength) is set. The result of the call does not tell the user's cancelling apart from a refusal; both give **False** and [**Cancelled**](#cancelled) **True**. The two differ only in [**Value**](#value). + +> [!WARNING] +> In version 1.1.0.0 the properties [**Multiline**](#multiline), [**PasswordChar**](#passwordchar), [**OwnerHwnd**](#ownerhwnd) and [**CustomPattern**](#custompattern) are stored but not used. The box is always a single line, it shows what is typed without masking it, it has no owner window and the owner stays usable while it is open, and the value **Custom** of [**Validation**](#validation) accepts any text that is not empty. A program that sets [**PasswordChar**](#passwordchar) to hide a secret shows the secret on the screen. [**cDialogPassword**](cDialogPassword) masks its entry. + +## Properties + +### AllowEmpty +{: .no_toc } + +Whether an empty entry is accepted. + +Syntax: *object*.**AllowEmpty** [ **=** *value* ] + +*value* +: A **Boolean**. The default is **True**. + +With **False**, [**ValidateInput**](#validateinput) refuses an empty string with `Input cannot be empty`. [**ShowSimple**](#showsimple) sets it to **True**, and [**ShowInteger**](#showinteger), [**ShowNumeric**](#shownumeric), [**ShowEmail**](#showemail), [**ShowPhone**](#showphone) and [**ShowURL**](#showurl) to **False**, and the setting stays. + +```tb check_build check_run +Dim box As New cDialogInputBox + +Debug.Print box.AllowEmpty ' True +box.AllowEmpty = False +Debug.Print "[" & box.ValidateInput("") & "]" ' [Input cannot be empty] +``` + +### Cancelled +{: .no_toc } + +Whether the last call to [**ShowDialog**](#showdialog) ended without a value, read-only. + +Syntax: *object*.**Cancelled** **As Boolean** + +**False** in a new object, and at the start of every call to [**ShowDialog**](#showdialog). **True** after the user cancelled, and after the third refused entry. + +```tb check_build check_run +Dim box As New cDialogInputBox + +Debug.Print box.Cancelled ' False +``` + +### CustomPattern +{: .no_toc } + +A pattern for the value **Custom** of [**Validation**](#validation), which the class does not use. + +Syntax: *object*.**CustomPattern** [ **=** *value* ] + +*value* +: A **String**. The default is an empty string. + +With [**Validation**](#validation) set to **Custom**, [**ValidateInput**](#validateinput) never compares the text with the pattern. It refuses only an empty text when a pattern is set, with `Input does not match required format`; any other text passes. To check a text against a pattern, use [**ValidateInput**](#validateinput) as the first check and make the second one in the program, for instance with the **tbRegExp** package. + +```tb check_build check_run +Dim box As New cDialogInputBox + +box.Validation = InputValidation.Custom +box.CustomPattern = "[0-9]+" +Debug.Print "[" & box.ValidateInput("letters only") & "]" ' [] +Debug.Print "[" & box.ValidateInput("") & "]" ' [Input does not match required format] +``` + +### DefaultValue +{: .no_toc } + +The text that the edit box holds when it opens. + +Syntax: *object*.**DefaultValue** [ **=** *value* ] + +*value* +: A **String**. The default is an empty string. + +The box shows it again after a refused entry. [**ShowSimple**](#showsimple), [**ShowInteger**](#showinteger) and [**ShowNumeric**](#shownumeric) set it from their *defaultValue* argument, [**ShowEmail**](#showemail) and [**ShowPhone**](#showphone) to an empty string and [**ShowURL**](#showurl) to `https://`. + +```tb check_build check_run +Dim box As New cDialogInputBox + +box.DefaultValue = "Guest" +Debug.Print box.DefaultValue ' Guest +``` + +### MaxLength +{: .no_toc } + +The longest text that is accepted. + +Syntax: *object*.**MaxLength** [ **=** *value* ] + +*value* +: A **Long**. 0, the default, means no limit. + +[**ValidateInput**](#validateinput) refuses a longer text with `Input must not exceed N characters`. The edit box itself does not stop the typing. + +```tb check_build check_run +Dim box As New cDialogInputBox + +box.MaxLength = 5 +Debug.Print "[" & box.ValidateInput("abcdef") & "]" ' [Input must not exceed 5 characters] +Debug.Print "[" & box.ValidateInput("abcde") & "]" ' [] +``` + +### MinLength +{: .no_toc } + +The shortest text that is accepted. + +Syntax: *object*.**MinLength** [ **=** *value* ] + +*value* +: A **Long**. 0, the default, means no minimum. + +[**ValidateInput**](#validateinput) refuses a shorter text, an empty one too, with `Input must be at least N characters`. This check comes first, so an empty text with a minimum gets this message and not the one of [**AllowEmpty**](#allowempty). + +```tb check_build check_run +Dim box As New cDialogInputBox + +box.MinLength = 3 +Debug.Print "[" & box.ValidateInput("ab") & "]" ' [Input must be at least 3 characters] +Debug.Print "[" & box.ValidateInput("abc") & "]" ' [] +``` + +### Multiline +{: .no_toc } + +A property that has no effect. + +Syntax: *object*.**Multiline** [ **=** *value* ] + +*value* +: A **Boolean**. The default is **False**. + +The value is stored. The box has one line whatever it is; see the warning [above](#how-the-box-works). + +```tb check_build check_run +Dim box As New cDialogInputBox + +Debug.Print box.Multiline ' False +``` + +### OwnerHwnd +{: .no_toc } + +A property that has no effect. + +Syntax: *object*.**OwnerHwnd** [ **=** *value* ] + +*value* +: A **LongPtr**. The default is 0. + +The value is stored. The box has no owner whatever it is; see the warning [above](#how-the-box-works). [**Reset**](#reset) sets it to 0. + +```tb check_build check_run +Dim box As New cDialogInputBox + +Debug.Print box.OwnerHwnd ' 0 +``` + +### PasswordChar +{: .no_toc } + +A property that has no effect. + +Syntax: *object*.**PasswordChar** [ **=** *value* ] + +*value* +: A **String**. The default is an empty string. + +The value is stored. The box does not mask the text, whatever it is; see the warning [above](#how-the-box-works). + +```tb check_build check_run +Dim box As New cDialogInputBox + +box.PasswordChar = "*" +Debug.Print box.PasswordChar ' * +``` + +### Placeholder +{: .no_toc } + +A hint that the box shows under the prompt. + +Syntax: *object*.**Placeholder** [ **=** *value* ] + +*value* +: A **String**. The default is an empty string. + +The hint is not shown in the edit box. When it is not empty, the prompt gets the line `(Hint: ` followed by the placeholder and `)`. [**ShowEmail**](#showemail), [**ShowPhone**](#showphone) and [**ShowURL**](#showurl) set it to a sample value, and it stays set for the later calls of other methods, [**ShowSimple**](#showsimple) included. Set it to an empty string to remove the hint. + +```tb check_build check_run +Dim box As New cDialogInputBox + +box.Placeholder = "like ABC" +Debug.Print box.Placeholder ' like ABC +``` + +### Prompt +{: .no_toc } + +The text above the edit box. + +Syntax: *object*.**Prompt** [ **=** *value* ] + +*value* +: A **String**. The default is `Please enter a value:`. + +```tb check_build check_run +Dim box As New cDialogInputBox + +Debug.Print box.Prompt ' Please enter a value: +``` + +### Title +{: .no_toc } + +The title of the box and of the messages about a refused entry. + +Syntax: *object*.**Title** [ **=** *value* ] + +*value* +: A **String**. The default is `Input`. + +```tb check_build check_run +Dim box As New cDialogInputBox + +Debug.Print box.Title ' Input +``` + +### Validation +{: .no_toc } + +The kind of text that is accepted. + +Syntax: *object*.**Validation** [ **=** *value* ] + +*value* +: An [**InputValidation**](#inputvalidation) member. The default is **None**. + +[**ValidateInput**](#validateinput) applies the rule after the checks of [**MinLength**](#minlength), [**MaxLength**](#maxlength) and [**AllowEmpty**](#allowempty). The [**Show\***](#showsimple) methods other than [**ShowDialog**](#showdialog) set it, and it stays set. + +```tb check_build check_run +Dim box As New cDialogInputBox + +Debug.Print box.Validation = InputValidation.None ' True +box.Validation = InputValidation.Email +Debug.Print box.Validation ' 5 +``` + +### Value +{: .no_toc } + +The text that [**ShowDialog**](#showdialog) accepted last, read-only. + +Syntax: *object*.**Value** **As String** + +An empty string in a new object, after a cancel, and after [**Reset**](#reset). A call that ends with three refusals leaves it as it was. + +```tb check_build check_run +Dim box As New cDialogInputBox + +Debug.Print Len(box.Value) ' 0 +``` + +## Methods + +### GetValueAsInteger +{: .no_toc } + +Returns [**Value**](#value) as a **Long**. + +Syntax: *object*.**GetValueAsInteger** ( ) **As Long** + +Returns 0 when [**Value**](#value) is not a number, and otherwise the **CLng** of it, which rounds: `3.5` gives 4. A value that a **Long** cannot hold raises error 6. + +```tb check_build +Dim box As New cDialogInputBox + +box.Validation = InputValidation.Numeric +If box.ShowDialog() Then Debug.Print box.GetValueAsInteger() +``` + +### GetValueAsNumber +{: .no_toc } + +Returns [**Value**](#value) as a **Double**. + +Syntax: *object*.**GetValueAsNumber** ( ) **As Double** + +Returns 0 when [**Value**](#value) is not a number, and otherwise the **CDbl** of it. + +```tb check_build check_run +Dim box As New cDialogInputBox + +Debug.Print box.GetValueAsNumber() ' 0 +``` + +### Reset +{: .no_toc } + +Restores every property to its starting value. + +Syntax: *object*.**Reset** + +Sets [**Title**](#title) to `Input`, [**Prompt**](#prompt) to `Please enter a value:`, [**Value**](#value), [**DefaultValue**](#defaultvalue), [**Placeholder**](#placeholder), [**CustomPattern**](#custompattern) and [**PasswordChar**](#passwordchar) to empty strings, [**MinLength**](#minlength), [**MaxLength**](#maxlength) and [**OwnerHwnd**](#ownerhwnd) to 0, [**Validation**](#validation) to **None**, [**AllowEmpty**](#allowempty) to **True** and [**Multiline**](#multiline) and [**Cancelled**](#cancelled) to **False**. + +```tb check_build check_run +Dim box As New cDialogInputBox + +box.Title = "Age" +box.Validation = InputValidation.Integer +box.Reset +Debug.Print box.Title ' Input +Debug.Print box.Validation = InputValidation.None ' True +``` + +### ShowDialog +{: .no_toc } + +Shows the box and checks the entry. + +Syntax: *object*.**ShowDialog** ( ) **As Boolean** + +Returns **True** when the user entered a text that passed the checks, and then [**Value**](#value) holds it. Returns **False** when the user cancelled or the third attempt failed, with [**Cancelled**](#cancelled) **True**. [How the box works](#how-the-box-works) lists the steps. + +```tb check_build +Dim box As New cDialogInputBox + +box.Title = "Product code" +box.Prompt = "Enter the product code:" +box.Validation = InputValidation.AlphaNumeric +box.MinLength = 4 +box.MaxLength = 8 +box.DefaultValue = "AB12" + +If box.ShowDialog() Then + Debug.Print "Code: " & box.Value +Else + Debug.Print "Cancelled" +End If +``` + +### ShowEmail +{: .no_toc } + +Asks for an email address. + +Syntax: *object*.**ShowEmail** ( *prompt* [ , *title* ] ) **As String** + +*prompt* +: *required* The text above the edit box. + +*title* +: *optional* The title. The default is `Enter Email`. + +Sets [**Prompt**](#prompt), [**Title**](#title), [**DefaultValue**](#defaultvalue) (empty), [**Validation**](#validation) (**Email**), [**AllowEmpty**](#allowempty) (**False**) and [**Placeholder**](#placeholder) (`example@domain.com`), calls [**ShowDialog**](#showdialog) and returns [**Value**](#value). Returns an empty string when [**ShowDialog**](#showdialog) returned **False**. An address passes when it has exactly one `@` and at least one `.` anywhere. + +```tb check_build +Dim box As New cDialogInputBox +Dim address As String + +address = box.ShowEmail("Where should the report be sent?") +If Not box.Cancelled Then Debug.Print "Send to " & address +``` + +### ShowInteger +{: .no_toc } + +Asks for a whole number. + +Syntax: *object*.**ShowInteger** ( *prompt* [ , *title* [ , *defaultValue* ] ] ) **As Long** + +*prompt* +: *required* The text above the edit box. + +*title* +: *optional* The title. The default is `Enter Integer`. + +*defaultValue* +: *optional* The text in the edit box. The default is an empty string. + +Sets the three properties that correspond to the arguments, [**Validation**](#validation) (**Integer**) and [**AllowEmpty**](#allowempty) (**False**), calls [**ShowDialog**](#showdialog) and returns [**Value**](#value) as a **Long**. A cancelled box returns 0, which is also a possible entry, so test [**Cancelled**](#cancelled). The text `1e3` passes and returns 1000. + +> [!NOTE] +> A text that passes the check but that a **Long** cannot hold, such as `99999999999`, raises error 6 after the box has closed. [**Value**](#value) then holds the text. + +```tb check_build +Dim box As New cDialogInputBox +Dim copies As Long + +copies = box.ShowInteger("How many copies?", "Print", "1") +If Not box.Cancelled Then Debug.Print copies & " copies" +``` + +### ShowNumeric +{: .no_toc } + +Asks for a number. + +Syntax: *object*.**ShowNumeric** ( *prompt* [ , *title* [ , *defaultValue* ] ] ) **As Double** + +*prompt* +: *required* The text above the edit box. + +*title* +: *optional* The title. The default is `Enter Number`. + +*defaultValue* +: *optional* The text in the edit box. The default is an empty string. + +As [**ShowInteger**](#showinteger), with [**Validation**](#validation) **Numeric** and a **Double** as the result: `3.5` returns 3.5. A cancelled box returns 0, so test [**Cancelled**](#cancelled). + +```tb check_build +Dim box As New cDialogInputBox +Dim price As Double + +price = box.ShowNumeric("Price?", "New article", "9.99") +If Not box.Cancelled Then Debug.Print price +``` + +### ShowPhone +{: .no_toc } + +Asks for a telephone number. + +Syntax: *object*.**ShowPhone** ( *prompt* [ , *title* ] ) **As String** + +*prompt* +: *required* The text above the edit box. + +*title* +: *optional* The title. The default is `Enter Phone`. + +Sets [**Prompt**](#prompt), [**Title**](#title), [**DefaultValue**](#defaultvalue) (empty), [**Validation**](#validation) (**Phone**), [**AllowEmpty**](#allowempty) (**False**) and [**Placeholder**](#placeholder) (`+1 (555) 123-4567`), calls [**ShowDialog**](#showdialog) and returns [**Value**](#value), or an empty string for **False**. Digits, spaces and `-`, `(`, `)`, `+` and `.` are accepted. + +```tb check_build +Dim box As New cDialogInputBox +Dim number As String + +number = box.ShowPhone("Telephone number?") +If Not box.Cancelled Then Debug.Print number +``` + +### ShowSimple +{: .no_toc } + +Asks for a text without rules. + +Syntax: *object*.**ShowSimple** ( *prompt* [ , *title* [ , *defaultValue* ] ] ) **As String** + +*prompt* +: *required* The text above the edit box. + +*title* +: *optional* The title. The default is `Input`. + +*defaultValue* +: *optional* The text in the edit box. The default is an empty string. + +Sets the three properties that correspond to the arguments, sets [**Validation**](#validation) to **None**, [**AllowEmpty**](#allowempty) to **True** and [**MinLength**](#minlength) and [**MaxLength**](#maxlength) to 0, calls [**ShowDialog**](#showdialog) and returns [**Value**](#value), or an empty string for **False**. An empty result does not tell a cancel from an empty entry: test [**Cancelled**](#cancelled). A [**Placeholder**](#placeholder) that an earlier call set is still shown. + +```tb check_build +Dim box As New cDialogInputBox +Dim answer As String + +answer = box.ShowSimple("Your name?", "Welcome", "Guest") +If Not box.Cancelled Then Debug.Print "Hello, " & answer +``` + +### ShowURL +{: .no_toc } + +Asks for an address on the web. + +Syntax: *object*.**ShowURL** ( *prompt* [ , *title* ] ) **As String** + +*prompt* +: *required* The text above the edit box. + +*title* +: *optional* The title. The default is `Enter URL`. + +Sets [**Prompt**](#prompt), [**Title**](#title), [**DefaultValue**](#defaultvalue) (`https://`), [**Validation**](#validation) (**URL**), [**AllowEmpty**](#allowempty) (**False**) and [**Placeholder**](#placeholder) (`https://example.com`), calls [**ShowDialog**](#showdialog) and returns [**Value**](#value), or an empty string for **False**. A text passes when it contains `://`, so the offered `https://` passes unchanged. + +```tb check_build +Dim box As New cDialogInputBox +Dim address As String + +address = box.ShowURL("Address of the site?") +If Not box.Cancelled Then Debug.Print address +``` + +### ValidateInput +{: .no_toc } + +Checks a text against the rules and returns the reason for a refusal. + +Syntax: *object*.**ValidateInput** ( *input* ) **As String** + +*input* +: *required* The text to check. + +Returns an empty string when the text passes, and otherwise the message that [**ShowDialog**](#showdialog) shows. The checks run in this order, and the first that fails decides: + +| Check | Message | +|---|---| +| Fewer characters than [**MinLength**](#minlength) | `Input must be at least N characters` | +| More characters than [**MaxLength**](#maxlength) (above 0) | `Input must not exceed N characters` | +| Empty, with [**AllowEmpty**](#allowempty) **False** | `Input cannot be empty` | +| **Numeric**: **IsNumeric** is **False** | `Input must be a valid number` | +| **Integer**: **IsNumeric** is **False**, or the text has a `.` or a `,` | `Input must be a valid integer` | +| **Alpha**: a character that is not a letter A to Z or a space | `Input must contain only letters` | +| **AlphaNumeric**: a character that is not a letter A to Z, a digit, a space or `_` | `Input must contain only letters and numbers` | +| **Email**: no `@`, no `.`, or more than one `@` | `Input must be a valid email address` | +| **Phone**: a character that is not a digit, a space, `-`, `(`, `)`, `+` or `.` | `Input must be a valid phone number` | +| **URL**: no `://` | `Input must be a valid URL (e.g., https://example.com)` | +| **Custom**: empty, with a [**CustomPattern**](#custompattern) set | `Input does not match required format` | + +A rule of **Numeric** to **URL** is not applied to an empty text, which passes it. The method changes nothing in the object. + +```tb check_build check_run +Dim box As New cDialogInputBox + +box.Validation = InputValidation.Integer +Debug.Print "[" & box.ValidateInput("42") & "]" ' [] +Debug.Print "[" & box.ValidateInput("4.2") & "]" ' [Input must be a valid integer] +Debug.Print "[" & box.ValidateInput("abc") & "]" ' [Input must be a valid integer] + +box.Validation = InputValidation.Email +Debug.Print "[" & box.ValidateInput("ann@example.com") & "]" ' [] +Debug.Print "[" & box.ValidateInput("ann@@example.com") & "]" ' [Input must be a valid email address] + +box.Validation = InputValidation.AlphaNumeric +Debug.Print "[" & box.ValidateInput("Item_42") & "]" ' [] +Debug.Print "[" & box.ValidateInput("Item-42") & "]" ' [Input must contain only letters and numbers] +``` + +## Enumerations + +### InputValidation +{: .no_toc } + +The kinds of text for [**Validation**](#validation). + +| Member | Value | Accepts | +|---|---|---| +| **None** | 0 | Any text | +| **Numeric** | 1 | What **IsNumeric** accepts | +| **Integer** | 2 | What **IsNumeric** accepts, without `.` and `,` | +| **Alpha** | 3 | Letters A to Z and spaces | +| **AlphaNumeric** | 4 | Letters A to Z, digits, spaces and `_` | +| **Email** | 5 | Text with one `@` and a `.` | +| **Phone** | 6 | Digits, spaces and `-`, `(`, `)`, `+`, `.` | +| **URL** | 7 | Text that contains `://` | +| **Custom** | 99 | Any text; see [**CustomPattern**](#custompattern) | + +## Example + +This example asks for a quantity between 1 and 99 and repeats the question until the user has entered one or cancelled. + +```tb check_build +Dim box As New cDialogInputBox +Dim quantity As Long + +box.Title = "Order" +box.Prompt = "Quantity (1 to 99)?" +box.Validation = InputValidation.Integer +box.AllowEmpty = False +box.MaxLength = 2 +box.DefaultValue = "1" + +If box.ShowDialog() Then + quantity = box.GetValueAsInteger() + If quantity >= 1 Then Debug.Print "Ordering " & quantity +Else + Debug.Print "No order" +End If +``` + +## See Also + +- [cDialogPassword](cDialogPassword) class -- asks for a password with a masked entry +- [cDialogDateTime](cDialogDateTime) class -- asks for a date and a time +- [TBMANLIB_Dialog](.) package -- overview diff --git a/docs/Reference/Contributed/TBMANLIB_Dialog/cDialogPageSetup.md b/docs/Reference/Contributed/TBMANLIB_Dialog/cDialogPageSetup.md new file mode 100644 index 000000000..4b5e04243 --- /dev/null +++ b/docs/Reference/Contributed/TBMANLIB_Dialog/cDialogPageSetup.md @@ -0,0 +1,492 @@ +--- +title: cDialogPageSetup +parent: TBMANLIB_Dialog Package +permalink: /tB/Packages/Contributed/TBMANLIB_Dialog/cDialogPageSetup +has_toc: false +--- + +# cDialogPageSetup class +{: .no_toc } + +A **cDialogPageSetup** shows the Windows Page Setup dialog, in which the user picks the paper, its orientation and the four margins of a page. It holds the page as properties, in inches, with ready-made sizes and margins, and works out the printable area. + +> [!WARNING] +> Three things in version 1.1.0.0 give a wrong result without an error. [**PaperWidth**](#paperwidth) and [**PaperHeight**](#paperheight) are not passed to the dialog, so it opens with the paper of the default printer whatever they hold. [**Orientation**](#orientation) is neither passed to the dialog nor read from it: a user who chooses *Landscape* leaves **Orientation** at **Portrait** and swaps the paper width and height instead. With [**Unit**](#unit) set to **Millimeters**, [**ToDescription**](#todescription) writes the values in inches and labels them `mm`. + +**Properties:** [**AllowMargins**](#allowmargins), [**AllowOrientation**](#alloworientation), [**AllowPaper**](#allowpaper), [**DefaultMinMargins**](#defaultminmargins), [**MarginBottom**](#marginbottom), [**MarginLeft**](#marginleft), [**MarginRight**](#marginright), [**MarginTop**](#margintop), [**Orientation**](#orientation), [**OwnerHwnd**](#ownerhwnd), [**PaperHeight**](#paperheight), [**PaperWidth**](#paperwidth), [**Unit**](#unit). +**Methods:** [**GetPrintableHeight**](#getprintableheight), [**GetPrintableWidth**](#getprintablewidth), [**Reset**](#reset), [**SetA4Size**](#seta4size), [**SetDefaultMargins**](#setdefaultmargins), [**SetLegalSize**](#setlegalsize), [**SetLetterSize**](#setlettersize), [**SetNarrowMargins**](#setnarrowmargins), [**ShowDialog**](#showdialog), [**ToDescription**](#todescription). +**Enumerations:** [**MeasurementUnit**](#measurementunit), [**PaperOrientation**](#paperorientation). + +Create an object with `Dim dlg As New cDialogPageSetup`. The class is declared `[COMCreatable(False)]`, so **CreateObject** cannot make one, and **New** can. + +```tb check_build +Dim dlg As New cDialogPageSetup + +dlg.SetNarrowMargins +If dlg.ShowDialog() Then + Debug.Print dlg.ToDescription() + Debug.Print dlg.GetPrintableWidth() & " x " & dlg.GetPrintableHeight() & " inches" +Else + Debug.Print "Cancelled" +End If +``` + +## How the dialog is shown + +[**ShowDialog**](#showdialog) calls `PageSetupDlgW` with the flag `PSD_MARGINS`, which is always set, and one of `PSD_INTHOUSANDTHSOFINCHES` and `PSD_INHUNDREDTHSOFMILLIMETERS`, which [**Unit**](#unit) selects. [**AllowMargins**](#allowmargins), [**AllowOrientation**](#alloworientation) and [**AllowPaper**](#allowpaper) set `PSD_DISABLEMARGINS`, `PSD_DISABLEORIENTATION` and `PSD_DISABLEPAPER` when they are **False**. The class sets no hook, template, minimum margins, help button or printer. The dialog is modal and has the title *Page Setup*. + +All the values the class holds are in **inches**, whatever [**Unit**](#unit) says. **Unit** decides only the unit in which the class talks to Windows: the margins are converted to thousandths of an inch or to hundredths of a millimeter on the way in, and converted back on the way out. The dialog shows its margins in that unit, with the heading *Margins (inches)* or *Margins (millimeters)*: one inch appears as `1` or `25.4`. + +When the user chooses **OK**, [**ShowDialog**](#showdialog) stores the paper size and the four margins that the dialog returns and returns **True**. When the user chooses **Cancel**, or closes the dialog, it returns **False** and leaves every property as it was. No error is raised in either case. + +The class gives the dialog no printer data and frees the data Windows returns, so the printer the user chose, the paper source and the other settings of the printer are lost. What remains is the size of the paper that the dialog shows when it closes, in [**PaperWidth**](#paperwidth) and [**PaperHeight**](#paperheight). The paper that the dialog offers first is the one of the default printer. + +## Properties + +### AllowMargins +{: .no_toc } + +Whether the user may change the margins. + +Syntax: *object*.**AllowMargins** [ **=** *value* ] + +A **Boolean**, **True** by default. When it is **False**, [**ShowDialog**](#showdialog) sets `PSD_DISABLEMARGINS`, and the four margin boxes are shown disabled with the values of [**MarginLeft**](#marginleft), [**MarginTop**](#margintop), [**MarginRight**](#marginright) and [**MarginBottom**](#marginbottom). + +```tb check_build +Dim dlg As New cDialogPageSetup + +dlg.AllowMargins = False +Debug.Print dlg.AllowMargins +``` + +### AllowOrientation +{: .no_toc } + +Whether the user may change the orientation. + +Syntax: *object*.**AllowOrientation** [ **=** *value* ] + +A **Boolean**, **True** by default. When it is **False**, [**ShowDialog**](#showdialog) sets `PSD_DISABLEORIENTATION`, and the *Portrait* and *Landscape* buttons are shown disabled. + +```tb check_build +Dim dlg As New cDialogPageSetup + +dlg.AllowOrientation = False +Debug.Print dlg.AllowOrientation +``` + +### AllowPaper +{: .no_toc } + +Whether the user may change the paper size and source. + +Syntax: *object*.**AllowPaper** [ **=** *value* ] + +A **Boolean**, **True** by default. When it is **False**, [**ShowDialog**](#showdialog) sets `PSD_DISABLEPAPER`, and the *Size* and *Source* boxes are shown disabled. + +```tb check_build +Dim dlg As New cDialogPageSetup + +dlg.AllowPaper = False +Debug.Print dlg.AllowPaper +``` + +### DefaultMinMargins +{: .no_toc } + +Whether the dialog uses the default minimum margins. + +Syntax: *object*.**DefaultMinMargins** [ **=** *value* ] + +A **Boolean**, **True** by default. It has no effect: when it is **True**, [**ShowDialog**](#showdialog) combines the flags with `PSD_DEFAULTMINMARGINS`, whose value is 0, and no minimum margin is ever passed. + +```tb check_build +Dim dlg As New cDialogPageSetup + +dlg.DefaultMinMargins = False +Debug.Print dlg.DefaultMinMargins +``` + +### MarginBottom +{: .no_toc } + +The bottom margin, in inches. + +Syntax: *object*.**MarginBottom** [ **=** *value* ] + +A **Double**, 1 by default. It is shown in the *Bottom* box of the dialog and is replaced by the value the user leaves there. A value is not checked. + +```tb check_build check_run +Dim dlg As New cDialogPageSetup + +dlg.MarginBottom = 2 +Debug.Print dlg.MarginBottom +' Output: +' 2 +``` + +### MarginLeft +{: .no_toc } + +The left margin, in inches. + +Syntax: *object*.**MarginLeft** [ **=** *value* ] + +A **Double**, 1 by default, handled as [**MarginBottom**](#marginbottom) is. + +```tb check_build +Dim dlg As New cDialogPageSetup + +dlg.MarginLeft = 1.25 +Debug.Print dlg.MarginLeft +``` + +### MarginRight +{: .no_toc } + +The right margin, in inches. + +Syntax: *object*.**MarginRight** [ **=** *value* ] + +A **Double**, 1 by default, handled as [**MarginBottom**](#marginbottom) is. + +```tb check_build +Dim dlg As New cDialogPageSetup + +dlg.MarginRight = 1.25 +Debug.Print dlg.MarginRight +``` + +### MarginTop +{: .no_toc } + +The top margin, in inches. + +Syntax: *object*.**MarginTop** [ **=** *value* ] + +A **Double**, 1 by default, handled as [**MarginBottom**](#marginbottom) is. + +```tb check_build +Dim dlg As New cDialogPageSetup + +dlg.MarginTop = 0.75 +Debug.Print dlg.MarginTop +``` + +### Orientation +{: .no_toc } + +The orientation of the page. + +Syntax: *object*.**Orientation** [ **=** *value* ] + +A [**PaperOrientation**](#paperorientation), **Portrait** by default. + +> [!WARNING] +> The property is only stored. [**ShowDialog**](#showdialog) does not pass it to the dialog, so a **Landscape** set before the call does not select *Landscape* in the dialog, and it does not read the orientation back. After a user chose *Landscape*, **Orientation** is as it was and [**PaperWidth**](#paperwidth) and [**PaperHeight**](#paperheight) have changed places. To find the orientation after the dialog, compare the two: the page is landscape when **PaperWidth** is the larger. [**ToDescription**](#todescription) names the stored value. + +```tb check_build +Dim dlg As New cDialogPageSetup + +dlg.Orientation = PaperOrientation.Landscape +If dlg.ShowDialog() Then + Debug.Print IIf(dlg.PaperWidth > dlg.PaperHeight, "Landscape", "Portrait") +End If +``` + +### OwnerHwnd +{: .no_toc } + +The window that owns the dialog. + +Syntax: *object*.**OwnerHwnd** [ **=** *value* ] + +A **LongPtr** window handle, 0 by default, which gives the dialog no owner. [**ShowDialog**](#showdialog) passes it as the owner window of the call. [**Reset**](#reset) does not change it. + +```tb check_build +Private Sub SetupPage(ByVal ownerWindow As LongPtr) + Dim dlg As New cDialogPageSetup + + dlg.OwnerHwnd = ownerWindow ' the handle of a window of the program + Debug.Print dlg.OwnerHwnd + Debug.Print dlg.ShowDialog() +End Sub +``` + +### PaperHeight +{: .no_toc } + +The height of the paper, in inches. + +Syntax: *object*.**PaperHeight** [ **=** *value* ] + +A **Double**, 11 by default. The value assigned is not passed to the dialog. After a [**ShowDialog**](#showdialog) that returned **True** it holds the height of the paper that the dialog shows, so it follows the printer and the orientation the user chose: for Letter paper it is 11 in portrait and 8.5 in landscape. + +```tb check_build +Dim dlg As New cDialogPageSetup + +dlg.SetA4Size +Debug.Print dlg.PaperHeight +``` + +### PaperWidth +{: .no_toc } + +The width of the paper, in inches. + +Syntax: *object*.**PaperWidth** [ **=** *value* ] + +A **Double**, 8.5 by default, handled as [**PaperHeight**](#paperheight) is. + +> [!WARNING] +> Setting **PaperWidth** and **PaperHeight**, or calling [**SetA4Size**](#seta4size) or [**SetLegalSize**](#setlegalsize), does not make the dialog open with that paper. The margins are passed, and the paper is the one of the default printer. After **OK** the two properties hold what the dialog returned, not what was set. + +```tb check_build +Dim dlg As New cDialogPageSetup + +dlg.PaperWidth = 8.5 +Debug.Print dlg.PaperWidth +``` + +### Unit +{: .no_toc } + +The unit in which the dialog shows the margins. + +Syntax: *object*.**Unit** [ **=** *value* ] + +A [**MeasurementUnit**](#measurementunit), **Inches** by default. It selects the unit of the dialog; the properties of the class stay in inches. See [How the dialog is shown](#how-the-dialog-is-shown). + +> [!WARNING] +> With **Millimeters**, [**ToDescription**](#todescription) still writes inches and labels them `mm`: a 1 inch margin is described as `1.00 mm`. + +```tb check_build check_run +Dim dlg As New cDialogPageSetup + +dlg.Unit = MeasurementUnit.Millimeters +dlg.MarginLeft = 2 ' still inches: 50.8 mm +Debug.Print dlg.MarginLeft +Debug.Print dlg.Unit +' Output: +' 2 +' 1 +``` + +## Methods + +### GetPrintableHeight +{: .no_toc } + +Returns the height of the area inside the margins, in inches. + +Syntax: *object*.**GetPrintableHeight** ( ) **As Double** + +[**PaperHeight**](#paperheight) minus [**MarginTop**](#margintop) and [**MarginBottom**](#marginbottom). The result is negative when the margins are larger than the paper. + +```tb check_build check_run +Dim dlg As New cDialogPageSetup + +dlg.PaperHeight = 12 +dlg.MarginTop = 2 +dlg.MarginBottom = 1 +Debug.Print dlg.GetPrintableHeight() +' Output: +' 9 +``` + +### GetPrintableWidth +{: .no_toc } + +Returns the width of the area inside the margins, in inches. + +Syntax: *object*.**GetPrintableWidth** ( ) **As Double** + +[**PaperWidth**](#paperwidth) minus [**MarginLeft**](#marginleft) and [**MarginRight**](#marginright). + +```tb check_build check_run +Dim dlg As New cDialogPageSetup + +dlg.SetNarrowMargins +Debug.Print dlg.GetPrintableWidth() * 2 +' Output: +' 15 +``` + +### Reset +{: .no_toc } + +Restores every property except the owner window to its starting value. + +Syntax: *object*.**Reset** + +[**Orientation**](#orientation) becomes **Portrait**, the paper Letter (8.5 by 11), the four margins 1, [**Unit**](#unit) **Inches**, the three **Allow** properties and [**DefaultMinMargins**](#defaultminmargins) **True**. [**OwnerHwnd**](#ownerhwnd) is not changed. + +```tb check_build check_run +Dim dlg As New cDialogPageSetup + +dlg.SetLegalSize +dlg.SetNarrowMargins +dlg.Reset +Debug.Print dlg.PaperHeight +Debug.Print dlg.MarginLeft +' Output: +' 11 +' 1 +``` + +### SetA4Size +{: .no_toc } + +Sets the paper to A4. + +Syntax: *object*.**SetA4Size** + +[**PaperWidth**](#paperwidth) becomes 210 / 25.4 and [**PaperHeight**](#paperheight) 297 / 25.4 inches, 8.2677 by 11.6929. [**Orientation**](#orientation) is not changed. See the warning on **PaperWidth**: the dialog does not take the size. + +```tb check_build check_run +Dim dlg As New cDialogPageSetup + +dlg.SetA4Size +Debug.Print Int(dlg.PaperWidth * 100) +Debug.Print Int(dlg.PaperHeight * 100) +' Output: +' 826 +' 1169 +``` + +### SetDefaultMargins +{: .no_toc } + +Sets all four margins to 1 inch. + +Syntax: *object*.**SetDefaultMargins** + +```tb check_build check_run +Dim dlg As New cDialogPageSetup + +dlg.SetNarrowMargins +dlg.SetDefaultMargins +Debug.Print dlg.MarginTop + dlg.MarginBottom +' Output: +' 2 +``` + +### SetLegalSize +{: .no_toc } + +Sets the paper to Legal. + +Syntax: *object*.**SetLegalSize** + +[**PaperWidth**](#paperwidth) becomes 8.5 and [**PaperHeight**](#paperheight) 14 inches. See the warning on **PaperWidth**. + +```tb check_build check_run +Dim dlg As New cDialogPageSetup + +dlg.SetLegalSize +Debug.Print dlg.PaperHeight +' Output: +' 14 +``` + +### SetLetterSize +{: .no_toc } + +Sets the paper to Letter. + +Syntax: *object*.**SetLetterSize** + +[**PaperWidth**](#paperwidth) becomes 8.5 and [**PaperHeight**](#paperheight) 11 inches. See the warning on **PaperWidth**. + +```tb check_build check_run +Dim dlg As New cDialogPageSetup + +dlg.SetA4Size +dlg.SetLetterSize +Debug.Print dlg.PaperHeight +' Output: +' 11 +``` + +### SetNarrowMargins +{: .no_toc } + +Sets all four margins to half an inch. + +Syntax: *object*.**SetNarrowMargins** + +```tb check_build check_run +Dim dlg As New cDialogPageSetup + +dlg.SetNarrowMargins +Debug.Print dlg.MarginLeft * 2 +' Output: +' 1 +``` + +### ShowDialog +{: .no_toc } + +Shows the Page Setup dialog. + +Syntax: *object*.**ShowDialog** ( ) **As Boolean** + +Returns **True** when the user chose **OK**, and then the paper size and the margins hold what the dialog returned. Returns **False** when the user cancelled, and the properties are unchanged. See [How the dialog is shown](#how-the-dialog-is-shown) and the warning at the head of the page. + +```tb check_build +Dim dlg As New cDialogPageSetup + +dlg.Unit = MeasurementUnit.Millimeters +dlg.AllowOrientation = False +If dlg.ShowDialog() Then + Debug.Print dlg.MarginLeft, dlg.MarginRight +End If +``` + +### ToDescription +{: .no_toc } + +Returns the page as a line of text. + +Syntax: *object*.**ToDescription** ( ) **As String** + +The form is `Paper: 8.50 x 11.00 in, Portrait, Margins: 1.00/1.00/1.00/1.00 in`: the paper width and height, the stored [**Orientation**](#orientation) as `Portrait` or `Landscape`, and the left, right, top and bottom margins, each with two decimals, and the unit `in` or `mm`. The numbers are formatted with the decimal separator of the regional settings of the computer. With [**Unit**](#unit) **Millimeters** the numbers are still inches and the label is `mm`: see the warning on **Unit**. + +```tb check_build +Dim dlg As New cDialogPageSetup + +dlg.SetA4Size +dlg.SetNarrowMargins +Debug.Print dlg.ToDescription() +``` + +## Enumerations + +The enumerations are declared inside the class. A program names their members with the name of the enumeration in front, as in the samples, or by the member alone, such as `Landscape`. The name of the class cannot be put in front: `cDialogPageSetup.PaperOrientation.Landscape` is refused with TB5079, *Unrecognized symbol 'cDialogPageSetup'*. + +### MeasurementUnit +{: .no_toc } + +The unit in which the dialog shows margins, the type of [**Unit**](#unit). + +| Member | Value | Meaning | +|:-------|------:|:--------| +| **Inches** | 0 | Thousandths of an inch are passed to Windows. | +| **Millimeters** | 1 | Hundredths of a millimeter are passed to Windows. | + +### PaperOrientation +{: .no_toc } + +The orientation of a page, the type of [**Orientation**](#orientation). + +| Member | Value | +|:-------|------:| +| **Portrait** | 1 | +| **Landscape** | 2 | + +## See Also + +- [cDialogPrint](cDialogPrint) class -- shows the Print dialog +- [cDialogFont](cDialogFont) class -- shows the Font dialog +- [cDialog](cDialog/) class -- shows the Open, Save As and Browse For Folder dialogs +- [TBMANLIB_Dialog](.) package -- overview diff --git a/docs/Reference/Contributed/TBMANLIB_Dialog/cDialogPassword.md b/docs/Reference/Contributed/TBMANLIB_Dialog/cDialogPassword.md new file mode 100644 index 000000000..69b1e2365 --- /dev/null +++ b/docs/Reference/Contributed/TBMANLIB_Dialog/cDialogPassword.md @@ -0,0 +1,553 @@ +--- +title: cDialogPassword +parent: TBMANLIB_Dialog Package +permalink: /tB/Packages/Contributed/TBMANLIB_Dialog/cDialogPassword +has_toc: false +--- + +# cDialogPassword class +{: .no_toc } + +A **cDialogPassword** asks the user for a password in a box whose entry is masked, checks the password against rules (length and kinds of characters) and asks again when it fails. The box is the **InputBox** of the run-time library, and the messages about a failed check are ordinary message boxes: the class has no window of its own. It also rates the strength of a password and offers a second method that asks for the old and the new password. + +A **cDialogPassword** is made with **New**: `Dim box As New cDialogPassword`. It cannot be made with **CreateObject**. + +The properties [**MinLength**](#minlength), [**MaxLength**](#maxlength), [**RequireUppercase**](#requireuppercase), [**RequireLowercase**](#requirelowercase), [**RequireNumber**](#requirenumber) and [**RequireSpecial**](#requirespecial) are the rules, and [**Validate**](#validate) applies them to a text. [**ShowDialog**](#showdialog) asks for a password and stores the accepted one in [**Password**](#password); with [**RequireConfirm**](#requireconfirm) it asks twice. [**ShowChangeDialog**](#showchangedialog) asks for the current password, a new one and its confirmation. + +```tb check_build +Dim box As New cDialogPassword + +box.Title = "Create account" +box.Prompt = "Choose a password:" +box.MinLength = 10 +box.RequireSpecial = True +box.RequireConfirm = True + +If box.ShowDialog() Then + Debug.Print "Strength: " & box.GetStrengthDescription(box.GetStrength(box.Password)) +Else + Debug.Print "No password chosen" +End If +``` + +## How the box works + +The box has the title [**Title**](#title). Its text is [**Prompt**](#prompt), a blank line, `Requirements:`, and one line for each rule that is on: `- Minimum N characters` always, then `- At least one uppercase letter`, `- At least one lowercase letter`, `- At least one number` and `- At least one special character`. The maximum length is not named. + +The class masks the edit box with the character of [**PasswordChar**](#passwordchar). It starts a short timer before it opens the box, and when the timer fires it looks for a window of the box's class with the title [**Title**](#title) and sends it the message that sets the mask. Only a box with exactly that title is masked. + +[**ShowDialog**](#showdialog) repeats the question until it has a password that passes or [**MaxAttempts**](#maxattempts) attempts have failed. A failure shows a message box with the reason and `Attempts remaining: N`, and the last failure shows the reason and `Maximum attempts reached.`. If [**RequireConfirm**](#requireconfirm) is **True**, a password that passes is asked for again in a second box, `Please confirm your password:` under the title `Title - Confirm`, and a difference counts as a failed attempt (`Passwords do not match!`). Cancelling any box ends the call. + +> [!WARNING] +> In version 1.1.0.0 only the first box of [**ShowDialog**](#showdialog) is masked. The confirmation box of [**RequireConfirm**](#requireconfirm) and the three boxes of [**ShowChangeDialog**](#showchangedialog) (`Title - Current Password`, `Title - New Password` and `Title - Confirm New Password`) show what the user types in clear text, because the timer looks for a window with exactly the title [**Title**](#title). A program that asks for a confirmation or a change of password shows the passwords on the screen. To keep the entry masked, ask with [**ShowDialog**](#showdialog) alone and compare the two entries in the program, or use another control. + +> [!NOTE] +> The properties [**ShowStrength**](#showstrength), [**AllowShowPassword**](#allowshowpassword) and [**OwnerHwnd**](#ownerhwnd) are stored but not used: the box has no strength indicator, no control that shows the password and no owner window. The password stays in [**Password**](#password), an ordinary string, until [**Reset**](#reset) empties it or the object ends. + +## Properties + +### AllowShowPassword +{: .no_toc } + +A property that has no effect. + +Syntax: *object*.**AllowShowPassword** [ **=** *value* ] + +*value* +: A **Boolean**. The default is **True**. + +The value is stored. The box has no control that shows the password. + +```tb check_build check_run +Dim box As New cDialogPassword + +Debug.Print box.AllowShowPassword ' True +``` + +### Cancelled +{: .no_toc } + +Whether the last call ended without a password, read-only. + +Syntax: *object*.**Cancelled** **As Boolean** + +**False** in a new object. [**ShowDialog**](#showdialog) sets it to **True** when the user cancelled a box or the last attempt failed, and to **False** when it accepted a password. [**ShowChangeDialog**](#showchangedialog) sets it to **True** only when the user cancels a box, and does not set it to **False**. [**Reset**](#reset) sets it to **False**. + +```tb check_build check_run +Dim box As New cDialogPassword + +Debug.Print box.Cancelled ' False +``` + +### MaxAttempts +{: .no_toc } + +The number of attempts that [**ShowDialog**](#showdialog) allows. + +Syntax: *object*.**MaxAttempts** [ **=** *value* ] + +*value* +: A **Long**. The default is 3. + +Every failed password and every difference between the password and its confirmation uses one attempt; cancelling does not. With 0 or less, [**ShowDialog**](#showdialog) shows nothing and returns **False** with [**Cancelled**](#cancelled) **True**. [**ShowChangeDialog**](#showchangedialog) does not use it. + +```tb check_build check_run +Dim box As New cDialogPassword + +Debug.Print box.MaxAttempts ' 3 +box.MaxAttempts = 5 +Debug.Print box.MaxAttempts ' 5 +``` + +### MaxLength +{: .no_toc } + +The longest password that is accepted. + +Syntax: *object*.**MaxLength** [ **=** *value* ] + +*value* +: A **Long**. The default is 128. + +[**Validate**](#validate) refuses a longer text with `Password must not exceed N characters`. + +```tb check_build check_run +Dim box As New cDialogPassword + +Debug.Print box.MaxLength ' 128 +box.MaxLength = 10 +Debug.Print box.Validate("Zq7-demo-xxx") ' Password must not exceed 10 characters +``` + +### MinLength +{: .no_toc } + +The shortest password that is accepted. + +Syntax: *object*.**MinLength** [ **=** *value* ] + +*value* +: A **Long**. The default is 8. + +[**Validate**](#validate) refuses a shorter text with `Password must be at least N characters`. With 0, an empty password passes when no character rule is on. + +```tb check_build check_run +Dim box As New cDialogPassword + +Debug.Print box.MinLength ' 8 +Debug.Print box.Validate("Zq7-") ' Password must be at least 8 characters +``` + +### OwnerHwnd +{: .no_toc } + +A property that has no effect. + +Syntax: *object*.**OwnerHwnd** [ **=** *value* ] + +*value* +: A **LongPtr**. The default is 0. + +The value is stored. The boxes have no owner. [**Reset**](#reset) does not change it. + +```tb check_build check_run +Dim box As New cDialogPassword + +Debug.Print box.OwnerHwnd ' 0 +``` + +### Password +{: .no_toc } + +The password that was last accepted, read-only. + +Syntax: *object*.**Password** **As String** + +Empty in a new object and after [**Reset**](#reset). [**ShowDialog**](#showdialog) and [**ShowChangeDialog**](#showchangedialog) set it when they accept a password. A cancelled or failed call leaves it as it was, so it can still hold the password of an earlier call: test the result of the call, not this property. + +```tb check_build check_run +Dim box As New cDialogPassword + +Debug.Print Len(box.Password) ' 0 +``` + +### PasswordChar +{: .no_toc } + +The character that masks the entry. + +Syntax: *object*.**PasswordChar** [ **=** *value* ] + +*value* +: A **String**. Only its first character is kept; an empty string sets the default, `*`. + +```tb check_build check_run +Dim box As New cDialogPassword + +Debug.Print box.PasswordChar ' * +box.PasswordChar = "#" +Debug.Print box.PasswordChar ' # +box.PasswordChar = "abc" +Debug.Print box.PasswordChar ' a +box.PasswordChar = "" +Debug.Print box.PasswordChar ' * +``` + +### Prompt +{: .no_toc } + +The first text of the first box. + +Syntax: *object*.**Prompt** [ **=** *value* ] + +*value* +: A **String**. The default is `Please enter your password:`. + +The list of requirements follows it. [**ShowChangeDialog**](#showchangedialog) and the confirmation box have texts of their own. + +```tb check_build check_run +Dim box As New cDialogPassword + +Debug.Print box.Prompt ' Please enter your password: +``` + +### RequireConfirm +{: .no_toc } + +Whether [**ShowDialog**](#showdialog) asks for the password twice. + +Syntax: *object*.**RequireConfirm** [ **=** *value* ] + +*value* +: A **Boolean**. The default is **False**. + +With **True**, the confirmation box shows the typed text in clear; see the warning [above](#how-the-box-works). + +```tb check_build check_run +Dim box As New cDialogPassword + +Debug.Print box.RequireConfirm ' False +``` + +### RequireLowercase +{: .no_toc } + +Whether a password needs a lowercase letter. + +Syntax: *object*.**RequireLowercase** [ **=** *value* ] + +*value* +: A **Boolean**. The default is **True**. Letters are `a` to `z`. + +```tb check_build check_run +Dim box As New cDialogPassword + +Debug.Print box.RequireLowercase ' True +Debug.Print box.Validate("ALLUPPERCASE1") ' Password must contain at least one lowercase letter +``` + +### RequireNumber +{: .no_toc } + +Whether a password needs a digit. + +Syntax: *object*.**RequireNumber** [ **=** *value* ] + +*value* +: A **Boolean**. The default is **True**. Digits are `0` to `9`. + +```tb check_build check_run +Dim box As New cDialogPassword + +Debug.Print box.RequireNumber ' True +Debug.Print box.Validate("NoDigitsHere") ' Password must contain at least one number +``` + +### RequireSpecial +{: .no_toc } + +Whether a password needs a special character. + +Syntax: *object*.**RequireSpecial** [ **=** *value* ] + +*value* +: A **Boolean**. The default is **False**. + +The special characters are ``! @ # $ % ^ & * ( ) _ + - = [ ] { } | ; : ' , . < > ? / ~`` and the grave accent. A space is not one. + +```tb check_build check_run +Dim box As New cDialogPassword + +box.RequireSpecial = True +Debug.Print box.Validate("Zq7demoxx") ' Password must contain at least one special character +Debug.Print "[" & box.Validate("Zq7-demo") & "]" ' [] +``` + +### RequireUppercase +{: .no_toc } + +Whether a password needs an uppercase letter. + +Syntax: *object*.**RequireUppercase** [ **=** *value* ] + +*value* +: A **Boolean**. The default is **True**. Letters are `A` to `Z`. + +```tb check_build check_run +Dim box As New cDialogPassword + +Debug.Print box.RequireUppercase ' True +Debug.Print box.Validate("alllowercase1") ' Password must contain at least one uppercase letter +``` + +### ShowStrength +{: .no_toc } + +A property that has no effect. + +Syntax: *object*.**ShowStrength** [ **=** *value* ] + +*value* +: A **Boolean**. The default is **True**. + +The value is stored. The box shows no strength; use [**GetStrength**](#getstrength) to rate a password. + +```tb check_build check_run +Dim box As New cDialogPassword + +Debug.Print box.ShowStrength ' True +``` + +### Title +{: .no_toc } + +The title of the first box and of the messages about a failed entry. + +Syntax: *object*.**Title** [ **=** *value* ] + +*value* +: A **String**. The default is `Enter Password`. + +The class finds the first box by this title to mask it, so a title that another window of the program also uses can lead to that window being masked instead. The other boxes add `- Confirm`, `- Current Password`, `- New Password` or `- Confirm New Password`. + +```tb check_build check_run +Dim box As New cDialogPassword + +Debug.Print box.Title ' Enter Password +``` + +## Methods + +### GetRequirementsDescription +{: .no_toc } + +Returns the rules as text. + +Syntax: *object*.**GetRequirementsDescription** ( ) **As String** + +Returns `Password requirements:`, then one line (separated by **vbCrLf**) for the length, `- N to M characters`, and one for each rule that is on, such as `- At least one uppercase letter (A-Z)`. The text of the box is shorter: it names the minimum and no maximum. + +```tb check_build check_run +Dim box As New cDialogPassword + +Debug.Print box.GetRequirementsDescription() +' Output: +' Password requirements: +' - 8 to 128 characters +' - At least one uppercase letter (A-Z) +' - At least one lowercase letter (a-z) +' - At least one number (0-9) +``` + +### GetStrength +{: .no_toc } + +Rates a password. + +Syntax: *object*.**GetStrength** ( *password* ) **As** [**PasswordStrength**](#passwordstrength) + +*password* +: *required* The text to rate. + +The class adds points: 1 for 8 or more characters, 1 more for 12 or more, 1 more for 16 or more, 1 each for an uppercase letter, a lowercase letter and a digit, and 2 for a special character (the list under [**RequireSpecial**](#requirespecial)). A score of 0 to 2 is **VeryWeak**, 3 or 4 **Weak**, 5 or 6 **Fair**, and 7 or 8 **Strong**. The rules of the object (minimum, maximum and the required kinds) play no part. + +> [!NOTE] +> The highest score is 8, so the result is never **VeryStrong**. + +```tb check_build check_run +Dim box As New cDialogPassword + +Debug.Print box.GetStrengthDescription(box.GetStrength("abc")) ' Very Weak +Debug.Print box.GetStrengthDescription(box.GetStrength("Password1")) ' Weak +Debug.Print box.GetStrengthDescription(box.GetStrength("Zq7-demo-xx")) ' Fair +Debug.Print box.GetStrengthDescription(box.GetStrength("Zq7-demo-xxxx")) ' Strong +Debug.Print box.GetStrengthDescription(box.GetStrength("Zq7-demo-xxxxxxxxxxxx")) ' Strong +``` + +### GetStrengthDescription +{: .no_toc } + +Names a strength in words. + +Syntax: *object*.**GetStrengthDescription** ( *strength* ) **As String** + +*strength* +: *required* A [**PasswordStrength**](#passwordstrength) member. + +Returns `Very Weak`, `Weak`, `Fair`, `Strong` or `Very Strong`, and an empty string for another value. + +```tb check_build check_run +Dim box As New cDialogPassword + +Debug.Print box.GetStrengthDescription(PasswordStrength.Fair) ' Fair +Debug.Print box.GetStrengthDescription(PasswordStrength.VeryStrong) ' Very Strong +``` + +### Reset +{: .no_toc } + +Restores the starting values. + +Syntax: *object*.**Reset** + +Empties [**Password**](#password) and sets every property but [**OwnerHwnd**](#ownerhwnd) to its default: [**Title**](#title) `Enter Password`, [**Prompt**](#prompt) `Please enter your password:`, [**PasswordChar**](#passwordchar) `*`, [**MinLength**](#minlength) 8, [**MaxLength**](#maxlength) 128, [**MaxAttempts**](#maxattempts) 3, [**RequireUppercase**](#requireuppercase), [**RequireLowercase**](#requirelowercase), [**RequireNumber**](#requirenumber), [**ShowStrength**](#showstrength) and [**AllowShowPassword**](#allowshowpassword) **True**, and [**RequireSpecial**](#requirespecial), [**RequireConfirm**](#requireconfirm) and [**Cancelled**](#cancelled) **False**. + +```tb check_build check_run +Dim box As New cDialogPassword + +box.MinLength = 12 +box.RequireSpecial = True +box.Reset +Debug.Print box.MinLength ' 8 +Debug.Print box.RequireSpecial ' False +``` + +### ShowChangeDialog +{: .no_toc } + +Asks for the current password, a new one and its confirmation. + +Syntax: *object*.**ShowChangeDialog** ( *oldPassword* ) **As Boolean** + +*oldPassword* +: *required* A **String** variable passed by reference. When it is not empty, the current password that the user types must equal it. After a success it holds the new password. + +Shows up to three boxes in a row, titled `Title - Current Password`, `Title - New Password` and `Title - Confirm New Password`, with their own texts (the second lists the requirements). The call stops at the first problem and does not repeat any box: + +- Cancelling a box: the result is **False** and [**Cancelled**](#cancelled) **True**. +- A current password that differs from *oldPassword* (checked only when *oldPassword* is not empty): `Incorrect current password.` +- A new password that [**Validate**](#validate) refuses: its message. +- A new password equal to the current one: `New password must be different from the current password.` +- A confirmation that differs from the new password: `Passwords do not match.` + +In each of the last four cases the result is **False**, [**Cancelled**](#cancelled) is not changed, and *oldPassword* and [**Password**](#password) are not changed. On success the result is **True** and the new password is in *oldPassword* and [**Password**](#password). The boxes show what is typed in clear text; see the warning [above](#how-the-box-works). + +```tb check_build +Dim box As New cDialogPassword +Dim stored As String + +stored = "Old-demo-pw1" +If box.ShowChangeDialog(stored) Then + Debug.Print "Password changed" +ElseIf box.Cancelled Then + Debug.Print "Cancelled" +Else + Debug.Print "Not changed" +End If +``` + +### ShowDialog +{: .no_toc } + +Asks for a password. + +Syntax: *object*.**ShowDialog** ( ) **As Boolean** + +Returns **True** when the user entered a password that passes [**Validate**](#validate) (and the confirmation, if [**RequireConfirm**](#requireconfirm) is on), and then [**Password**](#password) holds it and [**Cancelled**](#cancelled) is **False**. Returns **False**, with [**Cancelled**](#cancelled) **True**, when the user cancelled or [**MaxAttempts**](#maxattempts) attempts failed. [How the box works](#how-the-box-works) lists the steps. + +```tb check_build +Dim box As New cDialogPassword + +box.Title = "Unlock" +box.Prompt = "Password for the archive:" +box.MinLength = 6 +box.RequireNumber = False +box.RequireUppercase = False +box.MaxAttempts = 2 + +If box.ShowDialog() Then + Debug.Print "Password has " & Len(box.Password) & " characters" +Else + Debug.Print "No password" +End If +``` + +### Validate +{: .no_toc } + +Checks a password against the rules. + +Syntax: *object*.**Validate** ( *password* ) **As String** + +*password* +: *required* The text to check. + +Returns an empty string when the text passes and otherwise the reason. The checks run in this order, and the first that fails decides: + +| Check | Message | +|---|---| +| Fewer characters than [**MinLength**](#minlength) | `Password must be at least N characters` | +| More characters than [**MaxLength**](#maxlength) | `Password must not exceed N characters` | +| No uppercase letter, with [**RequireUppercase**](#requireuppercase) | `Password must contain at least one uppercase letter` | +| No lowercase letter, with [**RequireLowercase**](#requirelowercase) | `Password must contain at least one lowercase letter` | +| No digit, with [**RequireNumber**](#requirenumber) | `Password must contain at least one number` | +| No special character, with [**RequireSpecial**](#requirespecial) | `Password must contain at least one special character` | + +```tb check_build check_run +Dim box As New cDialogPassword + +Debug.Print box.Validate("short") ' Password must be at least 8 characters +Debug.Print box.Validate("alllowercase1") ' Password must contain at least one uppercase letter +Debug.Print "[" & box.Validate("Zq7-demo-xx") & "]" ' [] +``` + +## Enumerations + +### PasswordStrength +{: .no_toc } + +The result of [**GetStrength**](#getstrength). + +| Member | Value | +|---|---| +| **VeryWeak** | 0 | +| **Weak** | 1 | +| **Fair** | 2 | +| **Strong** | 3 | +| **VeryStrong** | 4 | + +## Example + +This example asks for a new password for an account and tells the user how good it is. + +```tb check_build +Dim box As New cDialogPassword +Dim current As String + +current = "Old-demo-pw1" +box.Title = "Change password" +box.MinLength = 10 +box.RequireSpecial = True + +Debug.Print box.GetRequirementsDescription() + +If box.ShowChangeDialog(current) Then + Debug.Print "New password is " & box.GetStrengthDescription(box.GetStrength(current)) +End If +``` + +## See Also + +- [cDialogInputBox](cDialogInputBox) class -- asks for one line of text, without a mask +- [cDialogCountdownMsgBox](cDialogCountdownMsgBox) class -- shows a message box that closes by itself +- [TBMANLIB_Dialog](.) package -- overview diff --git a/docs/Reference/Contributed/TBMANLIB_Dialog/cDialogPrint.md b/docs/Reference/Contributed/TBMANLIB_Dialog/cDialogPrint.md new file mode 100644 index 000000000..e6c36d9d8 --- /dev/null +++ b/docs/Reference/Contributed/TBMANLIB_Dialog/cDialogPrint.md @@ -0,0 +1,449 @@ +--- +title: cDialogPrint +parent: TBMANLIB_Dialog Package +permalink: /tB/Packages/Contributed/TBMANLIB_Dialog/cDialogPrint +has_toc: false +--- + +# cDialogPrint class +{: .no_toc } + +A **cDialogPrint** shows the Windows Print dialog, in which the user picks a printer, a range of pages and a number of copies. The class prints nothing: it returns the choices as properties and, by default, a device context for the printer the user chose, on which the program prints. It also describes the choice as a line of text. + +**Properties:** [**AllowPageRange**](#allowpagerange), [**AllowPrintToFile**](#allowprinttofile), [**AllowSelection**](#allowselection), [**Cancelled**](#cancelled), [**Collate**](#collate), [**Copies**](#copies), [**FromPage**](#frompage), [**hDC**](#hdc), [**HidePrintToFile**](#hideprinttofile), [**LastError**](#lasterror), [**MaxPage**](#maxpage), [**MinPage**](#minpage), [**OwnerHwnd**](#ownerhwnd), [**PrintRangeMode**](#printrangemode), [**PrintToFile**](#printtofile), [**ReturnDC**](#returndc), [**ToPage**](#topage). +**Methods:** [**Reset**](#reset), [**ShowDialog**](#showdialog), [**ToDescription**](#todescription). +**Enumerations:** [**PrintRange**](#printrange). + +Create an object with `Dim dlg As New cDialogPrint`. The class is declared `[COMCreatable(False)]`, so **CreateObject** cannot make one, and **New** can. + +```tb check_build +Private Declare PtrSafe Function PrDeleteDC Lib "gdi32" Alias "DeleteDC" (ByVal hDC As LongPtr) As Long + +Private Sub ChooseAndPrint() + Dim dlg As New cDialogPrint + + dlg.MinPage = 1 + dlg.MaxPage = 12 + dlg.FromPage = 1 + dlg.ToPage = 12 + If dlg.ShowDialog() Then + Debug.Print dlg.ToDescription() + ' ... print on dlg.hDC ... + PrDeleteDC dlg.hDC + ElseIf dlg.Cancelled Then + Debug.Print "Cancelled" + Else + Debug.Print "Error " & dlg.LastError + End If +End Sub +``` + +## How the dialog is shown + +[**ShowDialog**](#showdialog) calls `PrintDlgW`, the classic Print dialog, not the newer `PrintDlgEx`. The properties set these flags when they have the value shown: [**ReturnDC**](#returndc) **True** (`PD_RETURNDC`), [**Collate**](#collate) **True** (`PD_COLLATE`), [**PrintToFile**](#printtofile) **True** (`PD_PRINTTOFILE`), [**AllowSelection**](#allowselection) **False** (`PD_NOSELECTION`), [**AllowPageRange**](#allowpagerange) **False** (`PD_NOPAGENUMS`), [**HidePrintToFile**](#hideprinttofile) **True** (`PD_HIDEPRINTTOFILE`) and [**AllowPrintToFile**](#allowprinttofile) **False** (`PD_DISABLEPRINTTOFILE`). [**PrintRangeMode**](#printrangemode) adds `PD_SELECTION` or `PD_PAGENUMS`. The class passes no printer settings and sets no hook, template or help button. The dialog is modal and has the title *Print*; its printer is the default printer of the computer until the user changes it. + +When the user chooses **OK**, [**ShowDialog**](#showdialog) stores the page range, the number of copies, the collate and print-to-file choices, the kind of range and, with [**ReturnDC**](#returndc), the device context in the properties, and returns **True**. [**Cancelled**](#cancelled) is then **False**. When the user chooses **Cancel**, or closes the dialog, it returns **False** and sets **Cancelled** to **True**, and the other properties are unchanged. When the call itself fails, it returns **False**, leaves **Cancelled** **False** and stores the error code in [**LastError**](#lasterror). + +The class frees the printer data that Windows allocates for the dialog. The printer that the user chose, its paper, its orientation and the other settings of its driver are therefore not returned as such. They are in the device context, which is set up for the chosen printer with its settings, so a program that needs to know them asks the context with the API function `GetDeviceCaps`. + +> [!IMPORTANT] +> With [**ReturnDC**](#returndc) **True**, which is the default, every **OK** creates a device context that the class never deletes. The program must delete it with the API function `DeleteDC` when it has finished with it, as the sample at the head of the page does, or set **ReturnDC** to **False** when it does not need the context. [**Reset**](#reset), and a later [**ShowDialog**](#showdialog) that returns **True**, replace the handle without deleting the old context. + +> [!NOTE] +> The page numbers and the number of copies are passed to Windows as 16-bit integers. A [**FromPage**](#frompage), [**ToPage**](#topage), [**MinPage**](#minpage), [**MaxPage**](#maxpage) or [**Copies**](#copies) that does not fit, one above 32,767 or one below -32,768, makes [**ShowDialog**](#showdialog) raise error 6, *Overflow*, before the dialog opens. + +## Properties + +### AllowPageRange +{: .no_toc } + +Whether the user may choose a range of pages. + +Syntax: *object*.**AllowPageRange** [ **=** *value* ] + +A **Boolean**, **True** by default. When it is **False**, [**ShowDialog**](#showdialog) sets `PD_NOPAGENUMS`, and the *Pages* button and the *from* and *to* boxes are shown disabled. + +```tb check_build +Dim dlg As New cDialogPrint + +dlg.AllowPageRange = False +Debug.Print dlg.AllowPageRange +``` + +### AllowPrintToFile +{: .no_toc } + +Whether the user may choose *Print to file*. + +Syntax: *object*.**AllowPrintToFile** [ **=** *value* ] + +A **Boolean**, **True** by default. When it is **False**, [**ShowDialog**](#showdialog) sets `PD_DISABLEPRINTTOFILE`, and the *Print to file* box is shown disabled. To remove the box, use [**HidePrintToFile**](#hideprinttofile). + +```tb check_build +Dim dlg As New cDialogPrint + +dlg.AllowPrintToFile = False +Debug.Print dlg.AllowPrintToFile +``` + +### AllowSelection +{: .no_toc } + +Whether the user may choose *Selection*. + +Syntax: *object*.**AllowSelection** [ **=** *value* ] + +A **Boolean**, **True** by default. When it is **False**, [**ShowDialog**](#showdialog) sets `PD_NOSELECTION`, and the *Selection* button is shown disabled. Set it to **False** when the program has no selection to print. + +```tb check_build +Dim dlg As New cDialogPrint + +dlg.AllowSelection = False +Debug.Print dlg.AllowSelection +``` + +### Cancelled +{: .no_toc } + +Whether the last [**ShowDialog**](#showdialog) ended because the user cancelled. + +Syntax: *object*.**Cancelled** + +A read-only **Boolean**, **False** by default. A **ShowDialog** that returns **True** sets it to **False**, one the user cancelled sets it to **True**, and one that failed sets it to **False**, so the three outcomes can be told apart with [**LastError**](#lasterror). + +```tb check_build +Dim dlg As New cDialogPrint + +If Not dlg.ShowDialog() Then + If dlg.Cancelled Then Debug.Print "The user cancelled" +End If +``` + +### Collate +{: .no_toc } + +Whether the copies are collated. + +Syntax: *object*.**Collate** [ **=** *value* ] + +A **Boolean**, **False** by default. When it is **True**, [**ShowDialog**](#showdialog) sets `PD_COLLATE` and the *Collate* box starts selected. The box is disabled while [**Copies**](#copies) is 1. After **OK** it holds the state the user left. + +```tb check_build +Dim dlg As New cDialogPrint + +dlg.Copies = 3 +dlg.Collate = True +Debug.Print dlg.ToDescription() +``` + +### Copies +{: .no_toc } + +The number of copies. + +Syntax: *object*.**Copies** [ **=** *value* ] + +A **Long**, 1 by default. A value of 0 or less is ignored, without an error. The dialog shows it in the *Number of copies* box, and after **OK** it holds the number the user left. It must fit a 16-bit integer: see the note at the head of the page. + +```tb check_build check_run +Dim dlg As New cDialogPrint + +dlg.Copies = 3 +dlg.Copies = 0 ' ignored +dlg.Copies = -2 ' ignored +Debug.Print dlg.Copies +' Output: +' 3 +``` + +### FromPage +{: .no_toc } + +The first page of the range. + +Syntax: *object*.**FromPage** [ **=** *value* ] + +A **Long**, 1 by default. It is the *from* box of the dialog. After a [**ShowDialog**](#showdialog) that returned **True** it holds the first page the user typed or left; when the user chose *All* or *Selection*, it keeps the value the dialog was given. It is meaningful when [**PrintRangeMode**](#printrangemode) is **PageRange**. + +```tb check_build +Dim dlg As New cDialogPrint + +dlg.MinPage = 1 +dlg.MaxPage = 20 +dlg.PrintRangeMode = PrintRange.PageRange +dlg.FromPage = 3 +dlg.ToPage = 7 +If dlg.ShowDialog() Then Debug.Print dlg.FromPage & "-" & dlg.ToPage +``` + +### hDC +{: .no_toc } + +The device context of the chosen printer. + +Syntax: *object*.**hDC** + +A read-only **LongPtr**, 0 by default. After a [**ShowDialog**](#showdialog) that returned **True** with [**ReturnDC**](#returndc) **True**, it holds a device context for the printer that the user chose, with the settings the user chose, and the program must delete it: see the note under [How the dialog is shown](#how-the-dialog-is-shown). With **ReturnDC** **False** Windows returns no context and the property is 0. A cancelled or failed call leaves the property as it was. + +```tb check_build +Private Declare PtrSafe Function PrDeleteDC2 Lib "gdi32" Alias "DeleteDC" (ByVal hDC As LongPtr) As Long + +Private Sub UseContext() + Dim dlg As New cDialogPrint + + If dlg.ShowDialog() Then + Debug.Print dlg.hDC <> 0 + PrDeleteDC2 dlg.hDC + End If +End Sub +``` + +### HidePrintToFile +{: .no_toc } + +Whether the dialog hides *Print to file*. + +Syntax: *object*.**HidePrintToFile** [ **=** *value* ] + +A **Boolean**, **False** by default. When it is **True**, [**ShowDialog**](#showdialog) sets `PD_HIDEPRINTTOFILE`, and the *Print to file* box is not shown. + +```tb check_build +Dim dlg As New cDialogPrint + +dlg.HidePrintToFile = True +Debug.Print dlg.HidePrintToFile +``` + +### LastError +{: .no_toc } + +The error code of the last call that failed. + +Syntax: *object*.**LastError** + +A read-only **Long**, 0 by default. When the call to Windows fails and the user did not cancel, [**ShowDialog**](#showdialog) stores the value of `CommDlgExtendedError` here. A later call that succeeds or is cancelled does not reset it; only [**Reset**](#reset) does. + +```tb check_build +Dim dlg As New cDialogPrint + +If Not dlg.ShowDialog() Then + If Not dlg.Cancelled Then Debug.Print "Failed: " & dlg.LastError +End If +``` + +### MaxPage +{: .no_toc } + +The largest page number the user may enter. + +Syntax: *object*.**MaxPage** [ **=** *value* ] + +A **Long**, 9999 by default. It must fit a 16-bit integer: with 32,768 or more, [**ShowDialog**](#showdialog) raises error 6, *Overflow*. + +```tb check_build check_run +Dim dlg As New cDialogPrint + +dlg.MaxPage = 40000 +On Error Resume Next +Debug.Print dlg.ShowDialog() +Debug.Print Err.Number +' Output: +' 6 +``` + +### MinPage +{: .no_toc } + +The smallest page number the user may enter. + +Syntax: *object*.**MinPage** [ **=** *value* ] + +A **Long**, 1 by default. It must fit a 16-bit integer, as [**MaxPage**](#maxpage) must. + +```tb check_build +Dim dlg As New cDialogPrint + +dlg.MinPage = 1 +dlg.MaxPage = 50 +Debug.Print dlg.MinPage +``` + +### OwnerHwnd +{: .no_toc } + +The window that owns the dialog. + +Syntax: *object*.**OwnerHwnd** [ **=** *value* ] + +A **LongPtr** window handle, 0 by default, which gives the dialog no owner. [**ShowDialog**](#showdialog) passes it as the owner window of the call. [**Reset**](#reset) does not change it. + +```tb check_build +Private Sub ChoosePrinter(ByVal ownerWindow As LongPtr) + Dim dlg As New cDialogPrint + + dlg.OwnerHwnd = ownerWindow ' the handle of a window of the program + dlg.ReturnDC = False + Debug.Print dlg.OwnerHwnd + Debug.Print dlg.ShowDialog() +End Sub +``` + +### PrintRangeMode +{: .no_toc } + +Which pages are printed. + +Syntax: *object*.**PrintRangeMode** [ **=** *value* ] + +A [**PrintRange**](#printrange), **AllPages** by default. It selects the button of the *Print range* group that the dialog starts with, and after **OK** it holds the button that was selected. **PageRange** uses [**FromPage**](#frompage) and [**ToPage**](#topage). A number that is not a member of **PrintRange** is passed without a flag, so the dialog starts with *All*, and [**ToDescription**](#todescription) writes no range for it. + +```tb check_build +Dim dlg As New cDialogPrint + +dlg.PrintRangeMode = PrintRange.Selection +Debug.Print dlg.ToDescription() +``` + +### PrintToFile +{: .no_toc } + +Whether the output goes to a file. + +Syntax: *object*.**PrintToFile** [ **=** *value* ] + +A **Boolean**, **False** by default. When it is **True**, [**ShowDialog**](#showdialog) sets `PD_PRINTTOFILE` and the *Print to file* box starts selected. After **OK** it holds the state the user left. The class does not ask for a file name and does not print: it is for the program to send its output to a file when the property is **True**. + +```tb check_build +Dim dlg As New cDialogPrint + +dlg.PrintToFile = True +Debug.Print dlg.ToDescription() +``` + +### ReturnDC +{: .no_toc } + +Whether the dialog returns a device context for the chosen printer. + +Syntax: *object*.**ReturnDC** [ **=** *value* ] + +A **Boolean**, **True** by default. When it is **True**, [**ShowDialog**](#showdialog) sets `PD_RETURNDC` and [**hDC**](#hdc) holds the context after **OK**. The program must delete the context: see the note under [How the dialog is shown](#how-the-dialog-is-shown). + +```tb check_build +Dim dlg As New cDialogPrint + +dlg.ReturnDC = False ' the program only needs the page range +Debug.Print dlg.ReturnDC +``` + +### ToPage +{: .no_toc } + +The last page of the range. + +Syntax: *object*.**ToPage** [ **=** *value* ] + +A **Long**, 1 by default. It is the *to* box of the dialog, and is handled as [**FromPage**](#frompage) is. + +```tb check_build +Dim dlg As New cDialogPrint + +dlg.FromPage = 2 +dlg.ToPage = 5 +Debug.Print dlg.ToPage +``` + +## Methods + +### Reset +{: .no_toc } + +Restores the properties to their starting values. + +Syntax: *object*.**Reset** + +[**FromPage**](#frompage), [**ToPage**](#topage), [**MinPage**](#minpage) and [**Copies**](#copies) become 1, [**MaxPage**](#maxpage) 9999, [**PrintRangeMode**](#printrangemode) **AllPages**, [**Collate**](#collate), [**PrintToFile**](#printtofile) and [**HidePrintToFile**](#hideprinttofile) **False**, the three **Allow** properties and [**ReturnDC**](#returndc) **True**, [**hDC**](#hdc) and [**LastError**](#lasterror) 0 and [**Cancelled**](#cancelled) **False**. It does not delete a device context, and it does not change [**OwnerHwnd**](#ownerhwnd). + +```tb check_build check_run +Dim dlg As New cDialogPrint + +dlg.Copies = 4 +dlg.MaxPage = 40 +dlg.PrintRangeMode = PrintRange.Selection +dlg.Reset +Debug.Print dlg.ToDescription() +Debug.Print dlg.MaxPage +' Output: +' All pages, 1 copy +' 9999 +``` + +### ShowDialog +{: .no_toc } + +Shows the Print dialog. + +Syntax: *object*.**ShowDialog** ( ) **As Boolean** + +Returns **True** when the user chose **OK**, and then the properties hold the choice. Returns **False** when the user cancelled ([**Cancelled**](#cancelled) is **True**) or when the call failed ([**LastError**](#lasterror) holds the code). It raises error 6 when a page number or the number of copies does not fit a 16-bit integer. It sends nothing to a printer. See [How the dialog is shown](#how-the-dialog-is-shown). + +```tb check_build +Dim dlg As New cDialogPrint + +dlg.ReturnDC = False +dlg.MinPage = 1 +dlg.MaxPage = 20 +dlg.PrintRangeMode = PrintRange.PageRange +dlg.FromPage = 3 +dlg.ToPage = 7 +If dlg.ShowDialog() Then + Debug.Print dlg.ToDescription() +End If +``` + +### ToDescription +{: .no_toc } + +Returns the choice as a line of text. + +Syntax: *object*.**ToDescription** ( ) **As String** + +The range, then the number of copies, then `, Collated` and `, Print to file` for each that is **True**. The range is `All pages`, `Selection`, or `Pages ` followed by [**FromPage**](#frompage), ` - ` and [**ToPage**](#topage). The copies are `1 copy` or `n copies`. + +```tb check_build check_run +Dim dlg As New cDialogPrint + +Debug.Print dlg.ToDescription() +dlg.PrintRangeMode = PrintRange.PageRange +dlg.FromPage = 2 +dlg.ToPage = 4 +dlg.Copies = 3 +dlg.Collate = True +dlg.PrintToFile = True +Debug.Print dlg.ToDescription() +' Output: +' All pages, 1 copy +' Pages 2 - 4, 3 copies, Collated, Print to file +``` + +## Enumerations + +### PrintRange +{: .no_toc } + +Which pages are printed, the type of [**PrintRangeMode**](#printrangemode). + +| Member | Value | Meaning | +|:-------|------:|:--------| +| **AllPages** | 0 | Every page. | +| **Selection** | 1 | The selection of the program. | +| **PageRange** | 2 | The pages from [**FromPage**](#frompage) to [**ToPage**](#topage). | + +The enumeration is declared inside the class. A program names its members with the name of the enumeration in front, as in the samples, or by the member alone, such as `Selection`. The name of the class cannot be put in front: `cDialogPrint.PrintRange.Selection` is refused with TB5079, *Unrecognized symbol 'cDialogPrint'*. + +## See Also + +- [cDialogPageSetup](cDialogPageSetup) class -- shows the Page Setup dialog +- [cDialogFont](cDialogFont) class -- shows the Font dialog +- [cDialog](cDialog/) class -- shows the Open, Save As and Browse For Folder dialogs +- [TBMANLIB_Dialog](.) package -- overview diff --git a/docs/Reference/Contributed/TBMANLIB_Dialog/cDialogProgress.md b/docs/Reference/Contributed/TBMANLIB_Dialog/cDialogProgress.md new file mode 100644 index 000000000..296300e36 --- /dev/null +++ b/docs/Reference/Contributed/TBMANLIB_Dialog/cDialogProgress.md @@ -0,0 +1,679 @@ +--- +title: cDialogProgress +parent: TBMANLIB_Dialog Package +permalink: /tB/Packages/Contributed/TBMANLIB_Dialog/cDialogProgress +has_toc: false +--- + +# cDialogProgress class +{: .no_toc } + +A **cDialogProgress** keeps the state of a long task: a position between a minimum and a maximum, the time spent and an estimate of the time left, a message, and whether the task was cancelled. It raises events when the position changes or the task is cancelled, and describes the state as a line of text. + +**The class shows no window.** In version 1.1.0.0 it has no dialog, no progress bar and no cancel button, whatever its name, [**Title**](#title), [**ShowCancel**](#showcancel), [**Style**](#style) and [**OwnerHwnd**](#ownerhwnd) suggest. A program that wants to show the progress reads [**Percentage**](#percentage) or [**GetDescription**](#getdescription) and draws them itself, for instance on a form, and calls [**Cancel**](#cancel) from its own button. [**ShowProgress**](#showprogress) writes the description with **Debug.Print**, which appears in the debug console of the IDE and nowhere in a built program. + +A **cDialogProgress** is made with **New**: `Dim progress As New cDialogProgress`. It cannot be made with **CreateObject**. To receive the events, declare a variable `WithEvents`. + +[**Start**](#start) begins a task. [**Progress**](#progress), [**Increment**](#increment), [**PerformStep**](#performstep), [**SetPercentage**](#setpercentage) and [**Complete**](#complete) move the position, each raising [**ProgressChanged**](#progresschanged). [**Cancel**](#cancel) sets [**Cancelled**](#cancelled), which the task has to test: nothing stops a task by itself, and only [**SimulateProgress**](#simulateprogress) looks at the flag. + +```tb check_build +Dim progress As New cDialogProgress +Dim i As Long + +progress.Message = "Copying files" +progress.Max = 200 +progress.Start + +For i = 1 To 200 + If progress.Cancelled Then Exit For + ' ... copy file i ... + progress.Progress = i +Next i + +If Not progress.Cancelled Then progress.Complete +Debug.Print progress.Percentage & "%" +``` + +> [!NOTE] +> [**GetRemainingTime**](#getremainingtime), and with it [**GetRemainingTimeString**](#getremainingtimestring) and [**GetDescription**](#getdescription), raise error 11 (*Division by zero*) when they are called before the clock has ticked after [**Start**](#start) (the clock of Windows ticks about every 16 milliseconds) and [**Progress**](#progress) is above [**Min**](#min). A program that reports progress as it begins reads the description too early. Call them later, or handle the error. + +> [!NOTE] +> [**Percentage**](#percentage) and [**SetPercentage**](#setpercentage) multiply in a **Long**, so a range wider than about 21 million units can raise error 6 (*Overflow*): [**Percentage**](#percentage) with **Max** 100,000,000 and a position of 50,000,000 does, and so does [**SetPercentage**](#setpercentage) with 50. Use a smaller range, such as the number of items instead of the number of bytes. + +## Properties + +### Cancelled +{: .no_toc } + +Whether [**Cancel**](#cancel) was called, read-only. + +Syntax: *object*.**Cancelled** **As Boolean** + +**False** in a new object. [**Cancel**](#cancel) sets it to **True**; [**Start**](#start) and [**Reset**](#reset) set it to **False**. + +```tb check_build check_run +Dim progress As New cDialogProgress + +Debug.Print progress.Cancelled ' False +progress.Cancel +Debug.Print progress.Cancelled ' True +progress.Start +Debug.Print progress.Cancelled ' False +``` + +### DetailMessage +{: .no_toc } + +A second line of text for [**GetDescription**](#getdescription). + +Syntax: *object*.**DetailMessage** [ **=** *value* ] + +*value* +: A **String**. The default is an empty string. + +[**GetDescription**](#getdescription) adds it on a new line when it is not empty. + +```tb check_build check_run +Dim progress As New cDialogProgress + +progress.Style = ProgressStyle.Marquee +progress.Message = "Copying" +progress.DetailMessage = "report.txt" +Debug.Print Replace(progress.GetDescription(), vbCrLf, " | ") ' Copying | report.txt +``` + +### Max +{: .no_toc } + +The value of [**Progress**](#progress) at the end of the task. + +Syntax: *object*.**Max** [ **=** *value* ] + +*value* +: A **Long**. The default is 100. + +Changing it does not move [**Progress**](#progress). A value that is not above [**Min**](#min) makes [**Percentage**](#percentage) 0. + +```tb check_build check_run +Dim progress As New cDialogProgress + +Debug.Print progress.Max ' 100 +progress.Max = 250 +Debug.Print progress.Max ' 250 +``` + +### Message +{: .no_toc } + +The main text of [**GetDescription**](#getdescription). + +Syntax: *object*.**Message** [ **=** *value* ] + +*value* +: A **String**. The default is `Processing...`. + +```tb check_build check_run +Dim progress As New cDialogProgress + +Debug.Print progress.Message ' Processing... +``` + +### Min +{: .no_toc } + +The value of [**Progress**](#progress) at the start of the task. + +Syntax: *object*.**Min** [ **=** *value* ] + +*value* +: A **Long**. The default is 0. + +[**Start**](#start) sets [**Progress**](#progress) to it. Changing it does not move [**Progress**](#progress). + +```tb check_build check_run +Dim progress As New cDialogProgress + +progress.Min = 10 +progress.Max = 110 +progress.Start +Debug.Print progress.Progress ' 10 +Debug.Print progress.Percentage ' 0 +``` + +### OwnerHwnd +{: .no_toc } + +A property that has no effect. + +Syntax: *object*.**OwnerHwnd** [ **=** *value* ] + +*value* +: A **LongPtr**. The default is 0. + +The value is stored; there is no window to own. Unlike the other properties, [**Reset**](#reset) keeps it. + +```tb check_build check_run +Dim progress As New cDialogProgress + +progress.OwnerHwnd = 1234 +progress.Reset +Debug.Print progress.OwnerHwnd ' 1234 +``` + +### Percentage +{: .no_toc } + +The position as a percentage of the range, read-only. + +Syntax: *object*.**Percentage** **As Long** + +(**Progress** - **Min**) * 100 \ (**Max** - **Min**), rounded down; 0 when **Max** is not above **Min**. A position below **Min**, which [**Increment**](#increment) can reach with a negative [**Step**](#step), gives a negative number. See the second note [above](#cdialogprogress-class) for a range that is too wide. + +```tb check_build check_run +Dim progress As New cDialogProgress + +progress.Progress = 40 +Debug.Print progress.Percentage ' 40 +progress.Max = 200 +Debug.Print progress.Percentage ' 20 +progress.Max = 0 +Debug.Print progress.Percentage ' 0 +``` + +### Progress +{: .no_toc } + +The position of the task. + +Syntax: *object*.**Progress** [ **=** *value* ] + +*value* +: A **Long** from [**Min**](#min) to [**Max**](#max). + +A value outside the range is ignored, without an error or an event. Any other value, even the current one, is stored and raises [**ProgressChanged**](#progresschanged). The default is 0. + +```tb check_build check_run +Dim progress As New cDialogProgress + +progress.Progress = 40 +Debug.Print progress.Progress ' 40 +progress.Progress = 150 ' above Max: ignored +Debug.Print progress.Progress ' 40 +progress.Progress = -1 ' below Min: ignored +Debug.Print progress.Progress ' 40 +``` + +### ShowCancel +{: .no_toc } + +Whether [**ShowProgress**](#showprogress) adds a line on cancelling. + +Syntax: *object*.**ShowCancel** [ **=** *value* ] + +*value* +: A **Boolean**. The default is **True**. + +With **True**, [**ShowProgress**](#showprogress) ends its text with `(Press Ctrl+Break to cancel)`. The class does not react to that key; a task is cancelled by a call to [**Cancel**](#cancel). + +```tb check_build check_run +Dim progress As New cDialogProgress + +Debug.Print progress.ShowCancel ' True +``` + +### ShowElapsedTime +{: .no_toc } + +Whether [**GetDescription**](#getdescription) names the time spent. + +Syntax: *object*.**ShowElapsedTime** [ **=** *value* ] + +*value* +: A **Boolean**. The default is **True**. + +```tb check_build check_run +Dim progress As New cDialogProgress + +Debug.Print progress.ShowElapsedTime ' True +``` + +### ShowRemainingTime +{: .no_toc } + +Whether [**GetDescription**](#getdescription) names the estimated time left. + +Syntax: *object*.**ShowRemainingTime** [ **=** *value* ] + +*value* +: A **Boolean**. The default is **True**. + +```tb check_build check_run +Dim progress As New cDialogProgress + +Debug.Print progress.ShowRemainingTime ' True +``` + +### Step +{: .no_toc } + +The amount by which [**Increment**](#increment) moves [**Progress**](#progress). + +Syntax: *object*.**Step** [ **=** *value* ] + +*value* +: A **Long**. The default is 1. + +[**SimulateProgress**](#simulateprogress) uses it as the step of its loop. + +```tb check_build check_run +Dim progress As New cDialogProgress + +progress.Step = 25 +progress.Increment +progress.Increment +Debug.Print progress.Progress ' 50 +``` + +### Style +{: .no_toc } + +The kind of progress that [**GetDescription**](#getdescription) describes. + +Syntax: *object*.**Style** [ **=** *value* ] + +*value* +: A [**ProgressStyle**](#progressstyle) member. The default is **ProgressBar**. + +With **ProgressBar**, [**GetDescription**](#getdescription) adds the percentage and the times. With **Marquee** or **Steps** it returns the message alone, with the detail line. + +```tb check_build check_run +Dim progress As New cDialogProgress + +Debug.Print progress.Style = ProgressStyle.ProgressBar ' True +progress.Style = ProgressStyle.Steps +Debug.Print progress.Style ' 2 +``` + +### Title +{: .no_toc } + +A property that has no effect. + +Syntax: *object*.**Title** [ **=** *value* ] + +*value* +: A **String**. The default is `Progress`. + +The value is stored. The class shows no title bar and does not use it in its text. + +```tb check_build check_run +Dim progress As New cDialogProgress + +Debug.Print progress.Title ' Progress +``` + +## Methods + +### Cancel +{: .no_toc } + +Marks the task as cancelled. + +Syntax: *object*.**Cancel** + +Sets [**Cancelled**](#cancelled) to **True** and raises [**CancelClicked**](#cancelclicked). It stops nothing: the task has to test [**Cancelled**](#cancelled). Cancelling a task that was already cancelled raises the event again. + +```tb check_build check_run +Dim progress As New cDialogProgress + +progress.Cancel +Debug.Print progress.Cancelled ' True +``` + +### Complete +{: .no_toc } + +Moves the position to the end. + +Syntax: *object*.**Complete** + +Sets [**Progress**](#progress) to [**Max**](#max) and raises [**ProgressChanged**](#progresschanged) with that value, also when [**Progress**](#progress) was already there. + +```tb check_build check_run +Dim progress As New cDialogProgress + +progress.Max = 80 +progress.Complete +Debug.Print progress.Progress ' 80 +Debug.Print progress.Percentage ' 100 +``` + +### GetDescription +{: .no_toc } + +Returns the state as a line of text. + +Syntax: *object*.**GetDescription** ( ) **As String** + +Returns [**Message**](#message). With [**Style**](#style) **ProgressBar** it adds ` (N%)` with [**Percentage**](#percentage); then ` - Elapsed: ` and [**GetElapsedTimeString**](#getelapsedtimestring) when [**ShowElapsedTime**](#showelapsedtime) is **True**, and ` - Remaining: ` and [**GetRemainingTimeString**](#getremainingtimestring) when [**ShowRemainingTime**](#showremainingtime) is **True** and [**Progress**](#progress) is above [**Min**](#min). A non-empty [**DetailMessage**](#detailmessage) follows after **vbCrLf**. See the first note [above](#cdialogprogress-class) for the error that the remaining time can raise. + +```tb check_build check_run +Dim progress As New cDialogProgress + +progress.Message = "Copying" +progress.DetailMessage = "report.txt" +progress.Progress = 50 +Debug.Print Replace(progress.GetDescription(), vbCrLf, " | ") +' Copying (50%) - Elapsed: 0:00 - Remaining: 0:00 | report.txt + +progress.ShowElapsedTime = False +progress.ShowRemainingTime = False +Debug.Print Replace(progress.GetDescription(), vbCrLf, " | ") +' Copying (50%) | report.txt +``` + +### GetElapsedTime +{: .no_toc } + +Returns the whole seconds since [**Start**](#start). + +Syntax: *object*.**GetElapsedTime** ( ) **As Long** + +0 before the first [**Start**](#start). The count is made from the clock of Windows, which wraps around to negative numbers after 24.8 days of uptime; while it is negative at [**Start**](#start), the time functions return 0. + +```tb check_build check_run +Dim progress As New cDialogProgress + +Debug.Print progress.GetElapsedTime() ' 0 +``` + +### GetElapsedTimeString +{: .no_toc } + +Returns the time since [**Start**](#start) as text. + +Syntax: *object*.**GetElapsedTimeString** ( ) **As String** + +`m:ss`, or `h:mm:ss` from one hour on, such as `0:02` and `1:05:09`. + +```tb check_build check_run +Dim progress As New cDialogProgress + +Debug.Print progress.GetElapsedTimeString() ' 0:00 +``` + +### GetRemainingTime +{: .no_toc } + +Estimates the whole seconds that are left. + +Syntax: *object*.**GetRemainingTime** ( ) **As Long** + +Takes the speed so far, (**Progress** - **Min**) per millisecond since [**Start**](#start), and returns the time that the rest of the range needs at that speed. Returns 0 before [**Start**](#start) and while [**Progress**](#progress) is not above [**Min**](#min). It raises error 11 when no time has passed since [**Start**](#start); see the first note [above](#cdialogprogress-class). + +```tb check_build check_run +Dim progress As New cDialogProgress + +Debug.Print progress.GetRemainingTime() ' 0 +``` + +### GetRemainingTimeString +{: .no_toc } + +Returns [**GetRemainingTime**](#getremainingtime) as text. + +Syntax: *object*.**GetRemainingTimeString** ( ) **As String** + +In the form of [**GetElapsedTimeString**](#getelapsedtimestring). + +```tb check_build check_run +Dim progress As New cDialogProgress + +Debug.Print progress.GetRemainingTimeString() ' 0:00 +``` + +### Increment +{: .no_toc } + +Moves [**Progress**](#progress) by [**Step**](#step). + +Syntax: *object*.**Increment** + +Adds [**Step**](#step) to [**Progress**](#progress) and raises [**ProgressChanged**](#progresschanged). When the new value is above [**Max**](#max), nothing happens: no change and no event. The new value is not compared with [**Min**](#min), so a negative [**Step**](#step) can lead below it. + +```tb check_build check_run +Dim progress As New cDialogProgress + +progress.Step = 40 +progress.Increment +progress.Increment +progress.Increment ' 120 is above Max: ignored +Debug.Print progress.Progress ' 80 +``` + +### PerformStep +{: .no_toc } + +The same as [**Increment**](#increment). + +Syntax: *object*.**PerformStep** + +```tb check_build check_run +Dim progress As New cDialogProgress + +progress.PerformStep +Debug.Print progress.Progress ' 1 +``` + +### Reset +{: .no_toc } + +Restores the starting values. + +Syntax: *object*.**Reset** + +Sets [**Title**](#title) to `Progress`, [**Message**](#message) to `Processing...`, [**DetailMessage**](#detailmessage) to an empty string, [**Progress**](#progress) and [**Min**](#min) to 0, [**Max**](#max) to 100, [**Step**](#step) to 1, [**Style**](#style) to **ProgressBar**, the three **Show** properties to **True**, [**Cancelled**](#cancelled) to **False**, and forgets [**Start**](#start). It raises no event and leaves [**OwnerHwnd**](#ownerhwnd) as it is. + +```tb check_build check_run +Dim progress As New cDialogProgress + +progress.Max = 500 +progress.Message = "Importing" +progress.Reset +Debug.Print progress.Max ' 100 +Debug.Print progress.Message ' Processing... +``` + +### SetPercentage +{: .no_toc } + +Moves [**Progress**](#progress) to a percentage of the range. + +Syntax: *object*.**SetPercentage** ( *percent* ) + +*percent* +: *required* A **Long** from 0 to 100. Another value is ignored, without an error or an event. + +Sets [**Progress**](#progress) to **Min** + (**Max** - **Min**) * *percent* \ 100, rounded down, and raises [**ProgressChanged**](#progresschanged). See the second note [above](#cdialogprogress-class) for a range that is too wide. + +```tb check_build check_run +Dim progress As New cDialogProgress + +progress.Min = 10 +progress.Max = 110 +progress.SetPercentage 25 +Debug.Print progress.Progress ' 35 +progress.SetPercentage 101 ' ignored +Debug.Print progress.Progress ' 35 +progress.SetPercentage 100 +Debug.Print progress.Progress ' 110 +``` + +### ShowProgress +{: .no_toc } + +Writes the description with **Debug.Print**. + +Syntax: *object*.**ShowProgress** + +Prints [**GetDescription**](#getdescription), and when [**ShowCancel**](#showcancel) is **True** a blank line and `(Press Ctrl+Break to cancel)`. It opens no window, and a built program shows nothing. It raises the error of [**GetDescription**](#getdescription) when that does. + +```tb check_build +Dim progress As New cDialogProgress + +progress.Message = "Working" +progress.Progress = 30 +progress.ShowProgress +``` + +### SimulateProgress +{: .no_toc } + +Runs the task from the minimum to the maximum with a pause after every step, to try a program that reacts to the events. + +Syntax: *object*.**SimulateProgress** ( [ *delayMs* ] ) + +*delayMs* +: *optional* A **Long**, the pause after every step in milliseconds. The default is 100. + +Calls [**Start**](#start), then for *i* from [**Min**](#min) to [**Max**](#max) in steps of [**Step**](#step): stops when [**Cancelled**](#cancelled) is **True**, sets [**Progress**](#progress) to *i* (raising [**ProgressChanged**](#progresschanged)), calls [**ShowProgress**](#showprogress) and waits *delayMs*. Unless the task was cancelled it ends with [**Complete**](#complete), so the last position is reported twice. The thread waits and does not process messages, so a handler of [**ProgressChanged**](#progresschanged) is the place to call [**Cancel**](#cancel). A [**Step**](#step) of 0 never ends unless the task is cancelled, and a negative one skips the loop, so that only [**Complete**](#complete) runs. + +With a *delayMs* of 0 or 1 the call raises error 11 (*Division by zero*) at its second step, from the estimate of the remaining time; see the first note [above](#cdialogprogress-class). The default of 100 does not. + +```tb check_build +Dim progress As New cDialogProgress + +progress.Max = 5 +progress.SimulateProgress 50 +Debug.Print "Done at " & progress.Percentage & "%" +``` + +### Start +{: .no_toc } + +Begins a task. + +Syntax: *object*.**Start** + +Sets [**Progress**](#progress) to [**Min**](#min), [**Cancelled**](#cancelled) to **False** and notes the time for [**GetElapsedTime**](#getelapsedtime) and [**GetRemainingTime**](#getremainingtime). It raises no event, and does not change [**Message**](#message) and the other texts. + +```tb check_build check_run +Dim progress As New cDialogProgress + +progress.Progress = 60 +progress.Start +Debug.Print progress.Progress ' 0 +``` + +## Events + +### CancelClicked +{: .no_toc } + +Raised by [**Cancel**](#cancel). + +Syntax: **Event** *object*.**CancelClicked** ( ) + +The handler runs before [**Cancel**](#cancel) returns, and [**Cancelled**](#cancelled) is already **True**. + +### ProgressChanged +{: .no_toc } + +Raised when the position is set. + +Syntax: **Event** *object*.**ProgressChanged** ( *value* ) + +*value* +: The new position, a **Long**. + +Raised by [**Progress**](#progress) for a value inside the range, [**Increment**](#increment) and [**PerformStep**](#performstep) when they move, [**SetPercentage**](#setpercentage) for a percentage inside 0 to 100, and [**Complete**](#complete); also when the value is the same as before. [**Start**](#start) and [**Reset**](#reset) raise nothing. The handler runs before the call returns. + +A class that receives the events declares the variable with **WithEvents**: + +```tb check_build projname=dlg-progress slot=file +Class ProgressLog + Private WithEvents mProgress As cDialogProgress + + Public Sub Attach(ByVal source As cDialogProgress) + Set mProgress = source + End Sub + + Private Sub mProgress_ProgressChanged(ByVal value As Long) + Debug.Print "changed to " & value + End Sub + + Private Sub mProgress_CancelClicked() + Debug.Print "cancelled" + End Sub +End Class +``` + +This example counts the events of a short task. A value outside the range raises none, **Complete** raises one, and [**CancelClicked**](#cancelclicked) follows from **Cancel**: + +```tb check_run projname=dlg-progress +Dim progress As New cDialogProgress +Dim log As New ProgressLog + +log.Attach progress +progress.Max = 3 +progress.Start +progress.Increment +progress.Increment +progress.Progress = 9 ' outside the range: no event +progress.Complete +progress.Cancel +' Output: +' changed to 1 +' changed to 2 +' changed to 3 +' cancelled +``` + +## Enumerations + +### ProgressStyle +{: .no_toc } + +The kinds of progress for [**Style**](#style). Only [**GetDescription**](#getdescription) reads it. + +| Member | Value | Meaning | +|---|---|---| +| **ProgressBar** | 0 | A position in a range: the description has the percentage and the times | +| **Marquee** | 1 | A task of unknown length: the message alone | +| **Steps** | 2 | A task in steps: the message alone | + +## Example + +This example reports the progress of a task as text every 25 per cent, and stops when the task is cancelled by a call from elsewhere in the program. + +```tb check_build +Dim progress As New cDialogProgress +Dim item As Long + +progress.Message = "Processing items" +progress.Max = 100 +progress.Step = 5 +progress.Start + +For item = 1 To 20 + If progress.Cancelled Then Exit For + ' ... process item ... + progress.PerformStep + If progress.Percentage Mod 25 = 0 Then Debug.Print progress.Percentage & "% done" +Next item + +If progress.Cancelled Then + Debug.Print "Stopped at " & progress.Percentage & "%" +Else + progress.Complete + Debug.Print "Finished" +End If +``` + +## See Also + +- [cDialogCountdownMsgBox](cDialogCountdownMsgBox) class -- shows a message box that closes by itself +- [cDialogInputBox](cDialogInputBox) class -- asks for one line of text +- [TBMANLIB_Dialog](.) package -- overview + +``` diff --git a/docs/Reference/Contributed/TBMANLIB_Dialog/index.md b/docs/Reference/Contributed/TBMANLIB_Dialog/index.md index 3c4aa21ef..348baf21c 100644 --- a/docs/Reference/Contributed/TBMANLIB_Dialog/index.md +++ b/docs/Reference/Contributed/TBMANLIB_Dialog/index.md @@ -1,20 +1,52 @@ --- title: TBMANLIB_Dialog Package -parent: Contributed Packages +parent: Experimental Packages has_toc: false permalink: /tB/Packages/Contributed/TBMANLIB_Dialog/ -indexed_from: 1.0.0.1 +indexed_from: 1.1.0.0 +exclude_from_docs: + - IOleWindow + - IModalWindow + - IShellItem + - IShellItemArray + - IFileDialog + - IFileOpenDialog + - IFileSaveDialog + - TestModule --- # TBMANLIB_Dialog Package -**Author:** woeoio. **Version:** 1.0.0.1 +**Author:** woeoio. **Version:** 1.1.0.0 -A twinBASIC package with one class, **cDialog**, that shows the Windows Open, Save As and Browse For Folder dialogs, builds the file-type filters of the first two, splits a file path into folder, name and extension, and opens a file in the program that Windows associates with it. The property names follow VB6's **CommonDialog** control. The **CommonDialog** class of the [VBComDlg](../VBComDlg/CommonDialog) package is the fuller alternative, with flags, a filter index, cancel errors and the Color, Font and Print dialogs. +A twinBASIC package of dialogs. Its first class, **cDialog**, shows the Windows Open, Save As and Browse For Folder dialogs, builds the file-type filters of the first two, splits a file path into folder, name and extension, and opens a file in the program that Windows associates with it; its property names follow VB6's **CommonDialog** control. **cDialogEx** shows the Open, Save As and folder dialogs of Windows Vista and later. Five more classes show the Windows Color, Font, Find and Replace, Page Setup and Print dialogs, and five classes ask for a value or report progress (a message box with a time limit, a date and time, an input box, a password and a progress state); the last five use the run-time's own **InputBox** and **MsgBox** or draw nothing. Every class except **cDialog** is marked as not creatable with **CreateObject**, and is made with **New**. The module **mDialog** holds the types and conversion functions that they share. This section documents version 1.1.0.0, which added every class except **cDialog**. The **CommonDialog** class of the [VBComDlg](../VBComDlg/CommonDialog) package is a fuller alternative for the Windows common dialogs, with flags, a filter index and cancel errors. + +> [!WARNING] +> +> Several classes of version 1.1.0.0 fail at their main task, and no error is raised. [**cDialogFindReplace**](cDialogFindReplace) reports nothing to the program, and its **ShowReplace** can end a 32-bit program. [**cDialogPageSetup**](cDialogPageSetup) ignores the paper size and the orientation the program sets, and does not read back the orientation the user chose. [**cDialogEx**](cDialogEx) returns file and folder names without their folder, ignores **InitialDir**, and shows no file types for a filter written in the usual form. [**cDialogPassword**](cDialogPassword) shows the password as it is typed in its confirmation boxes. [**cDialogDateTime**](cDialogDateTime) keeps the parts of an entry it refused, refuses a time with AM or PM and no date, and changes the letters of a month name in a custom format. Four properties of [**cDialogInputBox**](cDialogInputBox), among them **PasswordChar**, have no effect. Each class's page describes its defects. [**cDialog**](cDialog/), the class for the Open, Save As and Browse For Folder dialogs, works. ## Classes - [cDialog](cDialog/) -- shows the Open, Save As and Browse For Folder dialogs, builds file filters, splits file paths and opens files. +- [cDialogEx](cDialogEx) -- shows the Open, Save As and folder dialogs of Windows Vista and later, with a file-type index, an owner window and a title-bar icon. +- [cDialogColor](cDialogColor) -- shows the Windows Color dialog and holds the color as `&H00BBGGRR`, `#RRGGBB` and `RGB(r, g, b)`. +- [cDialogFont](cDialogFont) -- shows the Windows Font dialog and holds the face, size, style, effects and color, with CSS and text descriptions. +- [cDialogFindReplace](cDialogFindReplace) -- shows the modeless Windows Find and Replace dialogs; in version 1.1.0.0 the program receives nothing from them. +- [cDialogPageSetup](cDialogPageSetup) -- shows the Windows Page Setup dialog and holds the paper and the margins in inches; in version 1.1.0.0 the paper size and the orientation are not passed. +- [cDialogPrint](cDialogPrint) -- shows the Windows Print dialog and holds the page range, the copies and a printer device context that the program must delete. +- [cDialogCountdownMsgBox](cDialogCountdownMsgBox) -- shows a message box that closes by itself after a number of seconds; the text never counts down. +- [cDialogDateTime](cDialogDateTime) -- asks for a date and a time in the run-time's input box, and formats them. +- [cDialogInputBox](cDialogInputBox) -- asks for a text, a number, an e-mail address, a telephone number or a web address in the run-time's input box. +- [cDialogPassword](cDialogPassword) -- asks for a password, or for a new password in place of an old one, in the run-time's input box. +- [cDialogProgress](cDialogProgress) -- tracks the state of a task and returns it as text; it shows no window. + +## Module + +- [mDialog](mDialog) -- the unit conversions, the HTML colour conversions, the paper sizes and four structure types that the classes share. + +## Test code and interfaces + +The package contains a module of test procedures, **TestModule**, whose procedures each open a dialog and report the result in a message box. Its procedures, such as `TestFileOpen` and `QuickTest`, are not part of the interface that a program uses, and this section does not document them. The seven interfaces **IOleWindow**, **IModalWindow**, **IShellItem**, **IShellItemArray**, **IFileDialog**, **IFileOpenDialog** and **IFileSaveDialog** are declarations of COM interfaces of Windows that **cDialogEx** calls inside. No member of **cDialogEx** takes or returns one, so a program does not use them to use the class, and this section does not document them either. ## Properties (cDialog) @@ -42,8 +74,10 @@ A twinBASIC package with one class, **cDialog**, that shows the Windows Open, Sa - [GetFileNameWithoutExt](cDialog/GetFileNameWithoutExt) -- returns the file name of a path without its extension. - [GetFilePath](cDialog/GetFilePath) -- returns the part of a path before its last backslash. - [OpenFile](cDialog/OpenFile) -- runs a shell verb, by default `open`, on a file or a program. +- [PathCombine](cDialog/PathCombine) -- joins two path parts with one backslash. - [Reset](cDialog/Reset) -- restores every property to its starting value. - [SelectAndOpenFile](cDialog/SelectAndOpenFile) -- shows the Open dialog and opens the chosen file or files. +- [SelectFiles](cDialog/SelectFiles) -- shows the Open dialog and returns the chosen files as a collection. - [SetCodeFilters](cDialog/SetCodeFilters) -- replaces the filter with one for source code files. - [SetCommonFilters](cDialog/SetCommonFilters) -- replaces the filter with Text Files and All Files. - [SetDocumentFilters](cDialog/SetDocumentFilters) -- replaces the filter with one for office documents. @@ -51,3 +85,8 @@ A twinBASIC package with one class, **cDialog**, that shows the Windows Open, Sa - [ShowBrowseForFolder](cDialog/ShowBrowseForFolder) -- shows the Browse For Folder dialog and returns the chosen folder. - [ShowOpen](cDialog/ShowOpen) -- shows the Open dialog and returns the chosen file. - [ShowSave](cDialog/ShowSave) -- shows the Save As dialog and returns the chosen file name. + +## See Also + +- [VBComDlg](../VBComDlg/) package -- the **CommonDialog** class, with flags, a filter index and cancel errors +- [Experimental Packages](../../Experimental/) -- the packages from TWINSERV that are not yet ready for use in a program diff --git a/docs/Reference/Contributed/TBMANLIB_Dialog/mDialog.md b/docs/Reference/Contributed/TBMANLIB_Dialog/mDialog.md new file mode 100644 index 000000000..14381390c --- /dev/null +++ b/docs/Reference/Contributed/TBMANLIB_Dialog/mDialog.md @@ -0,0 +1,159 @@ +--- +title: mDialog +parent: TBMANLIB_Dialog Package +permalink: /tB/Packages/Contributed/TBMANLIB_Dialog/mDialog +has_toc: false +--- + +# mDialog module +{: .no_toc } + +Holds the constants, the four structure types and the eight conversion functions that the dialog classes of the package share. The conversions change between inches, millimetres, points and twips, and between an HTML colour text and its red, green and blue parts. + +**Functions:** [HtmlToRGB](#htmltorgb), [InchesToMM](#inchestomm), [InchesToPoints](#inchestopoints), [InchesToTwips](#inchestotwips), [MMToInches](#mmtoinches), [PointsToInches](#pointstoinches), [RGBToHtml](#rgbtohtml), [TwipsToInches](#twipstoinches). + +**Constants:** [MM_PER_INCH](#constants), [POINTS_PER_INCH](#constants), [TWIPS_PER_INCH](#constants) and the eight [paper sizes](#paper-sizes). + +**Types:** [COMDLG_FILTERSPEC](#types), [GUID](#types), [POINT](#types), [RECT](#types). + +The members of **mDialog** are global: a program writes `InchesToMM(1)` and also `mDialog.InchesToMM(1)`. + +```tb check_build check_run +Debug.Print InchesToMM(8.5) ' 215.9 +Debug.Print InchesToPoints(1) ' 72 +Debug.Print InchesToTwips(1.5) ' 2160 +Debug.Print RGBToHtml(255, 128, 0) ' #FF8000 + +Dim r As Long, g As Long, b As Long +HtmlToRGB "#00ff7f", r, g, b +Debug.Print r; g; b ' 0 255 127 +``` + +## Functions + +### HtmlToRGB +{: .no_toc } + +Splits an HTML colour text into its red, green and blue parts. + +Syntax: **HtmlToRGB** ( *htmlColor*, *r*, *g*, *b* ) + +*htmlColor* +: *required* A **String** of six hexadecimal digits, with a leading `#` or without one, and with or without spaces at its ends: `#FF8000`, `ff8000`. + +*r*, *g*, *b* +: *required* **Long** variables, passed by reference. They receive the red, green and blue parts, each from 0 to 255. + +The text must have exactly six characters after the optional `#`. Any other text sets all three variables to 0: three-digit colours (`#FFF`), eight-digit colours (`#FF8000FF`), colour names, an empty string and text with characters that are not hexadecimal digits (`#GG0000`) give black, and no error is raised. A text with a sign character (`#-10000`, `#+12345`) gives black too. A space inside six characters is not always rejected: `#1 2345` gives 1, 35 and 69. + +### InchesToMM +{: .no_toc } + +Converts inches to millimetres. + +Syntax: **InchesToMM** ( *inches* ) **As Double** + +Returns *inches* multiplied by 25.4: 215.9 for 8.5. + +### InchesToPoints +{: .no_toc } + +Converts inches to points. + +Syntax: **InchesToPoints** ( *inches* ) **As Double** + +Returns *inches* multiplied by 72: 72 for 1. + +### InchesToTwips +{: .no_toc } + +Converts inches to twips. + +Syntax: **InchesToTwips** ( *inches* ) **As Long** + +Returns *inches* multiplied by 1,440, rounded to the nearest whole number with **CLng**: 2160 for 1.5, 0 for 0.0003 and 1 for 0.0004. + +### MMToInches +{: .no_toc } + +Converts millimetres to inches. + +Syntax: **MMToInches** ( *mm* ) **As Double** + +Returns *mm* divided by 25.4: 1 for 25.4, and 8.26771653543307 for 210. + +### PointsToInches +{: .no_toc } + +Converts points to inches. + +Syntax: **PointsToInches** ( *points* ) **As Double** + +Returns *points* divided by 72: 0.5 for 36. + +### RGBToHtml +{: .no_toc } + +Builds an HTML colour text from red, green and blue parts. + +Syntax: **RGBToHtml** ( *r*, *g*, *b* ) **As String** + +Each argument is a **Long**. The function takes the lowest eight bits of each (`And &HFF`), writes each as two upper-case hexadecimal digits and puts a `#` first. A value outside 0 to 255 is not rejected: it wraps, so `RGBToHtml(256, -1, 300)` gives `#00FF2C`. + +### TwipsToInches +{: .no_toc } + +Converts twips to inches. + +Syntax: **TwipsToInches** ( *twips* ) **As Double** + +*twips* is a **Long**. Returns it divided by 1,440: 0.5 for 720. + +## Constants + +| Constant | Type | Value | +|---|---|---| +| **MM_PER_INCH** | **Double** | 25.4 | +| **POINTS_PER_INCH** | **Long** | 72 | +| **TWIPS_PER_INCH** | **Long** | 1440 | + +### Paper sizes + +Paper sizes in inches, all **Double**. The A sizes are rounded to two decimals. + +| Constant | Value | +|---|---| +| **PAPER_LETTER_WIDTH**, **PAPER_LETTER_HEIGHT** | 8.5, 11 | +| **PAPER_LEGAL_WIDTH**, **PAPER_LEGAL_HEIGHT** | 8.5, 14 | +| **PAPER_A4_WIDTH**, **PAPER_A4_HEIGHT** | 8.27, 11.69 | +| **PAPER_A3_WIDTH**, **PAPER_A3_HEIGHT** | 11.69, 16.54 | + +## Types + +The structures are those of the Windows API, written for the dialog classes of the package. + +| Type | Members | Size, 32-bit and 64-bit | +|---|---|---| +| **COMDLG_FILTERSPEC** | **pszName** and **pszSpec**, both **LongPtr** | 8 and 16 bytes | +| **GUID** | **Data1** (**Long**), **Data2** and **Data3** (**Integer**), **Data4** (8 **Byte** values) | 16 bytes | +| **POINT** | **X** and **Y**, both **Long** | 8 bytes | +| **RECT** | **Left**, **Top**, **Right** and **Bottom**, all **Long** | 16 bytes | + +Declare **GUID** as `mDialog.GUID`: the name **GUID** alone refers to another type, and `Dim g As GUID` is refused with error TB5106 in a project that references the package. Writing the module name before **RECT**, **POINT** and **COMDLG_FILTERSPEC** as well makes sure that the type of the package is the one used. + +```tb check_build check_run +Dim g As mDialog.GUID +Dim rc As mDialog.RECT + +g.Data1 = 5 +rc.Left = 2 +rc.Right = 9 +Debug.Print g.Data1 ' 5 +Debug.Print rc.Right - rc.Left ' 7 +Debug.Print LenB(rc) ' 16 +``` + +## See Also + +- [TBMANLIB_Dialog](.) package -- overview +- [cDialogEx](cDialogEx) class -- uses **GUID** and **COMDLG_FILTERSPEC** diff --git a/docs/Reference/Contributed/tbRegExp/MatchCollection/Item.md b/docs/Reference/Contributed/tbRegExp/MatchCollection/Item.md index 977240eec..19a76e1ea 100644 --- a/docs/Reference/Contributed/tbRegExp/MatchCollection/Item.md +++ b/docs/Reference/Contributed/tbRegExp/MatchCollection/Item.md @@ -24,7 +24,7 @@ Returns a [**Match**](../Match/). The property is read-only. **Item** is the default member of the class, so `matches(1)` is the same as `matches.Item(1)`. -The position is zero-based, as in the VBScript object model that the package replaces, although the **Collection** inside the class is one-based. A match has no key, so the argument is always a position. +The position is zero-based, as in the VBScript object model that the package replaces. A match has no key, so the argument is always a position. A position below 0, or not below [**Count**](Count), raises run-time error `vbObjectError + 9` with the message *Requested item not found in Matches collection*. diff --git a/docs/Reference/Contributed/tbRegExp/MatchCollection/NewEnum.md b/docs/Reference/Contributed/tbRegExp/MatchCollection/NewEnum.md index 888fb26eb..62186d6f3 100644 --- a/docs/Reference/Contributed/tbRegExp/MatchCollection/NewEnum.md +++ b/docs/Reference/Contributed/tbRegExp/MatchCollection/NewEnum.md @@ -15,13 +15,13 @@ Syntax: *object*.**NewEnum** **As IUnknown** *object* : *required* An object expression that evaluates to a **MatchCollection**. -Returns an **IUnknown** that refers to the enumerator of the **Collection** holding the matches. +Returns an **IUnknown** that refers to a new enumerator over the matches, which starts at the first one. ## Remarks **NewEnum** is declared with the [**Enumerator**](../../../../Core/Attributes#enumerator) attribute, which makes it the member that [**For Each**](../../../../Core/For-Each-Next) calls to start an enumeration. [Custom Enumerators](../../../../../Features/Language/Custom-Enumerators) describes the rule. It is also declared [**Hidden**](../../../../Core/Attributes#hidden), so it does not appear in certain IntelliSense lists. -The enumerator is the one of the **Collection** inside the class, so each element is a **Match**, and the matches come in the order they were found. The element variable of a **For Each** loop can be declared **As Match**, as in the example. +Each element is a **Match**, and the matches come in the order they were found. A collection can be enumerated any number of times, and an enumeration inside another one is independent. The element variable of a **For Each** loop can be declared **As Match**, as in the example. A program does not need to call **NewEnum**. **For Each** does it. diff --git a/docs/Reference/Contributed/tbRegExp/RegExp/DotAll.md b/docs/Reference/Contributed/tbRegExp/RegExp/DotAll.md index 7f711041e..b74783e54 100644 --- a/docs/Reference/Contributed/tbRegExp/RegExp/DotAll.md +++ b/docs/Reference/Contributed/tbRegExp/RegExp/DotAll.md @@ -8,7 +8,7 @@ has_toc: false # DotAll {: .no_toc } -Returns or sets whether `.` in the pattern matches line terminators. +Returns or sets whether `.` in the pattern matches a line feed. ## Get @@ -28,11 +28,11 @@ Sets the value. Syntax: *object*.**DotAll** **=** *value* *value* -: A **Boolean**. **True** lets `.` match any character, line terminators included. **False** makes `.` match any character except a line terminator. +: A **Boolean**. **True** lets `.` match any character, a line feed included. **False** makes `.` match any character except a line feed. ## Remarks -The line terminators are the line feed (`vbLf`), the carriage return (`vbCr`), and the characters U+2028 and U+2029. +As in VBScript's **RegExp**, only the line feed (`vbLf`) is excluded when the value is **False**. The carriage return (`vbCr`) and the characters U+2028 and U+2029 are matched by `.` whatever the value. [**MultiLine**](MultiLine) treats all four as line terminators for `^` and `$`. The value is applied when [**Execute**](Execute), [**Test**](Test), [**Replace**](Replace) or [**Split**](Split) makes a match. Changing it does not compile the pattern again. The flag `s` of [**Flags**](Flags) reads and sets the same value. @@ -43,6 +43,7 @@ Dim re As RegExp Set re = New RegExp re.Pattern = "a.b" Debug.Print re.Test("a" & vbLf & "b") ' False +Debug.Print re.Test("a" & vbCr & "b") ' True re.DotAll = True Debug.Print re.Test("a" & vbLf & "b") ' True diff --git a/docs/Reference/Contributed/tbRegExp/RegExp/Execute.md b/docs/Reference/Contributed/tbRegExp/RegExp/Execute.md index 91d89210e..9508c60d5 100644 --- a/docs/Reference/Contributed/tbRegExp/RegExp/Execute.md +++ b/docs/Reference/Contributed/tbRegExp/RegExp/Execute.md @@ -26,18 +26,19 @@ With [**Global**](Global) set to **False**, the collection holds at most one mat [**IgnoreCase**](IgnoreCase), [**MultiLine**](MultiLine) and [**DotAll**](DotAll) change what counts as a match. -> [!WARNING] -> With **Global** set to **True**, a match of no characters is returned twice when the search had to skip at least one character to reach it, so the collection holds one **Match** too many at each such position. It happens to a pattern that matches no characters at a position after the one where the search started: `$`, `\b`, `^` with **MultiLine** set to **True**, and a lookahead or lookbehind on its own. A pattern such as `x*`, which matches no characters at the position where the search starts, is not affected. VBScript's **RegExp** returns each such match once. A match of one or more characters is never repeated, and with **Global** set to **False** nothing is. This is how version 1.4.0.0 behaves. - -The pattern in this example matches no characters, before each capital letter. `fooBarBaz` has two capitals, and the collection holds four matches. +A match can have no characters, as `(?=[A-Z])` has before each capital letter, and each such match is returned once. `fooBarBaz` has two capitals, so the collection holds two matches, at positions 3 and 6. ```tb check_build check_run Dim re As New RegExp re.Global = True re.Pattern = "(?=[A-Z])" -Debug.Print re.Execute("fooBarBaz").Count ' 4 +Debug.Print re.Execute("fooBarBaz").Count ' 2 +Debug.Print re.Execute("fooBarBaz")(1).FirstIndex ' 6 ``` +> [!WARNING] +> A match of more than a few thousand characters is cut short, and nothing is raised: `a*` on a string of 5,000 `a` returns one match that starts at position 1669 and is 3,331 characters long, not the whole string. The [overview of the package](../) describes the limit. + Each **Match** gives the matched text ([**Value**](../Match/Value)), the position where it starts ([**FirstIndex**](../Match/FirstIndex), counted from 0), its length, and the text of each group of the pattern ([**SubMatches**](../Match/SubMatches)). A group written `(?...)` can be read by its name. An invalid pattern raises the compile error, with the number in [**LastErrorNumber**](LastErrorNumber) and the description in [**LastError**](LastError). Check [**IsValid**](IsValid) first to avoid it. diff --git a/docs/Reference/Contributed/tbRegExp/RegExp/IsValid.md b/docs/Reference/Contributed/tbRegExp/RegExp/IsValid.md index 486f7d9d8..8f2d28c48 100644 --- a/docs/Reference/Contributed/tbRegExp/RegExp/IsValid.md +++ b/docs/Reference/Contributed/tbRegExp/RegExp/IsValid.md @@ -26,7 +26,7 @@ Check **IsValid** before [**Execute**](Execute), [**Test**](Test), [**Replace**] Although nothing is raised, the compile error of an invalid pattern stays in the **Err** object after the call that compiled the pattern returns. A program that tests **Err** for other errors calls **Err.Clear** first. > [!NOTE] -> Two faults are not found when the pattern is compiled, so **IsValid** returns **True** for them: a quantifier whose lower count is above its upper count, such as `a{2,1}`, and a backreference to a group that the pattern does not have, such as `(a)\2`. [**Execute**](Execute), [**Test**](Test), [**Replace**](Replace) and [**Split**](Split) then raise error 3000 when a search reaches the quantifier or the backreference, with the description that Windows gives that number, *The specified print monitor is unknown.* This is how version 1.4.0.0 behaves. +> A quantifier whose lower count is above its upper count, such as `a{2,1}`, is not found when the pattern is compiled, so **IsValid** returns **True** for it. [**Execute**](Execute), [**Test**](Test), [**Replace**](Replace) and [**Split**](Split) then raise error 3000 when a search reaches the quantifier, with the description that Windows gives that number, *The specified print monitor is unknown.* This is how version 1.7.0.0 behaves. ## Example diff --git a/docs/Reference/Contributed/tbRegExp/RegExp/LastErrorNumber.md b/docs/Reference/Contributed/tbRegExp/RegExp/LastErrorNumber.md index 2a4103bc1..8bc6b8c2d 100644 --- a/docs/Reference/Contributed/tbRegExp/RegExp/LastErrorNumber.md +++ b/docs/Reference/Contributed/tbRegExp/RegExp/LastErrorNumber.md @@ -25,12 +25,12 @@ The engine raises its pattern errors with numbers of the form `vbObjectError + 3 | *n* | Fault | Example | |-----|-------|---------| -| 1 | An escape the engine does not know, or a backslash at the end | `\k`, `\A`, `\p{L}`, `\` | +| 1 | A backslash at the end, a `\c` without a letter, or a `\0` followed by `8` or `9` | `\`, `\c1`, `\08` | | 2 | A `\x` or `\u` escape without its hexadecimal digits | `\u00` | | 3 | A range in a character class that runs backwards | `[z-a]` | | 4 | A character class without its `]` | `[abc` | | 6 | A group construct `(?<` that is neither a lookbehind nor a valid name | `(?<1a>x)` | -| 8 | A `{` that does not start a quantifier, or a count that is too large | `a{2` | +| 8 | A `{` that does not start a quantifier, or a count that is too large | `a{2`, `\p{L}` | | 9 | A group without its `)` | `(\d+` | | 11 | A `)` that closes no group | `)` | | 12 | A quantifier with nothing to repeat | `*a` | diff --git a/docs/Reference/Contributed/tbRegExp/RegExp/Replace.md b/docs/Reference/Contributed/tbRegExp/RegExp/Replace.md index 0801bf387..386cf3adf 100644 --- a/docs/Reference/Contributed/tbRegExp/RegExp/Replace.md +++ b/docs/Reference/Contributed/tbRegExp/RegExp/Replace.md @@ -25,7 +25,7 @@ Returns a new **String** with the replacements made. It is *sourceString* unchan ## Substitution items -A `$` in *replaceString* starts a substitution item. Any other text is copied as it is. +A `$` in *replaceString* starts a substitution item. Any other text is copied as it is, and so is a `$` that starts no item. | Item | Is replaced with | |------|------------------| @@ -33,32 +33,32 @@ A `$` in *replaceString* starts a substitution item. Any other text is copied as | `$&` | The matched text. | | `` $` `` | The part of *sourceString* before the match. | | `$'` | The part of *sourceString* after the match. | -| `$`*n* | The text of numbered group *n*, counting from 1. All the digits after the `$` are read as the number. It is an empty string when the group matched nothing or the pattern has no such group. `$0` raises a run-time error. | -| `$<`*name*`>` | The text of the group written `(?...)`. It is an empty string when the group matched nothing or no group has the name. | +| `$`*n* or `$`*nn* | The text of numbered group *n*, counting from 1. Two digits are read as the number when the pattern has a group with that number, otherwise one. It is an empty string when the group matched nothing. When the pattern has no group *n*, the `$` is copied, and so are the digits: `$0` and `$00` are never items, and `$3` in a pattern of two groups is the text `$3`. | +| `$<`*name*`>` | The text of the group written `(?...)`. It is an empty string when the group matched nothing or no group has the name. When the pattern has no named group, or the `>` is missing, or the name is empty, the `$` is copied. | | `$~` | Nothing. The item is dropped. | -A `$` at the end of *replaceString*, or followed by a character that is not in the table, raises error `vbObjectError + 3031`. The error is raised even when *sourceString* has no match, so a literal `$` is written `$$`. +A `$` that starts no item raises no error, so a replacement string can hold a literal `$` as it is, at the end or before any other character. Before a character that would make an item, write `$$`: `$$$&` inserts a `$` and then the matched text, as the last example does. > [!NOTE] -> VBScript's **RegExp** copies into the result, as written, any `$` that does not start one of its own items (`$$`, `$&`, `` $` ``, `$'` and `$`*n*). Here a `$` at the end or before another character raises an error, `$0` raises an error, `$~` is dropped and `$`*n* of a group the pattern does not have is an empty string. A replacement string written for VBScript that holds a literal `$` needs `$$`. +> Items are read as in VBScript's **RegExp**, with two differences: `$<`*name*`>` is an addition of this package, and `$~` is dropped, where VBScript copies `$~` into the result. ## Remarks With [**Global**](Global) set to **False**, only the first match is replaced. With **Global** set to **True**, every match is. [**IgnoreCase**](IgnoreCase), [**MultiLine**](MultiLine) and [**DotAll**](DotAll) change what counts as a match. -> [!WARNING] -> With **Global** set to **True**, the replacement is made twice at a position where a match of no characters was reached by skipping at least one character, such as the start of every line but the first with `^` and **MultiLine** set to **True**. The warning at [**Execute**](Execute#remarks) lists the patterns affected. VBScript's **RegExp** makes the replacement once. This is how version 1.4.0.0 behaves. - -In this example the pattern `^` is meant to put `> ` at the start of each of three lines. The second and third lines get it twice. +A match of no characters is replaced once, wherever it is found. In this example the pattern `^` puts `> ` at the start of each of three lines. ```tb check_build check_run Dim re As New RegExp re.Global = True re.MultiLine = True re.Pattern = "^" -Debug.Print Replace(re.Replace("a" & vbLf & "b" & vbLf & "c", "> "), vbLf, "/") ' > a/> > b/> > c +Debug.Print Replace(re.Replace("a" & vbLf & "b" & vbLf & "c", "> "), vbLf, "/") ' > a/> b/> c ``` +> [!WARNING] +> A match of more than a few thousand characters is cut short, and nothing is raised, so **Replace** replaces a shorter part of the text than the pattern describes. The [overview of the package](../) describes the limit. + An invalid pattern raises the compile error, with the number in [**LastErrorNumber**](LastErrorNumber) and the description in [**LastError**](LastError). Check [**IsValid**](IsValid) first to avoid it. ## Example @@ -74,6 +74,7 @@ Debug.Print re.Replace("ann@example.com, bob@test.com", "$2:$1") ' example:ann, re.Pattern = "\d+" Debug.Print re.Replace("5 and 12", "$$$&") ' $5 and $12 +Debug.Print re.Replace("x5", "$ $x $3") ' x$ $x $3 re.Pattern = "(?\w+) (?\w+)" Debug.Print re.Replace("Ada Lovelace", "$, $") ' Lovelace, Ada diff --git a/docs/Reference/Contributed/tbRegExp/RegExp/Split.md b/docs/Reference/Contributed/tbRegExp/RegExp/Split.md index 3796e21b5..7631e1d3e 100644 --- a/docs/Reference/Contributed/tbRegExp/RegExp/Split.md +++ b/docs/Reference/Contributed/tbRegExp/RegExp/Split.md @@ -30,10 +30,7 @@ When [**Pattern**](Pattern) finds no match, the collection holds one item, the w [**IgnoreCase**](IgnoreCase), [**MultiLine**](MultiLine) and [**DotAll**](DotAll) apply to the matching. -> [!WARNING] -> With **Global** set to **True**, a match of no characters that was reached by skipping at least one character splits the string twice, which adds an empty string to the collection. The warning at [**Execute**](Execute#remarks) lists the patterns affected. VBScript's **RegExp** has no **Split**, and its **Execute** reports such a match once. This is how version 1.4.0.0 behaves. - -In this example the string is to be split before each capital letter. The result holds an empty string between `foo` and `Bar`, and between `Bar` and `Baz`. +A match of no characters splits the string at its position, once. In this example the string is split before each capital letter. ```tb check_build check_run Dim re As New RegExp @@ -42,10 +39,13 @@ re.Pattern = "(?=[A-Z])" Dim parts As Collection Set parts = re.Split("fooBarBaz") -Debug.Print parts.Count ' 5 -Debug.Print parts(1) & "|" & parts(2) & "|" & parts(3) & "|" & parts(4) & "|" & parts(5) ' foo||Bar||Baz +Debug.Print parts.Count ' 3 +Debug.Print parts(1) & "|" & parts(2) & "|" & parts(3) ' foo|Bar|Baz ``` +> [!WARNING] +> A match of more than a few thousand characters is cut short, and nothing is raised, so **Split** splits at a shorter part of the text than the pattern describes. The [overview of the package](../) describes the limit. + When **Pattern** is not a valid regular expression, **Split** raises the compile error. Its number is [**LastErrorNumber**](LastErrorNumber), its description is [**LastError**](LastError), and its source is `RegExp`. Read [**IsValid**](IsValid) first to avoid the error. ## Example diff --git a/docs/Reference/Contributed/tbRegExp/RegExp/Test.md b/docs/Reference/Contributed/tbRegExp/RegExp/Test.md index 563c882e4..c770c205e 100644 --- a/docs/Reference/Contributed/tbRegExp/RegExp/Test.md +++ b/docs/Reference/Contributed/tbRegExp/RegExp/Test.md @@ -26,6 +26,9 @@ Returns **True** if [**Pattern**](Pattern) finds a match in *sourceString*, othe [**IgnoreCase**](IgnoreCase), [**MultiLine**](MultiLine) and [**DotAll**](DotAll) apply to the matching. [**Global**](Global) makes no difference, because one match is enough. +> [!WARNING] +> A match of more than a few thousand characters is not found, and nothing is raised: `^.*$` on a string of 5,000 characters returns **False**. The [overview of the package](../) describes the limit. + When **Pattern** is not a valid regular expression, **Test** raises the compile error. Its number is [**LastErrorNumber**](LastErrorNumber), its description is [**LastError**](LastError), and its source is `RegExp`. Read [**IsValid**](IsValid) first to avoid the error. ## Example diff --git a/docs/Reference/Contributed/tbRegExp/RegExp/index.md b/docs/Reference/Contributed/tbRegExp/RegExp/index.md index a27898229..a98ad2734 100644 --- a/docs/Reference/Contributed/tbRegExp/RegExp/index.md +++ b/docs/Reference/Contributed/tbRegExp/RegExp/index.md @@ -39,7 +39,7 @@ Four properties change how the pattern is matched. [**Flags**](Flags) reads and | [**Global**](Global) | `g` | All matches are used. When **False**, [**Execute**](Execute), [**Replace**](Replace) and [**Split**](Split) use only the first match. | | [**MultiLine**](MultiLine) | `m` | `^` and `$` also match at the start and end of each line. | | [**IgnoreCase**](IgnoreCase) | `i` | Letter case is ignored. | -| [**DotAll**](DotAll) | `s` | `.` also matches line terminators. | +| [**DotAll**](DotAll) | `s` | `.` also matches a line feed. | All four are **False** in a new instance. @@ -51,7 +51,7 @@ A pattern that does not compile does not raise an error at once. [**IsValid**](I ## Properties -- [DotAll](DotAll) -- returns or sets whether `.` matches line terminators +- [DotAll](DotAll) -- returns or sets whether `.` matches a line feed - [Flags](Flags) -- returns or sets the four options as a string of letters - [Global](Global) -- returns or sets whether all matches are used - [IgnoreCase](IgnoreCase) -- returns or sets whether letter case is ignored diff --git a/docs/Reference/Contributed/tbRegExp/SubMatches/NewEnum.md b/docs/Reference/Contributed/tbRegExp/SubMatches/NewEnum.md index 1506a1b88..9e295865c 100644 --- a/docs/Reference/Contributed/tbRegExp/SubMatches/NewEnum.md +++ b/docs/Reference/Contributed/tbRegExp/SubMatches/NewEnum.md @@ -16,7 +16,7 @@ Syntax: *object*.**NewEnum** **As IUnknown** *object* : *required* An object expression that evaluates to a **SubMatches**. -Returns an **IUnknown** that refers to the enumerator of a **Collection** holding the strings of the capture groups. +Returns an **IUnknown** that refers to a new enumerator over the strings of the capture groups, which starts at the first one. ## Remarks @@ -24,8 +24,6 @@ Returns an **IUnknown** that refers to the enumerator of a **Collection** holdin The elements come in the order of their positions, from 0 to [**Count**](Count) - 1, and each is the string of one group. A group that took no part in the match gives an empty string. A **Variant** variable receives each element, as in the example. -The class builds the **Collection** when the first enumeration asks for it, and keeps it for the later ones. Reading by position or by name with [**Item**](Item) does not build it. - A program does not need to call **NewEnum**. **For Each** does it. ## Example diff --git a/docs/Reference/Contributed/tbRegExp/index.md b/docs/Reference/Contributed/tbRegExp/index.md index bf9a50e80..4531bbf6d 100644 --- a/docs/Reference/Contributed/tbRegExp/index.md +++ b/docs/Reference/Contributed/tbRegExp/index.md @@ -1,26 +1,36 @@ --- title: tbRegExp Package -parent: Experimental Packages +parent: Contributed Packages has_toc: false exclude_from_docs: - StaticRegex -indexed_from: 1.4.0.0 +indexed_from: 1.7.0.0 permalink: /tB/Packages/Contributed/tbRegExp/ --- # tbRegExp Package {: .no_toc } -**Author:** GCuser99. **Version:** 1.4.0.0. **On GitHub:** [GCuser99/tbRegExp](https://github.com/GCuser99/tbRegExp), in the folder *Package*. +**Author:** GCuser99. **Version:** 1.7.0.0. **On GitHub:** [GCuser99/tbRegExp](https://github.com/GCuser99/tbRegExp), in the folder *Package*. The **tbRegExp** contributed package provides regular expressions for twinBASIC projects, as a replacement for the **RegExp** object of VBScript. It follows the same object model. A [**RegExp**](RegExp/) is given a pattern and options, its [**Execute**](RegExp/Execute) method returns a [**MatchCollection**](MatchCollection/) of [**Match**](Match/) objects, and each **Match** holds the text captured by the groups of the pattern as a [**SubMatches**](SubMatches/) object. Positions are zero-based, as in VBScript. -The matching is done by the [vba-regex](https://github.com/sihlfall/vba-regex) engine of sihlfall, which the package includes as a private module, **StaticRegex**. +> [!WARNING] +> A match can be at most about 3,300 characters long. A longer one is reported cut short, later in the string, or not at all, and no error is raised. The engine gives up a search from one starting position after 10,000 steps, where a step is one operation of the compiled pattern, and then starts again one character later. The limit is fixed inside the package, and a program cannot change it. A pattern that repeats one character, such as `a*`, `.*` or `[^,]*`, takes three steps for each character it matches, so its longest match is 3,331 characters, and a pattern with more to do for each character has a shorter longest match. On a string of 5,000 `a`, `a*` finds a match that starts at position 1669 and is 3,331 characters long, where VBScript's **RegExp** finds the whole string from position 0, and `^.*$` finds no match at all, so [**Test**](RegExp/Test) returns **False**. Each starting position that fails costs up to 10,000 steps, so a search over a long run is slow as well: `a*` over a run of 9,500 characters takes several seconds, and returns a match of 3,331 characters from position 6169. This is how version 1.7.0.0 behaves. + +```tb check_build check_run +Dim re As New RegExp +Dim m As Match +re.Pattern = "a*" +Set m = re.Execute(String(5000, "a"))(0) +Debug.Print m.FirstIndex & ", " & m.Length ' 1669, 3331 +re.Pattern = "^.*$" +Debug.Print re.Test(String(5000, "a")) ' False +``` -Beyond VBScript's object, **RegExp** has the [**DotAll**](RegExp/DotAll) and [**Flags**](RegExp/Flags) options, the [**Split**](RegExp/Split) method, and [**IsValid**](RegExp/IsValid), [**LastError**](RegExp/LastError) and [**LastErrorNumber**](RegExp/LastErrorNumber), which report an invalid pattern before it is used. [**Replace**](RegExp/Replace) reads a `$` in the replacement string differently from VBScript. +The matching is done by the [vba-regex](https://github.com/sihlfall/vba-regex) engine of sihlfall, which the package includes as a private module, **StaticRegex**. -> [!WARNING] -> With [**Global**](RegExp/Global) set to **True**, a match of no characters is reported twice when the search had to skip at least one character to reach it. It happens to a pattern that matches no characters at a position after the one where the search started: `$`, `\b`, `^` with [**MultiLine**](RegExp/MultiLine), and a lookahead or lookbehind on its own. A pattern such as `x*`, which matches no characters at the position where the search starts, is not affected. [**Execute**](RegExp/Execute) returns the match twice, [**Replace**](RegExp/Replace) makes its replacement twice and [**Split**](RegExp/Split) adds an extra empty string, and nothing is raised. VBScript's **RegExp** reports such a match once. A match of one or more characters is never repeated, and with **Global** set to **False** nothing is. This is how version 1.4.0.0 behaves. +Beyond VBScript's object, **RegExp** has the [**DotAll**](RegExp/DotAll) and [**Flags**](RegExp/Flags) options, the [**Split**](RegExp/Split) method, and [**IsValid**](RegExp/IsValid), [**LastError**](RegExp/LastError) and [**LastErrorNumber**](RegExp/LastErrorNumber), which report an invalid pattern before it is used. [**Replace**](RegExp/Replace) reads a `$` in the replacement string as VBScript does, and adds `$` for a named group. ## Pattern syntax @@ -44,7 +54,29 @@ Debug.Print re.Test("ABCDEF") ' True Debug.Print re.Test("ABCdef") ' False ``` -A pattern that holds `\k`, `\A`, `\Z`, `\p{...}`, an atomic group `(?>...)` or a conditional is not valid: [**IsValid**](RegExp/IsValid) returns **False**. A numbered backreference such as `\1` works when the pattern has that group; [**IsValid**](RegExp/IsValid) does not find a backreference to a group that is missing. `\w` matches only the ASCII letters and digits and `_`, as in VBScript, so `\w+` finds `caf` in `café`. +A pattern that holds an atomic group `(?>...)` or a conditional is not valid: [**IsValid**](RegExp/IsValid) returns **False**. A named backreference `\k`, `\A`, `\Z` and `\p{...}` are not supported either, but they do not make the pattern invalid, because a backslash before a letter that names no escape stands for the letter itself, as in VBScript: `\k` matches the text `k`, and `\A` matches `A`. The pattern `\p{L}` is invalid for another reason: `{L}` does not start a quantifier (see below). + +The escapes that name a control character or a character code are read as in VBScript: + +- `\c` followed by a letter, in either case, is a control character: `\cJ` and `\CJ` are the line feed. A `\c` followed by anything else makes the pattern invalid; +- `\0` is the character with code 0, `\0` followed by octal digits, such as `\012`, is that code, and `\8` and `\9` are the digits themselves; +- `\1` to `\9` and longer numbers are backreferences when the pattern has that many groups. Otherwise the digits are read as an octal character code, so `\101` is `A` and `(a)\2` is `a` followed by the character with code 2; [**IsValid**](RegExp/IsValid) returns **True** for both. A backreference to a group that has not yet taken part in the match, such as `\1(a)`, matches no characters; VBScript's **RegExp** finds no match for such a pattern; +- `\f`, `\n`, `\r` and `\t`, inside a character class as well, are the form feed, line feed, carriage return and tab. + +```tb check_build check_run +Dim re As New RegExp +re.Pattern = "\101" +Debug.Print re.Test("A") ' True +re.Pattern = "\CJ" +Debug.Print re.Test(vbLf) ' True +re.Pattern = "\k" +Debug.Print re.IsValid ' True +Debug.Print re.Test("k") ' True +re.Pattern = "(a)\2" +Debug.Print re.Test("a" & ChrW(2)) ' True +``` + +`\w` matches only the ASCII letters and digits and `_`, as in VBScript, so `\w+` finds `caf` in `café`. A `{` has to start a quantifier: `{n}`, `{n,}`, `{n,m}` or `{,m}`, which means `{0,m}`. A pattern in which a `{` starts none, such as `a{` or `a{x}`, is not valid; write `\{` for the character. VBScript reads a `{` that starts no quantifier, and `{,m}`, as ordinary text. A `]` or `}` that closes nothing is an ordinary character, as in VBScript. @@ -73,7 +105,7 @@ Debug.Print re.Execute("a{x}")(0).Value ' a{x} - [Item](MatchCollection/Item) -- returns one match by position (the default member) - [NewEnum](MatchCollection/NewEnum) -- returns the enumerator that For Each uses (hidden) - [RegExp](RegExp/) -- searches strings with a regular expression: finds the matches, tests for one, replaces them and splits a string at them - - [DotAll](RegExp/DotAll) -- returns or sets whether `.` matches line terminators + - [DotAll](RegExp/DotAll) -- returns or sets whether `.` matches a line feed - [Execute](RegExp/Execute) -- returns the matches in a string - [Flags](RegExp/Flags) -- returns or sets the four options as a string of letters - [Global](RegExp/Global) -- returns or sets whether all matches are used diff --git a/docs/Reference/Core/For-Each-Next.md b/docs/Reference/Core/For-Each-Next.md index f91220385..ee567c077 100644 --- a/docs/Reference/Core/For-Each-Next.md +++ b/docs/Reference/Core/For-Each-Next.md @@ -50,6 +50,10 @@ The **For...Each...Next** statement cannot be used with an array of user-defined An object of a class can be the *group* when the class has a member marked [**[Enumerator]**](Attributes#enumerator) that supplies an enumerator. See [Custom Enumerators](../../Features/Language/Custom-Enumerators) for how to write one. +> [!WARNING] +> +> In BETA 1005, a **For Each** that cannot get an enumerator from *group* never ends when it runs under `On Error Resume Next`, or under an error handler that ends with **Resume Next**. That happens when *group* is **Nothing**, an object of a class with no **[Enumerator]** member, or an object whose **[Enumerator]** member returns **Nothing** or raises an error. The error resumes into the loop body, and **Next** raises error 91 and goes back to the body, again and again. Test *group* before the loop. VB6 runs the body once, then raises error 92 at **Next**, and the loop ends. + ### Example This example uses the **For Each...Next** statement to search the **Text** property of all elements in a collection for the existence of the string "Hello". In the example, *MyObject* is a text-related object and is an element of the collection *MyCollection*. Both are generic names used for illustration purposes only. diff --git a/docs/Reference/Experimental.md b/docs/Reference/Experimental.md index 49e7742d5..9402d235d 100644 --- a/docs/Reference/Experimental.md +++ b/docs/Reference/Experimental.md @@ -14,12 +14,12 @@ These packages are published through TWINSERV, as the [Contributed Packages](../ - [**ArrayListLib**](../Contributed/ArrayListLib/) -- a list class in place of .NET's `ArrayList`. Version 1.6.151.1 does not compile in twinBASIC BETA 1005, and in a copy that does, a **For Each** over a list with no items fails, **ToString** of such a list never returns, **Reverse** leaves the two middle items of an even run in place, and a second **GetEnumerator** over the same list ignores its arguments. - [**BigNumberLib**](../Contributed/BigNumberLib/) -- arbitrary-precision signed integer arithmetic. Version 1.0.2.0 compiles, and has several members that give wrong results or do not return: setting **IsNegative** to **True**, a negative operand of the greatest common divisor and the least common multiple, a zero from **AssignFromLong**, and the modular inverse of zero. -- [**TBMANLIB**](../Contributed/TBMANLIB/) -- a toolkit of helpers: JSON, logs, INI files, strings, bytes, files and dictionaries. Version 1.0.0.5 compiles for 32-bit programs, crashes the compiler in a 64-bit build, and has several members that fail or give wrong results. +- [**TBMANLIB**](../Contributed/TBMANLIB/) -- a toolkit of helpers: JSON, an HTTP client, logs, INI and CSV files, timers, strings, bytes, files and dictionaries. Version 1.0.0.29 compiles for 32-bit programs, does not compile in a 64-bit build, and has several members that fail or give wrong results. - [**TBMANLIB_Aes**](../Contributed/TBMANLIB_Aes/) -- AES encryption of text and byte arrays. Version 1.0.0.4 compiles, and its cipher modes are not the ones their names say: the default mode, CBC, runs ECB, and the output does not match the standard test vectors. - [**TBMANLIB_ConsoleComponents**](../Contributed/TBMANLIB_ConsoleComponents/) -- text-interface components for console programs: progress bars, charts, tables and panels. Version 1.0.0.1 compiles, and its components cannot use a console object of the published TBMANLIB_ConsoleCore, since the package carries a copy of that package whose types the compiler treats as different. - [**TBMANLIB_ConsoleCore**](../Contributed/TBMANLIB_ConsoleCore/) -- a console library with colours, UTF-8 and a full-screen mode. Version 1.0.0.1 compiles, and it reads keys wrongly and has several helpers that give wrong results. - [**TBMANLIB_Diagnostic**](../Contributed/TBMANLIB_Diagnostic/) -- a diagnostic report of the computer and its processes, as text, JSON or HTML. Version 1.0.0.1 compiles. Its **Dictionary** class cannot be created, and several of its list and file helpers and two of its collectors fail or give wrong results. +- [**TBMANLIB_Dialog**](../Contributed/TBMANLIB_Dialog/) -- the Open, Save and folder dialogs (**cDialog**, **cDialogEx**), the Color, Font, Find, Replace, Page Setup and Print dialogs, and message, input, password, date and progress dialogs. Version 1.1.0.0 compiles, and several of its classes fail at their main task: the Find and Replace dialogs report nothing to the program, Page Setup ignores the paper size and orientation, **cDialogEx** returns file names without their folder, the password dialog shows the password in its confirmation boxes, and the date dialog keeps input it refused. **cDialog** works. - [**TBMANLIB_Xml**](../Contributed/TBMANLIB_Xml/) -- XML documents parsed, edited and written, in twinBASIC code. Version 1.0.0.1 compiles, and its parser misreads some common XML, such as self-closing elements and entities in text, and does not return for some malformed text. Its XPath engine reads some valid expressions as others, such as the step `..` and `and` with `or`. -- [**tbRegExp**](../Contributed/tbRegExp/) -- regular expressions, in place of VBScript's **RegExp** object. Version 1.4.0.0 compiles, and with **Global** set to **True** it reports a match of no characters twice when the search skipped characters to reach it, so **Execute**, **Replace** and **Split** give wrong results for patterns such as `$`, `\b` and a lookahead. - [**WinReg**](../Contributed/WinReg/) -- registry keys and values, read, written, deleted and enumerated. Version 1.0.1.1 does not compile in twinBASIC BETA 1005, and three of its methods give wrong results: **ReadString** returns the text followed by NUL characters, **GetRegistryValueType** returns **REG_NONE** for a value that holds data, and one overload of **ConvertValueToString** finds no value. - [**xCalendar**](../Contributed/xCalendar/) -- a calendar control, in place of the Calendar control of `MSCAL.OCX`. Version 1.0.17.0 compiles, and has defects, among them a grid that keeps the dates of the old month when **Value** is set to another month, and colour properties of the text that have no effect. diff --git a/docs/_book.yml b/docs/_book.yml index 3d729479a..d6985b495 100644 --- a/docs/_book.yml +++ b/docs/_book.yml @@ -544,13 +544,6 @@ parts: landing_is_target: true outline_closed: true - - title: TBMANLIB_Dialog Package - subtitle: Open, Save and folder dialogs in a class, with file filters and path helpers - landing_page: /tB/Packages/Contributed/TBMANLIB_Dialog/ - page: /tB/Packages/Contributed/TBMANLIB_Dialog/ - landing_is_target: true - outline_closed: true - - title: TBMANLIB_ListBoxHScroll Package subtitle: A horizontal scroll bar for a ListBox landing_page: /tB/Packages/Contributed/TBMANLIB_ListBoxHScroll/ @@ -572,6 +565,13 @@ parts: landing_is_target: true outline_closed: true + - title: tbRegExp Package + subtitle: Regular expressions, in place of VBScript's RegExp object + landing_page: /tB/Packages/Contributed/tbRegExp/ + page: /tB/Packages/Contributed/tbRegExp/ + landing_is_target: true + outline_closed: true + - title: tsJson Package subtitle: A JSON model with parsing, JSONPath queries, JSON Pointer, JSON Patch and JSON Schema validation landing_page: /tB/Packages/Contributed/tsJson/ @@ -662,6 +662,13 @@ parts: landing_is_target: true outline_closed: true + - title: TBMANLIB_Dialog Package + subtitle: Open, Save, folder, Color, Font, Print and other dialogs in classes, with file filters and path helpers + landing_page: /tB/Packages/Contributed/TBMANLIB_Dialog/ + page: /tB/Packages/Contributed/TBMANLIB_Dialog/ + landing_is_target: true + outline_closed: true + - title: TBMANLIB_Xml Package subtitle: XML documents parsed, edited and written, in twinBASIC code landing_page: /tB/Packages/Contributed/TBMANLIB_Xml/ @@ -669,13 +676,6 @@ parts: landing_is_target: true outline_closed: true - - title: tbRegExp Package - subtitle: Regular expressions, in place of VBScript's RegExp object - landing_page: /tB/Packages/Contributed/tbRegExp/ - page: /tB/Packages/Contributed/tbRegExp/ - landing_is_target: true - outline_closed: true - - title: WinReg Package subtitle: Registry keys and values, read, written, deleted and enumerated landing_page: /tB/Packages/Contributed/WinReg/ diff --git a/indexer/snapshots/contributed/TBMANLIB/api.json b/indexer/snapshots/contributed/TBMANLIB/api.json index d6aad3431..3ad4e247d 100644 --- a/indexer/snapshots/contributed/TBMANLIB/api.json +++ b/indexer/snapshots/contributed/TBMANLIB/api.json @@ -2,8 +2,37 @@ "format": 3, "package": "TBMANLIB", "project": "TBMANLIB", - "version": "1.0.0.5", + "version": "1.0.0.29", "types": [ + { + "name": "Consts", + "kind": "module", + "vis": "public", + "file": "Common/Consts.twin", + "signature": "Module Consts", + "members": [ + {"name":"TOOLS_HTTP_CONTENT_TYPE_FORM_MULTIPART","kind":"const","vis":"public","signature":"Public Const TOOLS_HTTP_CONTENT_TYPE_FORM_MULTIPART As String = \"multipart/form-data\""}, + {"name":"TOOLS_HTTP_CONTENT_TYPE_FORM_URLENCODED","kind":"const","vis":"public","signature":"Public Const TOOLS_HTTP_CONTENT_TYPE_FORM_URLENCODED As String = \"application/x-www-form-urlencoded\""}, + {"name":"TOOLS_HTTP_CONTENT_TYPE_JSON","kind":"const","vis":"public","signature":"Public Const TOOLS_HTTP_CONTENT_TYPE_JSON As String = \"application/json\""}, + {"name":"TOOLS_HTTP_CONTENT_TYPE_STREAM","kind":"const","vis":"public","signature":"Public Const TOOLS_HTTP_CONTENT_TYPE_STREAM As String = \"application/octet-stream\""}, + {"name":"TOOLS_HTTP_CONTENT_TYPE_TEXT_HTML","kind":"const","vis":"public","signature":"Public Const TOOLS_HTTP_CONTENT_TYPE_TEXT_HTML As String = \"text/html\""}, + {"name":"TOOLS_HTTP_CONTENT_TYPE_TEXT_PLAIN","kind":"const","vis":"public","signature":"Public Const TOOLS_HTTP_CONTENT_TYPE_TEXT_PLAIN As String = \"text/plain\""}, + {"name":"TOOLS_HTTP_HEADER_FIELD_CONTENT_LENGHT","kind":"const","vis":"public","signature":"Public Const TOOLS_HTTP_HEADER_FIELD_CONTENT_LENGHT As String = \"Content-Lenght\""}, + {"name":"TOOLS_HTTP_HEADER_FIELD_CONTENT_TYPE","kind":"const","vis":"public","signature":"Public Const TOOLS_HTTP_HEADER_FIELD_CONTENT_TYPE As String = \"Content-Type\""} + ] + }, + { + "name": "ForDialog", + "kind": "class", + "vis": "public", + "file": "Tools/Dialog/helper/ForDialog.twin", + "signature": "Class ForDialog", + "attrs": [{"name":"FormDesignerId","args":"\"3B89E512-84A3-4F99-8596-BA36442198E8\""},{"name":"PredeclaredId"}], + "description": "", + "members": [ + {"name":"New","kind":"sub","vis":"public","signature":"Sub New()"} + ] + }, { "name": "TBMAN", "kind": "module", @@ -12,14 +41,38 @@ "signature": "Module TBMAN", "members": [ {"name":"Csv","kind":"field","vis":"public","signature":"Public Csv As New TBMANLIB.cCsv"}, + {"name":"HttpClient","kind":"field","vis":"public","signature":"Public HttpClient As New TBMANLIB.cHttpClient"}, {"name":"Ini","kind":"field","vis":"public","signature":"Public Ini As New TBMANLIB.cIni"}, {"name":"Json","kind":"field","vis":"public","signature":"Public Json As New TBMANLIB.cJson"}, {"name":"Layer","kind":"field","vis":"public","signature":"Public Layer As New TBMANLIB.cLayer"}, {"name":"Logs","kind":"field","vis":"public","signature":"Public Logs As New TBMANLIB.cLogs"}, + {"name":"SysTray","kind":"field","vis":"public","signature":"Public SysTray As New TBMANLIB.cSysTray"}, {"name":"TimerUse","kind":"field","vis":"public","signature":"Public TimerUse As New TBMANLIB.cTimeuse"}, {"name":"Version","kind":"property","vis":"public","signature":"Public Property Get Version() As String","returnType":"String","accessor":"Get"} ] }, + { + "name": "Tests", + "kind": "module", + "vis": "public", + "file": "Tests.twin", + "signature": "Module Tests", + "members": [ + {"name":"TestHttpClient","kind":"sub","vis":"public","signature":"Sub TestHttpClient()"}, + {"name":"UploadAndDownloadDemo","kind":"sub","vis":"public","signature":"Sub UploadAndDownloadDemo()"} + ] + }, + { + "name": "Tools", + "kind": "module", + "vis": "public", + "file": "Common/Tools.twin", + "signature": "Module Tools", + "members": [ + {"name":"ToolsStr","kind":"field","vis":"public","signature":"Public ToolsStr As New cToolsStr"}, + {"name":"ToolsStream","kind":"field","vis":"public","signature":"Public ToolsStream As New cToolsStream"} + ] + }, { "name": "ToolsArray", "kind": "module", @@ -76,6 +129,28 @@ ], "members": [] }, + { + "name": "ToolsCN", + "kind": "module", + "vis": "public", + "file": "Tools/Chinese/ToolsCN.twin", + "signature": "Module ToolsCN", + "members": [ + {"name":"Dic","kind":"field","vis":"private","signature":"Dim Dic As Object"}, + {"name":"GetPinYin","kind":"function","vis":"public","signature":"Public Function GetPinYin(CnChar As String) As String","returnType":"String"}, + {"name":"InitDictinary","kind":"sub","vis":"private","signature":"Private Sub InitDictinary()"} + ] + }, + { + "name": "ToolsDialog", + "kind": "module", + "vis": "public", + "file": "Tools/Dialog/ToolsDialog.twin", + "signature": "Module ToolsDialog", + "members": [ + {"name":"LastError","kind":"field","vis":"public","signature":"Public LastError As String"} + ] + }, { "name": "ToolsDic", "kind": "module", @@ -86,7 +161,6 @@ {"name":"DeepCopy","kind":"function","vis":"public","signature":"Public Function DeepCopy(srcDic As Scripting.Dictionary) As Scripting.Dictionary","returnType":"Scripting.Dictionary"}, {"name":"FromWwwFormUrlencoded","kind":"function","vis":"public","signature":"Public Function FromWwwFormUrlencoded(Content As String, Dic As Scripting.Dictionary) As Boolean","returnType":"Boolean"}, {"name":"OverWrite","kind":"sub","vis":"public","signature":"Public Sub OverWrite(DistDic As Scripting.Dictionary, srcDic As Scripting.Dictionary, Optional OnlyKey As Boolean = True)"}, - {"name":"ToWwwFormUrlencoded","kind":"function","vis":"public","signature":"Public Function ToWwwFormUrlencoded(Dic As Scripting.Dictionary) As String","returnType":"String"}, {"name":"TowLevelDicAssign","kind":"sub","vis":"public","signature":"Public Sub TowLevelDicAssign(Dic As Scripting.Dictionary, Lv1Name As String, Lv2Name As String, Value As Variant)"} ] }, @@ -106,19 +180,81 @@ {"name":"Test","kind":"sub","vis":"public","signature":"Public Sub Test()"} ] }, + { + "name": "ToolsHttp", + "kind": "module", + "vis": "public", + "file": "Tools/Http/ToolsHttp.twin", + "signature": "Module ToolsHttp", + "members": [ + {"name":"AddToQueryString","kind":"function","vis":"public","signature":"Public Function AddToQueryString(ByVal Url As String, ByVal QS As String) As String","returnType":"String"}, + {"name":"C_CONTENT_TYPE_FORM_MULTIPART","kind":"const","vis":"public","signature":"Public Const C_CONTENT_TYPE_FORM_MULTIPART As String = \"multipart/form-data\""}, + {"name":"C_CONTENT_TYPE_FORM_URLENCODED","kind":"const","vis":"public","signature":"Public Const C_CONTENT_TYPE_FORM_URLENCODED As String = \"application/x-www-form-urlencoded\""}, + {"name":"C_CONTENT_TYPE_JSON","kind":"const","vis":"public","signature":"Public Const C_CONTENT_TYPE_JSON As String = \"application/json\""}, + {"name":"C_CONTENT_TYPE_STREAM","kind":"const","vis":"public","signature":"Public Const C_CONTENT_TYPE_STREAM As String = \"application/octet-stream\""}, + {"name":"C_CONTENT_TYPE_TEXT_HTML","kind":"const","vis":"public","signature":"Public Const C_CONTENT_TYPE_TEXT_HTML As String = \"text/html\""}, + {"name":"C_CONTENT_TYPE_TEXT_PLAIN","kind":"const","vis":"public","signature":"Public Const C_CONTENT_TYPE_TEXT_PLAIN As String = \"text/plain\""}, + {"name":"C_HEADER_FIELD_CONTENT_LENGHT","kind":"const","vis":"public","signature":"Public Const C_HEADER_FIELD_CONTENT_LENGHT As String = \"Content-Lenght\""}, + {"name":"C_HEADER_FIELD_CONTENT_TYPE","kind":"const","vis":"public","signature":"Public Const C_HEADER_FIELD_CONTENT_TYPE As String = \"Content-Type\""}, + {"name":"MakeContent","kind":"function","vis":"public","signature":"Public Function MakeContent(Dic As Scripting.Dictionary) As String","returnType":"String"}, + {"name":"MapMethod","kind":"function","vis":"public","signature":"Public Function MapMethod(Name As String) As Long","returnType":"Long"}, + {"name":"MapMethodName","kind":"function","vis":"public","signature":"Public Function MapMethodName(Index As Long) As String","returnType":"String"}, + {"name":"ParseContent","kind":"function","vis":"public","signature":"Public Function ParseContent(Content As String, Obj As Scripting.Dictionary) As Boolean","returnType":"Boolean"}, + {"name":"ParseKeyValue","kind":"function","vis":"public","signature":"Public Function ParseKeyValue(Content As String, Obj As Scripting.Dictionary) As Boolean","returnType":"Boolean"}, + {"name":"Test","kind":"sub","vis":"public","signature":"Sub Test()"}, + {"name":"UrlDecode","kind":"function","vis":"public","signature":"Public Function UrlDecode(ByVal Url As String) As String","returnType":"String"}, + {"name":"UrlDecodeUtf8","kind":"function","vis":"public","signature":"Public Function UrlDecodeUtf8(ByVal Url As String) As String","returnType":"String"}, + {"name":"UrlEncodUtf8","kind":"function","vis":"public","signature":"Public Function UrlEncodUtf8(ByVal szInput As Variant) As String","returnType":"String"}, + {"name":"UrlEncode","kind":"function","vis":"public","signature":"Public Function UrlEncode(ByRef strURL As String) As String","returnType":"String"} + ] + }, + { + "name": "EnumRequestContentType", + "kind": "enum", + "in": "ToolsHttp", + "vis": "public", + "file": "Tools/Http/ToolsHttp.twin", + "signature": "Public Enum EnumRequestContentType", + "values": [ + {"name":"JsonString","value":"1"}, + {"name":"FormEncode","value":"2"}, + {"name":"FormMultiPart","value":"3"}, + {"name":"TextPlain","value":"4"}, + {"name":"TextHtml","value":"5"} + ] + }, + { + "name": "EnumRequestMethod", + "kind": "enum", + "in": "ToolsHttp", + "vis": "public", + "file": "Tools/Http/ToolsHttp.twin", + "signature": "Public Enum EnumRequestMethod", + "values": [ + {"name":"ReqPost","value":"1"}, + {"name":"ReqGet","value":"2"}, + {"name":"ReqPut","value":"3"}, + {"name":"ReqDelete","value":"4"}, + {"name":"ReqOptions","value":"5"} + ] + }, + { + "name": "ToolsHttpMimeType", + "kind": "module", + "vis": "public", + "file": "Tools/Http/ToolsHttpMimeType.twin", + "signature": "Module ToolsHttpMimeType", + "members": [ + {"name":"MapMimeType","kind":"function","vis":"public","signature":"Public Function MapMimeType(FilePath As String) As String","returnType":"String"} + ] + }, { "name": "ToolsJScript", "kind": "module", "vis": "public", "file": "Tools/JScript/ToolsJScript.twin", "signature": "Module ToolsJScript", - "members": [ - {"name":"Init","kind":"sub","vis":"private","signature":"Private Sub Init()"}, - {"name":"IsInit","kind":"field","vis":"private","signature":"Dim IsInit As Boolean"}, - {"name":"MSSC","kind":"field","vis":"private","signature":"Dim MSSC As New MSScriptControl.ScriptControl"}, - {"name":"NewArr","kind":"function","vis":"public","signature":"Public Function NewArr(ParamArray Item() As Variant) As Object","returnType":"Object"}, - {"name":"NewObj","kind":"function","vis":"public","signature":"Public Function NewObj() As Object","returnType":"Object"} - ] + "members": [] }, { "name": "ToolsJsonUcs", @@ -262,56 +398,6 @@ {"name":"GetRandRange","kind":"function","vis":"public","signature":"Public Function GetRandRange(a As Long, b As Long) As Long","returnType":"Long"} ] }, - { - "name": "ToolsStr", - "kind": "module", - "vis": "public", - "file": "Tools/String/ToolsStr.twin", - "signature": "Module ToolsStr", - "members": [ - {"name":"GetFirstChar","kind":"function","vis":"public","signature":"Public Function GetFirstChar(Txt As String, Optional Length As Long = 1) As String","returnType":"String"}, - {"name":"GetGUID","kind":"function","vis":"public","signature":"Public Function GetGUID(Optional isFull As Boolean) As String","returnType":"String"}, - {"name":"GetLastChar","kind":"function","vis":"public","signature":"Public Function GetLastChar(Txt As String, Optional Length As Long = 1) As String","returnType":"String"}, - {"name":"GetNewIds","kind":"function","vis":"public","signature":"Public Function GetNewIds(Optional ByVal Bath As Long) As String","returnType":"String"}, - {"name":"GetRandByte","kind":"function","vis":"public","signature":"Public Function GetRandByte() As Byte()","returnType":"Byte()"}, - {"name":"GetRandStr","kind":"function","vis":"public","signature":"Public Function GetRandStr(Optional ByVal Lens As Integer = 32, Optional Zuhe As String = \"1aA\") As String","returnType":"String"}, - {"name":"HasStr","kind":"function","vis":"public","signature":"Public Function HasStr(FindStr As String, FullStr As String, Optional StartPos As Long = 1, Optional CompType As VbCompareMethod = vbTextCompare) As Long","returnType":"Long"}, - {"name":"InsertSpan","kind":"function","vis":"public","signature":"Public Function InsertSpan(ByRef inputStr As String, ByVal Span As String, ByVal SetpNum As Long, Optional HeadFoot As Boolean) As String","returnType":"String"}, - {"name":"IsEmptyEx","kind":"function","vis":"public","signature":"Public Function IsEmptyEx(Text As String) As Boolean","returnType":"Boolean"}, - {"name":"IsString","kind":"function","vis":"public","signature":"Public Function IsString(var As Variant) As Boolean","returnType":"Boolean"}, - {"name":"JoinStr","kind":"function","vis":"public","signature":"Public Function JoinStr(Span As String, ParamArray Strings() As Variant) As String","returnType":"String"}, - {"name":"LeftEx","kind":"function","vis":"public","signature":"Public Function LeftEx(Text As Variant, Length As Long) As String","returnType":"String"}, - {"name":"LenBytes","kind":"function","vis":"public","signature":"Public Function LenBytes(inputArray() As Byte) As Long","returnType":"Long"}, - {"name":"MidEx","kind":"function","vis":"public","signature":"Public Function MidEx(FullStr As String, Lstr As String, Rstr As String, Optional starindex As Long) As String","returnType":"String"}, - {"name":"PercentDecode","kind":"function","vis":"public","signature":"Function PercentDecode(encodedStr As String) As String","returnType":"String"}, - {"name":"PercentEncode","kind":"function","vis":"public","signature":"Public Function PercentEncode(inputStr As String) As String","returnType":"String"}, - {"name":"RightEx","kind":"function","vis":"public","signature":"Public Function RightEx(Text As Variant, Length As Long) As String","returnType":"String"}, - {"name":"SliceString","kind":"function","vis":"public","signature":"Public Function SliceString(ByVal Arr As String, ByVal StartPos As Long, Optional ByVal EndPos As Long = -1) As String","returnType":"String"}, - {"name":"SubStr","kind":"function","vis":"public","signature":"Public Function SubStr(ByVal Txt As String, ByVal txtF$, Optional ByVal txtE$, Optional RetInt As Boolean) As String","returnType":"String"}, - {"name":"ToBytes","kind":"function","vis":"public","signature":"Public Function ToBytes(inputString As String) As Byte()","returnType":"Byte()"}, - {"name":"ToHex","kind":"function","vis":"public","signature":"Public Function ToHex(inputArray() As Byte) As String","returnType":"String"}, - {"name":"ToString","kind":"function","vis":"public","signature":"Public Function ToString(inputArray() As Byte) As String","returnType":"String"}, - {"name":"TrimEx","kind":"function","vis":"public","signature":"Public Function TrimEx(ByRef Text As String) As String","returnType":"String"}, - {"name":"UniVbCrLf","kind":"function","vis":"public","signature":"Public Function UniVbCrLf(Text As String) As String","returnType":"String"}, - {"name":"UnicodeDecode","kind":"function","vis":"public","signature":"Public Function UnicodeDecode(Text As String, Optional PreFix As String = \"\\u\") As String","returnType":"String"}, - {"name":"UnicodeEncode","kind":"function","vis":"public","signature":"Public Function UnicodeEncode(ByVal inputString As String, Optional PreFix As String = \"\\u\") As String","returnType":"String"} - ] - }, - { - "name": "ToolsStream", - "kind": "module", - "vis": "public", - "file": "Tools/FSO/ToolsStream.twin", - "signature": "Module ToolsStream", - "members": [ - {"name":"Inst","kind":"field","vis":"public","signature":"Public Inst As New ADODB.Stream"}, - {"name":"LastError","kind":"field","vis":"public","signature":"Public LastError As String"}, - {"name":"LoadFileAsBinary","kind":"function","vis":"public","signature":"Public Function LoadFileAsBinary(ByVal Path As String, OutData() As Byte) As Boolean","returnType":"Boolean"}, - {"name":"LoadFileAsText","kind":"function","vis":"public","signature":"Public Function LoadFileAsText(ByVal FileName As String, Optional CharSet As String = \"UTF-8\") As String","returnType":"String"}, - {"name":"SaveFileAsBinary","kind":"function","vis":"public","signature":"Public Function SaveFileAsBinary(ByVal Path As String, OutData() As Byte) As Boolean","returnType":"Boolean"}, - {"name":"SaveFileAsText","kind":"function","vis":"public","signature":"Public Function SaveFileAsText(ByVal FileName As String, Data As String, Optional CharSet As String = \"UTF-8\") As Boolean","returnType":"Boolean"} - ] - }, { "name": "ToolsUtf8", "kind": "module", @@ -334,13 +420,102 @@ "file": "Libs/FileSystem/Csv/cCsv.twin", "signature": "Class cCsv", "members": [ - {"name":"FileStream","kind":"field","vis":"private","signature":"Dim FileStream As Scripting.TextStream"}, - {"name":"FixFileExtName","kind":"sub","vis":"private","signature":"Private Sub FixFileExtName(Path As String)"}, - {"name":"Fso","kind":"field","vis":"private","signature":"Dim Fso As New Scripting.FileSystemObject"}, + {"name":"Class_Initialize","kind":"sub","vis":"private","signature":"Private Sub Class_Initialize()"}, + {"name":"CurrentLineDic","kind":"field","vis":"public","signature":"Public CurrentLineDic As New Scripting.Dictionary"}, + {"name":"CurrentLineNumber","kind":"field","vis":"public","signature":"Public CurrentLineNumber As Long"}, + {"name":"Data","kind":"field","vis":"public","signature":"Public Data As New Scripting.Dictionary"}, + {"name":"DeleteColumn","kind":"function","vis":"public","signature":"Public Function DeleteColumn(Optional ColumnNumber As Long) As Boolean","returnType":"Boolean"}, + {"name":"Delimiter","kind":"field","vis":"public","signature":"Public Delimiter As String"}, + {"name":"GetLine","kind":"function","vis":"public","signature":"Public Function GetLine(ByVal LineNumber As Long, Optional ByRef LineDic As Scripting.Dictionary) As Boolean","returnType":"Boolean"}, + {"name":"HasHeadTitle","kind":"field","vis":"public","signature":"Public HasHeadTitle As Boolean"}, + {"name":"HeadTitle","kind":"field","vis":"public","signature":"Public HeadTitle As New Scripting.Dictionary"}, + {"name":"HeadTitleWidth","kind":"field","vis":"public","signature":"Public HeadTitleWidth As New Scripting.Dictionary"}, + {"name":"HeadTitleWidthAuto","kind":"field","vis":"public","signature":"Public HeadTitleWidthAuto As Boolean"}, + {"name":"HeadTitleWidthDefault","kind":"field","vis":"public","signature":"Public HeadTitleWidthDefault As Long"}, + {"name":"HeadTitleWidthMax","kind":"field","vis":"public","signature":"Public HeadTitleWidthMax As Long"}, + {"name":"HeadTitleWidthWord","kind":"field","vis":"public","signature":"Public HeadTitleWidthWord As Long"}, + {"name":"Initialize","kind":"sub","vis":"private","signature":"Private Sub Initialize()"}, + {"name":"IsFirstLineToHead","kind":"field","vis":"public","signature":"Public IsFirstLineToHead As Boolean"}, {"name":"LastError","kind":"field","vis":"public","signature":"Public LastError As String"}, - {"name":"LoadFrom","kind":"function","vis":"public","signature":"Public Function LoadFrom(ByVal Path As String) As cIni","returnType":"cIni"}, - {"name":"Root","kind":"field","vis":"public","signature":"Public Root As Scripting.Dictionary"}, - {"name":"SaveTo","kind":"function","vis":"public","signature":"Public Function SaveTo(Optional ByVal Path As String) As Boolean","returnType":"Boolean"} + {"name":"LoadFrom","kind":"function","vis":"public","signature":"Public Function LoadFrom(ByVal FilePath As String, Optional IsFirstHead As Boolean = True) As cCsv","returnType":"cCsv"}, + {"name":"NewColumn","kind":"function","vis":"public","signature":"Public Function NewColumn(ByVal Title As String, Optional Width As Long, Optional Content As String) As Boolean","returnType":"Boolean"}, + {"name":"NewLine","kind":"function","vis":"public","signature":"Public Function NewLine(ParamArray FieldsData() As Variant) As Boolean","returnType":"Boolean"}, + {"name":"ParseCsvLine","kind":"function","vis":"private","signature":"Private Function ParseCsvLine(ByVal Content As String, ByRef LineDic As Scripting.Dictionary) As Boolean","returnType":"Boolean"}, + {"name":"QuoteCsvField","kind":"function","vis":"private","signature":"Private Function QuoteCsvField(ByVal field As String) As String","returnType":"String"}, + {"name":"Reset","kind":"sub","vis":"public","signature":"Public Sub Reset(Optional OnlyData As Boolean)"}, + {"name":"SaveTo","kind":"function","vis":"public","signature":"Public Function SaveTo(ByVal FilePath As String) As Boolean","returnType":"Boolean"}, + {"name":"SetCurrentLineData","kind":"function","vis":"public","signature":"Public Function SetCurrentLineData(ByVal ColumnNumber As Long, ByVal Dat As Variant, Optional ByRef LineDic As Scripting.Dictionary) As Boolean","returnType":"Boolean"}, + {"name":"SetHeadTitleWidth","kind":"function","vis":"public","signature":"Public Function SetHeadTitleWidth(ByVal ColumnNumber As Long, ByVal ColumnWidth As Long) As Boolean","returnType":"Boolean"}, + {"name":"SetHeadTitleWidthByDic","kind":"function","vis":"public","signature":"Public Function SetHeadTitleWidthByDic(ByVal Dic As Scripting.Dictionary) As Boolean","returnType":"Boolean"}, + {"name":"SetLine","kind":"function","vis":"public","signature":"Public Function SetLine(ByVal LineNumber As Long, ByRef LineDic As Scripting.Dictionary) As Boolean","returnType":"Boolean"}, + {"name":"SyncColumnLength","kind":"sub","vis":"private","signature":"Private Sub SyncColumnLength(LineDic As Scripting.Dictionary)"}, + {"name":"SyncColumnWidth","kind":"sub","vis":"private","signature":"Private Sub SyncColumnWidth()"}, + {"name":"Y","kind":"field","vis":"private","signature":"Dim Y As Variant"}, + {"name":"value","kind":"property","vis":"public","signature":"Public Property Get value(ByVal LineNumber As Long, ByVal ColumnNumber As Long) As Variant","returnType":"Variant","accessor":"Get"}, + {"name":"value","kind":"property","vis":"public","signature":"Public Property Set value(ByVal LineNumber As Long, ByVal ColumnNumber As Long, ByVal Dat As Variant)","accessor":"Set"}, + {"name":"x","kind":"field","vis":"private","signature":"Dim x As Variant"}, + {"name":"z","kind":"field","vis":"private","signature":"Dim z As Variant"} + ] + }, + { + "name": "cHttpClient", + "kind": "class", + "vis": "public", + "file": "Libs/HttpClient/cHttpClient.twin", + "signature": "Class cHttpClient", + "members": [ + {"name":"AllowRedirects","kind":"function","vis":"public","signature":"Public Function AllowRedirects(Bool As Boolean) As cHttpClient","returnType":"cHttpClient"}, + {"name":"Async","kind":"function","vis":"public","signature":"Public Function Async(Bool As Boolean) As cHttpClient","returnType":"cHttpClient"}, + {"name":"Class_Initialize","kind":"sub","vis":"private","signature":"Private Sub Class_Initialize()"}, + {"name":"Class_Terminate","kind":"sub","vis":"private","signature":"Private Sub Class_Terminate()"}, + {"name":"ConcatByteArrays","kind":"function","vis":"private","signature":"Private Function ConcatByteArrays(ByRef Arr1() As Byte, ByRef Arr2() As Byte) As Byte()","returnType":"Byte()"}, + {"name":"Cookies","kind":"field","vis":"public","signature":"Public Cookies As New Scripting.Dictionary"}, + {"name":"CopyMemory","kind":"sub","vis":"private","declare":true,"signature":"Private Declare Sub CopyMemory Lib \"kernel32\" Alias \"RtlMoveMemory\" (Destination As Any, Source As Any, ByVal length As Long)"}, + {"name":"DebugInfo","kind":"field","vis":"public","signature":"Public DebugInfo As New cJson"}, + {"name":"DebugStart","kind":"field","vis":"public","signature":"Public DebugStart As Boolean"}, + {"name":"DownloadFile","kind":"function","vis":"public","signature":"Public Function DownloadFile(ByVal url As String, ByVal savePath As String, Optional Overwrite As Boolean = True) As Boolean","returnType":"Boolean"}, + {"name":"DownloadFileAsync","kind":"function","vis":"public","signature":"Public Function DownloadFileAsync(ByVal url As String, ByVal savePath As String, Optional Overwrite As Boolean = True) As cHttpClient","returnType":"cHttpClient"}, + {"name":"EnableRedirects","kind":"field","vis":"public","signature":"Public EnableRedirects As Boolean"}, + {"name":"Fetch","kind":"function","vis":"public","signature":"Public Function Fetch(Method As EnumRequestMethod, ByVal url As String, Optional Body As String) As cHttpClient","returnType":"cHttpClient"}, + {"name":"FinishDownloadFile","kind":"function","vis":"public","signature":"Public Function FinishDownloadFile() As Boolean","returnType":"Boolean"}, + {"name":"FollowRedirect","kind":"function","vis":"public","signature":"Public Function FollowRedirect(Optional RedirectMethod As EnumRequestMethod = 0) As cHttpClient","returnType":"cHttpClient"}, + {"name":"GetRedirectUrl","kind":"function","vis":"public","signature":"Public Function GetRedirectUrl() As String","returnType":"String"}, + {"name":"Inst","kind":"field","vis":"private","signature":"Dim WithEvents Inst As WinHttp.WinHttpRequest"}, + {"name":"IsAsync","kind":"field","vis":"private","signature":"Dim IsAsync As Boolean"}, + {"name":"Json","kind":"field","vis":"private","signature":"Dim Json As New cJson"}, + {"name":"LastError","kind":"field","vis":"public","signature":"Public LastError As String"}, + {"name":"MapRequestContentType","kind":"function","vis":"public","signature":"Public Function MapRequestContentType(ReqType As EnumRequestContentType, Optional ContentType As String) As String","returnType":"String"}, + {"name":"MaxRedirects","kind":"field","vis":"public","signature":"Public MaxRedirects As Long"}, + {"name":"OnResponseFinished","kind":"event","vis":"public","signature":"Public Event OnResponseFinished()"}, + {"name":"RedirectCount","kind":"field","vis":"private","signature":"Dim RedirectCount As Long"}, + {"name":"RequestChartSet","kind":"field","vis":"public","signature":"Public RequestChartSet As String"}, + {"name":"RequestContentType","kind":"field","vis":"public","signature":"Public RequestContentType As String"}, + {"name":"RequestData","kind":"field","vis":"public","signature":"Public RequestData() As Byte"}, + {"name":"RequestDataBody","kind":"field","vis":"public","signature":"Public RequestDataBody As New Scripting.Dictionary"}, + {"name":"RequestDataForm","kind":"field","vis":"public","signature":"Public RequestDataForm As New Scripting.Dictionary"}, + {"name":"RequestDataJson","kind":"field","vis":"public","signature":"Public RequestDataJson As New cJson"}, + {"name":"RequestDataQuery","kind":"field","vis":"public","signature":"Public RequestDataQuery As New Scripting.Dictionary"}, + {"name":"RequestHeaders","kind":"field","vis":"public","signature":"Public RequestHeaders As New Scripting.Dictionary"}, + {"name":"RequestTimeOut","kind":"field","vis":"public","signature":"Public RequestTimeOut As Long"}, + {"name":"ResetRedirectCount","kind":"function","vis":"public","signature":"Public Function ResetRedirectCount() As cHttpClient","returnType":"cHttpClient"}, + {"name":"ResponseHeaders","kind":"field","vis":"public","signature":"Public ResponseHeaders As New Scripting.Dictionary"}, + {"name":"ResponseRaw","kind":"field","vis":"public","signature":"Public ResponseRaw As Variant"}, + {"name":"ReturnBody","kind":"function","vis":"public","signature":"Public Function ReturnBody() As Byte()","returnType":"Byte()"}, + {"name":"ReturnJson","kind":"function","vis":"public","signature":"Public Function ReturnJson(Optional IsUtf8 As Boolean = True, Optional IsConvert As Boolean) As cJson","returnType":"cJson"}, + {"name":"ReturnStream","kind":"function","vis":"public","signature":"Public Function ReturnStream() As Variant","returnType":"Variant"}, + {"name":"ReturnText","kind":"function","vis":"public","signature":"Public Function ReturnText(Optional IsUtf8 As Boolean = True, Optional IsConvert As Boolean) As String","returnType":"String"}, + {"name":"Send","kind":"function","vis":"public","signature":"Public Function Send(Method As EnumRequestMethod, ByVal Url As String, Optional Body As String) As cHttpClient","returnType":"cHttpClient"}, + {"name":"SendDelete","kind":"function","vis":"public","signature":"Public Function SendDelete(ByVal url As String, Optional Body As String) As cHttpClient","returnType":"cHttpClient"}, + {"name":"SendGet","kind":"function","vis":"public","signature":"Public Function SendGet(ByVal url As String, Optional Body As String) As cHttpClient","returnType":"cHttpClient"}, + {"name":"SendOptions","kind":"function","vis":"public","signature":"Public Function SendOptions(ByVal url As String, Optional Body As String) As cHttpClient","returnType":"cHttpClient"}, + {"name":"SendPost","kind":"function","vis":"public","signature":"Public Function SendPost(ByVal url As String, Optional Body As String) As cHttpClient","returnType":"cHttpClient"}, + {"name":"SendPut","kind":"function","vis":"public","signature":"Public Function SendPut(ByVal url As String, Optional Body As String) As cHttpClient","returnType":"cHttpClient"}, + {"name":"SetCookies","kind":"function","vis":"public","signature":"Public Function SetCookies(ByVal Value As String) As cHttpClient","returnType":"cHttpClient"}, + {"name":"SetRequestContentType","kind":"function","vis":"public","signature":"Public Function SetRequestContentType(ReqType As EnumRequestContentType, Optional ContentType As String) As String","returnType":"String"}, + {"name":"ShellExecute","kind":"function","vis":"private","declare":true,"signature":"Private Declare Function ShellExecute Lib \"shell32.dll\" Alias \"ShellExecuteA\" ( ByVal hWnd As Long, ByVal lpOperation As String, ByVal lpFile As String, ByVal lpParameters As String, ByVal lpDirectory As String, ByVal nShowCmd As Long) As Long","returnType":"Long"}, + {"name":"ShowPage","kind":"sub","vis":"public","signature":"Public Sub ShowPage(url As String)"}, + {"name":"UploadFile","kind":"function","vis":"public","signature":"Public Function UploadFile(ByVal url As String, ByVal filePath As String, Optional fieldName As String = \"file\", Optional additionalFormData As Scripting.Dictionary = Nothing) As cHttpClient","returnType":"cHttpClient"}, + {"name":"UploadFileSimple","kind":"function","vis":"public","signature":"Public Function UploadFileSimple(ByVal url As String, ByVal filePath As String) As Boolean","returnType":"Boolean"} ] }, { @@ -349,7 +524,17 @@ "vis": "public", "file": "Libs/FileSystem/Ini/cIni.twin", "signature": "Class cIni", - "members": [] + "members": [ + {"name":"FileStream","kind":"field","vis":"private","signature":"Dim FileStream As Scripting.TextStream"}, + {"name":"FixFileExtName","kind":"sub","vis":"private","signature":"Private Sub FixFileExtName(Path As String)"}, + {"name":"Fso","kind":"field","vis":"private","signature":"Dim Fso As New Scripting.FileSystemObject"}, + {"name":"LastError","kind":"field","vis":"public","signature":"Public LastError As String"}, + {"name":"LoadFrom","kind":"function","vis":"public","signature":"Public Function LoadFrom(ByVal Path As String) As cIni","returnType":"cIni"}, + {"name":"Root","kind":"property","vis":"public","signature":"Public Property Get Root() As Scripting.Dictionary","returnType":"Scripting.Dictionary","accessor":"Get"}, + {"name":"RootDic","kind":"field","vis":"private","signature":"Dim RootDic As New Scripting.Dictionary"}, + {"name":"SaveTo","kind":"function","vis":"public","signature":"Public Function SaveTo(Optional ByVal Path As String) As Boolean","returnType":"Boolean"}, + {"name":"Section","kind":"property","vis":"public","signature":"Public Property Get Section(ByVal Key As String) As New Scripting.Dictionary","returnType":"New Scripting.Dictionary","accessor":"Get"} + ] }, { "name": "cJson", @@ -368,6 +553,7 @@ {"name":"Item","kind":"property","vis":"public","signature":"Public Property Let Item(ByVal Key As String, Dat As Variant)","accessor":"Let"}, {"name":"Items","kind":"property","vis":"public","signature":"Public Property Get Items(ByVal Index As Long) As Variant","returnType":"Variant","accessor":"Get"}, {"name":"Items","kind":"property","vis":"public","signature":"Public Property Set Items(ByVal Index As Long, Dat As Variant)","accessor":"Set"}, + {"name":"Items","kind":"property","vis":"public","signature":"Public Property Let Items(ByVal Index As Long, Dat As Variant)","accessor":"Let"}, {"name":"LastError","kind":"field","vis":"public","signature":"Public LastError As String"}, {"name":"LastSuccess","kind":"property","vis":"public","signature":"Public Property Get LastSuccess() As Boolean","returnType":"Boolean","accessor":"Get"}, {"name":"LoadFrom","kind":"function","vis":"public","signature":"Public Function LoadFrom(ByVal Path As String, Optional CharSet As String = \"UTF-8\") As cJson","returnType":"cJson"}, @@ -381,8 +567,7 @@ {"name":"RootText","kind":"field","vis":"public","signature":"Public RootText As String"}, {"name":"SaveTo","kind":"function","vis":"public","signature":"Public Function SaveTo( ByVal FileName As String, Optional CharSet As String = \"UTF-8\", Optional Whitespace As Variant, Optional FromUnicode As Boolean) As String","returnType":"String"}, {"name":"ToArray","kind":"function","vis":"public","signature":"Public Function ToArray() As Variant","returnType":"Variant"}, - {"name":"WhiteSpaceSet","kind":"field","vis":"public","signature":"Public WhiteSpaceSet As Variant"}, - {"name":"uuItems","kind":"property","vis":"public","signature":"Public Property Let uuItems(ByVal Index As Long, Dat As Variant)","accessor":"Let"} + {"name":"WhiteSpaceSet","kind":"field","vis":"public","signature":"Public WhiteSpaceSet As Variant"} ] }, { @@ -457,13 +642,89 @@ {"name":"Custom","value":"5"} ] }, + { + "name": "cSubClass", + "kind": "class", + "vis": "public", + "file": "Libs/SubClass/cSubClass.twin", + "signature": "Class cSubClass", + "members": [ + {"name":"Callback","kind":"function","vis":"public","signature":"Public Function Callback(ByVal Msg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long","returnType":"Long"}, + {"name":"ClassPoolKey","kind":"field","vis":"private","signature":"Dim ClassPoolKey As String"}, + {"name":"GWL_WNDPROC","kind":"const","vis":"private","signature":"Private Const GWL_WNDPROC = -4"}, + {"name":"GetWindowLong","kind":"function","vis":"private","declare":true,"signature":"Private Declare Function GetWindowLong Lib \"user32\" Alias \"GetWindowLongW\" (ByVal hwnd As Long, ByVal nIndex As Long) As Long","returnType":"Long"}, + {"name":"Hook","kind":"sub","vis":"public","signature":"Public Sub Hook(ByVal hWnd As Long)"}, + {"name":"NewWinRet","kind":"field","vis":"private","signature":"Dim NewWinRet As Long"}, + {"name":"OrgWinRet","kind":"field","vis":"private","signature":"Dim OrgWinRet As Long"}, + {"name":"SetWindowLong","kind":"function","vis":"private","declare":true,"signature":"Private Declare Function SetWindowLong Lib \"user32\" Alias \"SetWindowLongW\" (ByVal hwnd As Long, ByVal nIndex As Long, ByVal dwNewLong As Long) As Long","returnType":"Long"}, + {"name":"UnHook","kind":"sub","vis":"public","signature":"Public Sub UnHook()"}, + {"name":"WindowProc","kind":"event","vis":"public","signature":"Public Event WindowProc(Msg As Long, wParam As Long, lParam As Long, CancelNextCall As Boolean)"}, + {"name":"mHWnd","kind":"field","vis":"public","signature":"Public mHWnd As Long"} + ] + }, + { + "name": "cSysTray", + "kind": "class", + "vis": "public", + "file": "Libs/TaskBar/SysTray/cSysTray.twin", + "signature": "Class cSysTray", + "members": [] + }, { "name": "cTimer", "kind": "class", "vis": "public", - "file": "Libs/DateTime/cTimer.twin", + "file": "Libs/DateTime/Timer/cTimer.twin", "signature": "Class cTimer", - "members": [] + "members": [ + {"name":"Class_Initialize","kind":"sub","vis":"private","signature":"Private Sub Class_Initialize()"}, + {"name":"Class_Terminate","kind":"sub","vis":"private","signature":"Private Sub Class_Terminate()"}, + {"name":"DEBUGMODE","kind":"const","vis":"private","signature":"Private Const DEBUGMODE As Boolean = False"}, + {"name":"Disable","kind":"sub","vis":"public","signature":"Sub Disable()"}, + {"name":"Enable","kind":"sub","vis":"public","signature":"Sub Enable()"}, + {"name":"EnableTimer","kind":"sub","vis":"public","signature":"Sub EnableTimer(mInterval As Long)"}, + {"name":"Enabled","kind":"property","vis":"public","signature":"Property Get Enabled() As Boolean","returnType":"Boolean","accessor":"Get"}, + {"name":"Enabled","kind":"property","vis":"public","signature":"Property Let Enabled(activateIt As Boolean)","accessor":"Let"}, + {"name":"Index","kind":"property","vis":"friend","signature":"Friend Property Let Index(lIndex As Long)","accessor":"Let"}, + {"name":"Index","kind":"property","vis":"friend","signature":"Friend Property Get Index() As Long","returnType":"Long","accessor":"Get"}, + {"name":"Interval","kind":"field","vis":"public","signature":"Public Interval As Long"}, + {"name":"KillTimer","kind":"function","vis":"private","declare":true,"signature":"Private Declare PtrSafe Function KillTimer Lib \"user32\" (ByVal hwnd As Long, ByVal nIDEvent As Long) As Long","returnType":"Long"}, + {"name":"ParentsColKey","kind":"property","vis":"friend","signature":"Friend Property Let ParentsColKey(key As Long)","accessor":"Let"}, + {"name":"ParentsColKey","kind":"property","vis":"friend","signature":"Friend Property Get ParentsColKey() As Long","returnType":"Long","accessor":"Get"}, + {"name":"RaiseTimer_Event","kind":"sub","vis":"public","signature":"Public Sub RaiseTimer_Event()"}, + {"name":"Reset","kind":"sub","vis":"public","signature":"Sub Reset()"}, + {"name":"SetTimer","kind":"function","vis":"private","declare":true,"signature":"Private Declare PtrSafe Function SetTimer Lib \"user32\" (ByVal hwnd As Long, ByVal nIDEvent As Long, ByVal uElapse As Long, ByVal lpTimerFunc As LongPtr) As Long","returnType":"Long"}, + {"name":"Tag","kind":"field","vis":"public","signature":"Public Tag As Variant"}, + {"name":"Timer","kind":"event","vis":"public","signature":"Public Event Timer()"}, + {"name":"TimerCreate","kind":"function","vis":"private","signature":"Private Function TimerCreate() As Boolean","returnType":"Boolean"}, + {"name":"TimerDestroy","kind":"function","vis":"private","signature":"Private Function TimerDestroy() As Long","returnType":"Long"}, + {"name":"TimerExists","kind":"function","vis":"private","signature":"Private Function TimerExists() As Boolean","returnType":"Boolean"}, + {"name":"dbg","kind":"function","vis":"private","signature":"Private Function dbg(msg As String) As Variant","returnType":"Variant"}, + {"name":"isActive","kind":"field","vis":"private","signature":"Private isActive As Boolean"}, + {"name":"mIndex","kind":"field","vis":"private","signature":"Private mIndex As Long"}, + {"name":"mParentsColKey","kind":"field","vis":"private","signature":"Private mParentsColKey As Variant"}, + {"name":"timerId","kind":"field","vis":"private","signature":"Private timerId As Long"} + ] + }, + { + "name": "cTimers", + "kind": "class", + "vis": "public", + "file": "Libs/DateTime/Timer/cTimers.twin", + "signature": "Class cTimers", + "members": [ + {"name":"Add","kind":"function","vis":"public","signature":"Function Add() As Integer","returnType":"Integer"}, + {"name":"BulkLoad","kind":"function","vis":"public","signature":"Function BulkLoad(num As Integer) As Variant","returnType":"Variant"}, + {"name":"Class_Initialize","kind":"sub","vis":"private","signature":"Private Sub Class_Initialize()"}, + {"name":"Class_Terminate","kind":"sub","vis":"private","signature":"Private Sub Class_Terminate()"}, + {"name":"Count","kind":"property","vis":"public","signature":"Property Get Count() As Integer","returnType":"Integer","accessor":"Get"}, + {"name":"Item","kind":"function","vis":"public","signature":"Function Item(ByVal Index As Integer) As cTimer","returnType":"cTimer"}, + {"name":"RaiseTimer_Event","kind":"function","vis":"friend","signature":"Friend Function RaiseTimer_Event(ByVal Index As Integer) As Variant","returnType":"Variant"}, + {"name":"Timer","kind":"event","vis":"public","signature":"Public Event Timer(ByVal Index As Integer)"}, + {"name":"mClassKey","kind":"field","vis":"private","signature":"Private mClassKey As Integer"}, + {"name":"mCol","kind":"field","vis":"private","signature":"Private mCol As New Collection"}, + {"name":"mCount","kind":"field","vis":"private","signature":"Private mCount As Integer"} + ] }, { "name": "cTimeuse", @@ -477,6 +738,93 @@ {"name":"Start","kind":"sub","vis":"public","signature":"Public Sub Start()"}, {"name":"startTime","kind":"field","vis":"private","signature":"Dim startTime As Long"} ] + }, + { + "name": "cToolsStr", + "kind": "class", + "vis": "public", + "file": "Tools/String/cToolsStr.twin", + "signature": "Class cToolsStr", + "members": [ + {"name":"GetFirstChar","kind":"function","vis":"public","signature":"Public Function GetFirstChar(Txt As String, Optional Length As Long = 1) As String","returnType":"String"}, + {"name":"GetGUID","kind":"function","vis":"public","signature":"Public Function GetGUID(Optional isFull As Boolean) As String","returnType":"String"}, + {"name":"GetLastChar","kind":"function","vis":"public","signature":"Public Function GetLastChar(Txt As String, Optional Length As Long = 1) As String","returnType":"String"}, + {"name":"GetNewIds","kind":"function","vis":"public","signature":"Public Function GetNewIds(Optional ByVal Bath As Long) As String","returnType":"String"}, + {"name":"GetRandByte","kind":"function","vis":"public","signature":"Public Function GetRandByte() As Byte()","returnType":"Byte()"}, + {"name":"GetRandStr","kind":"function","vis":"public","signature":"Public Function GetRandStr(Optional ByVal Lens As Integer = 32, Optional Zuhe As String = \"1aA\") As String","returnType":"String"}, + {"name":"HasStr","kind":"function","vis":"public","signature":"Public Function HasStr(FindStr As String, FullStr As String, Optional StartPos As Long = 1, Optional CompType As VbCompareMethod = vbTextCompare) As Long","returnType":"Long"}, + {"name":"InsertSpan","kind":"function","vis":"public","signature":"Public Function InsertSpan(ByRef inputStr As String, ByVal Span As String, ByVal SetpNum As Long, Optional HeadFoot As Boolean) As String","returnType":"String"}, + {"name":"IsEmptyEx","kind":"function","vis":"public","signature":"Public Function IsEmptyEx(Text As String) As Boolean","returnType":"Boolean"}, + {"name":"IsString","kind":"function","vis":"public","signature":"Public Function IsString(var As Variant) As Boolean","returnType":"Boolean"}, + {"name":"JoinLinesFromCollection","kind":"function","vis":"public","signature":"Public Function JoinLinesFromCollection(Coll As Collection) As String","returnType":"String"}, + {"name":"JoinStr","kind":"function","vis":"public","signature":"Public Function JoinStr(Span As String, ParamArray Strings() As Variant) As String","returnType":"String"}, + {"name":"LeftEx","kind":"function","vis":"public","signature":"Public Function LeftEx(Text As Variant, Length As Long) As String","returnType":"String"}, + {"name":"LenBytes","kind":"function","vis":"public","signature":"Public Function LenBytes(inputArray() As Byte) As Long","returnType":"Long"}, + {"name":"MidEx","kind":"function","vis":"public","signature":"Public Function MidEx(FullStr As String, Lstr As String, Rstr As String, Optional starindex As Long) As String","returnType":"String"}, + {"name":"PercentDecode","kind":"function","vis":"public","signature":"Function PercentDecode(encodedStr As String) As String","returnType":"String"}, + {"name":"PercentEncode","kind":"function","vis":"public","signature":"Public Function PercentEncode(inputStr As String) As String","returnType":"String"}, + {"name":"RightEx","kind":"function","vis":"public","signature":"Public Function RightEx(Text As Variant, Length As Long) As String","returnType":"String"}, + {"name":"SliceString","kind":"function","vis":"public","signature":"Public Function SliceString(ByVal Arr As String, ByVal StartPos As Long, Optional ByVal EndPos As Long = -1) As String","returnType":"String"}, + {"name":"SplitLinesToCollection","kind":"function","vis":"public","signature":"Public Function SplitLinesToCollection(ByVal Text As String) As Collection","returnType":"Collection"}, + {"name":"SubStr","kind":"function","vis":"public","signature":"Public Function SubStr(ByVal Txt As String, ByVal txtF$, Optional ByVal txtE$, Optional RetInt As Boolean) As String","returnType":"String"}, + {"name":"ToBytes","kind":"function","vis":"public","signature":"Public Function ToBytes(inputString As String) As Byte()","returnType":"Byte()"}, + {"name":"ToHex","kind":"function","vis":"public","signature":"Public Function ToHex(inputArray() As Byte) As String","returnType":"String"}, + {"name":"ToString","kind":"function","vis":"public","signature":"Public Function ToString(inputArray() As Byte) As String","returnType":"String"}, + {"name":"TrimEx","kind":"function","vis":"public","signature":"Public Function TrimEx(ByRef Text As String) As String","returnType":"String"}, + {"name":"UniVbCrLf","kind":"function","vis":"public","signature":"Public Function UniVbCrLf(Text As String) As String","returnType":"String"}, + {"name":"UnicodeDecode","kind":"function","vis":"public","signature":"Public Function UnicodeDecode(Text As String, Optional PreFix As String = \"\\u\") As String","returnType":"String"}, + {"name":"UnicodeEncode","kind":"function","vis":"public","signature":"Public Function UnicodeEncode(ByVal inputString As String, Optional PreFix As String = \"\\u\") As String","returnType":"String"} + ] + }, + { + "name": "cToolsStream", + "kind": "class", + "vis": "public", + "file": "Tools/FSO/cToolsStream.twin", + "signature": "Class cToolsStream", + "members": [ + {"name":"Class_Initialize","kind":"sub","vis":"private","signature":"Private Sub Class_Initialize()"}, + {"name":"Class_Terminate","kind":"sub","vis":"private","signature":"Private Sub Class_Terminate()"}, + {"name":"Inst","kind":"field","vis":"public","signature":"Public Inst As New ADODB.Stream"}, + {"name":"LastError","kind":"field","vis":"public","signature":"Public LastError As String"}, + {"name":"LineData","kind":"field","vis":"public","signature":"Public LineData As New Collection"}, + {"name":"LineNumber","kind":"field","vis":"public","signature":"Public LineNumber As Long"}, + {"name":"LoadFileAsBinary","kind":"function","vis":"public","signature":"Public Function LoadFileAsBinary(ByVal Path As String, OutData() As Byte) As Boolean","returnType":"Boolean"}, + {"name":"LoadFileAsText","kind":"function","vis":"public","signature":"Public Function LoadFileAsText(ByVal FileName As String, Optional ByVal CharSet As String = \"UTF-8\") As String","returnType":"String"}, + {"name":"ReadLine","kind":"function","vis":"public","signature":"Public Function ReadLine(Optional ByVal Line As Long) As String","returnType":"String"}, + {"name":"SaveFileAsBinary","kind":"function","vis":"public","signature":"Public Function SaveFileAsBinary(ByVal Path As String, OutData() As Byte) As Boolean","returnType":"Boolean"}, + {"name":"SaveFileAsText","kind":"function","vis":"public","signature":"Public Function SaveFileAsText(ByVal FileName As String, Optional Data As String, Optional ByVal CharSet As String = \"UTF-8\") As Boolean","returnType":"Boolean"}, + {"name":"UseLine","kind":"field","vis":"public","signature":"Public UseLine As Boolean"}, + {"name":"UseLineMode","kind":"function","vis":"public","signature":"Public Function UseLineMode(v As Boolean) As cToolsStream","returnType":"cToolsStream"}, + {"name":"WriteLine","kind":"function","vis":"public","signature":"Public Function WriteLine(Text As String, Optional ByVal Line As Long) As Boolean","returnType":"Boolean"} + ] + }, + { + "name": "mCallBackSubClass", + "kind": "module", + "vis": "public", + "file": "Libs/SubClass/inc/mCallBackSubClass.twin", + "signature": "Module mCallBackSubClass", + "members": [ + {"name":"CallWindowProc","kind":"function","vis":"private","declare":true,"signature":"Private Declare Function CallWindowProc Lib \"user32\" Alias \"CallWindowProcW\" (ByVal lpPrevWndFunc As Long, ByVal hwnd As Long, ByVal Msg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long","returnType":"Long"}, + {"name":"Callback","kind":"function","vis":"public","signature":"Public Function Callback(ByVal wHwnd As Long, ByVal wMsg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long","returnType":"Long"}, + {"name":"ClassPool","kind":"field","vis":"public","signature":"Public ClassPool As New Collection"} + ] + }, + { + "name": "mTimer", + "kind": "module", + "vis": "public", + "file": "Libs/DateTime/Timer/mTimer.twin", + "signature": "Module mTimer", + "members": [ + {"name":"CTimersCol","kind":"field","vis":"public","signature":"Public CTimersCol As New Collection"}, + {"name":"KillTimer","kind":"function","vis":"private","declare":true,"signature":"Private Declare PtrSafe Function KillTimer Lib \"user32\" (ByVal hwnd As Long, ByVal nIDEvent As Long) As Long","returnType":"Long"}, + {"name":"RegisterTimerCollection","kind":"function","vis":"public","signature":"Function RegisterTimerCollection(c As cTimers) As Integer","returnType":"Integer"}, + {"name":"TimerProc","kind":"sub","vis":"public","signature":"Sub TimerProc(ByVal hwnd As Long, ByVal uMsg As Long, ByVal idEvent As Long, ByVal dwTime As Long)"}, + {"name":"mTimersColCount","kind":"field","vis":"private","signature":"Private mTimersColCount As Integer"}, + {"name":"timers","kind":"field","vis":"public","signature":"Public timers As New Collection"} + ] } ], "aliases": [], diff --git a/indexer/snapshots/contributed/TBMANLIB_Dialog/api.json b/indexer/snapshots/contributed/TBMANLIB_Dialog/api.json index 5513a1226..5f163a503 100644 --- a/indexer/snapshots/contributed/TBMANLIB_Dialog/api.json +++ b/indexer/snapshots/contributed/TBMANLIB_Dialog/api.json @@ -2,8 +2,177 @@ "format": 3, "package": "TBMANLIB_Dialog", "project": "TBMANLIB_Dialog", - "version": "1.0.0.1", + "version": "1.1.0.0", "types": [ + { + "name": "IFileDialog", + "kind": "interface", + "vis": "public", + "file": "cDialogEx.twin", + "signature": "Interface IFileDialog Extends IModalWindow", + "extends": "IModalWindow", + "attrs": [{"name":"ComImport"},{"name":"InterfaceId","args":"\"42f85136-db7e-439c-85f1-e4075d135fc8\""}], + "members": [ + {"name":"AddPlace","kind":"function","vis":"public","signature":"Function AddPlace(ByRef psi As Any, ByVal fdap As Long) As Long","returnType":"Long","attrs":[{"name":"PreserveSig"}]}, + {"name":"Advise","kind":"function","vis":"public","signature":"Function Advise(ByVal pfde As LongPtr, ByRef pdwCookie As LongPtr) As Long","returnType":"Long","attrs":[{"name":"PreserveSig"}]}, + {"name":"ClearClientData","kind":"sub","vis":"public","signature":"Sub ClearClientData()"}, + {"name":"Close","kind":"sub","vis":"public","signature":"Sub Close(ByVal hr As Long)"}, + {"name":"GetCurrentSelection","kind":"sub","vis":"public","signature":"Sub GetCurrentSelection(ByRef ppsi As Any)"}, + {"name":"GetFileName","kind":"sub","vis":"public","signature":"Sub GetFileName(ByRef pszName As String)"}, + {"name":"GetFileTypeIndex","kind":"sub","vis":"public","signature":"Sub GetFileTypeIndex(ByRef piFileType As Long)"}, + {"name":"GetFolder","kind":"sub","vis":"public","signature":"Sub GetFolder(ByRef ppsi As Any)"}, + {"name":"GetOptions","kind":"sub","vis":"public","signature":"Sub GetOptions(ByRef pfos As Long)"}, + {"name":"GetResult","kind":"sub","vis":"public","signature":"Sub GetResult(ByRef ppsi As IShellItem)"}, + {"name":"SetClientGuid","kind":"sub","vis":"public","signature":"Sub SetClientGuid(ByRef guid As GUID)"}, + {"name":"SetDefaultExtension","kind":"sub","vis":"public","signature":"Sub SetDefaultExtension(ByVal pszDefaultExtension As String)"}, + {"name":"SetDefaultFolder","kind":"sub","vis":"public","signature":"Sub SetDefaultFolder(ByRef psi As IShellItem)"}, + {"name":"SetFileName","kind":"sub","vis":"public","signature":"Sub SetFileName(ByVal pszName As String)"}, + {"name":"SetFileNameLabel","kind":"sub","vis":"public","signature":"Sub SetFileNameLabel(ByVal pszLabel As String)"}, + {"name":"SetFileTypeIndex","kind":"sub","vis":"public","signature":"Sub SetFileTypeIndex(ByVal iFileType As Long)"}, + {"name":"SetFileTypes","kind":"sub","vis":"public","signature":"Sub SetFileTypes(ByVal cFileTypes As Long, ByRef rgFilterTypes As COMDLG_FILTERSPEC)"}, + {"name":"SetFilter","kind":"sub","vis":"public","signature":"Sub SetFilter(ByRef pFilter As Any)"}, + {"name":"SetFolder","kind":"sub","vis":"public","signature":"Sub SetFolder(ByRef psi As IShellItem)"}, + {"name":"SetOkButtonLabel","kind":"sub","vis":"public","signature":"Sub SetOkButtonLabel(ByVal pszText As String)"}, + {"name":"SetOptions","kind":"sub","vis":"public","signature":"Sub SetOptions(ByVal fos As Long)"}, + {"name":"SetTitle","kind":"sub","vis":"public","signature":"Sub SetTitle(ByVal pszTitle As String)"}, + {"name":"Unadvise","kind":"function","vis":"public","signature":"Function Unadvise(ByVal dwCookie As LongPtr) As Long","returnType":"Long","attrs":[{"name":"PreserveSig"}]} + ] + }, + { + "name": "IFileOpenDialog", + "kind": "interface", + "vis": "public", + "file": "cDialogEx.twin", + "signature": "Interface IFileOpenDialog Extends IFileDialog", + "extends": "IFileDialog", + "attrs": [{"name":"ComImport"},{"name":"InterfaceId","args":"\"d57c7288-d4ad-4768-be02-9d969532d960\""}], + "members": [ + {"name":"GetResults","kind":"sub","vis":"public","signature":"Sub GetResults(ByRef ppenum As IShellItemArray)"}, + {"name":"GetSelectedItems","kind":"sub","vis":"public","signature":"Sub GetSelectedItems(ByRef ppsi As IShellItemArray)"} + ] + }, + { + "name": "IFileSaveDialog", + "kind": "interface", + "vis": "public", + "file": "cDialogEx.twin", + "signature": "Interface IFileSaveDialog Extends IFileDialog", + "extends": "IFileDialog", + "attrs": [{"name":"ComImport"},{"name":"InterfaceId","args":"\"84bccd23-5fde-4cdb-aea4-af64b83d78ab\""}], + "members": [ + {"name":"ApplyProperties","kind":"sub","vis":"public","signature":"Sub ApplyProperties(ByRef psi As IShellItem, ByRef pStore As Any, ByVal hwnd As LongPtr, ByRef pSink As Any)"}, + {"name":"GetProperties","kind":"sub","vis":"public","signature":"Sub GetProperties(ByRef ppStore As Any)"}, + {"name":"SetCollectedProperties","kind":"sub","vis":"public","signature":"Sub SetCollectedProperties(ByRef pList As Any, ByVal fAppendDefault As Long)"}, + {"name":"SetProperties","kind":"sub","vis":"public","signature":"Sub SetProperties(ByRef pStore As Any)"}, + {"name":"SetSaveAsItem","kind":"sub","vis":"public","signature":"Sub SetSaveAsItem(ByRef psi As IShellItem)"} + ] + }, + { + "name": "IModalWindow", + "kind": "interface", + "vis": "public", + "file": "cDialogEx.twin", + "signature": "Interface IModalWindow Extends IUnknown", + "extends": "IUnknown", + "attrs": [{"name":"ComImport"},{"name":"InterfaceId","args":"\"b4db1657-70d7-485e-8e3e-6fcb5a5c1802\""}], + "members": [ + {"name":"Show","kind":"function","vis":"public","signature":"Function Show(ByVal hwndOwner As LongPtr) As Long","returnType":"Long","attrs":[{"name":"PreserveSig"}]} + ] + }, + { + "name": "IOleWindow", + "kind": "interface", + "vis": "public", + "file": "cDialogEx.twin", + "signature": "Interface IOleWindow Extends IUnknown", + "extends": "IUnknown", + "attrs": [{"name":"ComImport"},{"name":"InterfaceId","args":"\"00000114-0000-0000-C000-000000000046\""}], + "members": [ + {"name":"ContextSensitiveHelp","kind":"sub","vis":"public","signature":"Sub ContextSensitiveHelp(ByVal fEnterMode As Long)"}, + {"name":"GetWindow","kind":"function","vis":"public","signature":"Function GetWindow(ByRef phwnd As LongPtr) As Long","returnType":"Long","attrs":[{"name":"PreserveSig"}]} + ] + }, + { + "name": "IShellItem", + "kind": "interface", + "vis": "public", + "file": "cDialogEx.twin", + "signature": "Interface IShellItem Extends IUnknown", + "extends": "IUnknown", + "attrs": [{"name":"ComImport"},{"name":"InterfaceId","args":"\"43826d1e-e718-42ee-bc55-a1e261c37bfe\""}], + "members": [ + {"name":"BindToHandler","kind":"sub","vis":"public","signature":"Sub BindToHandler(ByRef pbc As Any, ByRef bhid As GUID, ByRef ppv As LongPtr)"}, + {"name":"Compare","kind":"sub","vis":"public","signature":"Sub Compare(ByRef psi As Any, ByVal hint As Long, ByRef piOrder As Long)"}, + {"name":"GetAttributes","kind":"sub","vis":"public","signature":"Sub GetAttributes(ByVal sfgaoMask As Long, ByRef psfgaoAttribs As Long)"}, + {"name":"GetDisplayName","kind":"sub","vis":"public","signature":"Sub GetDisplayName(ByVal sigdnName As Long, ByRef ppszName As LongPtr)"}, + {"name":"GetParent","kind":"sub","vis":"public","signature":"Sub GetParent(ByRef ppsi As Any)"} + ] + }, + { + "name": "IShellItemArray", + "kind": "interface", + "vis": "public", + "file": "cDialogEx.twin", + "signature": "Interface IShellItemArray Extends IUnknown", + "extends": "IUnknown", + "attrs": [{"name":"ComImport"},{"name":"InterfaceId","args":"\"B63EA76D-1F85-456F-A19C-48159EFA858B\""}], + "members": [ + {"name":"BindToHandler","kind":"sub","vis":"public","signature":"Sub BindToHandler(ByRef pbc As Any, ByRef bhid As GUID, ByRef riid As GUID, ByRef ppvOut As LongPtr)"}, + {"name":"EnumItems","kind":"sub","vis":"public","signature":"Sub EnumItems(ByRef ppenumShellItems As Any)"}, + {"name":"GetAttributes","kind":"sub","vis":"public","signature":"Sub GetAttributes(ByVal AttribFlags As Long, ByVal sfgaoMask As Long, ByRef psfgaoAttribs As Long)"}, + {"name":"GetCount","kind":"sub","vis":"public","signature":"Sub GetCount(ByRef pdwNumItems As Long)"}, + {"name":"GetItemAt","kind":"sub","vis":"public","signature":"Sub GetItemAt(ByVal dwIndex As Long, ByRef ppsi As IShellItem)"}, + {"name":"GetPropertyDescriptionList","kind":"sub","vis":"public","signature":"Sub GetPropertyDescriptionList(ByRef keyType As Any, ByRef riid As GUID, ByRef ppv As LongPtr)"}, + {"name":"GetPropertyStore","kind":"sub","vis":"public","signature":"Sub GetPropertyStore(ByVal flags As Long, ByRef riid As GUID, ByRef ppv As LongPtr)"} + ] + }, + { + "name": "TestModule", + "kind": "module", + "vis": "public", + "file": "test/TestModule.twin", + "signature": "Public Module TestModule", + "members": [ + {"name":"GetForegroundWindow","kind":"function","vis":"private","declare":true,"signature":"Private Declare PtrSafe Function GetForegroundWindow Lib \"user32\" () As LongPtr","returnType":"LongPtr"}, + {"name":"QuickTest","kind":"sub","vis":"public","signature":"Public Sub QuickTest()"}, + {"name":"TestClassicFolderBrowse","kind":"sub","vis":"public","signature":"Public Sub TestClassicFolderBrowse()"}, + {"name":"TestColorDialog","kind":"sub","vis":"public","signature":"Public Sub TestColorDialog()"}, + {"name":"TestColorDialogHtml","kind":"sub","vis":"public","signature":"Public Sub TestColorDialogHtml()"}, + {"name":"TestCountdownMsgBox","kind":"sub","vis":"public","signature":"Public Sub TestCountdownMsgBox()"}, + {"name":"TestCountdownQuestion","kind":"sub","vis":"public","signature":"Public Sub TestCountdownQuestion()"}, + {"name":"TestCountdownWarning","kind":"sub","vis":"public","signature":"Public Sub TestCountdownWarning()"}, + {"name":"TestDatePicker","kind":"sub","vis":"public","signature":"Public Sub TestDatePicker()"}, + {"name":"TestDateTimeCustomFormat","kind":"sub","vis":"public","signature":"Public Sub TestDateTimeCustomFormat()"}, + {"name":"TestDateTimePicker","kind":"sub","vis":"public","signature":"Public Sub TestDateTimePicker()"}, + {"name":"TestFileOpen","kind":"sub","vis":"public","signature":"Public Sub TestFileOpen()"}, + {"name":"TestFileOpenMultiSelect","kind":"sub","vis":"public","signature":"Public Sub TestFileOpenMultiSelect()"}, + {"name":"TestFileSave","kind":"sub","vis":"public","signature":"Public Sub TestFileSave()"}, + {"name":"TestFindDialog","kind":"sub","vis":"public","signature":"Public Sub TestFindDialog()"}, + {"name":"TestFolderBrowse","kind":"sub","vis":"public","signature":"Public Sub TestFolderBrowse()"}, + {"name":"TestFontDialog","kind":"sub","vis":"public","signature":"Public Sub TestFontDialog()"}, + {"name":"TestFontDialogWithLimits","kind":"sub","vis":"public","signature":"Public Sub TestFontDialogWithLimits()"}, + {"name":"TestGetPresetFilter","kind":"sub","vis":"public","signature":"Public Sub TestGetPresetFilter()"}, + {"name":"TestIconDialog","kind":"sub","vis":"public","signature":"Public Sub TestIconDialog()"}, + {"name":"TestInputBoxEmail","kind":"sub","vis":"public","signature":"Public Sub TestInputBoxEmail()"}, + {"name":"TestInputBoxNumeric","kind":"sub","vis":"public","signature":"Public Sub TestInputBoxNumeric()"}, + {"name":"TestInputBoxPhone","kind":"sub","vis":"public","signature":"Public Sub TestInputBoxPhone()"}, + {"name":"TestInputBoxSimple","kind":"sub","vis":"public","signature":"Public Sub TestInputBoxSimple()"}, + {"name":"TestInputBoxURL","kind":"sub","vis":"public","signature":"Public Sub TestInputBoxURL()"}, + {"name":"TestInputBoxWithLimits","kind":"sub","vis":"public","signature":"Public Sub TestInputBoxWithLimits()"}, + {"name":"TestPageSetupDialog","kind":"sub","vis":"public","signature":"Public Sub TestPageSetupDialog()"}, + {"name":"TestPageSetupDialogA4","kind":"sub","vis":"public","signature":"Public Sub TestPageSetupDialogA4()"}, + {"name":"TestPasswordChangeDialog","kind":"sub","vis":"public","signature":"Public Sub TestPasswordChangeDialog()"}, + {"name":"TestPasswordDialog","kind":"sub","vis":"public","signature":"Public Sub TestPasswordDialog()"}, + {"name":"TestPasswordDialogWithConfirm","kind":"sub","vis":"public","signature":"Public Sub TestPasswordDialogWithConfirm()"}, + {"name":"TestPrintDialog","kind":"sub","vis":"public","signature":"Public Sub TestPrintDialog()"}, + {"name":"TestPrintDialogPageRange","kind":"sub","vis":"public","signature":"Public Sub TestPrintDialogPageRange()"}, + {"name":"TestProgressDialog","kind":"sub","vis":"public","signature":"Public Sub TestProgressDialog()"}, + {"name":"TestReplaceDialog","kind":"sub","vis":"public","signature":"Public Sub TestReplaceDialog()"}, + {"name":"TestSelectFiles","kind":"sub","vis":"public","signature":"Public Sub TestSelectFiles()"}, + {"name":"TestTimePicker","kind":"sub","vis":"public","signature":"Public Sub TestTimePicker()"}, + {"name":"TestUtilityFunctions","kind":"sub","vis":"public","signature":"Public Sub TestUtilityFunctions()"} + ] + }, { "name": "cDialog", "kind": "class", @@ -27,7 +196,7 @@ {"name":"Class_Initialize","kind":"sub","vis":"private","signature":"Private Sub Class_Initialize()"}, {"name":"Class_Terminate","kind":"sub","vis":"private","signature":"Private Sub Class_Terminate()"}, {"name":"ClearFilter","kind":"sub","vis":"public","signature":"Public Sub ClearFilter()"}, - {"name":"CoTaskMemFree","kind":"function","vis":"private","declare":true,"signature":"Private Declare PtrSafe Function CoTaskMemFree Lib \"ole32.dll\" (ByVal hMem As Long) As Long","returnType":"Long"}, + {"name":"CoTaskMemFree","kind":"sub","vis":"private","declare":true,"signature":"Private Declare PtrSafe Sub CoTaskMemFree Lib \"ole32.dll\" (ByVal hMem As LongPtr)"}, {"name":"CreatePrompt","kind":"property","vis":"public","signature":"Public Property Let CreatePrompt(ByVal vValue As Boolean)","accessor":"Let"}, {"name":"CreatePrompt","kind":"property","vis":"public","signature":"Public Property Get CreatePrompt() As Boolean","returnType":"Boolean","accessor":"Get"}, {"name":"DefaultExt","kind":"property","vis":"public","signature":"Public Property Let DefaultExt(ByVal vValue As String)","accessor":"Let"}, @@ -50,14 +219,10 @@ {"name":"GetFilePath","kind":"function","vis":"public","signature":"Public Function GetFilePath(ByVal strFullPath As String) As String","returnType":"String"}, {"name":"GetOpenFileName","kind":"function","vis":"private","declare":true,"signature":"Private Declare PtrSafe Function GetOpenFileName Lib \"comdlg32.dll\" Alias \"GetOpenFileNameW\" (pOpenfilename As OPENFILENAME) As Long","returnType":"Long"}, {"name":"GetSaveFileName","kind":"function","vis":"private","declare":true,"signature":"Private Declare PtrSafe Function GetSaveFileName Lib \"comdlg32.dll\" Alias \"GetSaveFileNameW\" (pOpenfilename As OPENFILENAME) As Long","returnType":"Long"}, - {"name":"GlobalAlloc","kind":"function","vis":"private","declare":true,"signature":"Private Declare PtrSafe Function GlobalAlloc Lib \"kernel32\" (ByVal wFlags As Long, ByVal dwBytes As Long) As Long","returnType":"Long"}, - {"name":"GlobalLock","kind":"function","vis":"private","declare":true,"signature":"Private Declare PtrSafe Function GlobalLock Lib \"kernel32\" (ByVal hMem As Long) As Long","returnType":"Long"}, - {"name":"GlobalUnlock","kind":"function","vis":"private","declare":true,"signature":"Private Declare PtrSafe Function GlobalUnlock Lib \"kernel32\" (ByVal hMem As Long) As Long","returnType":"Long"}, {"name":"HideReadOnly","kind":"property","vis":"public","signature":"Public Property Let HideReadOnly(ByVal vValue As Boolean)","accessor":"Let"}, {"name":"HideReadOnly","kind":"property","vis":"public","signature":"Public Property Get HideReadOnly() As Boolean","returnType":"Boolean","accessor":"Get"}, {"name":"InitialDir","kind":"property","vis":"public","signature":"Public Property Let InitialDir(ByVal vValue As String)","accessor":"Let"}, {"name":"InitialDir","kind":"property","vis":"public","signature":"Public Property Get InitialDir() As String","returnType":"String","accessor":"Get"}, - {"name":"LocalFree","kind":"function","vis":"private","declare":true,"signature":"Private Declare PtrSafe Function LocalFree Lib \"kernel32\" (ByVal hMem As Long) As Long","returnType":"Long"}, {"name":"MAX_PATH","kind":"const","vis":"private","signature":"Private Const MAX_PATH = 260"}, {"name":"MultiSelect","kind":"property","vis":"public","signature":"Public Property Let MultiSelect(ByVal vValue As Boolean)","accessor":"Let"}, {"name":"MultiSelect","kind":"property","vis":"public","signature":"Public Property Get MultiSelect() As Boolean","returnType":"Boolean","accessor":"Get"}, @@ -89,27 +254,28 @@ {"name":"OFN_READONLY","kind":"const","vis":"private","signature":"Private Const OFN_READONLY = &H1"}, {"name":"OFN_SHAREAWARE","kind":"const","vis":"private","signature":"Private Const OFN_SHAREAWARE = &H4000"}, {"name":"OFN_SHOWHELP","kind":"const","vis":"private","signature":"Private Const OFN_SHOWHELP = &H10"}, - {"name":"OpenFile","kind":"function","vis":"public","signature":"Public Function OpenFile(ByVal strFilePath As String, Optional ByVal strOperation As String = \"open\") As Long","returnType":"Long"}, + {"name":"OFN_STRUCTSIZE","kind":"const","vis":"private","signatures":[{"cond":"Win64","signature":"Private Const OFN_STRUCTSIZE As Long = 152"},{"cond":"Not (Win64)","signature":"Private Const OFN_STRUCTSIZE As Long = 88"}]}, + {"name":"OpenFile","kind":"function","vis":"public","signature":"Public Function OpenFile(ByVal strFilePath As String, Optional ByVal strOperation As String = \"open\") As LongPtr","returnType":"LongPtr"}, {"name":"OverwritePrompt","kind":"property","vis":"public","signature":"Public Property Let OverwritePrompt(ByVal vValue As Boolean)","accessor":"Let"}, {"name":"OverwritePrompt","kind":"property","vis":"public","signature":"Public Property Get OverwritePrompt() As Boolean","returnType":"Boolean","accessor":"Get"}, + {"name":"PathCombine","kind":"function","vis":"public","signature":"Public Function PathCombine(ByVal strPath1 As String, ByVal strPath2 As String) As String","returnType":"String"}, {"name":"PathMustExist","kind":"property","vis":"public","signature":"Public Property Let PathMustExist(ByVal vValue As Boolean)","accessor":"Let"}, {"name":"PathMustExist","kind":"property","vis":"public","signature":"Public Property Get PathMustExist() As Boolean","returnType":"Boolean","accessor":"Get"}, {"name":"ReadOnly","kind":"property","vis":"public","signature":"Public Property Let ReadOnly(ByVal vValue As Boolean)","accessor":"Let"}, {"name":"ReadOnly","kind":"property","vis":"public","signature":"Public Property Get ReadOnly() As Boolean","returnType":"Boolean","accessor":"Get"}, {"name":"Reset","kind":"sub","vis":"public","signature":"Public Sub Reset()"}, - {"name":"SHBrowseForFolder","kind":"function","vis":"private","declare":true,"signature":"Private Declare PtrSafe Function SHBrowseForFolder Lib \"shell32.dll\" (lpbi As BROWSEINFO) As Long","returnType":"Long"}, - {"name":"SHGetPathFromIDList","kind":"function","vis":"private","declare":true,"signature":"Private Declare PtrSafe Function SHGetPathFromIDList Lib \"shell32.dll\" Alias \"SHGetPathFromIDListA\" (ByVal pidl As Long, ByVal pszPath As String) As Long","returnType":"Long"}, - {"name":"SelectAndOpenFile","kind":"function","vis":"public","signature":"Public Function SelectAndOpenFile() As Long","returnType":"Long"}, + {"name":"SHBrowseForFolder","kind":"function","vis":"private","declare":true,"signature":"Private Declare PtrSafe Function SHBrowseForFolder Lib \"shell32.dll\" Alias \"SHBrowseForFolderW\" (lpbi As BROWSEINFO) As LongPtr","returnType":"LongPtr"}, + {"name":"SHGetPathFromIDList","kind":"function","vis":"private","declare":true,"signature":"Private Declare PtrSafe Function SHGetPathFromIDList Lib \"shell32.dll\" Alias \"SHGetPathFromIDListW\" (ByVal pidl As LongPtr, ByVal pszPath As LongPtr) As Long","returnType":"Long"}, + {"name":"SelectAndOpenFile","kind":"function","vis":"public","signature":"Public Function SelectAndOpenFile() As LongPtr","returnType":"LongPtr"}, + {"name":"SelectFiles","kind":"function","vis":"public","signature":"Public Function SelectFiles() As Collection","returnType":"Collection"}, {"name":"SetCodeFilters","kind":"sub","vis":"public","signature":"Public Sub SetCodeFilters()"}, {"name":"SetCommonFilters","kind":"sub","vis":"public","signature":"Public Sub SetCommonFilters()"}, {"name":"SetDocumentFilters","kind":"sub","vis":"public","signature":"Public Sub SetDocumentFilters()"}, {"name":"SetImageFilters","kind":"sub","vis":"public","signature":"Public Sub SetImageFilters()"}, - {"name":"ShellExecute","kind":"function","vis":"private","declare":true,"returnType":"Long","signatures":[{"cond":"VBA7","signature":"Private Declare PtrSafe Function ShellExecute Lib \"shell32.dll\" Alias \"ShellExecuteA\" (ByVal hwnd As Long, ByVal lpOperation As String, ByVal lpFile As String, ByVal lpParameters As String, ByVal lpDirectory As String, ByVal nShowCmd As Long) As Long"},{"cond":"Not (VBA7)","signature":"Private Declare Function ShellExecute Lib \"shell32.dll\" Alias \"ShellExecuteA\" (ByVal hwnd As Long, ByVal lpOperation As String, ByVal lpFile As String, ByVal lpParameters As String, ByVal lpDirectory As String, ByVal nShowCmd As Long) As Long"}]}, + {"name":"ShellExecute","kind":"function","vis":"private","declare":true,"signature":"Private Declare PtrSafe Function ShellExecute Lib \"shell32.dll\" Alias \"ShellExecuteW\" (ByVal hwnd As LongPtr, ByVal lpOperation As LongPtr, ByVal lpFile As LongPtr, ByVal lpParameters As LongPtr, ByVal lpDirectory As LongPtr, ByVal nShowCmd As Long) As LongPtr","returnType":"LongPtr"}, {"name":"ShowBrowseForFolder","kind":"function","vis":"public","signature":"Public Function ShowBrowseForFolder() As String","returnType":"String"}, {"name":"ShowOpen","kind":"function","vis":"public","signature":"Public Function ShowOpen() As Variant","returnType":"Variant"}, {"name":"ShowSave","kind":"function","vis":"public","signature":"Public Function ShowSave() As String","returnType":"String"}, - {"name":"lstrcpy","kind":"function","vis":"private","declare":true,"signature":"Private Declare PtrSafe Function lstrcpy Lib \"kernel32\" Alias \"lstrcpyA\" (ByVal lpString1 As Long, ByVal lpString2 As Long) As Long","returnType":"Long"}, - {"name":"lstrlen","kind":"function","vis":"private","declare":true,"signature":"Private Declare PtrSafe Function lstrlen Lib \"kernel32\" Alias \"lstrlenA\" (ByVal lpString As Long) As Long","returnType":"Long"}, {"name":"mData","kind":"field","vis":"private","signature":"Private mData As cDialogData"} ] }, @@ -122,13 +288,13 @@ "file": "cDialog.twin", "signature": "Private Type BROWSEINFO", "fields": [ - {"name":"hwndOwner","vis":"public","signature":"hwndOwner As Long"}, - {"name":"pidlRoot","vis":"public","signature":"pidlRoot As Long"}, - {"name":"pszDisplayName","vis":"public","signature":"pszDisplayName As String"}, - {"name":"lpszTitle","vis":"public","signature":"lpszTitle As String"}, + {"name":"hwndOwner","vis":"public","signature":"hwndOwner As LongPtr"}, + {"name":"pidlRoot","vis":"public","signature":"pidlRoot As LongPtr"}, + {"name":"pszDisplayName","vis":"public","signature":"pszDisplayName As LongPtr"}, + {"name":"lpszTitle","vis":"public","signature":"lpszTitle As LongPtr"}, {"name":"ulFlags","vis":"public","signature":"ulFlags As Long"}, - {"name":"lpfnCallback","vis":"public","signature":"lpfnCallback As Long"}, - {"name":"lParam","vis":"public","signature":"lParam As Long"}, + {"name":"lpfnCallback","vis":"public","signature":"lpfnCallback As LongPtr"}, + {"name":"lParam","vis":"public","signature":"lParam As LongPtr"}, {"name":"iImage","vis":"public","signature":"iImage As Long"} ], "members": [] @@ -197,6 +363,1691 @@ {"name":"mblnNewDialogStyle","vis":"public","signature":"mblnNewDialogStyle As Boolean"} ], "members": [] + }, + { + "name": "cDialogColor", + "kind": "class", + "vis": "public", + "file": "more/cDialogColor.twin", + "signature": "Public Class cDialogColor", + "attrs": [{"name":"COMCreatable","args":"False"}], + "members": [ + {"name":"AnyColor","kind":"property","vis":"public","signature":"Public Property Get AnyColor() As Boolean","returnType":"Boolean","accessor":"Get"}, + {"name":"AnyColor","kind":"property","vis":"public","signature":"Public Property Let AnyColor(ByVal value As Boolean)","accessor":"Let"}, + {"name":"Blue","kind":"property","vis":"public","signature":"Public Property Get Blue() As Long","returnType":"Long","accessor":"Get"}, + {"name":"Blue","kind":"property","vis":"public","signature":"Public Property Let Blue(ByVal value As Long)","accessor":"Let"}, + {"name":"CC_ANYCOLOR","kind":"const","vis":"private","signature":"Private Const CC_ANYCOLOR = &H100"}, + {"name":"CC_ENABLEHOOK","kind":"const","vis":"private","signature":"Private Const CC_ENABLEHOOK = &H10"}, + {"name":"CC_ENABLETEMPLATE","kind":"const","vis":"private","signature":"Private Const CC_ENABLETEMPLATE = &H20"}, + {"name":"CC_ENABLETEMPLATEHANDLE","kind":"const","vis":"private","signature":"Private Const CC_ENABLETEMPLATEHANDLE = &H40"}, + {"name":"CC_FULLOPEN","kind":"const","vis":"private","signature":"Private Const CC_FULLOPEN = &H2"}, + {"name":"CC_PREVENTFULLOPEN","kind":"const","vis":"private","signature":"Private Const CC_PREVENTFULLOPEN = &H4"}, + {"name":"CC_RGBINIT","kind":"const","vis":"private","signature":"Private Const CC_RGBINIT = &H1"}, + {"name":"CC_SHOWHELP","kind":"const","vis":"private","signature":"Private Const CC_SHOWHELP = &H8"}, + {"name":"CC_SOLIDCOLOR","kind":"const","vis":"private","signature":"Private Const CC_SOLIDCOLOR = &H80"}, + {"name":"CC_STRUCTSIZE","kind":"const","vis":"private","signatures":[{"cond":"Win64","signature":"Private Const CC_STRUCTSIZE As Long = 72"},{"cond":"Not (Win64)","signature":"Private Const CC_STRUCTSIZE As Long = 36"}]}, + {"name":"ChooseColor","kind":"function","vis":"private","declare":true,"signature":"Private Declare PtrSafe Function ChooseColor Lib \"comdlg32.dll\" Alias \"ChooseColorW\" (pChoosecolor As CHOOSECOLOR) As Long","returnType":"Long"}, + {"name":"Class_Initialize","kind":"sub","vis":"private","signature":"Private Sub Class_Initialize()"}, + {"name":"Color","kind":"property","vis":"public","signature":"Public Property Get Color() As Long","returnType":"Long","accessor":"Get"}, + {"name":"Color","kind":"property","vis":"public","signature":"Public Property Let Color(ByVal value As Long)","accessor":"Let"}, + {"name":"FromHtmlColor","kind":"sub","vis":"public","signature":"Public Sub FromHtmlColor(ByVal htmlColor As String)"}, + {"name":"FullOpen","kind":"property","vis":"public","signature":"Public Property Get FullOpen() As Boolean","returnType":"Boolean","accessor":"Get"}, + {"name":"FullOpen","kind":"property","vis":"public","signature":"Public Property Let FullOpen(ByVal value As Boolean)","accessor":"Let"}, + {"name":"GetCustomColor","kind":"function","vis":"public","signature":"Public Function GetCustomColor(ByVal index As Long) As Long","returnType":"Long"}, + {"name":"Green","kind":"property","vis":"public","signature":"Public Property Get Green() As Long","returnType":"Long","accessor":"Get"}, + {"name":"Green","kind":"property","vis":"public","signature":"Public Property Let Green(ByVal value As Long)","accessor":"Let"}, + {"name":"OwnerHwnd","kind":"property","vis":"public","signature":"Public Property Get OwnerHwnd() As LongPtr","returnType":"LongPtr","accessor":"Get"}, + {"name":"OwnerHwnd","kind":"property","vis":"public","signature":"Public Property Let OwnerHwnd(ByVal value As LongPtr)","accessor":"Let"}, + {"name":"PreventFullOpen","kind":"property","vis":"public","signature":"Public Property Get PreventFullOpen() As Boolean","returnType":"Boolean","accessor":"Get"}, + {"name":"PreventFullOpen","kind":"property","vis":"public","signature":"Public Property Let PreventFullOpen(ByVal value As Boolean)","accessor":"Let"}, + {"name":"Red","kind":"property","vis":"public","signature":"Public Property Get Red() As Long","returnType":"Long","accessor":"Get"}, + {"name":"Red","kind":"property","vis":"public","signature":"Public Property Let Red(ByVal value As Long)","accessor":"Let"}, + {"name":"Reset","kind":"sub","vis":"public","signature":"Public Sub Reset()"}, + {"name":"SetCustomColor","kind":"sub","vis":"public","signature":"Public Sub SetCustomColor(ByVal index As Long, ByVal color As Long)"}, + {"name":"SetRgb","kind":"sub","vis":"public","signature":"Public Sub SetRgb(ByVal red As Long, ByVal green As Long, ByVal blue As Long)"}, + {"name":"ShowDialog","kind":"function","vis":"public","signature":"Public Function ShowDialog() As Boolean","returnType":"Boolean"}, + {"name":"SolidColor","kind":"property","vis":"public","signature":"Public Property Get SolidColor() As Boolean","returnType":"Boolean","accessor":"Get"}, + {"name":"SolidColor","kind":"property","vis":"public","signature":"Public Property Let SolidColor(ByVal value As Boolean)","accessor":"Let"}, + {"name":"ToHtmlColor","kind":"function","vis":"public","signature":"Public Function ToHtmlColor() As String","returnType":"String"}, + {"name":"ToRgbString","kind":"function","vis":"public","signature":"Public Function ToRgbString() As String","returnType":"String"}, + {"name":"mData","kind":"field","vis":"private","signature":"Private mData As cDialogColorData"} + ] + }, + { + "name": "CHOOSECOLOR", + "kind": "type", + "in": "cDialogColor", + "vis": "private", + "public": false, + "file": "more/cDialogColor.twin", + "signature": "Private Type CHOOSECOLOR", + "fields": [ + {"name":"lStructSize","vis":"public","signature":"lStructSize As Long"}, + {"name":"hwndOwner","vis":"public","signature":"hwndOwner As LongPtr"}, + {"name":"hInstance","vis":"public","signature":"hInstance As LongPtr"}, + {"name":"rgbResult","vis":"public","signature":"rgbResult As Long"}, + {"name":"lpCustColors","vis":"public","signature":"lpCustColors As LongPtr"}, + {"name":"flags","vis":"public","signature":"flags As Long"}, + {"name":"lCustData","vis":"public","signature":"lCustData As LongPtr"}, + {"name":"lpfnHook","vis":"public","signature":"lpfnHook As LongPtr"}, + {"name":"lpTemplateName","vis":"public","signature":"lpTemplateName As LongPtr"} + ], + "members": [] + }, + { + "name": "cDialogColorData", + "kind": "type", + "in": "cDialogColor", + "vis": "private", + "public": false, + "file": "more/cDialogColor.twin", + "signature": "Private Type cDialogColorData", + "fields": [ + {"name":"mColor","vis":"public","signature":"mColor As Long"}, + {"name":"mFullOpen","vis":"public","signature":"mFullOpen As Boolean"}, + {"name":"mPreventFullOpen","vis":"public","signature":"mPreventFullOpen As Boolean"}, + {"name":"mSolidColor","vis":"public","signature":"mSolidColor As Boolean"}, + {"name":"mAnyColor","vis":"public","signature":"mAnyColor As Boolean"}, + {"name":"mOwnerHwnd","vis":"public","signature":"mOwnerHwnd As LongPtr"}, + {"name":"mCustomColors","vis":"public","signature":"mCustomColors(0 To 15) As Long"} + ], + "members": [] + }, + { + "name": "cDialogCountdownMsgBox", + "kind": "class", + "vis": "public", + "file": "more/cDialogCountdownMsgBox.twin", + "signature": "Public Class cDialogCountdownMsgBox", + "attrs": [{"name":"COMCreatable","args":"False"}], + "members": [ + {"name":"AutoClose","kind":"property","vis":"public","signature":"Public Property Get AutoClose() As Boolean","returnType":"Boolean","accessor":"Get"}, + {"name":"AutoClose","kind":"property","vis":"public","signature":"Public Property Let AutoClose(ByVal value As Boolean)","accessor":"Let"}, + {"name":"Buttons","kind":"property","vis":"public","signature":"Public Property Get Buttons() As Long","returnType":"Long","accessor":"Get"}, + {"name":"Buttons","kind":"property","vis":"public","signature":"Public Property Let Buttons(ByVal value As Long)","accessor":"Let"}, + {"name":"Class_Initialize","kind":"sub","vis":"private","signature":"Private Sub Class_Initialize()"}, + {"name":"CountdownSeconds","kind":"property","vis":"public","signature":"Public Property Get CountdownSeconds() As Long","returnType":"Long","accessor":"Get"}, + {"name":"CountdownSeconds","kind":"property","vis":"public","signature":"Public Property Let CountdownSeconds(ByVal value As Long)","accessor":"Let"}, + {"name":"DefaultButton","kind":"property","vis":"public","signature":"Public Property Get DefaultButton() As Long","returnType":"Long","accessor":"Get"}, + {"name":"DefaultButton","kind":"property","vis":"public","signature":"Public Property Let DefaultButton(ByVal value As Long)","accessor":"Let"}, + {"name":"EnableWindow","kind":"function","vis":"private","declare":true,"signature":"Private Declare PtrSafe Function EnableWindow Lib \"user32\" (ByVal hWnd As LongPtr, ByVal fEnable As Long) As Long","returnType":"Long"}, + {"name":"FindWindow","kind":"function","vis":"private","declare":true,"signature":"Private Declare PtrSafe Function FindWindow Lib \"user32\" Alias \"FindWindowW\" (ByVal lpClassName As LongPtr, ByVal lpWindowName As LongPtr) As LongPtr","returnType":"LongPtr"}, + {"name":"GetDlgItem","kind":"function","vis":"private","declare":true,"signature":"Private Declare PtrSafe Function GetDlgItem Lib \"user32\" (ByVal hDlg As LongPtr, ByVal nIDDlgItem As Long) As LongPtr","returnType":"LongPtr"}, + {"name":"GetResultDescription","kind":"function","vis":"public","signature":"Public Function GetResultDescription() As String","returnType":"String"}, + {"name":"IDABORT","kind":"const","vis":"private","signature":"Private Const IDABORT As Long = 3"}, + {"name":"IDCANCEL","kind":"const","vis":"private","signature":"Private Const IDCANCEL As Long = 2"}, + {"name":"IDIGNORE","kind":"const","vis":"private","signature":"Private Const IDIGNORE As Long = 5"}, + {"name":"IDNO","kind":"const","vis":"private","signature":"Private Const IDNO As Long = 7"}, + {"name":"IDOK","kind":"const","vis":"private","signature":"Private Const IDOK As Long = 1"}, + {"name":"IDRETRY","kind":"const","vis":"private","signature":"Private Const IDRETRY As Long = 4"}, + {"name":"IDTIMEOUT","kind":"const","vis":"private","signature":"Private Const IDTIMEOUT As Long = 32000"}, + {"name":"IDYES","kind":"const","vis":"private","signature":"Private Const IDYES As Long = 6"}, + {"name":"Icon","kind":"property","vis":"public","signature":"Public Property Get Icon() As Long","returnType":"Long","accessor":"Get"}, + {"name":"Icon","kind":"property","vis":"public","signature":"Public Property Let Icon(ByVal value As Long)","accessor":"Let"}, + {"name":"IsTimedOut","kind":"function","vis":"public","signature":"Public Function IsTimedOut() As Boolean","returnType":"Boolean"}, + {"name":"KillTimer","kind":"function","vis":"private","declare":true,"signature":"Private Declare PtrSafe Function KillTimer Lib \"user32\" (ByVal hWnd As LongPtr, ByVal nIDEvent As LongPtr) As Long","returnType":"Long"}, + {"name":"Message","kind":"property","vis":"public","signature":"Public Property Get Message() As String","returnType":"String","accessor":"Get"}, + {"name":"Message","kind":"property","vis":"public","signature":"Public Property Let Message(ByVal value As String)","accessor":"Let"}, + {"name":"MessageBoxTimeout","kind":"function","vis":"private","declare":true,"signature":"Private Declare PtrSafe Function MessageBoxTimeout Lib \"user32\" Alias \"MessageBoxTimeoutW\" (ByVal hWnd As LongPtr, ByVal lpText As LongPtr, ByVal lpCaption As LongPtr, ByVal uType As Long, ByVal wLanguageId As Long, ByVal dwMilliseconds As Long) As Long","returnType":"Long"}, + {"name":"OwnerHwnd","kind":"property","vis":"public","signature":"Public Property Get OwnerHwnd() As LongPtr","returnType":"LongPtr","accessor":"Get"}, + {"name":"OwnerHwnd","kind":"property","vis":"public","signature":"Public Property Let OwnerHwnd(ByVal value As LongPtr)","accessor":"Let"}, + {"name":"RemainingSeconds","kind":"property","vis":"public","signature":"Public Property Get RemainingSeconds() As Long","returnType":"Long","accessor":"Get"}, + {"name":"Reset","kind":"sub","vis":"public","signature":"Public Sub Reset()"}, + {"name":"Result","kind":"property","vis":"public","signature":"Public Property Get Result() As DialogResult","returnType":"DialogResult","accessor":"Get"}, + {"name":"SetTimer","kind":"function","vis":"private","declare":true,"signature":"Private Declare PtrSafe Function SetTimer Lib \"user32\" (ByVal hWnd As LongPtr, ByVal nIDEvent As LongPtr, ByVal uElapse As Long, ByVal lpTimerFunc As LongPtr) As LongPtr","returnType":"LongPtr"}, + {"name":"SetWindowText","kind":"function","vis":"private","declare":true,"signature":"Private Declare PtrSafe Function SetWindowText Lib \"user32\" Alias \"SetWindowTextW\" (ByVal hWnd As LongPtr, ByVal lpString As LongPtr) As Long","returnType":"Long"}, + {"name":"ShowConfirm","kind":"function","vis":"public","signature":"Public Function ShowConfirm(ByVal message As String, Optional ByVal title As String = \"Confirm\", Optional ByVal seconds As Long = 30) As DialogResult","returnType":"DialogResult"}, + {"name":"ShowCustom","kind":"function","vis":"public","signature":"Public Function ShowCustom(ByVal message As String, ByVal title As String, ByVal buttons As Long, ByVal icon As Long, Optional ByVal seconds As Long = 10) As DialogResult","returnType":"DialogResult"}, + {"name":"ShowDialog","kind":"function","vis":"public","signature":"Public Function ShowDialog() As DialogResult","returnType":"DialogResult"}, + {"name":"ShowError","kind":"function","vis":"public","signature":"Public Function ShowError(ByVal message As String, Optional ByVal title As String = \"Error\", Optional ByVal seconds As Long = 20) As DialogResult","returnType":"DialogResult"}, + {"name":"ShowInfo","kind":"function","vis":"public","signature":"Public Function ShowInfo(ByVal message As String, Optional ByVal title As String = \"Information\", Optional ByVal seconds As Long = 10) As DialogResult","returnType":"DialogResult"}, + {"name":"ShowProgress","kind":"property","vis":"public","signature":"Public Property Get ShowProgress() As Boolean","returnType":"Boolean","accessor":"Get"}, + {"name":"ShowProgress","kind":"property","vis":"public","signature":"Public Property Let ShowProgress(ByVal value As Boolean)","accessor":"Let"}, + {"name":"ShowQuestion","kind":"function","vis":"public","signature":"Public Function ShowQuestion(ByVal message As String, Optional ByVal title As String = \"Question\", Optional ByVal seconds As Long = 30) As DialogResult","returnType":"DialogResult"}, + {"name":"ShowWarning","kind":"function","vis":"public","signature":"Public Function ShowWarning(ByVal message As String, Optional ByVal title As String = \"Warning\", Optional ByVal seconds As Long = 15) As DialogResult","returnType":"DialogResult"}, + {"name":"Title","kind":"property","vis":"public","signature":"Public Property Get Title() As String","returnType":"String","accessor":"Get"}, + {"name":"Title","kind":"property","vis":"public","signature":"Public Property Let Title(ByVal value As String)","accessor":"Let"}, + {"name":"mData","kind":"field","vis":"private","signature":"Private mData As cDialogCountdownMsgBoxData"} + ] + }, + { + "name": "DialogResult", + "kind": "enum", + "in": "cDialogCountdownMsgBox", + "vis": "public", + "file": "more/cDialogCountdownMsgBox.twin", + "signature": "Public Enum DialogResult", + "values": [ + {"name":"drOK","value":"1"}, + {"name":"drCancel","value":"2"}, + {"name":"drAbort","value":"3"}, + {"name":"drRetry","value":"4"}, + {"name":"drIgnore","value":"5"}, + {"name":"drYes","value":"6"}, + {"name":"drNo","value":"7"}, + {"name":"drTimeout","value":"32000"} + ] + }, + { + "name": "MessageBoxButtons", + "kind": "enum", + "in": "cDialogCountdownMsgBox", + "vis": "public", + "file": "more/cDialogCountdownMsgBox.twin", + "signature": "Public Enum MessageBoxButtons", + "values": [ + {"name":"MB_OK","value":"&H0"}, + {"name":"MB_OKCANCEL","value":"&H1"}, + {"name":"MB_ABORTRETRYIGNORE","value":"&H2"}, + {"name":"MB_YESNOCANCEL","value":"&H3"}, + {"name":"MB_YESNO","value":"&H4"}, + {"name":"MB_RETRYCANCEL","value":"&H5"} + ] + }, + { + "name": "MessageBoxDefaultButton", + "kind": "enum", + "in": "cDialogCountdownMsgBox", + "vis": "public", + "file": "more/cDialogCountdownMsgBox.twin", + "signature": "Public Enum MessageBoxDefaultButton", + "values": [ + {"name":"MB_DEFBUTTON1","value":"&H0"}, + {"name":"MB_DEFBUTTON2","value":"&H100"}, + {"name":"MB_DEFBUTTON3","value":"&H200"} + ] + }, + { + "name": "MessageBoxIcons", + "kind": "enum", + "in": "cDialogCountdownMsgBox", + "vis": "public", + "file": "more/cDialogCountdownMsgBox.twin", + "signature": "Public Enum MessageBoxIcons", + "values": [ + {"name":"MB_ICONSTOP","value":"&H10"}, + {"name":"MB_ICONQUESTION","value":"&H20"}, + {"name":"MB_ICONEXCLAMATION","value":"&H30"}, + {"name":"MB_ICONINFORMATION","value":"&H40"} + ] + }, + { + "name": "cDialogCountdownMsgBoxData", + "kind": "type", + "in": "cDialogCountdownMsgBox", + "vis": "private", + "public": false, + "file": "more/cDialogCountdownMsgBox.twin", + "signature": "Private Type cDialogCountdownMsgBoxData", + "fields": [ + {"name":"mTitle","vis":"public","signature":"mTitle As String"}, + {"name":"mMessage","vis":"public","signature":"mMessage As String"}, + {"name":"mCountdownSeconds","vis":"public","signature":"mCountdownSeconds As Long"}, + {"name":"mRemainingSeconds","vis":"public","signature":"mRemainingSeconds As Long"}, + {"name":"mButtons","vis":"public","signature":"mButtons As Long"}, + {"name":"mIcon","vis":"public","signature":"mIcon As Long"}, + {"name":"mDefaultButton","vis":"public","signature":"mDefaultButton As Long"}, + {"name":"mShowProgress","vis":"public","signature":"mShowProgress As Boolean"}, + {"name":"mAutoClose","vis":"public","signature":"mAutoClose As Boolean"}, + {"name":"mOwnerHwnd","vis":"public","signature":"mOwnerHwnd As LongPtr"}, + {"name":"mResult","vis":"public","signature":"mResult As DialogResult"} + ], + "members": [] + }, + { + "name": "cDialogDateTime", + "kind": "class", + "vis": "public", + "file": "more/cDialogDateTime.twin", + "signature": "Public Class cDialogDateTime", + "attrs": [{"name":"COMCreatable","args":"False"}], + "members": [ + {"name":"AddDays","kind":"sub","vis":"public","signature":"Public Sub AddDays(ByVal days As Long)"}, + {"name":"AddHours","kind":"sub","vis":"public","signature":"Public Sub AddHours(ByVal hours As Long)"}, + {"name":"Cancelled","kind":"property","vis":"public","signature":"Public Property Get Cancelled() As Boolean","returnType":"Boolean","accessor":"Get"}, + {"name":"Class_Initialize","kind":"sub","vis":"private","signature":"Private Sub Class_Initialize()"}, + {"name":"CustomFormat","kind":"property","vis":"public","signature":"Public Property Get CustomFormat() As String","returnType":"String","accessor":"Get"}, + {"name":"CustomFormat","kind":"property","vis":"public","signature":"Public Property Let CustomFormat(ByVal value As String)","accessor":"Let"}, + {"name":"DateTimeValue","kind":"property","vis":"public","signature":"Public Property Get DateTimeValue() As Date","returnType":"Date","accessor":"Get"}, + {"name":"DateTimeValue","kind":"property","vis":"public","signature":"Public Property Let DateTimeValue(ByVal value As Date)","accessor":"Let"}, + {"name":"DateValue","kind":"property","vis":"public","signature":"Public Property Get DateValue() As Date","returnType":"Date","accessor":"Get"}, + {"name":"DateValue","kind":"property","vis":"public","signature":"Public Property Let DateValue(ByVal value As Date)","accessor":"Let"}, + {"name":"Day","kind":"property","vis":"public","signature":"Public Property Get Day() As Long","returnType":"Long","accessor":"Get"}, + {"name":"Day","kind":"property","vis":"public","signature":"Public Property Let Day(ByVal value As Long)","accessor":"Let"}, + {"name":"FileTimeToSystemTime","kind":"function","vis":"private","declare":true,"signature":"Private Declare PtrSafe Function FileTimeToSystemTime Lib \"kernel32\" (lpFileTime As FILETIME, lpSystemTime As SYSTEMTIME) As Long","returnType":"Long"}, + {"name":"FormatDate","kind":"function","vis":"public","signature":"Public Function FormatDate(Optional ByVal formatType As DateFormat = DateFormat.ShortDate) As String","returnType":"String"}, + {"name":"FormatDateTime","kind":"function","vis":"public","signature":"Public Function FormatDateTime() As String","returnType":"String"}, + {"name":"FormatTime","kind":"function","vis":"public","signature":"Public Function FormatTime() As String","returnType":"String"}, + {"name":"GetDayOfWeek","kind":"function","vis":"public","signature":"Public Function GetDayOfWeek() As Long","returnType":"Long"}, + {"name":"GetDayOfWeekName","kind":"function","vis":"public","signature":"Public Function GetDayOfWeekName() As String","returnType":"String"}, + {"name":"GetDaysInMonth","kind":"function","vis":"public","signature":"Public Function GetDaysInMonth(ByVal year As Long, ByVal month As Long) As Long","returnType":"Long"}, + {"name":"GetLocalTime","kind":"function","vis":"private","declare":true,"signature":"Private Declare PtrSafe Function GetLocalTime Lib \"kernel32\" (lpSystemTime As SYSTEMTIME) As Long","returnType":"Long"}, + {"name":"GetMonthName","kind":"function","vis":"public","signature":"Public Function GetMonthName(Optional ByVal month As Long = 0) As String","returnType":"String"}, + {"name":"Hour","kind":"property","vis":"public","signature":"Public Property Get Hour() As Long","returnType":"Long","accessor":"Get"}, + {"name":"Hour","kind":"property","vis":"public","signature":"Public Property Let Hour(ByVal value As Long)","accessor":"Let"}, + {"name":"InitNames","kind":"sub","vis":"private","signature":"Private Sub InitNames()"}, + {"name":"Is24HourFormat","kind":"property","vis":"public","signature":"Public Property Get Is24HourFormat() As Boolean","returnType":"Boolean","accessor":"Get"}, + {"name":"Is24HourFormat","kind":"property","vis":"public","signature":"Public Property Let Is24HourFormat(ByVal value As Boolean)","accessor":"Let"}, + {"name":"MAX_DATE_LENGTH","kind":"const","vis":"private","signature":"Private Const MAX_DATE_LENGTH = 80"}, + {"name":"MaxYear","kind":"property","vis":"public","signature":"Public Property Get MaxYear() As Long","returnType":"Long","accessor":"Get"}, + {"name":"MaxYear","kind":"property","vis":"public","signature":"Public Property Let MaxYear(ByVal value As Long)","accessor":"Let"}, + {"name":"Min","kind":"function","vis":"private","signature":"Private Function Min(ByVal a As Long, ByVal b As Long) As Long","returnType":"Long"}, + {"name":"MinYear","kind":"property","vis":"public","signature":"Public Property Get MinYear() As Long","returnType":"Long","accessor":"Get"}, + {"name":"MinYear","kind":"property","vis":"public","signature":"Public Property Let MinYear(ByVal value As Long)","accessor":"Let"}, + {"name":"Minute","kind":"property","vis":"public","signature":"Public Property Get Minute() As Long","returnType":"Long","accessor":"Get"}, + {"name":"Minute","kind":"property","vis":"public","signature":"Public Property Let Minute(ByVal value As Long)","accessor":"Let"}, + {"name":"Mode","kind":"property","vis":"public","signature":"Public Property Get Mode() As PickerMode","returnType":"PickerMode","accessor":"Get"}, + {"name":"Mode","kind":"property","vis":"public","signature":"Public Property Let Mode(ByVal value As PickerMode)","accessor":"Let"}, + {"name":"Month","kind":"property","vis":"public","signature":"Public Property Get Month() As Long","returnType":"Long","accessor":"Get"}, + {"name":"Month","kind":"property","vis":"public","signature":"Public Property Let Month(ByVal value As Long)","accessor":"Let"}, + {"name":"OwnerHwnd","kind":"property","vis":"public","signature":"Public Property Get OwnerHwnd() As LongPtr","returnType":"LongPtr","accessor":"Get"}, + {"name":"OwnerHwnd","kind":"property","vis":"public","signature":"Public Property Let OwnerHwnd(ByVal value As LongPtr)","accessor":"Let"}, + {"name":"ParseDateTime","kind":"function","vis":"private","signature":"Private Function ParseDateTime(ByVal inputStr As String) As Boolean","returnType":"Boolean"}, + {"name":"Prompt","kind":"property","vis":"public","signature":"Public Property Get Prompt() As String","returnType":"String","accessor":"Get"}, + {"name":"Prompt","kind":"property","vis":"public","signature":"Public Property Let Prompt(ByVal value As String)","accessor":"Let"}, + {"name":"Reset","kind":"sub","vis":"public","signature":"Public Sub Reset()"}, + {"name":"Second","kind":"property","vis":"public","signature":"Public Property Get Second() As Long","returnType":"Long","accessor":"Get"}, + {"name":"Second","kind":"property","vis":"public","signature":"Public Property Let Second(ByVal value As Long)","accessor":"Let"}, + {"name":"SetLocalTime","kind":"function","vis":"private","declare":true,"signature":"Private Declare PtrSafe Function SetLocalTime Lib \"kernel32\" (lpSystemTime As SYSTEMTIME) As Long","returnType":"Long"}, + {"name":"SetNow","kind":"sub","vis":"public","signature":"Public Sub SetNow()"}, + {"name":"SetToday","kind":"sub","vis":"public","signature":"Public Sub SetToday()"}, + {"name":"ShowDatePicker","kind":"function","vis":"public","signature":"Public Function ShowDatePicker(Optional ByVal title As String = \"Select Date\") As Boolean","returnType":"Boolean"}, + {"name":"ShowDialog","kind":"function","vis":"public","signature":"Public Function ShowDialog() As Boolean","returnType":"Boolean"}, + {"name":"ShowSeconds","kind":"property","vis":"public","signature":"Public Property Get ShowSeconds() As Boolean","returnType":"Boolean","accessor":"Get"}, + {"name":"ShowSeconds","kind":"property","vis":"public","signature":"Public Property Let ShowSeconds(ByVal value As Boolean)","accessor":"Let"}, + {"name":"ShowTimePicker","kind":"function","vis":"public","signature":"Public Function ShowTimePicker(Optional ByVal title As String = \"Select Time\") As Boolean","returnType":"Boolean"}, + {"name":"SystemTimeToFileTime","kind":"function","vis":"private","declare":true,"signature":"Private Declare PtrSafe Function SystemTimeToFileTime Lib \"kernel32\" (lpSystemTime As SYSTEMTIME, lpFileTime As FILETIME) As Long","returnType":"Long"}, + {"name":"TimeValue","kind":"property","vis":"public","signature":"Public Property Get TimeValue() As Date","returnType":"Date","accessor":"Get"}, + {"name":"TimeValue","kind":"property","vis":"public","signature":"Public Property Let TimeValue(ByVal value As Date)","accessor":"Let"}, + {"name":"Title","kind":"property","vis":"public","signature":"Public Property Get Title() As String","returnType":"String","accessor":"Get"}, + {"name":"Title","kind":"property","vis":"public","signature":"Public Property Let Title(ByVal value As String)","accessor":"Let"}, + {"name":"Year","kind":"property","vis":"public","signature":"Public Property Get Year() As Long","returnType":"Long","accessor":"Get"}, + {"name":"Year","kind":"property","vis":"public","signature":"Public Property Let Year(ByVal value As Long)","accessor":"Let"}, + {"name":"mData","kind":"field","vis":"private","signature":"Private mData As cDialogDateTimeData"}, + {"name":"mDayNames","kind":"field","vis":"private","signature":"Private mDayNames() As String"}, + {"name":"mMonthNames","kind":"field","vis":"private","signature":"Private mMonthNames() As String"} + ] + }, + { + "name": "DateFormat", + "kind": "enum", + "in": "cDialogDateTime", + "vis": "public", + "file": "more/cDialogDateTime.twin", + "signature": "Public Enum DateFormat", + "values": [ + {"name":"ShortDate","value":"0"}, + {"name":"LongDate","value":"1"}, + {"name":"Custom","value":"2"} + ] + }, + { + "name": "FILETIME", + "kind": "type", + "in": "cDialogDateTime", + "vis": "private", + "public": false, + "file": "more/cDialogDateTime.twin", + "signature": "Private Type FILETIME", + "fields": [ + {"name":"dwLowDateTime","vis":"public","signature":"dwLowDateTime As Long"}, + {"name":"dwHighDateTime","vis":"public","signature":"dwHighDateTime As Long"} + ], + "members": [] + }, + { + "name": "PickerMode", + "kind": "enum", + "in": "cDialogDateTime", + "vis": "public", + "file": "more/cDialogDateTime.twin", + "signature": "Public Enum PickerMode", + "values": [ + {"name":"DateTime","value":"0"}, + {"name":"DateOnly","value":"1"}, + {"name":"TimeOnly","value":"2"} + ] + }, + { + "name": "SYSTEMTIME", + "kind": "type", + "in": "cDialogDateTime", + "vis": "private", + "public": false, + "file": "more/cDialogDateTime.twin", + "signature": "Private Type SYSTEMTIME", + "fields": [ + {"name":"wYear","vis":"public","signature":"wYear As Integer"}, + {"name":"wMonth","vis":"public","signature":"wMonth As Integer"}, + {"name":"wDayOfWeek","vis":"public","signature":"wDayOfWeek As Integer"}, + {"name":"wDay","vis":"public","signature":"wDay As Integer"}, + {"name":"wHour","vis":"public","signature":"wHour As Integer"}, + {"name":"wMinute","vis":"public","signature":"wMinute As Integer"}, + {"name":"wSecond","vis":"public","signature":"wSecond As Integer"}, + {"name":"wMilliseconds","vis":"public","signature":"wMilliseconds As Integer"} + ], + "members": [] + }, + { + "name": "cDialogDateTimeData", + "kind": "type", + "in": "cDialogDateTime", + "vis": "private", + "public": false, + "file": "more/cDialogDateTime.twin", + "signature": "Private Type cDialogDateTimeData", + "fields": [ + {"name":"mTitle","vis":"public","signature":"mTitle As String"}, + {"name":"mPrompt","vis":"public","signature":"mPrompt As String"}, + {"name":"mYear","vis":"public","signature":"mYear As Long"}, + {"name":"mMonth","vis":"public","signature":"mMonth As Long"}, + {"name":"mDay","vis":"public","signature":"mDay As Long"}, + {"name":"mHour","vis":"public","signature":"mHour As Long"}, + {"name":"mMinute","vis":"public","signature":"mMinute As Long"}, + {"name":"mSecond","vis":"public","signature":"mSecond As Long"}, + {"name":"mMode","vis":"public","signature":"mMode As PickerMode"}, + {"name":"mMinYear","vis":"public","signature":"mMinYear As Long"}, + {"name":"mMaxYear","vis":"public","signature":"mMaxYear As Long"}, + {"name":"mShowSeconds","vis":"public","signature":"mShowSeconds As Boolean"}, + {"name":"mShowMilliseconds","vis":"public","signature":"mShowMilliseconds As Boolean"}, + {"name":"m24HourFormat","vis":"public","signature":"m24HourFormat As Boolean"}, + {"name":"mCustomFormat","vis":"public","signature":"mCustomFormat As String"}, + {"name":"mOwnerHwnd","vis":"public","signature":"mOwnerHwnd As LongPtr"}, + {"name":"mCancelled","vis":"public","signature":"mCancelled As Boolean"} + ], + "members": [] + }, + { + "name": "cDialogEx", + "kind": "class", + "vis": "public", + "file": "cDialogEx.twin", + "signature": "Class cDialogEx", + "attrs": [{"name":"COMCreatable","args":"False"}], + "members": [ + {"name":"AddFilter","kind":"sub","vis":"public","signature":"Public Sub AddFilter(ByVal strDescription As String, ByVal strExtension As String)"}, + {"name":"ApplyInitialDir","kind":"sub","vis":"private","signature":"Private Sub ApplyInitialDir(ByRef pFileDialog As IFileDialog)"}, + {"name":"CLSCTX_INPROC_SERVER","kind":"const","vis":"private","signature":"Private Const CLSCTX_INPROC_SERVER As Long = 1"}, + {"name":"Class_Initialize","kind":"sub","vis":"private","signature":"Private Sub Class_Initialize()"}, + {"name":"Class_Terminate","kind":"sub","vis":"private","signature":"Private Sub Class_Terminate()"}, + {"name":"CleanupIconSink","kind":"sub","vis":"private","signature":"Private Sub CleanupIconSink()"}, + {"name":"ClearFilter","kind":"sub","vis":"public","signature":"Public Sub ClearFilter()"}, + {"name":"ClearIcon","kind":"sub","vis":"public","signature":"Public Sub ClearIcon()"}, + {"name":"CoCreateInstance","kind":"function","vis":"private","declare":true,"signature":"Private Declare PtrSafe Function CoCreateInstance Lib \"ole32\" ( ByRef rclsid As GUID, ByVal pUnkOuter As LongPtr, ByVal dwClsContext As Long, ByRef riid As GUID, ByRef ppv As Any ) As Long","returnType":"Long"}, + {"name":"CoInitialize","kind":"function","vis":"private","declare":true,"signature":"Private Declare PtrSafe Function CoInitialize Lib \"ole32\" ( ByVal pvReserved As LongPtr ) As Long","returnType":"Long"}, + {"name":"CoTaskMemFree","kind":"sub","vis":"private","declare":true,"signature":"Private Declare PtrSafe Sub CoTaskMemFree Lib \"ole32\" ( ByVal pv As LongPtr )"}, + {"name":"CoUninitialize","kind":"sub","vis":"private","declare":true,"signature":"Private Declare PtrSafe Sub CoUninitialize Lib \"ole32\" ()"}, + {"name":"CopyMemory","kind":"sub","vis":"private","declare":true,"signature":"Private Declare PtrSafe Sub CopyMemory Lib \"kernel32\" Alias \"RtlMoveMemory\" ( ByRef Destination As Any, ByRef Source As Any, ByVal Length As Long )"}, + {"name":"CreateEventSink","kind":"sub","vis":"private","signature":"Private Sub CreateEventSink()"}, + {"name":"CreatePrompt","kind":"property","vis":"public","signature":"Public Property Get CreatePrompt() As Boolean","returnType":"Boolean","accessor":"Get"}, + {"name":"CreatePrompt","kind":"property","vis":"public","signature":"Public Property Let CreatePrompt(ByVal value As Boolean)","accessor":"Let"}, + {"name":"DefaultExt","kind":"property","vis":"public","signature":"Public Property Get DefaultExt() As String","returnType":"String","accessor":"Get"}, + {"name":"DefaultExt","kind":"property","vis":"public","signature":"Public Property Let DefaultExt(ByVal value As String)","accessor":"Let"}, + {"name":"DestroyEventSink","kind":"sub","vis":"private","signature":"Private Sub DestroyEventSink()"}, + {"name":"DestroyIcon","kind":"function","vis":"private","declare":true,"signature":"Private Declare PtrSafe Function DestroyIcon Lib \"user32\" ( ByVal hIcon As LongPtr ) As Long","returnType":"Long"}, + {"name":"DialogTitle","kind":"property","vis":"public","signature":"Public Property Get DialogTitle() As String","returnType":"String","accessor":"Get"}, + {"name":"DialogTitle","kind":"property","vis":"public","signature":"Public Property Let DialogTitle(ByVal value As String)","accessor":"Let"}, + {"name":"E_NOINTERFACE","kind":"const","vis":"private","signature":"Private Const E_NOINTERFACE As Long = &H80004002"}, + {"name":"E_NOTIMPL","kind":"const","vis":"private","signature":"Private Const E_NOTIMPL As Long = &H80004001"}, + {"name":"FDE_VTABLE_SLOTS","kind":"const","vis":"private","signature":"Private Const FDE_VTABLE_SLOTS As Long = 10"}, + {"name":"FOS_ALLNONSTORAGEITEMS","kind":"const","vis":"private","signature":"Private Const FOS_ALLNONSTORAGEITEMS As Long = &H80"}, + {"name":"FOS_ALLOWMULTISELECT","kind":"const","vis":"private","signature":"Private Const FOS_ALLOWMULTISELECT As Long = &H200"}, + {"name":"FOS_CREATEPROMPT","kind":"const","vis":"private","signature":"Private Const FOS_CREATEPROMPT As Long = &H2000"}, + {"name":"FOS_DEFAULTNOMINIMODE","kind":"const","vis":"private","signature":"Private Const FOS_DEFAULTNOMINIMODE As Long = &H20000000"}, + {"name":"FOS_DONTADDTORECENT","kind":"const","vis":"private","signature":"Private Const FOS_DONTADDTORECENT As Long = &H2000000"}, + {"name":"FOS_FILEMUSTEXIST","kind":"const","vis":"private","signature":"Private Const FOS_FILEMUSTEXIST As Long = &H1000"}, + {"name":"FOS_FORCEFILESYSTEM","kind":"const","vis":"private","signature":"Private Const FOS_FORCEFILESYSTEM As Long = &H40"}, + {"name":"FOS_FORCESHOWHIDDEN","kind":"const","vis":"private","signature":"Private Const FOS_FORCESHOWHIDDEN As Long = &H10000000"}, + {"name":"FOS_HIDEMRUPLACES","kind":"const","vis":"private","signature":"Private Const FOS_HIDEMRUPLACES As Long = &H20000"}, + {"name":"FOS_HIDEPINNEDPLACES","kind":"const","vis":"private","signature":"Private Const FOS_HIDEPINNEDPLACES As Long = &H40000"}, + {"name":"FOS_NOCHANGEDIR","kind":"const","vis":"private","signature":"Private Const FOS_NOCHANGEDIR As Long = &H8"}, + {"name":"FOS_NODEREFERENCELINKS","kind":"const","vis":"private","signature":"Private Const FOS_NODEREFERENCELINKS As Long = &H100000"}, + {"name":"FOS_NOREADONLYRETURN","kind":"const","vis":"private","signature":"Private Const FOS_NOREADONLYRETURN As Long = &H8000&"}, + {"name":"FOS_NOTESTFILECREATE","kind":"const","vis":"private","signature":"Private Const FOS_NOTESTFILECREATE As Long = &H10000"}, + {"name":"FOS_NOVALIDATE","kind":"const","vis":"private","signature":"Private Const FOS_NOVALIDATE As Long = &H100"}, + {"name":"FOS_OVERWRITEPROMPT","kind":"const","vis":"private","signature":"Private Const FOS_OVERWRITEPROMPT As Long = &H2"}, + {"name":"FOS_PATHMUSTEXIST","kind":"const","vis":"private","signature":"Private Const FOS_PATHMUSTEXIST As Long = &H800"}, + {"name":"FOS_PICKFOLDERS","kind":"const","vis":"private","signature":"Private Const FOS_PICKFOLDERS As Long = &H20"}, + {"name":"FOS_SHAREAWARE","kind":"const","vis":"private","signature":"Private Const FOS_SHAREAWARE As Long = &H4000"}, + {"name":"FOS_STRICTFILETYPES","kind":"const","vis":"private","signature":"Private Const FOS_STRICTFILETYPES As Long = &H4"}, + {"name":"FileMustExist","kind":"property","vis":"public","signature":"Public Property Get FileMustExist() As Boolean","returnType":"Boolean","accessor":"Get"}, + {"name":"FileMustExist","kind":"property","vis":"public","signature":"Public Property Let FileMustExist(ByVal value As Boolean)","accessor":"Let"}, + {"name":"FileName","kind":"property","vis":"public","signature":"Public Property Get FileName() As String","returnType":"String","accessor":"Get"}, + {"name":"FileName","kind":"property","vis":"public","signature":"Public Property Let FileName(ByVal value As String)","accessor":"Let"}, + {"name":"Filter","kind":"property","vis":"public","signature":"Public Property Get Filter() As String","returnType":"String","accessor":"Get"}, + {"name":"Filter","kind":"property","vis":"public","signature":"Public Property Let Filter(ByVal value As String)","accessor":"Let"}, + {"name":"FilterIndex","kind":"property","vis":"public","signature":"Public Property Get FilterIndex() As Long","returnType":"Long","accessor":"Get"}, + {"name":"FilterIndex","kind":"property","vis":"public","signature":"Public Property Let FilterIndex(ByVal value As Long)","accessor":"Let"}, + {"name":"GetCLSID_FileOpenDialog","kind":"function","vis":"private","signature":"Private Function GetCLSID_FileOpenDialog() As GUID","returnType":"GUID"}, + {"name":"GetCLSID_FileSaveDialog","kind":"function","vis":"private","signature":"Private Function GetCLSID_FileSaveDialog() As GUID","returnType":"GUID"}, + {"name":"GetDisplayNameFromShellItem","kind":"function","vis":"private","signature":"Private Function GetDisplayNameFromShellItem(ByVal pShellItem As IShellItem, ByVal sigdnName As Long) As String","returnType":"String"}, + {"name":"GetFileExtension","kind":"function","vis":"public","signature":"Public Function GetFileExtension(ByVal strFullPath As String) As String","returnType":"String"}, + {"name":"GetFileName","kind":"function","vis":"public","signature":"Public Function GetFileName(ByVal strFullPath As String) As String","returnType":"String"}, + {"name":"GetFileNameWithoutExt","kind":"function","vis":"public","signature":"Public Function GetFileNameWithoutExt(ByVal strFullPath As String) As String","returnType":"String"}, + {"name":"GetFilePath","kind":"function","vis":"public","signature":"Public Function GetFilePath(ByVal strFullPath As String) As String","returnType":"String"}, + {"name":"GetIID_IFileDialogEvents","kind":"function","vis":"private","signature":"Private Function GetIID_IFileDialogEvents() As GUID","returnType":"GUID"}, + {"name":"GetIID_IFileOpenDialog","kind":"function","vis":"private","signature":"Private Function GetIID_IFileOpenDialog() As GUID","returnType":"GUID"}, + {"name":"GetIID_IFileSaveDialog","kind":"function","vis":"private","signature":"Private Function GetIID_IFileSaveDialog() As GUID","returnType":"GUID"}, + {"name":"GetIID_IOleWindow","kind":"function","vis":"private","signature":"Private Function GetIID_IOleWindow() As GUID","returnType":"GUID"}, + {"name":"GetIID_IShellItem","kind":"function","vis":"private","signature":"Private Function GetIID_IShellItem() As GUID","returnType":"GUID"}, + {"name":"GetIID_IUnknown","kind":"function","vis":"private","signature":"Private Function GetIID_IUnknown() As GUID","returnType":"GUID"}, + {"name":"GetPresetFilter","kind":"function","vis":"public","signature":"Public Function GetPresetFilter(ByVal filterName As String) As String","returnType":"String"}, + {"name":"ICON_BIG","kind":"const","vis":"private","signature":"Private Const ICON_BIG As LongPtr = 1"}, + {"name":"ICON_SMALL","kind":"const","vis":"private","signature":"Private Const ICON_SMALL As LongPtr = 0"}, + {"name":"InitialDir","kind":"property","vis":"public","signature":"Public Property Get InitialDir() As String","returnType":"String","accessor":"Get"}, + {"name":"InitialDir","kind":"property","vis":"public","signature":"Public Property Let InitialDir(ByVal value As String)","accessor":"Let"}, + {"name":"IsEqualGUID","kind":"function","vis":"private","signature":"Private Function IsEqualGUID(ByRef g1 As GUID, ByRef g2 As GUID) As Boolean","returnType":"Boolean"}, + {"name":"MemCopy","kind":"sub","vis":"private","declare":true,"signature":"Private Declare PtrSafe Sub MemCopy Lib \"kernel32\" Alias \"RtlMoveMemory\" ( ByVal Destination As LongPtr, ByRef Source As Any, ByVal Length As Long )"}, + {"name":"MultiSelect","kind":"property","vis":"public","signature":"Public Property Get MultiSelect() As Boolean","returnType":"Boolean","accessor":"Get"}, + {"name":"MultiSelect","kind":"property","vis":"public","signature":"Public Property Let MultiSelect(ByVal value As Boolean)","accessor":"Let"}, + {"name":"NoChangeDir","kind":"property","vis":"public","signature":"Public Property Get NoChangeDir() As Boolean","returnType":"Boolean","accessor":"Get"}, + {"name":"NoChangeDir","kind":"property","vis":"public","signature":"Public Property Let NoChangeDir(ByVal value As Boolean)","accessor":"Let"}, + {"name":"OpenFile","kind":"function","vis":"public","signature":"Public Function OpenFile(ByVal strFilePath As String, Optional ByVal strOperation As String = \"open\") As LongPtr","returnType":"LongPtr"}, + {"name":"OverwritePrompt","kind":"property","vis":"public","signature":"Public Property Get OverwritePrompt() As Boolean","returnType":"Boolean","accessor":"Get"}, + {"name":"OverwritePrompt","kind":"property","vis":"public","signature":"Public Property Let OverwritePrompt(ByVal value As Boolean)","accessor":"Let"}, + {"name":"OwnerHwnd","kind":"property","vis":"public","signature":"Public Property Get OwnerHwnd() As LongPtr","returnType":"LongPtr","accessor":"Get"}, + {"name":"OwnerHwnd","kind":"property","vis":"public","signature":"Public Property Let OwnerHwnd(ByVal value As LongPtr)","accessor":"Let"}, + {"name":"ParseFilter","kind":"function","vis":"private","signature":"Private Function ParseFilter(ByRef arrFilters() As COMDLG_FILTERSPEC, ByRef arrNames() As String, ByRef arrSpecs() As String) As Long","returnType":"Long"}, + {"name":"PathCombine","kind":"function","vis":"public","signature":"Public Function PathCombine(ByVal strPath1 As String, ByVal strPath2 As String) As String","returnType":"String"}, + {"name":"PathMustExist","kind":"property","vis":"public","signature":"Public Property Get PathMustExist() As Boolean","returnType":"Boolean","accessor":"Get"}, + {"name":"PathMustExist","kind":"property","vis":"public","signature":"Public Property Let PathMustExist(ByVal value As Boolean)","accessor":"Let"}, + {"name":"PickFolders","kind":"property","vis":"public","signature":"Public Property Get PickFolders() As Boolean","returnType":"Boolean","accessor":"Get"}, + {"name":"PickFolders","kind":"property","vis":"public","signature":"Public Property Let PickFolders(ByVal value As Boolean)","accessor":"Let"}, + {"name":"Reset","kind":"sub","vis":"public","signature":"Public Sub Reset()"}, + {"name":"SHCreateItemFromParsingName","kind":"function","vis":"private","declare":true,"signature":"Private Declare PtrSafe Function SHCreateItemFromParsingName Lib \"shell32\" ( ByVal pszPath As LongPtr, ByVal pbc As LongPtr, ByRef riid As GUID, ByRef ppv As Any ) As Long","returnType":"Long"}, + {"name":"SHGSI_ICON","kind":"const","vis":"private","signature":"Private Const SHGSI_ICON As Long = &H100"}, + {"name":"SHGSI_LARGEICON","kind":"const","vis":"private","signature":"Private Const SHGSI_LARGEICON As Long = &H0"}, + {"name":"SHGSI_SMALLICON","kind":"const","vis":"private","signature":"Private Const SHGSI_SMALLICON As Long = &H1"}, + {"name":"SHGetStockIconInfo","kind":"function","vis":"private","declare":true,"signature":"Private Declare PtrSafe Function SHGetStockIconInfo Lib \"shell32\" ( ByVal siid As Long, ByVal uFlags As Long, ByRef psii As SHSTOCKICONINFO ) As Long","returnType":"Long"}, + {"name":"SIGDN_DESKTOPABSOLUTEPARSING","kind":"const","vis":"private","signature":"Private Const SIGDN_DESKTOPABSOLUTEPARSING As Long = &H80028000"}, + {"name":"SIGDN_FILESYSPATH","kind":"const","vis":"private","signature":"Private Const SIGDN_FILESYSPATH As Long = &H80058001"}, + {"name":"SIGDN_NORMALDISPLAY","kind":"const","vis":"private","signature":"Private Const SIGDN_NORMALDISPLAY As Long = &H0"}, + {"name":"S_FALSE","kind":"const","vis":"private","signature":"Private Const S_FALSE As Long = 1"}, + {"name":"S_OK","kind":"const","vis":"private","signature":"Private Const S_OK As Long = 0"}, + {"name":"SelectAndOpenFile","kind":"function","vis":"public","signature":"Public Function SelectAndOpenFile() As LongPtr","returnType":"LongPtr"}, + {"name":"SelectFiles","kind":"function","vis":"public","signature":"Public Function SelectFiles() As Collection","returnType":"Collection"}, + {"name":"SendMessageW","kind":"function","vis":"private","declare":true,"signature":"Private Declare PtrSafe Function SendMessageW Lib \"user32\" Alias \"SendMessageW\" ( ByVal hwnd As LongPtr, ByVal wMsg As Long, ByVal wParam As LongPtr, ByVal lParam As LongPtr ) As LongPtr","returnType":"LongPtr"}, + {"name":"SetCodeFilters","kind":"sub","vis":"public","signature":"Public Sub SetCodeFilters()"}, + {"name":"SetCommonFilters","kind":"sub","vis":"public","signature":"Public Sub SetCommonFilters()"}, + {"name":"SetCustomIcon","kind":"sub","vis":"public","signature":"Public Sub SetCustomIcon(ByVal iconHandle As LongPtr)"}, + {"name":"SetDocumentFilters","kind":"sub","vis":"public","signature":"Public Sub SetDocumentFilters()"}, + {"name":"SetImageFilters","kind":"sub","vis":"public","signature":"Public Sub SetImageFilters()"}, + {"name":"SetSystemIcon","kind":"sub","vis":"public","signature":"Public Sub SetSystemIcon(ByVal iconId As SystemIconId)"}, + {"name":"SetupIconSink","kind":"sub","vis":"private","signature":"Private Sub SetupIconSink()"}, + {"name":"ShellExecute","kind":"function","vis":"private","declare":true,"signature":"Private Declare PtrSafe Function ShellExecute Lib \"shell32.dll\" Alias \"ShellExecuteW\" ( ByVal hwnd As LongPtr, ByVal lpOperation As LongPtr, ByVal lpFile As LongPtr, ByVal lpParameters As LongPtr, ByVal lpDirectory As LongPtr, ByVal nShowCmd As Long ) As LongPtr","returnType":"LongPtr"}, + {"name":"ShowBrowseForFolder","kind":"function","vis":"public","signature":"Public Function ShowBrowseForFolder() As String","returnType":"String"}, + {"name":"ShowFileOpenDialog","kind":"function","vis":"private","signature":"Private Function ShowFileOpenDialog() As Variant","returnType":"Variant"}, + {"name":"ShowFileSaveDialog","kind":"function","vis":"private","signature":"Private Function ShowFileSaveDialog() As String","returnType":"String"}, + {"name":"ShowFolderDialog","kind":"function","vis":"private","signature":"Private Function ShowFolderDialog() As String","returnType":"String"}, + {"name":"ShowOpen","kind":"function","vis":"public","signature":"Public Function ShowOpen() As Variant","returnType":"Variant"}, + {"name":"ShowSave","kind":"function","vis":"public","signature":"Public Function ShowSave() As String","returnType":"String"}, + {"name":"Sink_AddRef","kind":"function","vis":"private","signature":"Private Function Sink_AddRef(ByVal this As LongPtr) As Long","returnType":"Long"}, + {"name":"Sink_OnFileOk","kind":"function","vis":"private","signature":"Private Function Sink_OnFileOk(ByVal this As LongPtr, ByVal pfd As LongPtr) As Long","returnType":"Long"}, + {"name":"Sink_OnFolderChange","kind":"function","vis":"private","signature":"Private Function Sink_OnFolderChange(ByVal this As LongPtr, ByVal pfd As LongPtr) As Long","returnType":"Long"}, + {"name":"Sink_OnFolderChanging","kind":"function","vis":"private","signature":"Private Function Sink_OnFolderChanging(ByVal this As LongPtr, ByVal pfd As LongPtr, ByVal psiFolder As LongPtr) As Long","returnType":"Long"}, + {"name":"Sink_OnOverwrite","kind":"function","vis":"private","signature":"Private Function Sink_OnOverwrite(ByVal this As LongPtr, ByVal pfd As LongPtr, ByVal psi As LongPtr, ByRef pResponse As Long) As Long","returnType":"Long"}, + {"name":"Sink_OnSelectionChange","kind":"function","vis":"private","signature":"Private Function Sink_OnSelectionChange(ByVal this As LongPtr, ByVal pfd As LongPtr) As Long","returnType":"Long"}, + {"name":"Sink_OnShareViolation","kind":"function","vis":"private","signature":"Private Function Sink_OnShareViolation(ByVal this As LongPtr, ByVal pfd As LongPtr, ByVal psi As LongPtr, ByRef pResponse As Long) As Long","returnType":"Long"}, + {"name":"Sink_OnTypeChange","kind":"function","vis":"private","signature":"Private Function Sink_OnTypeChange(ByVal this As LongPtr, ByVal pfd As LongPtr) As Long","returnType":"Long"}, + {"name":"Sink_QueryInterface","kind":"function","vis":"private","signature":"Private Function Sink_QueryInterface(ByVal this As LongPtr, ByRef riid As GUID, ByRef ppvObj As LongPtr) As Long","returnType":"Long"}, + {"name":"Sink_Release","kind":"function","vis":"private","signature":"Private Function Sink_Release(ByVal this As LongPtr) As Long","returnType":"Long"}, + {"name":"WM_SETICON","kind":"const","vis":"private","signature":"Private Const WM_SETICON As Long = &H80"}, + {"name":"lstrlenW","kind":"function","vis":"private","declare":true,"signature":"Private Declare PtrSafe Function lstrlenW Lib \"kernel32\" ( ByVal lpString As LongPtr ) As Long","returnType":"Long"}, + {"name":"m_AdviseCookie","kind":"field","vis":"private","signature":"Private m_AdviseCookie As LongPtr"}, + {"name":"m_CreatePrompt","kind":"field","vis":"private","signature":"Private m_CreatePrompt As Boolean"}, + {"name":"m_DefaultExt","kind":"field","vis":"private","signature":"Private m_DefaultExt As String"}, + {"name":"m_DialogTitle","kind":"field","vis":"private","signature":"Private m_DialogTitle As String"}, + {"name":"m_FileMustExist","kind":"field","vis":"private","signature":"Private m_FileMustExist As Boolean"}, + {"name":"m_FileName","kind":"field","vis":"private","signature":"Private m_FileName As String"}, + {"name":"m_Filter","kind":"field","vis":"private","signature":"Private m_Filter As String"}, + {"name":"m_FilterIndex","kind":"field","vis":"private","signature":"Private m_FilterIndex As Long"}, + {"name":"m_IconApplied","kind":"field","vis":"private","signature":"Private m_IconApplied As Boolean"}, + {"name":"m_IconHandle","kind":"field","vis":"private","signature":"Private m_IconHandle As LongPtr"}, + {"name":"m_IconType","kind":"field","vis":"private","signature":"Private m_IconType As Long"}, + {"name":"m_InitialDir","kind":"field","vis":"private","signature":"Private m_InitialDir As String"}, + {"name":"m_LoadedIconHandle","kind":"field","vis":"private","signature":"Private m_LoadedIconHandle As LongPtr"}, + {"name":"m_MultiSelect","kind":"field","vis":"private","signature":"Private m_MultiSelect As Boolean"}, + {"name":"m_NoChangeDir","kind":"field","vis":"private","signature":"Private m_NoChangeDir As Boolean"}, + {"name":"m_OverwritePrompt","kind":"field","vis":"private","signature":"Private m_OverwritePrompt As Boolean"}, + {"name":"m_OwnerHwnd","kind":"field","vis":"private","signature":"Private m_OwnerHwnd As LongPtr"}, + {"name":"m_PathMustExist","kind":"field","vis":"private","signature":"Private m_PathMustExist As Boolean"}, + {"name":"m_PickFolders","kind":"field","vis":"private","signature":"Private m_PickFolders As Boolean"}, + {"name":"m_pFileDialog","kind":"field","vis":"private","signature":"Private m_pFileDialog As IFileDialog"}, + {"name":"m_pSinkObj","kind":"field","vis":"private","signature":"Private m_pSinkObj As LongPtr"}, + {"name":"m_pVTable","kind":"field","vis":"private","signature":"Private m_pVTable As LongPtr"} + ] + }, + { + "name": "SHSTOCKICONINFO", + "kind": "type", + "in": "cDialogEx", + "vis": "private", + "public": false, + "file": "cDialogEx.twin", + "signature": "Private Type SHSTOCKICONINFO", + "fields": [ + {"name":"cbSize","vis":"public","signature":"cbSize As Long"}, + {"name":"hIcon","vis":"public","signature":"hIcon As LongPtr"}, + {"name":"iSysIconIndex","vis":"public","signature":"iSysIconIndex As Long"}, + {"name":"iIconIndex","vis":"public","signature":"iIconIndex As Long"}, + {"name":"szPath","vis":"public","signature":"szPath(0 To 519) As Byte"} + ], + "members": [] + }, + { + "name": "SystemIconId", + "kind": "enum", + "in": "cDialogEx", + "vis": "public", + "file": "cDialogEx.twin", + "signature": "Public Enum SystemIconId", + "values": [ + {"name":"SIID_INVALID","value":"-1"}, + {"name":"SIID_DOCUMENT","value":"0"}, + {"name":"SIID_FOLDER","value":"3"}, + {"name":"SIID_DISK","value":"6"}, + {"name":"SIID_SERVER","value":"15"}, + {"name":"SIID_DRIVEFIXED","value":"8"}, + {"name":"SIID_DRIVECD","value":"10"}, + {"name":"SIID_DRIVEHD","value":"11"}, + {"name":"SIID_FOLDEROPEN","value":"5"}, + {"name":"SIID_STAROFFILE","value":"30"}, + {"name":"SIID_SHARE","value":"28"} + ] + }, + { + "name": "cDialogFindReplace", + "kind": "class", + "vis": "public", + "file": "more/cDialogFindReplace.twin", + "signature": "Public Class cDialogFindReplace", + "attrs": [{"name":"COMCreatable","args":"False"}], + "members": [ + {"name":"AllowMatchCase","kind":"property","vis":"public","signature":"Public Property Get AllowMatchCase() As Boolean","returnType":"Boolean","accessor":"Get"}, + {"name":"AllowMatchCase","kind":"property","vis":"public","signature":"Public Property Let AllowMatchCase(ByVal value As Boolean)","accessor":"Let"}, + {"name":"AllowUpDown","kind":"property","vis":"public","signature":"Public Property Get AllowUpDown() As Boolean","returnType":"Boolean","accessor":"Get"}, + {"name":"AllowUpDown","kind":"property","vis":"public","signature":"Public Property Let AllowUpDown(ByVal value As Boolean)","accessor":"Let"}, + {"name":"AllowWholeWord","kind":"property","vis":"public","signature":"Public Property Get AllowWholeWord() As Boolean","returnType":"Boolean","accessor":"Get"}, + {"name":"AllowWholeWord","kind":"property","vis":"public","signature":"Public Property Let AllowWholeWord(ByVal value As Boolean)","accessor":"Let"}, + {"name":"Class_Initialize","kind":"sub","vis":"private","signature":"Private Sub Class_Initialize()"}, + {"name":"CommDlgExtendedError","kind":"function","vis":"private","declare":true,"signature":"Private Declare PtrSafe Function CommDlgExtendedError Lib \"comdlg32.dll\" () As Long","returnType":"Long"}, + {"name":"Direction","kind":"property","vis":"public","signature":"Public Property Get Direction() As SearchDirection","returnType":"SearchDirection","accessor":"Get"}, + {"name":"Direction","kind":"property","vis":"public","signature":"Public Property Let Direction(ByVal value As SearchDirection)","accessor":"Let"}, + {"name":"FR_DIALOGTERM","kind":"const","vis":"private","signature":"Private Const FR_DIALOGTERM = &H40"}, + {"name":"FR_DOWN","kind":"const","vis":"private","signature":"Private Const FR_DOWN = &H1"}, + {"name":"FR_ENABLEHOOK","kind":"const","vis":"private","signature":"Private Const FR_ENABLEHOOK = &H100"}, + {"name":"FR_ENABLETEMPLATE","kind":"const","vis":"private","signature":"Private Const FR_ENABLETEMPLATE = &H200"}, + {"name":"FR_ENABLETEMPLATEHANDLE","kind":"const","vis":"private","signature":"Private Const FR_ENABLETEMPLATEHANDLE = &H2000"}, + {"name":"FR_FINDNEXT","kind":"const","vis":"private","signature":"Private Const FR_FINDNEXT = &H8"}, + {"name":"FR_HIDEMATCHCASE","kind":"const","vis":"private","signature":"Private Const FR_HIDEMATCHCASE = &H8000"}, + {"name":"FR_HIDEUPDOWN","kind":"const","vis":"private","signature":"Private Const FR_HIDEUPDOWN = &H4000"}, + {"name":"FR_HIDEWHOLEWORD","kind":"const","vis":"private","signature":"Private Const FR_HIDEWHOLEWORD = &H10000"}, + {"name":"FR_MATCHCASE","kind":"const","vis":"private","signature":"Private Const FR_MATCHCASE = &H4"}, + {"name":"FR_NOMATCHCASE","kind":"const","vis":"private","signature":"Private Const FR_NOMATCHCASE = &H800"}, + {"name":"FR_NOUPDOWN","kind":"const","vis":"private","signature":"Private Const FR_NOUPDOWN = &H400"}, + {"name":"FR_NOWHOLEWORD","kind":"const","vis":"private","signature":"Private Const FR_NOWHOLEWORD = &H1000"}, + {"name":"FR_NOWRAPAROUND","kind":"const","vis":"private","signature":"Private Const FR_NOWRAPAROUND = &H10000"}, + {"name":"FR_REPLACE","kind":"const","vis":"private","signature":"Private Const FR_REPLACE = &H10"}, + {"name":"FR_REPLACEALL","kind":"const","vis":"private","signature":"Private Const FR_REPLACEALL = &H20"}, + {"name":"FR_SHOWHELP","kind":"const","vis":"private","signature":"Private Const FR_SHOWHELP = &H80"}, + {"name":"FR_STRUCTSIZE","kind":"const","vis":"private","signatures":[{"cond":"Win64","signature":"Private Const FR_STRUCTSIZE As Long = 80"},{"cond":"Not (Win64)","signature":"Private Const FR_STRUCTSIZE As Long = 40"}]}, + {"name":"FR_WHOLEWORD","kind":"const","vis":"private","signature":"Private Const FR_WHOLEWORD = &H2"}, + {"name":"FindText","kind":"function","vis":"private","declare":true,"signature":"Private Declare PtrSafe Function FindText Lib \"comdlg32.dll\" Alias \"FindTextW\" (pFindreplace As FINDREPLACE) As LongPtr","returnType":"LongPtr"}, + {"name":"FindWhat","kind":"property","vis":"public","signature":"Public Property Get FindWhat() As String","returnType":"String","accessor":"Get"}, + {"name":"FindWhat","kind":"property","vis":"public","signature":"Public Property Let FindWhat(ByVal value As String)","accessor":"Let"}, + {"name":"IsVisible","kind":"function","vis":"public","signature":"Public Function IsVisible() As Boolean","returnType":"Boolean"}, + {"name":"LastAction","kind":"property","vis":"public","signature":"Public Property Get LastAction() As FindAction","returnType":"FindAction","accessor":"Get"}, + {"name":"LastError","kind":"property","vis":"public","signature":"Public Property Get LastError() As Long","returnType":"Long","accessor":"Get"}, + {"name":"MAX_FIND_LEN","kind":"const","vis":"private","signature":"Private Const MAX_FIND_LEN = 256"}, + {"name":"MatchCase","kind":"property","vis":"public","signature":"Public Property Get MatchCase() As Boolean","returnType":"Boolean","accessor":"Get"}, + {"name":"MatchCase","kind":"property","vis":"public","signature":"Public Property Let MatchCase(ByVal value As Boolean)","accessor":"Let"}, + {"name":"MessageId","kind":"property","vis":"public","signature":"Public Property Get MessageId() As Long","returnType":"Long","accessor":"Get"}, + {"name":"Mode","kind":"property","vis":"public","signature":"Public Property Get Mode() As DialogMode","returnType":"DialogMode","accessor":"Get"}, + {"name":"Mode","kind":"property","vis":"public","signature":"Public Property Let Mode(ByVal value As DialogMode)","accessor":"Let"}, + {"name":"OwnerHwnd","kind":"property","vis":"public","signature":"Public Property Get OwnerHwnd() As LongPtr","returnType":"LongPtr","accessor":"Get"}, + {"name":"OwnerHwnd","kind":"property","vis":"public","signature":"Public Property Let OwnerHwnd(ByVal value As LongPtr)","accessor":"Let"}, + {"name":"RegisterWindowMessage","kind":"function","vis":"private","declare":true,"signature":"Private Declare PtrSafe Function RegisterWindowMessage Lib \"user32\" Alias \"RegisterWindowMessageW\" (ByVal lpString As LongPtr) As Long","returnType":"Long"}, + {"name":"ReplaceText","kind":"function","vis":"private","declare":true,"signature":"Private Declare PtrSafe Function ReplaceText Lib \"comdlg32.dll\" Alias \"ReplaceTextW\" (pFindreplace As FINDREPLACE) As LongPtr","returnType":"LongPtr"}, + {"name":"ReplaceWith","kind":"property","vis":"public","signature":"Public Property Get ReplaceWith() As String","returnType":"String","accessor":"Get"}, + {"name":"ReplaceWith","kind":"property","vis":"public","signature":"Public Property Let ReplaceWith(ByVal value As String)","accessor":"Let"}, + {"name":"Reset","kind":"sub","vis":"public","signature":"Public Sub Reset()"}, + {"name":"ShowFind","kind":"function","vis":"public","signature":"Public Function ShowFind() As Boolean","returnType":"Boolean"}, + {"name":"ShowReplace","kind":"function","vis":"public","signature":"Public Function ShowReplace() As Boolean","returnType":"Boolean"}, + {"name":"ToDescription","kind":"function","vis":"public","signature":"Public Function ToDescription() As String","returnType":"String"}, + {"name":"UpdateFromFlags","kind":"sub","vis":"public","signature":"Public Sub UpdateFromFlags(ByVal flags As Long)"}, + {"name":"WholeWord","kind":"property","vis":"public","signature":"Public Property Get WholeWord() As Boolean","returnType":"Boolean","accessor":"Get"}, + {"name":"WholeWord","kind":"property","vis":"public","signature":"Public Property Let WholeWord(ByVal value As Boolean)","accessor":"Let"}, + {"name":"hWnd","kind":"property","vis":"public","signature":"Public Property Get hWnd() As LongPtr","returnType":"LongPtr","accessor":"Get"}, + {"name":"mData","kind":"field","vis":"private","signature":"Private mData As cDialogFindReplaceData"}, + {"name":"mFindBuffer","kind":"field","vis":"private","signature":"Private mFindBuffer As String"}, + {"name":"mReplaceBuffer","kind":"field","vis":"private","signature":"Private mReplaceBuffer As String"} + ] + }, + { + "name": "DialogMode", + "kind": "enum", + "in": "cDialogFindReplace", + "vis": "public", + "file": "more/cDialogFindReplace.twin", + "signature": "Public Enum DialogMode", + "values": [ + {"name":"FindMode","value":"0"}, + {"name":"ReplaceMode","value":"1"} + ] + }, + { + "name": "FINDREPLACE", + "kind": "type", + "in": "cDialogFindReplace", + "vis": "private", + "public": false, + "file": "more/cDialogFindReplace.twin", + "signature": "Private Type FINDREPLACE", + "fields": [ + {"name":"lStructSize","vis":"public","signature":"lStructSize As Long"}, + {"name":"hwndOwner","vis":"public","signature":"hwndOwner As LongPtr"}, + {"name":"hInstance","vis":"public","signature":"hInstance As LongPtr"}, + {"name":"flags","vis":"public","signature":"flags As Long"}, + {"name":"lpstrFindWhat","vis":"public","signature":"lpstrFindWhat As LongPtr"}, + {"name":"lpstrReplaceWith","vis":"public","signature":"lpstrReplaceWith As LongPtr"}, + {"name":"wFindWhatLen","vis":"public","signature":"wFindWhatLen As Integer"}, + {"name":"wReplaceWithLen","vis":"public","signature":"wReplaceWithLen As Integer"}, + {"name":"lCustData","vis":"public","signature":"lCustData As LongPtr"}, + {"name":"lpfnHook","vis":"public","signature":"lpfnHook As LongPtr"}, + {"name":"lpTemplateName","vis":"public","signature":"lpTemplateName As LongPtr"} + ], + "members": [] + }, + { + "name": "FindAction", + "kind": "enum", + "in": "cDialogFindReplace", + "vis": "public", + "file": "more/cDialogFindReplace.twin", + "signature": "Public Enum FindAction", + "values": [ + {"name":"None","value":"0"}, + {"name":"FindNext","value":"1"}, + {"name":"Replace","value":"2"}, + {"name":"ReplaceAll","value":"3"}, + {"name":"DialogClosed","value":"4"} + ] + }, + { + "name": "SearchDirection", + "kind": "enum", + "in": "cDialogFindReplace", + "vis": "public", + "file": "more/cDialogFindReplace.twin", + "signature": "Public Enum SearchDirection", + "values": [ + {"name":"Up","value":"0"}, + {"name":"Down","value":"1"} + ] + }, + { + "name": "cDialogFindReplaceData", + "kind": "type", + "in": "cDialogFindReplace", + "vis": "private", + "public": false, + "file": "more/cDialogFindReplace.twin", + "signature": "Private Type cDialogFindReplaceData", + "fields": [ + {"name":"mOwnerHwnd","vis":"public","signature":"mOwnerHwnd As LongPtr"}, + {"name":"mFindWhat","vis":"public","signature":"mFindWhat As String"}, + {"name":"mReplaceWith","vis":"public","signature":"mReplaceWith As String"}, + {"name":"mMatchCase","vis":"public","signature":"mMatchCase As Boolean"}, + {"name":"mWholeWord","vis":"public","signature":"mWholeWord As Boolean"}, + {"name":"mDirection","vis":"public","signature":"mDirection As SearchDirection"}, + {"name":"mMode","vis":"public","signature":"mMode As DialogMode"}, + {"name":"mAllowUpDown","vis":"public","signature":"mAllowUpDown As Boolean"}, + {"name":"mAllowMatchCase","vis":"public","signature":"mAllowMatchCase As Boolean"}, + {"name":"mAllowWholeWord","vis":"public","signature":"mAllowWholeWord As Boolean"}, + {"name":"mhWnd","vis":"public","signature":"mhWnd As LongPtr"}, + {"name":"mLastError","vis":"public","signature":"mLastError As Long"}, + {"name":"mLastAction","vis":"public","signature":"mLastAction As FindAction"}, + {"name":"mMessageId","vis":"public","signature":"mMessageId As Long"} + ], + "members": [] + }, + { + "name": "cDialogFont", + "kind": "class", + "vis": "public", + "file": "more/cDialogFont.twin", + "signature": "Public Class cDialogFont", + "attrs": [{"name":"COMCreatable","args":"False"}], + "members": [ + {"name":"Bold","kind":"property","vis":"public","signature":"Public Property Get Bold() As Boolean","returnType":"Boolean","accessor":"Get"}, + {"name":"Bold","kind":"property","vis":"public","signature":"Public Property Let Bold(ByVal value As Boolean)","accessor":"Let"}, + {"name":"CF_ANSIONLY","kind":"const","vis":"private","signature":"Private Const CF_ANSIONLY = &H400"}, + {"name":"CF_APPLY","kind":"const","vis":"private","signature":"Private Const CF_APPLY = &H200"}, + {"name":"CF_BOTH","kind":"const","vis":"private","signature":"Private Const CF_BOTH = (CF_SCREENFONTS Or CF_PRINTERFONTS)"}, + {"name":"CF_EFFECTS","kind":"const","vis":"private","signature":"Private Const CF_EFFECTS = &H100"}, + {"name":"CF_ENABLEHOOK","kind":"const","vis":"private","signature":"Private Const CF_ENABLEHOOK = &H8"}, + {"name":"CF_ENABLETEMPLATE","kind":"const","vis":"private","signature":"Private Const CF_ENABLETEMPLATE = &H10"}, + {"name":"CF_ENABLETEMPLATEHANDLE","kind":"const","vis":"private","signature":"Private Const CF_ENABLETEMPLATEHANDLE = &H20"}, + {"name":"CF_FIXEDPITCHONLY","kind":"const","vis":"private","signature":"Private Const CF_FIXEDPITCHONLY = &H4000"}, + {"name":"CF_FORCEFONTEXIST","kind":"const","vis":"private","signature":"Private Const CF_FORCEFONTEXIST = &H10000"}, + {"name":"CF_INITTOLOGFONTSTRUCT","kind":"const","vis":"private","signature":"Private Const CF_INITTOLOGFONTSTRUCT = &H40"}, + {"name":"CF_LIMITSIZE","kind":"const","vis":"private","signature":"Private Const CF_LIMITSIZE = &H2000"}, + {"name":"CF_NOFACESEL","kind":"const","vis":"private","signature":"Private Const CF_NOFACESEL = &H80000"}, + {"name":"CF_NOOEMFONTS","kind":"const","vis":"private","signature":"Private Const CF_NOOEMFONTS = CF_NOVECTORFONTS"}, + {"name":"CF_NOSCRIPTSEL","kind":"const","vis":"private","signature":"Private Const CF_NOSCRIPTSEL = &H800000"}, + {"name":"CF_NOSIMULATIONS","kind":"const","vis":"private","signature":"Private Const CF_NOSIMULATIONS = &H1000"}, + {"name":"CF_NOSIZESEL","kind":"const","vis":"private","signature":"Private Const CF_NOSIZESEL = &H200000"}, + {"name":"CF_NOSTYLESEL","kind":"const","vis":"private","signature":"Private Const CF_NOSTYLESEL = &H100000"}, + {"name":"CF_NOVECTORFONTS","kind":"const","vis":"private","signature":"Private Const CF_NOVECTORFONTS = &H800"}, + {"name":"CF_NOVERTFONTS","kind":"const","vis":"private","signature":"Private Const CF_NOVERTFONTS = &H1000000"}, + {"name":"CF_PRINTERFONTS","kind":"const","vis":"private","signature":"Private Const CF_PRINTERFONTS = &H2"}, + {"name":"CF_SCALABLEONLY","kind":"const","vis":"private","signature":"Private Const CF_SCALABLEONLY = &H20000"}, + {"name":"CF_SCREENFONTS","kind":"const","vis":"private","signature":"Private Const CF_SCREENFONTS = &H1"}, + {"name":"CF_SCRIPTSONLY","kind":"const","vis":"private","signature":"Private Const CF_SCRIPTSONLY = CF_ANSIONLY"}, + {"name":"CF_SELECTSCRIPT","kind":"const","vis":"private","signature":"Private Const CF_SELECTSCRIPT = &H400000"}, + {"name":"CF_SHOWHELP","kind":"const","vis":"private","signature":"Private Const CF_SHOWHELP = &H4"}, + {"name":"CF_STRUCTSIZE","kind":"const","vis":"private","signatures":[{"cond":"Win64","signature":"Private Const CF_STRUCTSIZE As Long = 104"},{"cond":"Not (Win64)","signature":"Private Const CF_STRUCTSIZE As Long = 60"}]}, + {"name":"CF_TTONLY","kind":"const","vis":"private","signature":"Private Const CF_TTONLY = &H40000"}, + {"name":"CF_USESTYLE","kind":"const","vis":"private","signature":"Private Const CF_USESTYLE = &H80"}, + {"name":"CF_WYSIWYG","kind":"const","vis":"private","signature":"Private Const CF_WYSIWYG = &H8000"}, + {"name":"ChooseFont","kind":"function","vis":"private","declare":true,"signature":"Private Declare PtrSafe Function ChooseFont Lib \"comdlg32.dll\" Alias \"ChooseFontW\" (pChoosefont As CHOOSEFONT) As Long","returnType":"Long"}, + {"name":"Class_Initialize","kind":"sub","vis":"private","signature":"Private Sub Class_Initialize()"}, + {"name":"Color","kind":"property","vis":"public","signature":"Public Property Get Color() As Long","returnType":"Long","accessor":"Get"}, + {"name":"Color","kind":"property","vis":"public","signature":"Public Property Let Color(ByVal value As Long)","accessor":"Let"}, + {"name":"FaceName","kind":"property","vis":"public","signature":"Public Property Get FaceName() As String","returnType":"String","accessor":"Get"}, + {"name":"FaceName","kind":"property","vis":"public","signature":"Public Property Let FaceName(ByVal value As String)","accessor":"Let"}, + {"name":"FixedPitchOnly","kind":"property","vis":"public","signature":"Public Property Get FixedPitchOnly() As Boolean","returnType":"Boolean","accessor":"Get"}, + {"name":"FixedPitchOnly","kind":"property","vis":"public","signature":"Public Property Let FixedPitchOnly(ByVal value As Boolean)","accessor":"Let"}, + {"name":"ForceFontExist","kind":"property","vis":"public","signature":"Public Property Get ForceFontExist() As Boolean","returnType":"Boolean","accessor":"Get"}, + {"name":"ForceFontExist","kind":"property","vis":"public","signature":"Public Property Let ForceFontExist(ByVal value As Boolean)","accessor":"Let"}, + {"name":"Italic","kind":"property","vis":"public","signature":"Public Property Get Italic() As Boolean","returnType":"Boolean","accessor":"Get"}, + {"name":"Italic","kind":"property","vis":"public","signature":"Public Property Let Italic(ByVal value As Boolean)","accessor":"Let"}, + {"name":"MaxSize","kind":"property","vis":"public","signature":"Public Property Get MaxSize() As Long","returnType":"Long","accessor":"Get"}, + {"name":"MaxSize","kind":"property","vis":"public","signature":"Public Property Let MaxSize(ByVal value As Long)","accessor":"Let"}, + {"name":"MinSize","kind":"property","vis":"public","signature":"Public Property Get MinSize() As Long","returnType":"Long","accessor":"Get"}, + {"name":"MinSize","kind":"property","vis":"public","signature":"Public Property Let MinSize(ByVal value As Long)","accessor":"Let"}, + {"name":"OwnerHwnd","kind":"property","vis":"public","signature":"Public Property Get OwnerHwnd() As LongPtr","returnType":"LongPtr","accessor":"Get"}, + {"name":"OwnerHwnd","kind":"property","vis":"public","signature":"Public Property Let OwnerHwnd(ByVal value As LongPtr)","accessor":"Let"}, + {"name":"Reset","kind":"sub","vis":"public","signature":"Public Sub Reset()"}, + {"name":"ScreenFontsOnly","kind":"property","vis":"public","signature":"Public Property Get ScreenFontsOnly() As Boolean","returnType":"Boolean","accessor":"Get"}, + {"name":"ScreenFontsOnly","kind":"property","vis":"public","signature":"Public Property Let ScreenFontsOnly(ByVal value As Boolean)","accessor":"Let"}, + {"name":"ShowDialog","kind":"function","vis":"public","signature":"Public Function ShowDialog() As Boolean","returnType":"Boolean"}, + {"name":"ShowEffects","kind":"property","vis":"public","signature":"Public Property Get ShowEffects() As Boolean","returnType":"Boolean","accessor":"Get"}, + {"name":"ShowEffects","kind":"property","vis":"public","signature":"Public Property Let ShowEffects(ByVal value As Boolean)","accessor":"Let"}, + {"name":"Size","kind":"property","vis":"public","signature":"Public Property Get Size() As Double","returnType":"Double","accessor":"Get"}, + {"name":"Size","kind":"property","vis":"public","signature":"Public Property Let Size(ByVal value As Double)","accessor":"Let"}, + {"name":"StrikeOut","kind":"property","vis":"public","signature":"Public Property Get StrikeOut() As Boolean","returnType":"Boolean","accessor":"Get"}, + {"name":"StrikeOut","kind":"property","vis":"public","signature":"Public Property Let StrikeOut(ByVal value As Boolean)","accessor":"Let"}, + {"name":"Style","kind":"property","vis":"public","signature":"Public Property Get Style() As FontStyle","returnType":"FontStyle","accessor":"Get"}, + {"name":"Style","kind":"property","vis":"public","signature":"Public Property Let Style(ByVal value As FontStyle)","accessor":"Let"}, + {"name":"ToCssFont","kind":"function","vis":"public","signature":"Public Function ToCssFont() As String","returnType":"String"}, + {"name":"ToDescription","kind":"function","vis":"public","signature":"Public Function ToDescription() As String","returnType":"String"}, + {"name":"Underline","kind":"property","vis":"public","signature":"Public Property Get Underline() As Boolean","returnType":"Boolean","accessor":"Get"}, + {"name":"Underline","kind":"property","vis":"public","signature":"Public Property Let Underline(ByVal value As Boolean)","accessor":"Let"}, + {"name":"mData","kind":"field","vis":"private","signature":"Private mData As cDialogFontData"} + ] + }, + { + "name": "CHOOSEFONT", + "kind": "type", + "in": "cDialogFont", + "vis": "private", + "public": false, + "file": "more/cDialogFont.twin", + "signature": "Private Type CHOOSEFONT", + "fields": [ + {"name":"lStructSize","vis":"public","signature":"lStructSize As Long"}, + {"name":"hwndOwner","vis":"public","signature":"hwndOwner As LongPtr"}, + {"name":"hDC","vis":"public","signature":"hDC As LongPtr"}, + {"name":"lpLogFont","vis":"public","signature":"lpLogFont As LongPtr"}, + {"name":"iPointSize","vis":"public","signature":"iPointSize As Long"}, + {"name":"flags","vis":"public","signature":"flags As Long"}, + {"name":"rgbColors","vis":"public","signature":"rgbColors As Long"}, + {"name":"lCustData","vis":"public","signature":"lCustData As LongPtr"}, + {"name":"lpfnHook","vis":"public","signature":"lpfnHook As LongPtr"}, + {"name":"lpTemplateName","vis":"public","signature":"lpTemplateName As LongPtr"}, + {"name":"hInstance","vis":"public","signature":"hInstance As LongPtr"}, + {"name":"lpszStyle","vis":"public","signature":"lpszStyle As LongPtr"}, + {"name":"nFontType","vis":"public","signature":"nFontType As Integer"}, + {"name":"MISSING_ALIGNMENT","vis":"public","signature":"MISSING_ALIGNMENT As Integer"}, + {"name":"nSizeMin","vis":"public","signature":"nSizeMin As Long"}, + {"name":"nSizeMax","vis":"public","signature":"nSizeMax As Long"} + ], + "members": [] + }, + { + "name": "FontFamily", + "kind": "enum", + "in": "cDialogFont", + "vis": "public", + "file": "more/cDialogFont.twin", + "signature": "Public Enum FontFamily", + "values": [ + {"name":"FF_DONTCARE","value":"0"}, + {"name":"FF_ROMAN","value":"16"}, + {"name":"FF_SWISS","value":"32"}, + {"name":"FF_MODERN","value":"48"}, + {"name":"FF_SCRIPT","value":"64"}, + {"name":"FF_DECORATIVE","value":"80"} + ] + }, + { + "name": "FontPitch", + "kind": "enum", + "in": "cDialogFont", + "vis": "public", + "file": "more/cDialogFont.twin", + "signature": "Public Enum FontPitch", + "values": [ + {"name":"DEFAULT_PITCH","value":"0"}, + {"name":"FIXED_PITCH","value":"1"}, + {"name":"VARIABLE_PITCH","value":"2"} + ] + }, + { + "name": "FontStyle", + "kind": "enum", + "in": "cDialogFont", + "vis": "public", + "file": "more/cDialogFont.twin", + "signature": "Public Enum FontStyle", + "values": [ + {"name":"StyleRegular","value":"0"}, + {"name":"StyleBold","value":"1"}, + {"name":"StyleItalic","value":"2"}, + {"name":"StyleBoldItalic","value":"3"} + ] + }, + { + "name": "FontWeight", + "kind": "enum", + "in": "cDialogFont", + "vis": "public", + "file": "more/cDialogFont.twin", + "signature": "Public Enum FontWeight", + "values": [ + {"name":"FW_DONTCARE","value":"0"}, + {"name":"FW_THIN","value":"100"}, + {"name":"FW_EXTRALIGHT","value":"200"}, + {"name":"FW_LIGHT","value":"300"}, + {"name":"FW_NORMAL","value":"400"}, + {"name":"FW_MEDIUM","value":"500"}, + {"name":"FW_SEMIBOLD","value":"600"}, + {"name":"FW_BOLD","value":"700"}, + {"name":"FW_EXTRABOLD","value":"800"}, + {"name":"FW_HEAVY","value":"900"} + ] + }, + { + "name": "LOGFONT", + "kind": "type", + "in": "cDialogFont", + "vis": "private", + "public": false, + "file": "more/cDialogFont.twin", + "signature": "Private Type LOGFONT", + "fields": [ + {"name":"lfHeight","vis":"public","signature":"lfHeight As Long"}, + {"name":"lfWidth","vis":"public","signature":"lfWidth As Long"}, + {"name":"lfEscapement","vis":"public","signature":"lfEscapement As Long"}, + {"name":"lfOrientation","vis":"public","signature":"lfOrientation As Long"}, + {"name":"lfWeight","vis":"public","signature":"lfWeight As Long"}, + {"name":"lfItalic","vis":"public","signature":"lfItalic As Byte"}, + {"name":"lfUnderline","vis":"public","signature":"lfUnderline As Byte"}, + {"name":"lfStrikeOut","vis":"public","signature":"lfStrikeOut As Byte"}, + {"name":"lfCharSet","vis":"public","signature":"lfCharSet As Byte"}, + {"name":"lfOutPrecision","vis":"public","signature":"lfOutPrecision As Byte"}, + {"name":"lfClipPrecision","vis":"public","signature":"lfClipPrecision As Byte"}, + {"name":"lfQuality","vis":"public","signature":"lfQuality As Byte"}, + {"name":"lfPitchAndFamily","vis":"public","signature":"lfPitchAndFamily As Byte"}, + {"name":"lfFaceName","vis":"public","signature":"lfFaceName(0 To 63) As Byte"} + ], + "members": [] + }, + { + "name": "cDialogFontData", + "kind": "type", + "in": "cDialogFont", + "vis": "private", + "public": false, + "file": "more/cDialogFont.twin", + "signature": "Private Type cDialogFontData", + "fields": [ + {"name":"mFaceName","vis":"public","signature":"mFaceName As String"}, + {"name":"mSize","vis":"public","signature":"mSize As Double"}, + {"name":"mStyle","vis":"public","signature":"mStyle As FontStyle"}, + {"name":"mColor","vis":"public","signature":"mColor As Long"}, + {"name":"mUnderline","vis":"public","signature":"mUnderline As Boolean"}, + {"name":"mStrikeOut","vis":"public","signature":"mStrikeOut As Boolean"}, + {"name":"mOwnerHwnd","vis":"public","signature":"mOwnerHwnd As LongPtr"}, + {"name":"mMinSize","vis":"public","signature":"mMinSize As Long"}, + {"name":"mMaxSize","vis":"public","signature":"mMaxSize As Long"}, + {"name":"mFixedPitchOnly","vis":"public","signature":"mFixedPitchOnly As Boolean"}, + {"name":"mForceFontExist","vis":"public","signature":"mForceFontExist As Boolean"}, + {"name":"mShowEffects","vis":"public","signature":"mShowEffects As Boolean"}, + {"name":"mScreenFontsOnly","vis":"public","signature":"mScreenFontsOnly As Boolean"} + ], + "members": [] + }, + { + "name": "cDialogInputBox", + "kind": "class", + "vis": "public", + "file": "more/cDialogInputBox.twin", + "signature": "Public Class cDialogInputBox", + "attrs": [{"name":"COMCreatable","args":"False"}], + "members": [ + {"name":"AllowEmpty","kind":"property","vis":"public","signature":"Public Property Get AllowEmpty() As Boolean","returnType":"Boolean","accessor":"Get"}, + {"name":"AllowEmpty","kind":"property","vis":"public","signature":"Public Property Let AllowEmpty(ByVal value As Boolean)","accessor":"Let"}, + {"name":"Cancelled","kind":"property","vis":"public","signature":"Public Property Get Cancelled() As Boolean","returnType":"Boolean","accessor":"Get"}, + {"name":"Class_Initialize","kind":"sub","vis":"private","signature":"Private Sub Class_Initialize()"}, + {"name":"CustomPattern","kind":"property","vis":"public","signature":"Public Property Get CustomPattern() As String","returnType":"String","accessor":"Get"}, + {"name":"CustomPattern","kind":"property","vis":"public","signature":"Public Property Let CustomPattern(ByVal value As String)","accessor":"Let"}, + {"name":"DefaultValue","kind":"property","vis":"public","signature":"Public Property Get DefaultValue() As String","returnType":"String","accessor":"Get"}, + {"name":"DefaultValue","kind":"property","vis":"public","signature":"Public Property Let DefaultValue(ByVal value As String)","accessor":"Let"}, + {"name":"GetValueAsInteger","kind":"function","vis":"public","signature":"Public Function GetValueAsInteger() As Long","returnType":"Long"}, + {"name":"GetValueAsNumber","kind":"function","vis":"public","signature":"Public Function GetValueAsNumber() As Double","returnType":"Double"}, + {"name":"MaxLength","kind":"property","vis":"public","signature":"Public Property Get MaxLength() As Long","returnType":"Long","accessor":"Get"}, + {"name":"MaxLength","kind":"property","vis":"public","signature":"Public Property Let MaxLength(ByVal value As Long)","accessor":"Let"}, + {"name":"MinLength","kind":"property","vis":"public","signature":"Public Property Get MinLength() As Long","returnType":"Long","accessor":"Get"}, + {"name":"MinLength","kind":"property","vis":"public","signature":"Public Property Let MinLength(ByVal value As Long)","accessor":"Let"}, + {"name":"Multiline","kind":"property","vis":"public","signature":"Public Property Get Multiline() As Boolean","returnType":"Boolean","accessor":"Get"}, + {"name":"Multiline","kind":"property","vis":"public","signature":"Public Property Let Multiline(ByVal value As Boolean)","accessor":"Let"}, + {"name":"OwnerHwnd","kind":"property","vis":"public","signature":"Public Property Get OwnerHwnd() As LongPtr","returnType":"LongPtr","accessor":"Get"}, + {"name":"OwnerHwnd","kind":"property","vis":"public","signature":"Public Property Let OwnerHwnd(ByVal value As LongPtr)","accessor":"Let"}, + {"name":"PasswordChar","kind":"property","vis":"public","signature":"Public Property Get PasswordChar() As String","returnType":"String","accessor":"Get"}, + {"name":"PasswordChar","kind":"property","vis":"public","signature":"Public Property Let PasswordChar(ByVal value As String)","accessor":"Let"}, + {"name":"Placeholder","kind":"property","vis":"public","signature":"Public Property Get Placeholder() As String","returnType":"String","accessor":"Get"}, + {"name":"Placeholder","kind":"property","vis":"public","signature":"Public Property Let Placeholder(ByVal value As String)","accessor":"Let"}, + {"name":"Prompt","kind":"property","vis":"public","signature":"Public Property Get Prompt() As String","returnType":"String","accessor":"Get"}, + {"name":"Prompt","kind":"property","vis":"public","signature":"Public Property Let Prompt(ByVal value As String)","accessor":"Let"}, + {"name":"Reset","kind":"sub","vis":"public","signature":"Public Sub Reset()"}, + {"name":"ShowDialog","kind":"function","vis":"public","signature":"Public Function ShowDialog() As Boolean","returnType":"Boolean"}, + {"name":"ShowEmail","kind":"function","vis":"public","signature":"Public Function ShowEmail(ByVal prompt As String, Optional ByVal title As String = \"Enter Email\") As String","returnType":"String"}, + {"name":"ShowInteger","kind":"function","vis":"public","signature":"Public Function ShowInteger(ByVal prompt As String, Optional ByVal title As String = \"Enter Integer\", Optional ByVal defaultValue As String = \"\") As Long","returnType":"Long"}, + {"name":"ShowNumeric","kind":"function","vis":"public","signature":"Public Function ShowNumeric(ByVal prompt As String, Optional ByVal title As String = \"Enter Number\", Optional ByVal defaultValue As String = \"\") As Double","returnType":"Double"}, + {"name":"ShowPhone","kind":"function","vis":"public","signature":"Public Function ShowPhone(ByVal prompt As String, Optional ByVal title As String = \"Enter Phone\") As String","returnType":"String"}, + {"name":"ShowSimple","kind":"function","vis":"public","signature":"Public Function ShowSimple(ByVal prompt As String, Optional ByVal title As String = \"Input\", Optional ByVal defaultValue As String = \"\") As String","returnType":"String"}, + {"name":"ShowURL","kind":"function","vis":"public","signature":"Public Function ShowURL(ByVal prompt As String, Optional ByVal title As String = \"Enter URL\") As String","returnType":"String"}, + {"name":"Title","kind":"property","vis":"public","signature":"Public Property Get Title() As String","returnType":"String","accessor":"Get"}, + {"name":"Title","kind":"property","vis":"public","signature":"Public Property Let Title(ByVal value As String)","accessor":"Let"}, + {"name":"ValidateInput","kind":"function","vis":"public","signature":"Public Function ValidateInput(ByVal input As String) As String","returnType":"String"}, + {"name":"Validation","kind":"property","vis":"public","signature":"Public Property Get Validation() As InputValidation","returnType":"InputValidation","accessor":"Get"}, + {"name":"Validation","kind":"property","vis":"public","signature":"Public Property Let Validation(ByVal value As InputValidation)","accessor":"Let"}, + {"name":"Value","kind":"property","vis":"public","signature":"Public Property Get Value() As String","returnType":"String","accessor":"Get"}, + {"name":"mData","kind":"field","vis":"private","signature":"Private mData As cDialogInputBoxData"} + ] + }, + { + "name": "InputValidation", + "kind": "enum", + "in": "cDialogInputBox", + "vis": "public", + "file": "more/cDialogInputBox.twin", + "signature": "Public Enum InputValidation", + "values": [ + {"name":"None","value":"0"}, + {"name":"Numeric","value":"1"}, + {"name":"Integer","value":"2"}, + {"name":"Alpha","value":"3"}, + {"name":"AlphaNumeric","value":"4"}, + {"name":"Email","value":"5"}, + {"name":"Phone","value":"6"}, + {"name":"URL","value":"7"}, + {"name":"Custom","value":"99"} + ] + }, + { + "name": "cDialogInputBoxData", + "kind": "type", + "in": "cDialogInputBox", + "vis": "private", + "public": false, + "file": "more/cDialogInputBox.twin", + "signature": "Private Type cDialogInputBoxData", + "fields": [ + {"name":"mTitle","vis":"public","signature":"mTitle As String"}, + {"name":"mPrompt","vis":"public","signature":"mPrompt As String"}, + {"name":"mDefaultValue","vis":"public","signature":"mDefaultValue As String"}, + {"name":"mPlaceholder","vis":"public","signature":"mPlaceholder As String"}, + {"name":"mValue","vis":"public","signature":"mValue As String"}, + {"name":"mMultiline","vis":"public","signature":"mMultiline As Boolean"}, + {"name":"mMaxLength","vis":"public","signature":"mMaxLength As Long"}, + {"name":"mMinLength","vis":"public","signature":"mMinLength As Long"}, + {"name":"mValidation","vis":"public","signature":"mValidation As InputValidation"}, + {"name":"mCustomPattern","vis":"public","signature":"mCustomPattern As String"}, + {"name":"mAllowEmpty","vis":"public","signature":"mAllowEmpty As Boolean"}, + {"name":"mPasswordChar","vis":"public","signature":"mPasswordChar As String"}, + {"name":"mOwnerHwnd","vis":"public","signature":"mOwnerHwnd As LongPtr"}, + {"name":"mCancelled","vis":"public","signature":"mCancelled As Boolean"} + ], + "members": [] + }, + { + "name": "cDialogPageSetup", + "kind": "class", + "vis": "public", + "file": "more/cDialogPageSetup.twin", + "signature": "Public Class cDialogPageSetup", + "attrs": [{"name":"COMCreatable","args":"False"}], + "members": [ + {"name":"AllowMargins","kind":"property","vis":"public","signature":"Public Property Get AllowMargins() As Boolean","returnType":"Boolean","accessor":"Get"}, + {"name":"AllowMargins","kind":"property","vis":"public","signature":"Public Property Let AllowMargins(ByVal value As Boolean)","accessor":"Let"}, + {"name":"AllowOrientation","kind":"property","vis":"public","signature":"Public Property Get AllowOrientation() As Boolean","returnType":"Boolean","accessor":"Get"}, + {"name":"AllowOrientation","kind":"property","vis":"public","signature":"Public Property Let AllowOrientation(ByVal value As Boolean)","accessor":"Let"}, + {"name":"AllowPaper","kind":"property","vis":"public","signature":"Public Property Get AllowPaper() As Boolean","returnType":"Boolean","accessor":"Get"}, + {"name":"AllowPaper","kind":"property","vis":"public","signature":"Public Property Let AllowPaper(ByVal value As Boolean)","accessor":"Let"}, + {"name":"Class_Initialize","kind":"sub","vis":"private","signature":"Private Sub Class_Initialize()"}, + {"name":"DefaultMinMargins","kind":"property","vis":"public","signature":"Public Property Get DefaultMinMargins() As Boolean","returnType":"Boolean","accessor":"Get"}, + {"name":"DefaultMinMargins","kind":"property","vis":"public","signature":"Public Property Let DefaultMinMargins(ByVal value As Boolean)","accessor":"Let"}, + {"name":"GetPrintableHeight","kind":"function","vis":"public","signature":"Public Function GetPrintableHeight() As Double","returnType":"Double"}, + {"name":"GetPrintableWidth","kind":"function","vis":"public","signature":"Public Function GetPrintableWidth() As Double","returnType":"Double"}, + {"name":"GlobalFree","kind":"function","vis":"private","declare":true,"signature":"Private Declare PtrSafe Function GlobalFree Lib \"kernel32\" (ByVal hMem As LongPtr) As LongPtr","returnType":"LongPtr"}, + {"name":"GlobalLock","kind":"function","vis":"private","declare":true,"signature":"Private Declare PtrSafe Function GlobalLock Lib \"kernel32\" (ByVal hMem As LongPtr) As LongPtr","returnType":"LongPtr"}, + {"name":"GlobalUnlock","kind":"function","vis":"private","declare":true,"signature":"Private Declare PtrSafe Function GlobalUnlock Lib \"kernel32\" (ByVal hMem As LongPtr) As Long","returnType":"Long"}, + {"name":"HundredthsMMToInches","kind":"function","vis":"private","signature":"Private Function HundredthsMMToInches(ByVal hundredthsMM As Long) As Double","returnType":"Double"}, + {"name":"InchesToHundredthsMM","kind":"function","vis":"private","signature":"Private Function InchesToHundredthsMM(ByVal inches As Double) As Long","returnType":"Long"}, + {"name":"InchesToThousandths","kind":"function","vis":"private","signature":"Private Function InchesToThousandths(ByVal inches As Double) As Long","returnType":"Long"}, + {"name":"MM_PER_INCH","kind":"const","vis":"private","signature":"Private Const MM_PER_INCH = 25.4"}, + {"name":"MarginBottom","kind":"property","vis":"public","signature":"Public Property Get MarginBottom() As Double","returnType":"Double","accessor":"Get"}, + {"name":"MarginBottom","kind":"property","vis":"public","signature":"Public Property Let MarginBottom(ByVal value As Double)","accessor":"Let"}, + {"name":"MarginLeft","kind":"property","vis":"public","signature":"Public Property Get MarginLeft() As Double","returnType":"Double","accessor":"Get"}, + {"name":"MarginLeft","kind":"property","vis":"public","signature":"Public Property Let MarginLeft(ByVal value As Double)","accessor":"Let"}, + {"name":"MarginRight","kind":"property","vis":"public","signature":"Public Property Get MarginRight() As Double","returnType":"Double","accessor":"Get"}, + {"name":"MarginRight","kind":"property","vis":"public","signature":"Public Property Let MarginRight(ByVal value As Double)","accessor":"Let"}, + {"name":"MarginTop","kind":"property","vis":"public","signature":"Public Property Get MarginTop() As Double","returnType":"Double","accessor":"Get"}, + {"name":"MarginTop","kind":"property","vis":"public","signature":"Public Property Let MarginTop(ByVal value As Double)","accessor":"Let"}, + {"name":"MulDiv","kind":"function","vis":"private","declare":true,"signature":"Private Declare PtrSafe Function MulDiv Lib \"kernel32\" (ByVal nNumber As Long, ByVal nNumerator As Long, ByVal nDenominator As Long) As Long","returnType":"Long"}, + {"name":"Orientation","kind":"property","vis":"public","signature":"Public Property Get Orientation() As PaperOrientation","returnType":"PaperOrientation","accessor":"Get"}, + {"name":"Orientation","kind":"property","vis":"public","signature":"Public Property Let Orientation(ByVal value As PaperOrientation)","accessor":"Let"}, + {"name":"OwnerHwnd","kind":"property","vis":"public","signature":"Public Property Get OwnerHwnd() As LongPtr","returnType":"LongPtr","accessor":"Get"}, + {"name":"OwnerHwnd","kind":"property","vis":"public","signature":"Public Property Let OwnerHwnd(ByVal value As LongPtr)","accessor":"Let"}, + {"name":"PSD_DEFAULTMINMARGINS","kind":"const","vis":"private","signature":"Private Const PSD_DEFAULTMINMARGINS = &H0"}, + {"name":"PSD_DISABLEMARGINS","kind":"const","vis":"private","signature":"Private Const PSD_DISABLEMARGINS = &H10"}, + {"name":"PSD_DISABLEORIENTATION","kind":"const","vis":"private","signature":"Private Const PSD_DISABLEORIENTATION = &H100"}, + {"name":"PSD_DISABLEPAGEPAINTING","kind":"const","vis":"private","signature":"Private Const PSD_DISABLEPAGEPAINTING = &H80000"}, + {"name":"PSD_DISABLEPAPER","kind":"const","vis":"private","signature":"Private Const PSD_DISABLEPAPER = &H200"}, + {"name":"PSD_DISABLEPRINTER","kind":"const","vis":"private","signature":"Private Const PSD_DISABLEPRINTER = &H20"}, + {"name":"PSD_ENABLEPAGEPAINTHOOK","kind":"const","vis":"private","signature":"Private Const PSD_ENABLEPAGEPAINTHOOK = &H40000"}, + {"name":"PSD_ENABLEPAGESETUPHOOK","kind":"const","vis":"private","signature":"Private Const PSD_ENABLEPAGESETUPHOOK = &H2000"}, + {"name":"PSD_ENABLEPAGESETUPTEMPLATE","kind":"const","vis":"private","signature":"Private Const PSD_ENABLEPAGESETUPTEMPLATE = &H8000"}, + {"name":"PSD_ENABLEPAGESETUPTEMPLATEHANDLE","kind":"const","vis":"private","signature":"Private Const PSD_ENABLEPAGESETUPTEMPLATEHANDLE = &H20000"}, + {"name":"PSD_INHUNDREDTHSOFMILLIMETERS","kind":"const","vis":"private","signature":"Private Const PSD_INHUNDREDTHSOFMILLIMETERS = &H8"}, + {"name":"PSD_INTHOUSANDTHSOFINCHES","kind":"const","vis":"private","signature":"Private Const PSD_INTHOUSANDTHSOFINCHES = &H4"}, + {"name":"PSD_INWININIINTLMEASURE","kind":"const","vis":"private","signature":"Private Const PSD_INWININIINTLMEASURE = &H0"}, + {"name":"PSD_MARGINS","kind":"const","vis":"private","signature":"Private Const PSD_MARGINS = &H2"}, + {"name":"PSD_MINMARGINS","kind":"const","vis":"private","signature":"Private Const PSD_MINMARGINS = &H1"}, + {"name":"PSD_NONETWORKBUTTON","kind":"const","vis":"private","signature":"Private Const PSD_NONETWORKBUTTON = &H200000"}, + {"name":"PSD_NOWARNING","kind":"const","vis":"private","signature":"Private Const PSD_NOWARNING = &H80"}, + {"name":"PSD_RETURNDEFAULT","kind":"const","vis":"private","signature":"Private Const PSD_RETURNDEFAULT = &H400"}, + {"name":"PSD_SHOWHELP","kind":"const","vis":"private","signature":"Private Const PSD_SHOWHELP = &H800"}, + {"name":"PSD_STRUCTSIZE","kind":"const","vis":"private","signatures":[{"cond":"Win64","signature":"Private Const PSD_STRUCTSIZE As Long = 128"},{"cond":"Not (Win64)","signature":"Private Const PSD_STRUCTSIZE As Long = 84"}]}, + {"name":"PageSetupDlg","kind":"function","vis":"private","declare":true,"signature":"Private Declare PtrSafe Function PageSetupDlg Lib \"comdlg32.dll\" Alias \"PageSetupDlgW\" (pPagesetupdlg As PAGESETUPDLG) As Long","returnType":"Long"}, + {"name":"PaperHeight","kind":"property","vis":"public","signature":"Public Property Get PaperHeight() As Double","returnType":"Double","accessor":"Get"}, + {"name":"PaperHeight","kind":"property","vis":"public","signature":"Public Property Let PaperHeight(ByVal value As Double)","accessor":"Let"}, + {"name":"PaperWidth","kind":"property","vis":"public","signature":"Public Property Get PaperWidth() As Double","returnType":"Double","accessor":"Get"}, + {"name":"PaperWidth","kind":"property","vis":"public","signature":"Public Property Let PaperWidth(ByVal value As Double)","accessor":"Let"}, + {"name":"Reset","kind":"sub","vis":"public","signature":"Public Sub Reset()"}, + {"name":"SetA4Size","kind":"sub","vis":"public","signature":"Public Sub SetA4Size()"}, + {"name":"SetDefaultMargins","kind":"sub","vis":"public","signature":"Public Sub SetDefaultMargins()"}, + {"name":"SetLegalSize","kind":"sub","vis":"public","signature":"Public Sub SetLegalSize()"}, + {"name":"SetLetterSize","kind":"sub","vis":"public","signature":"Public Sub SetLetterSize()"}, + {"name":"SetNarrowMargins","kind":"sub","vis":"public","signature":"Public Sub SetNarrowMargins()"}, + {"name":"ShowDialog","kind":"function","vis":"public","signature":"Public Function ShowDialog() As Boolean","returnType":"Boolean"}, + {"name":"TWIPS_PER_INCH","kind":"const","vis":"private","signature":"Private Const TWIPS_PER_INCH = 1440"}, + {"name":"ThousandthsToInches","kind":"function","vis":"private","signature":"Private Function ThousandthsToInches(ByVal thousandths As Long) As Double","returnType":"Double"}, + {"name":"ToDescription","kind":"function","vis":"public","signature":"Public Function ToDescription() As String","returnType":"String"}, + {"name":"Unit","kind":"property","vis":"public","signature":"Public Property Get Unit() As MeasurementUnit","returnType":"MeasurementUnit","accessor":"Get"}, + {"name":"Unit","kind":"property","vis":"public","signature":"Public Property Let Unit(ByVal value As MeasurementUnit)","accessor":"Let"}, + {"name":"mData","kind":"field","vis":"private","signature":"Private mData As cDialogPageSetupData"} + ] + }, + { + "name": "MeasurementUnit", + "kind": "enum", + "in": "cDialogPageSetup", + "vis": "public", + "file": "more/cDialogPageSetup.twin", + "signature": "Public Enum MeasurementUnit", + "values": [ + {"name":"Inches","value":"0"}, + {"name":"Millimeters","value":"1"} + ] + }, + { + "name": "PAGESETUPDLG", + "kind": "type", + "in": "cDialogPageSetup", + "vis": "private", + "public": false, + "file": "more/cDialogPageSetup.twin", + "signature": "Private Type PAGESETUPDLG", + "fields": [ + {"name":"lStructSize","vis":"public","signature":"lStructSize As Long"}, + {"name":"hwndOwner","vis":"public","signature":"hwndOwner As LongPtr"}, + {"name":"hDevMode","vis":"public","signature":"hDevMode As LongPtr"}, + {"name":"hDevNames","vis":"public","signature":"hDevNames As LongPtr"}, + {"name":"flags","vis":"public","signature":"flags As Long"}, + {"name":"ptPaperSize","vis":"public","signature":"ptPaperSize As POINTL"}, + {"name":"rtMinMargin","vis":"public","signature":"rtMinMargin As RECT"}, + {"name":"rtMargin","vis":"public","signature":"rtMargin As RECT"}, + {"name":"hInstance","vis":"public","signature":"hInstance As LongPtr"}, + {"name":"lCustData","vis":"public","signature":"lCustData As LongPtr"}, + {"name":"lpfnPageSetupHook","vis":"public","signature":"lpfnPageSetupHook As LongPtr"}, + {"name":"lpfnPagePaintHook","vis":"public","signature":"lpfnPagePaintHook As LongPtr"}, + {"name":"lpPageSetupTemplateName","vis":"public","signature":"lpPageSetupTemplateName As LongPtr"}, + {"name":"hPageSetupTemplate","vis":"public","signature":"hPageSetupTemplate As LongPtr"} + ], + "members": [] + }, + { + "name": "POINTL", + "kind": "type", + "in": "cDialogPageSetup", + "vis": "private", + "public": false, + "file": "more/cDialogPageSetup.twin", + "signature": "Private Type POINTL", + "fields": [ + {"name":"X","vis":"public","signature":"X As Long"}, + {"name":"Y","vis":"public","signature":"Y As Long"} + ], + "members": [] + }, + { + "name": "PaperOrientation", + "kind": "enum", + "in": "cDialogPageSetup", + "vis": "public", + "file": "more/cDialogPageSetup.twin", + "signature": "Public Enum PaperOrientation", + "values": [ + {"name":"Portrait","value":"1"}, + {"name":"Landscape","value":"2"} + ] + }, + { + "name": "RECT", + "kind": "type", + "in": "cDialogPageSetup", + "vis": "private", + "public": false, + "file": "more/cDialogPageSetup.twin", + "signature": "Private Type RECT", + "fields": [ + {"name":"Left","vis":"public","signature":"Left As Long"}, + {"name":"Top","vis":"public","signature":"Top As Long"}, + {"name":"Right","vis":"public","signature":"Right As Long"}, + {"name":"Bottom","vis":"public","signature":"Bottom As Long"} + ], + "members": [] + }, + { + "name": "cDialogPageSetupData", + "kind": "type", + "in": "cDialogPageSetup", + "vis": "private", + "public": false, + "file": "more/cDialogPageSetup.twin", + "signature": "Private Type cDialogPageSetupData", + "fields": [ + {"name":"mOwnerHwnd","vis":"public","signature":"mOwnerHwnd As LongPtr"}, + {"name":"mOrientation","vis":"public","signature":"mOrientation As PaperOrientation"}, + {"name":"mPaperWidth","vis":"public","signature":"mPaperWidth As Double"}, + {"name":"mPaperHeight","vis":"public","signature":"mPaperHeight As Double"}, + {"name":"mMarginLeft","vis":"public","signature":"mMarginLeft As Double"}, + {"name":"mMarginRight","vis":"public","signature":"mMarginRight As Double"}, + {"name":"mMarginTop","vis":"public","signature":"mMarginTop As Double"}, + {"name":"mMarginBottom","vis":"public","signature":"mMarginBottom As Double"}, + {"name":"mMinMarginLeft","vis":"public","signature":"mMinMarginLeft As Double"}, + {"name":"mMinMarginRight","vis":"public","signature":"mMinMarginRight As Double"}, + {"name":"mMinMarginTop","vis":"public","signature":"mMinMarginTop As Double"}, + {"name":"mMinMarginBottom","vis":"public","signature":"mMinMarginBottom As Double"}, + {"name":"mUnit","vis":"public","signature":"mUnit As MeasurementUnit"}, + {"name":"mAllowMargins","vis":"public","signature":"mAllowMargins As Boolean"}, + {"name":"mAllowOrientation","vis":"public","signature":"mAllowOrientation As Boolean"}, + {"name":"mAllowPaper","vis":"public","signature":"mAllowPaper As Boolean"}, + {"name":"mDefaultMinMargins","vis":"public","signature":"mDefaultMinMargins As Boolean"} + ], + "members": [] + }, + { + "name": "cDialogPassword", + "kind": "class", + "vis": "public", + "file": "more/cDialogPassword.twin", + "signature": "Public Class cDialogPassword", + "attrs": [{"name":"COMCreatable","args":"False"}], + "members": [ + {"name":"AllowShowPassword","kind":"property","vis":"public","signature":"Public Property Get AllowShowPassword() As Boolean","returnType":"Boolean","accessor":"Get"}, + {"name":"AllowShowPassword","kind":"property","vis":"public","signature":"Public Property Let AllowShowPassword(ByVal value As Boolean)","accessor":"Let"}, + {"name":"Cancelled","kind":"property","vis":"public","signature":"Public Property Get Cancelled() As Boolean","returnType":"Boolean","accessor":"Get"}, + {"name":"Class_Initialize","kind":"sub","vis":"private","signature":"Private Sub Class_Initialize()"}, + {"name":"EM_SETPASSWORDCHAR","kind":"const","vis":"private","signature":"Private Const EM_SETPASSWORDCHAR As Long = &H00CC"}, + {"name":"FindWindowExW","kind":"function","vis":"private","declare":true,"signature":"Private Declare PtrSafe Function FindWindowExW Lib \"user32\" (ByVal hWndParent As LongPtr, ByVal hWndChildAfter As LongPtr, ByVal lpClassName As LongPtr, ByVal lpWindowName As LongPtr) As LongPtr","returnType":"LongPtr"}, + {"name":"FindWindowW","kind":"function","vis":"private","declare":true,"signature":"Private Declare PtrSafe Function FindWindowW Lib \"user32\" (ByVal lpClassName As LongPtr, ByVal lpWindowName As LongPtr) As LongPtr","returnType":"LongPtr"}, + {"name":"GW_CHILD","kind":"const","vis":"private","signature":"Private Const GW_CHILD As Long = 5"}, + {"name":"GW_HWNDNEXT","kind":"const","vis":"private","signature":"Private Const GW_HWNDNEXT As Long = 2"}, + {"name":"GetClassNameW","kind":"function","vis":"private","declare":true,"signature":"Private Declare PtrSafe Function GetClassNameW Lib \"user32\" (ByVal hWnd As LongPtr, ByVal lpClassName As LongPtr, ByVal nMaxCount As Long) As Long","returnType":"Long"}, + {"name":"GetDlgItem","kind":"function","vis":"private","declare":true,"signature":"Private Declare PtrSafe Function GetDlgItem Lib \"user32\" (ByVal hDlg As LongPtr, ByVal nIDDlgItem As Long) As LongPtr","returnType":"LongPtr"}, + {"name":"GetRequirementsDescription","kind":"function","vis":"public","signature":"Public Function GetRequirementsDescription() As String","returnType":"String"}, + {"name":"GetStrength","kind":"function","vis":"public","signature":"Public Function GetStrength(ByVal password As String) As PasswordStrength","returnType":"PasswordStrength"}, + {"name":"GetStrengthDescription","kind":"function","vis":"public","signature":"Public Function GetStrengthDescription(ByVal strength As PasswordStrength) As String","returnType":"String"}, + {"name":"GetWindow","kind":"function","vis":"private","declare":true,"signature":"Private Declare PtrSafe Function GetWindow lib \"user32\" (ByVal hWnd As LongPtr, ByVal wCmd As Long) As LongPtr","returnType":"LongPtr"}, + {"name":"KillTimer","kind":"function","vis":"private","declare":true,"signature":"Private Declare PtrSafe Function KillTimer Lib \"user32\" (ByVal hWnd As LongPtr, ByVal nIDEvent As LongPtr) As Long","returnType":"Long"}, + {"name":"MaskedInputBox","kind":"function","vis":"private","signature":"Private Function MaskedInputBox(ByVal prompt As String, ByVal title As String, ByVal defaultVal As String) As String","returnType":"String"}, + {"name":"MaxAttempts","kind":"property","vis":"public","signature":"Public Property Get MaxAttempts() As Long","returnType":"Long","accessor":"Get"}, + {"name":"MaxAttempts","kind":"property","vis":"public","signature":"Public Property Let MaxAttempts(ByVal value As Long)","accessor":"Let"}, + {"name":"MaxLength","kind":"property","vis":"public","signature":"Public Property Get MaxLength() As Long","returnType":"Long","accessor":"Get"}, + {"name":"MaxLength","kind":"property","vis":"public","signature":"Public Property Let MaxLength(ByVal value As Long)","accessor":"Let"}, + {"name":"MinLength","kind":"property","vis":"public","signature":"Public Property Get MinLength() As Long","returnType":"Long","accessor":"Get"}, + {"name":"MinLength","kind":"property","vis":"public","signature":"Public Property Let MinLength(ByVal value As Long)","accessor":"Let"}, + {"name":"OwnerHwnd","kind":"property","vis":"public","signature":"Public Property Get OwnerHwnd() As LongPtr","returnType":"LongPtr","accessor":"Get"}, + {"name":"OwnerHwnd","kind":"property","vis":"public","signature":"Public Property Let OwnerHwnd(ByVal value As LongPtr)","accessor":"Let"}, + {"name":"Password","kind":"property","vis":"public","signature":"Public Property Get Password() As String","returnType":"String","accessor":"Get"}, + {"name":"PasswordChar","kind":"property","vis":"public","signature":"Public Property Get PasswordChar() As String","returnType":"String","accessor":"Get"}, + {"name":"PasswordChar","kind":"property","vis":"public","signature":"Public Property Let PasswordChar(ByVal value As String)","accessor":"Let"}, + {"name":"Prompt","kind":"property","vis":"public","signature":"Public Property Get Prompt() As String","returnType":"String","accessor":"Get"}, + {"name":"Prompt","kind":"property","vis":"public","signature":"Public Property Let Prompt(ByVal value As String)","accessor":"Let"}, + {"name":"RequireConfirm","kind":"property","vis":"public","signature":"Public Property Get RequireConfirm() As Boolean","returnType":"Boolean","accessor":"Get"}, + {"name":"RequireConfirm","kind":"property","vis":"public","signature":"Public Property Let RequireConfirm(ByVal value As Boolean)","accessor":"Let"}, + {"name":"RequireLowercase","kind":"property","vis":"public","signature":"Public Property Get RequireLowercase() As Boolean","returnType":"Boolean","accessor":"Get"}, + {"name":"RequireLowercase","kind":"property","vis":"public","signature":"Public Property Let RequireLowercase(ByVal value As Boolean)","accessor":"Let"}, + {"name":"RequireNumber","kind":"property","vis":"public","signature":"Public Property Get RequireNumber() As Boolean","returnType":"Boolean","accessor":"Get"}, + {"name":"RequireNumber","kind":"property","vis":"public","signature":"Public Property Let RequireNumber(ByVal value As Boolean)","accessor":"Let"}, + {"name":"RequireSpecial","kind":"property","vis":"public","signature":"Public Property Get RequireSpecial() As Boolean","returnType":"Boolean","accessor":"Get"}, + {"name":"RequireSpecial","kind":"property","vis":"public","signature":"Public Property Let RequireSpecial(ByVal value As Boolean)","accessor":"Let"}, + {"name":"RequireUppercase","kind":"property","vis":"public","signature":"Public Property Get RequireUppercase() As Boolean","returnType":"Boolean","accessor":"Get"}, + {"name":"RequireUppercase","kind":"property","vis":"public","signature":"Public Property Let RequireUppercase(ByVal value As Boolean)","accessor":"Let"}, + {"name":"Reset","kind":"sub","vis":"public","signature":"Public Sub Reset()"}, + {"name":"SendMessageW","kind":"function","vis":"private","declare":true,"signature":"Private Declare PtrSafe Function SendMessageW Lib \"user32\" (ByVal hWnd As LongPtr, ByVal wMsg As Long, ByVal wParam As LongPtr, ByVal lParam As LongPtr) As LongPtr","returnType":"LongPtr"}, + {"name":"SetTimer","kind":"function","vis":"private","declare":true,"signature":"Private Declare PtrSafe Function SetTimer Lib \"user32\" (ByVal hWnd As LongPtr, ByVal nIDEvent As LongPtr, ByVal uElapse As Long, ByVal lpTimerFunc As LongPtr) As LongPtr","returnType":"LongPtr"}, + {"name":"ShowChangeDialog","kind":"function","vis":"public","signature":"Public Function ShowChangeDialog(ByRef oldPassword As String) As Boolean","returnType":"Boolean"}, + {"name":"ShowDialog","kind":"function","vis":"public","signature":"Public Function ShowDialog() As Boolean","returnType":"Boolean"}, + {"name":"ShowStrength","kind":"property","vis":"public","signature":"Public Property Get ShowStrength() As Boolean","returnType":"Boolean","accessor":"Get"}, + {"name":"ShowStrength","kind":"property","vis":"public","signature":"Public Property Let ShowStrength(ByVal value As Boolean)","accessor":"Let"}, + {"name":"TimerProc","kind":"sub","vis":"private","signature":"Private Sub TimerProc(ByVal hWnd As LongPtr, ByVal uMsg As Long, ByVal idEvent As LongPtr, ByVal dwTime As Long)"}, + {"name":"Title","kind":"property","vis":"public","signature":"Public Property Get Title() As String","returnType":"String","accessor":"Get"}, + {"name":"Title","kind":"property","vis":"public","signature":"Public Property Let Title(ByVal value As String)","accessor":"Let"}, + {"name":"Validate","kind":"function","vis":"public","signature":"Public Function Validate(ByVal password As String) As String","returnType":"String"}, + {"name":"mData","kind":"field","vis":"private","signature":"Private mData As cDialogPasswordData"} + ] + }, + { + "name": "PasswordStrength", + "kind": "enum", + "in": "cDialogPassword", + "vis": "public", + "file": "more/cDialogPassword.twin", + "signature": "Public Enum PasswordStrength", + "values": [ + {"name":"VeryWeak","value":"0"}, + {"name":"Weak","value":"1"}, + {"name":"Fair","value":"2"}, + {"name":"Strong","value":"3"}, + {"name":"VeryStrong","value":"4"} + ] + }, + { + "name": "cDialogPasswordData", + "kind": "type", + "in": "cDialogPassword", + "vis": "private", + "public": false, + "file": "more/cDialogPassword.twin", + "signature": "Private Type cDialogPasswordData", + "fields": [ + {"name":"mTitle","vis":"public","signature":"mTitle As String"}, + {"name":"mPrompt","vis":"public","signature":"mPrompt As String"}, + {"name":"mPassword","vis":"public","signature":"mPassword As String"}, + {"name":"mConfirmPassword","vis":"public","signature":"mConfirmPassword As String"}, + {"name":"mPasswordChar","vis":"public","signature":"mPasswordChar As String"}, + {"name":"mShowStrength","vis":"public","signature":"mShowStrength As Boolean"}, + {"name":"mRequireConfirm","vis":"public","signature":"mRequireConfirm As Boolean"}, + {"name":"mMinLength","vis":"public","signature":"mMinLength As Long"}, + {"name":"mMaxLength","vis":"public","signature":"mMaxLength As Long"}, + {"name":"mRequireUppercase","vis":"public","signature":"mRequireUppercase As Boolean"}, + {"name":"mRequireLowercase","vis":"public","signature":"mRequireLowercase As Boolean"}, + {"name":"mRequireNumber","vis":"public","signature":"mRequireNumber As Boolean"}, + {"name":"mRequireSpecial","vis":"public","signature":"mRequireSpecial As Boolean"}, + {"name":"mAllowShowPassword","vis":"public","signature":"mAllowShowPassword As Boolean"}, + {"name":"mMaxAttempts","vis":"public","signature":"mMaxAttempts As Long"}, + {"name":"mAttemptCount","vis":"public","signature":"mAttemptCount As Long"}, + {"name":"mOwnerHwnd","vis":"public","signature":"mOwnerHwnd As LongPtr"}, + {"name":"mCancelled","vis":"public","signature":"mCancelled As Boolean"}, + {"name":"mTimerId","vis":"public","signature":"mTimerId As LongPtr"} + ], + "members": [] + }, + { + "name": "cDialogPrint", + "kind": "class", + "vis": "public", + "file": "more/cDialogPrint.twin", + "signature": "Public Class cDialogPrint", + "attrs": [{"name":"COMCreatable","args":"False"}], + "members": [ + {"name":"AllowPageRange","kind":"property","vis":"public","signature":"Public Property Get AllowPageRange() As Boolean","returnType":"Boolean","accessor":"Get"}, + {"name":"AllowPageRange","kind":"property","vis":"public","signature":"Public Property Let AllowPageRange(ByVal value As Boolean)","accessor":"Let"}, + {"name":"AllowPrintToFile","kind":"property","vis":"public","signature":"Public Property Get AllowPrintToFile() As Boolean","returnType":"Boolean","accessor":"Get"}, + {"name":"AllowPrintToFile","kind":"property","vis":"public","signature":"Public Property Let AllowPrintToFile(ByVal value As Boolean)","accessor":"Let"}, + {"name":"AllowSelection","kind":"property","vis":"public","signature":"Public Property Get AllowSelection() As Boolean","returnType":"Boolean","accessor":"Get"}, + {"name":"AllowSelection","kind":"property","vis":"public","signature":"Public Property Let AllowSelection(ByVal value As Boolean)","accessor":"Let"}, + {"name":"Cancelled","kind":"property","vis":"public","signature":"Public Property Get Cancelled() As Boolean","returnType":"Boolean","accessor":"Get"}, + {"name":"Class_Initialize","kind":"sub","vis":"private","signature":"Private Sub Class_Initialize()"}, + {"name":"Class_Terminate","kind":"sub","vis":"private","signature":"Private Sub Class_Terminate()"}, + {"name":"Collate","kind":"property","vis":"public","signature":"Public Property Get Collate() As Boolean","returnType":"Boolean","accessor":"Get"}, + {"name":"Collate","kind":"property","vis":"public","signature":"Public Property Let Collate(ByVal value As Boolean)","accessor":"Let"}, + {"name":"CommDlgExtendedError","kind":"function","vis":"private","declare":true,"signature":"Private Declare PtrSafe Function CommDlgExtendedError Lib \"comdlg32.dll\" () As Long","returnType":"Long"}, + {"name":"Copies","kind":"property","vis":"public","signature":"Public Property Get Copies() As Long","returnType":"Long","accessor":"Get"}, + {"name":"Copies","kind":"property","vis":"public","signature":"Public Property Let Copies(ByVal value As Long)","accessor":"Let"}, + {"name":"FromPage","kind":"property","vis":"public","signature":"Public Property Get FromPage() As Long","returnType":"Long","accessor":"Get"}, + {"name":"FromPage","kind":"property","vis":"public","signature":"Public Property Let FromPage(ByVal value As Long)","accessor":"Let"}, + {"name":"GlobalFree","kind":"function","vis":"private","declare":true,"signature":"Private Declare PtrSafe Function GlobalFree Lib \"kernel32\" (ByVal hMem As LongPtr) As LongPtr","returnType":"LongPtr"}, + {"name":"GlobalLock","kind":"function","vis":"private","declare":true,"signature":"Private Declare PtrSafe Function GlobalLock Lib \"kernel32\" (ByVal hMem As LongPtr) As LongPtr","returnType":"LongPtr"}, + {"name":"GlobalUnlock","kind":"function","vis":"private","declare":true,"signature":"Private Declare PtrSafe Function GlobalUnlock Lib \"kernel32\" (ByVal hMem As LongPtr) As Long","returnType":"Long"}, + {"name":"HidePrintToFile","kind":"property","vis":"public","signature":"Public Property Get HidePrintToFile() As Boolean","returnType":"Boolean","accessor":"Get"}, + {"name":"HidePrintToFile","kind":"property","vis":"public","signature":"Public Property Let HidePrintToFile(ByVal value As Boolean)","accessor":"Let"}, + {"name":"LastError","kind":"property","vis":"public","signature":"Public Property Get LastError() As Long","returnType":"Long","accessor":"Get"}, + {"name":"MaxPage","kind":"property","vis":"public","signature":"Public Property Get MaxPage() As Long","returnType":"Long","accessor":"Get"}, + {"name":"MaxPage","kind":"property","vis":"public","signature":"Public Property Let MaxPage(ByVal value As Long)","accessor":"Let"}, + {"name":"MinPage","kind":"property","vis":"public","signature":"Public Property Get MinPage() As Long","returnType":"Long","accessor":"Get"}, + {"name":"MinPage","kind":"property","vis":"public","signature":"Public Property Let MinPage(ByVal value As Long)","accessor":"Let"}, + {"name":"OwnerHwnd","kind":"property","vis":"public","signature":"Public Property Get OwnerHwnd() As LongPtr","returnType":"LongPtr","accessor":"Get"}, + {"name":"OwnerHwnd","kind":"property","vis":"public","signature":"Public Property Let OwnerHwnd(ByVal value As LongPtr)","accessor":"Let"}, + {"name":"PD_ALLPAGES","kind":"const","vis":"private","signature":"Private Const PD_ALLPAGES = &H0"}, + {"name":"PD_COLLATE","kind":"const","vis":"private","signature":"Private Const PD_COLLATE = &H10"}, + {"name":"PD_DISABLEPRINTTOFILE","kind":"const","vis":"private","signature":"Private Const PD_DISABLEPRINTTOFILE = &H80000"}, + {"name":"PD_ENABLEPRINTHOOK","kind":"const","vis":"private","signature":"Private Const PD_ENABLEPRINTHOOK = &H1000"}, + {"name":"PD_ENABLEPRINTTEMPLATE","kind":"const","vis":"private","signature":"Private Const PD_ENABLEPRINTTEMPLATE = &H4000"}, + {"name":"PD_ENABLEPRINTTEMPLATEHANDLE","kind":"const","vis":"private","signature":"Private Const PD_ENABLEPRINTTEMPLATEHANDLE = &H10000"}, + {"name":"PD_ENABLESETUPHOOK","kind":"const","vis":"private","signature":"Private Const PD_ENABLESETUPHOOK = &H2000"}, + {"name":"PD_ENABLESETUPTEMPLATE","kind":"const","vis":"private","signature":"Private Const PD_ENABLESETUPTEMPLATE = &H8000"}, + {"name":"PD_ENABLESETUPTEMPLATEHANDLE","kind":"const","vis":"private","signature":"Private Const PD_ENABLESETUPTEMPLATEHANDLE = &H20000"}, + {"name":"PD_HIDEPRINTTOFILE","kind":"const","vis":"private","signature":"Private Const PD_HIDEPRINTTOFILE = &H100000"}, + {"name":"PD_NONETWORKBUTTON","kind":"const","vis":"private","signature":"Private Const PD_NONETWORKBUTTON = &H200000"}, + {"name":"PD_NOPAGENUMS","kind":"const","vis":"private","signature":"Private Const PD_NOPAGENUMS = &H8"}, + {"name":"PD_NOSELECTION","kind":"const","vis":"private","signature":"Private Const PD_NOSELECTION = &H4"}, + {"name":"PD_NOWARNING","kind":"const","vis":"private","signature":"Private Const PD_NOWARNING = &H80"}, + {"name":"PD_PAGENUMS","kind":"const","vis":"private","signature":"Private Const PD_PAGENUMS = &H2"}, + {"name":"PD_PRINTSETUP","kind":"const","vis":"private","signature":"Private Const PD_PRINTSETUP = &H40"}, + {"name":"PD_PRINTTOFILE","kind":"const","vis":"private","signature":"Private Const PD_PRINTTOFILE = &H20"}, + {"name":"PD_RETURNDC","kind":"const","vis":"private","signature":"Private Const PD_RETURNDC = &H100"}, + {"name":"PD_RETURNDEFAULT","kind":"const","vis":"private","signature":"Private Const PD_RETURNDEFAULT = &H400"}, + {"name":"PD_RETURNIC","kind":"const","vis":"private","signature":"Private Const PD_RETURNIC = &H200"}, + {"name":"PD_SELECTION","kind":"const","vis":"private","signature":"Private Const PD_SELECTION = &H1"}, + {"name":"PD_SHOWHELP","kind":"const","vis":"private","signature":"Private Const PD_SHOWHELP = &H800"}, + {"name":"PD_STRUCTSIZE","kind":"const","vis":"private","signatures":[{"cond":"Win64","signature":"Private Const PD_STRUCTSIZE As Long = 120"},{"cond":"Not (Win64)","signature":"Private Const PD_STRUCTSIZE As Long = 66"}]}, + {"name":"PD_USEDEVMODECOPIES","kind":"const","vis":"private","signature":"Private Const PD_USEDEVMODECOPIES = &H40000"}, + {"name":"PD_USEDEVMODECOPIESANDCOLLATE","kind":"const","vis":"private","signature":"Private Const PD_USEDEVMODECOPIESANDCOLLATE = &H40000"}, + {"name":"PrintDlg","kind":"function","vis":"private","declare":true,"signature":"Private Declare PtrSafe Function PrintDlg Lib \"comdlg32.dll\" Alias \"PrintDlgW\" (pPrintdlg As PRINTDLG) As Long","returnType":"Long"}, + {"name":"PrintRangeMode","kind":"property","vis":"public","signature":"Public Property Get PrintRangeMode() As PrintRange","returnType":"PrintRange","accessor":"Get"}, + {"name":"PrintRangeMode","kind":"property","vis":"public","signature":"Public Property Let PrintRangeMode(ByVal value As PrintRange)","accessor":"Let"}, + {"name":"PrintToFile","kind":"property","vis":"public","signature":"Public Property Get PrintToFile() As Boolean","returnType":"Boolean","accessor":"Get"}, + {"name":"PrintToFile","kind":"property","vis":"public","signature":"Public Property Let PrintToFile(ByVal value As Boolean)","accessor":"Let"}, + {"name":"Reset","kind":"sub","vis":"public","signature":"Public Sub Reset()"}, + {"name":"ReturnDC","kind":"property","vis":"public","signature":"Public Property Get ReturnDC() As Boolean","returnType":"Boolean","accessor":"Get"}, + {"name":"ReturnDC","kind":"property","vis":"public","signature":"Public Property Let ReturnDC(ByVal value As Boolean)","accessor":"Let"}, + {"name":"ShowDialog","kind":"function","vis":"public","signature":"Public Function ShowDialog() As Boolean","returnType":"Boolean"}, + {"name":"ToDescription","kind":"function","vis":"public","signature":"Public Function ToDescription() As String","returnType":"String"}, + {"name":"ToPage","kind":"property","vis":"public","signature":"Public Property Get ToPage() As Long","returnType":"Long","accessor":"Get"}, + {"name":"ToPage","kind":"property","vis":"public","signature":"Public Property Let ToPage(ByVal value As Long)","accessor":"Let"}, + {"name":"hDC","kind":"property","vis":"public","signature":"Public Property Get hDC() As LongPtr","returnType":"LongPtr","accessor":"Get"}, + {"name":"mData","kind":"field","vis":"private","signature":"Private mData As cDialogPrintData"} + ] + }, + { + "name": "PRINTDLG", + "kind": "type", + "in": "cDialogPrint", + "vis": "private", + "public": false, + "file": "more/cDialogPrint.twin", + "signature": "Private Type PRINTDLG", + "fields": [ + {"name":"lStructSize","vis":"public","signature":"lStructSize As Long"}, + {"name":"hwndOwner","vis":"public","signature":"hwndOwner As LongPtr"}, + {"name":"hDevMode","vis":"public","signature":"hDevMode As LongPtr"}, + {"name":"hDevNames","vis":"public","signature":"hDevNames As LongPtr"}, + {"name":"hDC","vis":"public","signature":"hDC As LongPtr"}, + {"name":"flags","vis":"public","signature":"flags As Long"}, + {"name":"nFromPage","vis":"public","signature":"nFromPage As Integer"}, + {"name":"nToPage","vis":"public","signature":"nToPage As Integer"}, + {"name":"nMinPage","vis":"public","signature":"nMinPage As Integer"}, + {"name":"nMaxPage","vis":"public","signature":"nMaxPage As Integer"}, + {"name":"nCopies","vis":"public","signature":"nCopies As Integer"}, + {"name":"hInstance","vis":"public","signature":"hInstance As LongPtr"}, + {"name":"lCustData","vis":"public","signature":"lCustData As LongPtr"}, + {"name":"lpfnPrintHook","vis":"public","signature":"lpfnPrintHook As LongPtr"}, + {"name":"lpfnSetupHook","vis":"public","signature":"lpfnSetupHook As LongPtr"}, + {"name":"lpPrintTemplateName","vis":"public","signature":"lpPrintTemplateName As LongPtr"}, + {"name":"lpSetupTemplateName","vis":"public","signature":"lpSetupTemplateName As LongPtr"}, + {"name":"hPrintTemplate","vis":"public","signature":"hPrintTemplate As LongPtr"}, + {"name":"hSetupTemplate","vis":"public","signature":"hSetupTemplate As LongPtr"} + ], + "members": [] + }, + { + "name": "PrintRange", + "kind": "enum", + "in": "cDialogPrint", + "vis": "public", + "file": "more/cDialogPrint.twin", + "signature": "Public Enum PrintRange", + "values": [ + {"name":"AllPages","value":"0"}, + {"name":"Selection","value":"1"}, + {"name":"PageRange","value":"2"} + ] + }, + { + "name": "cDialogPrintData", + "kind": "type", + "in": "cDialogPrint", + "vis": "private", + "public": false, + "file": "more/cDialogPrint.twin", + "signature": "Private Type cDialogPrintData", + "fields": [ + {"name":"mOwnerHwnd","vis":"public","signature":"mOwnerHwnd As LongPtr"}, + {"name":"mFromPage","vis":"public","signature":"mFromPage As Long"}, + {"name":"mToPage","vis":"public","signature":"mToPage As Long"}, + {"name":"mMinPage","vis":"public","signature":"mMinPage As Long"}, + {"name":"mMaxPage","vis":"public","signature":"mMaxPage As Long"}, + {"name":"mCopies","vis":"public","signature":"mCopies As Long"}, + {"name":"mCollate","vis":"public","signature":"mCollate As Boolean"}, + {"name":"mPrintToFile","vis":"public","signature":"mPrintToFile As Boolean"}, + {"name":"mPrintRange","vis":"public","signature":"mPrintRange As PrintRange"}, + {"name":"mAllowSelection","vis":"public","signature":"mAllowSelection As Boolean"}, + {"name":"mAllowPageRange","vis":"public","signature":"mAllowPageRange As Boolean"}, + {"name":"mAllowPrintToFile","vis":"public","signature":"mAllowPrintToFile As Boolean"}, + {"name":"mHidePrintToFile","vis":"public","signature":"mHidePrintToFile As Boolean"}, + {"name":"mReturnDC","vis":"public","signature":"mReturnDC As Boolean"}, + {"name":"mhDC","vis":"public","signature":"mhDC As LongPtr"}, + {"name":"mLastError","vis":"public","signature":"mLastError As Long"}, + {"name":"mCancelled","vis":"public","signature":"mCancelled As Boolean"} + ], + "members": [] + }, + { + "name": "cDialogProgress", + "kind": "class", + "vis": "public", + "file": "more/cDialogProgress.twin", + "signature": "Public Class cDialogProgress", + "attrs": [{"name":"COMCreatable","args":"False"}], + "members": [ + {"name":"Cancel","kind":"sub","vis":"public","signature":"Public Sub Cancel()"}, + {"name":"CancelClicked","kind":"event","vis":"public","signature":"Public Event CancelClicked()"}, + {"name":"Cancelled","kind":"property","vis":"public","signature":"Public Property Get Cancelled() As Boolean","returnType":"Boolean","accessor":"Get"}, + {"name":"Class_Initialize","kind":"sub","vis":"private","signature":"Private Sub Class_Initialize()"}, + {"name":"Complete","kind":"sub","vis":"public","signature":"Public Sub Complete()"}, + {"name":"DetailMessage","kind":"property","vis":"public","signature":"Public Property Get DetailMessage() As String","returnType":"String","accessor":"Get"}, + {"name":"DetailMessage","kind":"property","vis":"public","signature":"Public Property Let DetailMessage(ByVal value As String)","accessor":"Let"}, + {"name":"FormatTime","kind":"function","vis":"private","signature":"Private Function FormatTime(ByVal seconds As Long) As String","returnType":"String"}, + {"name":"GetDescription","kind":"function","vis":"public","signature":"Public Function GetDescription() As String","returnType":"String"}, + {"name":"GetElapsedTime","kind":"function","vis":"public","signature":"Public Function GetElapsedTime() As Long","returnType":"Long"}, + {"name":"GetElapsedTimeString","kind":"function","vis":"public","signature":"Public Function GetElapsedTimeString() As String","returnType":"String"}, + {"name":"GetRemainingTime","kind":"function","vis":"public","signature":"Public Function GetRemainingTime() As Long","returnType":"Long"}, + {"name":"GetRemainingTimeString","kind":"function","vis":"public","signature":"Public Function GetRemainingTimeString() As String","returnType":"String"}, + {"name":"GetTickCount","kind":"function","vis":"private","declare":true,"signature":"Private Declare PtrSafe Function GetTickCount Lib \"kernel32\" () As Long","returnType":"Long"}, + {"name":"Increment","kind":"sub","vis":"public","signature":"Public Sub Increment()"}, + {"name":"Max","kind":"property","vis":"public","signature":"Public Property Get Max() As Long","returnType":"Long","accessor":"Get"}, + {"name":"Max","kind":"property","vis":"public","signature":"Public Property Let Max(ByVal value As Long)","accessor":"Let"}, + {"name":"Message","kind":"property","vis":"public","signature":"Public Property Get Message() As String","returnType":"String","accessor":"Get"}, + {"name":"Message","kind":"property","vis":"public","signature":"Public Property Let Message(ByVal value As String)","accessor":"Let"}, + {"name":"Min","kind":"property","vis":"public","signature":"Public Property Get Min() As Long","returnType":"Long","accessor":"Get"}, + {"name":"Min","kind":"property","vis":"public","signature":"Public Property Let Min(ByVal value As Long)","accessor":"Let"}, + {"name":"OwnerHwnd","kind":"property","vis":"public","signature":"Public Property Get OwnerHwnd() As LongPtr","returnType":"LongPtr","accessor":"Get"}, + {"name":"OwnerHwnd","kind":"property","vis":"public","signature":"Public Property Let OwnerHwnd(ByVal value As LongPtr)","accessor":"Let"}, + {"name":"Percentage","kind":"property","vis":"public","signature":"Public Property Get Percentage() As Long","returnType":"Long","accessor":"Get"}, + {"name":"PerformStep","kind":"sub","vis":"public","signature":"Public Sub PerformStep()"}, + {"name":"Progress","kind":"property","vis":"public","signature":"Public Property Get Progress() As Long","returnType":"Long","accessor":"Get"}, + {"name":"Progress","kind":"property","vis":"public","signature":"Public Property Let Progress(ByVal value As Long)","accessor":"Let"}, + {"name":"ProgressChanged","kind":"event","vis":"public","signature":"Public Event ProgressChanged(ByVal value As Long)"}, + {"name":"Reset","kind":"sub","vis":"public","signature":"Public Sub Reset()"}, + {"name":"SetPercentage","kind":"sub","vis":"public","signature":"Public Sub SetPercentage(ByVal percent As Long)"}, + {"name":"ShowCancel","kind":"property","vis":"public","signature":"Public Property Get ShowCancel() As Boolean","returnType":"Boolean","accessor":"Get"}, + {"name":"ShowCancel","kind":"property","vis":"public","signature":"Public Property Let ShowCancel(ByVal value As Boolean)","accessor":"Let"}, + {"name":"ShowElapsedTime","kind":"property","vis":"public","signature":"Public Property Get ShowElapsedTime() As Boolean","returnType":"Boolean","accessor":"Get"}, + {"name":"ShowElapsedTime","kind":"property","vis":"public","signature":"Public Property Let ShowElapsedTime(ByVal value As Boolean)","accessor":"Let"}, + {"name":"ShowProgress","kind":"sub","vis":"public","signature":"Public Sub ShowProgress()"}, + {"name":"ShowRemainingTime","kind":"property","vis":"public","signature":"Public Property Get ShowRemainingTime() As Boolean","returnType":"Boolean","accessor":"Get"}, + {"name":"ShowRemainingTime","kind":"property","vis":"public","signature":"Public Property Let ShowRemainingTime(ByVal value As Boolean)","accessor":"Let"}, + {"name":"SimulateProgress","kind":"sub","vis":"public","signature":"Public Sub SimulateProgress(Optional ByVal delayMs As Long = 100)"}, + {"name":"Sleep","kind":"function","vis":"private","declare":true,"signature":"Private Declare PtrSafe Function Sleep Lib \"kernel32\" (ByVal dwMilliseconds As Long) As Long","returnType":"Long"}, + {"name":"Start","kind":"sub","vis":"public","signature":"Public Sub Start()"}, + {"name":"Step","kind":"property","vis":"public","signature":"Public Property Get Step() As Long","returnType":"Long","accessor":"Get"}, + {"name":"Step","kind":"property","vis":"public","signature":"Public Property Let Step(ByVal value As Long)","accessor":"Let"}, + {"name":"Style","kind":"property","vis":"public","signature":"Public Property Get Style() As ProgressStyle","returnType":"ProgressStyle","accessor":"Get"}, + {"name":"Style","kind":"property","vis":"public","signature":"Public Property Let Style(ByVal value As ProgressStyle)","accessor":"Let"}, + {"name":"Title","kind":"property","vis":"public","signature":"Public Property Get Title() As String","returnType":"String","accessor":"Get"}, + {"name":"Title","kind":"property","vis":"public","signature":"Public Property Let Title(ByVal value As String)","accessor":"Let"}, + {"name":"mData","kind":"field","vis":"private","signature":"Private mData As cDialogProgressData"} + ] + }, + { + "name": "ProgressStyle", + "kind": "enum", + "in": "cDialogProgress", + "vis": "public", + "file": "more/cDialogProgress.twin", + "signature": "Public Enum ProgressStyle", + "values": [ + {"name":"ProgressBar","value":"0"}, + {"name":"Marquee","value":"1"}, + {"name":"Steps","value":"2"} + ] + }, + { + "name": "cDialogProgressData", + "kind": "type", + "in": "cDialogProgress", + "vis": "private", + "public": false, + "file": "more/cDialogProgress.twin", + "signature": "Private Type cDialogProgressData", + "fields": [ + {"name":"mTitle","vis":"public","signature":"mTitle As String"}, + {"name":"mMessage","vis":"public","signature":"mMessage As String"}, + {"name":"mDetailMessage","vis":"public","signature":"mDetailMessage As String"}, + {"name":"mProgress","vis":"public","signature":"mProgress As Long"}, + {"name":"mMin","vis":"public","signature":"mMin As Long"}, + {"name":"mMax","vis":"public","signature":"mMax As Long"}, + {"name":"mStep","vis":"public","signature":"mStep As Long"}, + {"name":"mStyle","vis":"public","signature":"mStyle As ProgressStyle"}, + {"name":"mShowCancel","vis":"public","signature":"mShowCancel As Boolean"}, + {"name":"mShowElapsedTime","vis":"public","signature":"mShowElapsedTime As Boolean"}, + {"name":"mShowRemainingTime","vis":"public","signature":"mShowRemainingTime As Boolean"}, + {"name":"mCancelled","vis":"public","signature":"mCancelled As Boolean"}, + {"name":"mStartTime","vis":"public","signature":"mStartTime As Long"}, + {"name":"mOwnerHwnd","vis":"public","signature":"mOwnerHwnd As LongPtr"} + ], + "members": [] + }, + { + "name": "mDialog", + "kind": "module", + "vis": "public", + "file": "mDialog.twin", + "signature": "Public Module mDialog", + "members": [ + {"name":"HtmlToRGB","kind":"sub","vis":"public","signature":"Public Sub HtmlToRGB(ByVal htmlColor As String, ByRef r As Long, ByRef g As Long, ByRef b As Long)"}, + {"name":"InchesToMM","kind":"function","vis":"public","signature":"Public Function InchesToMM(ByVal inches As Double) As Double","returnType":"Double"}, + {"name":"InchesToPoints","kind":"function","vis":"public","signature":"Public Function InchesToPoints(ByVal inches As Double) As Double","returnType":"Double"}, + {"name":"InchesToTwips","kind":"function","vis":"public","signature":"Public Function InchesToTwips(ByVal inches As Double) As Long","returnType":"Long"}, + {"name":"MMToInches","kind":"function","vis":"public","signature":"Public Function MMToInches(ByVal mm As Double) As Double","returnType":"Double"}, + {"name":"MM_PER_INCH","kind":"const","vis":"public","signature":"Public Const MM_PER_INCH As Double = 25.4"}, + {"name":"PAPER_A3_HEIGHT","kind":"const","vis":"public","signature":"Public Const PAPER_A3_HEIGHT As Double = 16.54"}, + {"name":"PAPER_A3_WIDTH","kind":"const","vis":"public","signature":"Public Const PAPER_A3_WIDTH As Double = 11.69"}, + {"name":"PAPER_A4_HEIGHT","kind":"const","vis":"public","signature":"Public Const PAPER_A4_HEIGHT As Double = 11.69"}, + {"name":"PAPER_A4_WIDTH","kind":"const","vis":"public","signature":"Public Const PAPER_A4_WIDTH As Double = 8.27"}, + {"name":"PAPER_LEGAL_HEIGHT","kind":"const","vis":"public","signature":"Public Const PAPER_LEGAL_HEIGHT As Double = 14"}, + {"name":"PAPER_LEGAL_WIDTH","kind":"const","vis":"public","signature":"Public Const PAPER_LEGAL_WIDTH As Double = 8.5"}, + {"name":"PAPER_LETTER_HEIGHT","kind":"const","vis":"public","signature":"Public Const PAPER_LETTER_HEIGHT As Double = 11"}, + {"name":"PAPER_LETTER_WIDTH","kind":"const","vis":"public","signature":"Public Const PAPER_LETTER_WIDTH As Double = 8.5"}, + {"name":"POINTS_PER_INCH","kind":"const","vis":"public","signature":"Public Const POINTS_PER_INCH As Long = 72"}, + {"name":"PointsToInches","kind":"function","vis":"public","signature":"Public Function PointsToInches(ByVal points As Double) As Double","returnType":"Double"}, + {"name":"RGBToHtml","kind":"function","vis":"public","signature":"Public Function RGBToHtml(ByVal r As Long, ByVal g As Long, ByVal b As Long) As String","returnType":"String"}, + {"name":"TWIPS_PER_INCH","kind":"const","vis":"public","signature":"Public Const TWIPS_PER_INCH As Long = 1440"}, + {"name":"TwipsToInches","kind":"function","vis":"public","signature":"Public Function TwipsToInches(ByVal twips As Long) As Double","returnType":"Double"} + ] + }, + { + "name": "COMDLG_FILTERSPEC", + "kind": "type", + "in": "mDialog", + "vis": "public", + "file": "mDialog.twin", + "signature": "Public Type COMDLG_FILTERSPEC", + "fields": [ + {"name":"pszName","vis":"public","signature":"pszName As LongPtr"}, + {"name":"pszSpec","vis":"public","signature":"pszSpec As LongPtr"} + ], + "members": [] + }, + { + "name": "GUID", + "kind": "type", + "in": "mDialog", + "vis": "public", + "file": "mDialog.twin", + "signature": "Public Type GUID", + "fields": [ + {"name":"Data1","vis":"public","signature":"Data1 As Long"}, + {"name":"Data2","vis":"public","signature":"Data2 As Integer"}, + {"name":"Data3","vis":"public","signature":"Data3 As Integer"}, + {"name":"Data4","vis":"public","signature":"Data4(0 To 7) As Byte"} + ], + "members": [] + }, + { + "name": "POINT", + "kind": "type", + "in": "mDialog", + "vis": "public", + "file": "mDialog.twin", + "signature": "Public Type POINT", + "fields": [ + {"name":"X","vis":"public","signature":"X As Long"}, + {"name":"Y","vis":"public","signature":"Y As Long"} + ], + "members": [] + }, + { + "name": "RECT", + "kind": "type", + "in": "mDialog", + "vis": "public", + "file": "mDialog.twin", + "signature": "Public Type RECT", + "fields": [ + {"name":"Left","vis":"public","signature":"Left As Long"}, + {"name":"Top","vis":"public","signature":"Top As Long"}, + {"name":"Right","vis":"public","signature":"Right As Long"}, + {"name":"Bottom","vis":"public","signature":"Bottom As Long"} + ], + "members": [] } ], "aliases": [], diff --git a/indexer/snapshots/contributed/tbRegExp/api.json b/indexer/snapshots/contributed/tbRegExp/api.json index f6296c2ad..4fd98c44d 100644 --- a/indexer/snapshots/contributed/tbRegExp/api.json +++ b/indexer/snapshots/contributed/tbRegExp/api.json @@ -2,8 +2,24 @@ "format": 3, "package": "tbRegExp", "project": "tbRegExp", - "version": "1.4.0.0", + "version": "1.7.0.0", "types": [ + { + "name": "IEnumVARIANT", + "kind": "interface", + "vis": "private", + "public": false, + "file": "RegExp.twin", + "signature": "Private Interface IEnumVARIANT Extends stdole.IUnknown", + "extends": "stdole.IUnknown", + "attrs": [{"name":"InterfaceId","args":"\"00020404-0000-0000-C000-000000000046\""}], + "members": [ + {"name":"Clone","kind":"function","vis":"public","signature":"Function Clone() As IEnumVARIANT","returnType":"IEnumVARIANT"}, + {"name":"Next","kind":"sub","vis":"public","signature":"Sub Next(ByVal celt As Long, ByRef rgVar As Variant, ByRef pCeltFetched As Long)"}, + {"name":"Reset","kind":"sub","vis":"public","signature":"Sub Reset()"}, + {"name":"Skip","kind":"sub","vis":"public","signature":"Sub Skip(ByVal celt As Long)"} + ] + }, { "name": "Match", "kind": "class", @@ -34,13 +50,38 @@ "attrs": [{"name":"PredeclaredId","args":"False"},{"name":"Hidden","args":"False"}], "description": "This class represents a collection of match objects.", "members": [ - {"name":"Class_Initialize","kind":"sub","vis":"private","signature":"Private Sub Class_Initialize()"}, {"name":"Class_Terminate","kind":"sub","vis":"private","signature":"Private Sub Class_Terminate()"}, {"name":"Count","kind":"property","vis":"public","signature":"Public Property Get Count() As Long","returnType":"Long","accessor":"Get","description":"Returns the number of Match objects found in the source string."}, {"name":"Internal_Add","kind":"sub","vis":"friend","signature":"Friend Sub Internal_Add(obj As Match)"}, + {"name":"Internal_ItemAt","kind":"function","vis":"friend","signature":"Friend Function Internal_ItemAt(ByVal pIndex As Long) As Match","returnType":"Match"}, {"name":"Item","kind":"property","vis":"public","signature":"Public Property Get Item(ByVal pItemIndex As Variant) As Match","returnType":"Match","accessor":"Get","attrs":[{"name":"DefaultMember"}],"description":"Returns the Match object associated with the provided index"}, {"name":"NewEnum","kind":"function","vis":"public","signature":"Public Function NewEnum() As IUnknown","returnType":"IUnknown","hidden":true,"attrs":[{"name":"Hidden"},{"name":"Enumerator"}]}, - {"name":"colMatches","kind":"field","vis":"private","signature":"Private colMatches As Collection"} + {"name":"mCap","kind":"field","vis":"private","signature":"Private mCap As Long"}, + {"name":"mCount","kind":"field","vis":"private","signature":"Private mCount As Long"}, + {"name":"mItems","kind":"field","vis":"private","signature":"Private mItems() As Match"} + ] + }, + { + "name": "MatchCollectionEnum", + "kind": "class", + "vis": "private", + "public": false, + "file": "RegExp.twin", + "signature": "Private Class MatchCollectionEnum", + "implements": ["IEnumVARIANT"], + "hidden": true, + "attrs": [{"name":"PredeclaredId","args":"False"},{"name":"Hidden","args":"True"}], + "members": [ + {"name":"Attach","kind":"sub","vis":"friend","signature":"Friend Sub Attach(ByVal src As MatchCollection, ByVal startPos As Long)"}, + {"name":"Class_Terminate","kind":"sub","vis":"private","signature":"Private Sub Class_Terminate()"}, + {"name":"Clone","kind":"function","vis":"private","signature":"Private Function Clone() As IEnumVARIANT Implements IEnumVARIANT.Clone","returnType":"IEnumVARIANT"}, + {"name":"Next","kind":"sub","vis":"private","signature":"Private Sub Next(ByVal celt As Long, ByRef rgVar As Variant, ByRef pCeltFetched As Long) Implements IEnumVARIANT.Next"}, + {"name":"Reset","kind":"sub","vis":"private","signature":"Private Sub Reset() Implements IEnumVARIANT.Reset"}, + {"name":"S_FALSE","kind":"const","vis":"private","signature":"Private Const S_FALSE As Long = 1"}, + {"name":"Skip","kind":"sub","vis":"private","signature":"Private Sub Skip(ByVal celt As Long) Implements IEnumVARIANT.Skip"}, + {"name":"mCount","kind":"field","vis":"private","signature":"Private mCount As Long"}, + {"name":"mPos","kind":"field","vis":"private","signature":"Private mPos As Long"}, + {"name":"mSrc","kind":"field","vis":"private","signature":"Private mSrc As MatchCollection"} ] }, { @@ -52,6 +93,7 @@ "attrs": [{"name":"PredeclaredId","args":"False"},{"name":"Hidden","args":"False"}], "description": "This class exposes a drop-in replacement for VBSript's regular expressions object model.", "members": [ + {"name":"CacheCaptureInfo","kind":"sub","vis":"private","signature":"Private Sub CacheCaptureInfo()"}, {"name":"Class_Initialize","kind":"sub","vis":"private","signature":"Private Sub Class_Initialize()"}, {"name":"DescribeRegexError","kind":"function","vis":"private","signature":"Private Function DescribeRegexError(ByVal errNum As Long) As String","returnType":"String"}, {"name":"DotAll","kind":"property","vis":"public","signature":"Public Property Let DotAll(ByVal pDotAll As Boolean)","accessor":"Let","description":"If 'True', '.' pattern will match across new line charactors. Defaults to 'False'"}, @@ -61,7 +103,6 @@ {"name":"Execute","kind":"function","vis":"public","signature":"Public Function Execute(ByVal sourceString As String) As MatchCollection","returnType":"MatchCollection","description":"Returns a MatchCollection. Uses the Pattern property to search the sourceString."}, {"name":"Flags","kind":"property","vis":"public","signature":"Public Property Let Flags(ByVal pFlags As String)","accessor":"Let","description":"Sets or Gets the options in use: g-global; m-multiline; i-ignore case; s-dot all. Example: 'gm' sets Global and MultiLine to 'True', IgnoreCase and DotAll to 'False'."}, {"name":"Flags","kind":"property","vis":"public","signature":"Public Property Get Flags() As String","returnType":"String","accessor":"Get"}, - {"name":"GetNamedCapturesInfo","kind":"sub","vis":"private","signature":"Private Sub GetNamedCapturesInfo(ByRef colNames As Collection, ByRef colEntryPoints As Collection)"}, {"name":"Global","kind":"property","vis":"public","signature":"Public Property Let Global(ByVal pGlobal As Boolean)","accessor":"Let","description":"If 'True', all matches will be returned. If 'False', only the first match will be returned. Defaults to 'False'"}, {"name":"Global","kind":"property","vis":"public","signature":"Public Property Get Global() As Boolean","returnType":"Boolean","accessor":"Get"}, {"name":"IgnoreCase","kind":"property","vis":"public","signature":"Public Property Let IgnoreCase(ByVal pIgnoreCase As Boolean)","accessor":"Let","description":"If 'True', matches will be case-insensitive. If 'False', matches will be case-sensitive. Defaults to 'False'"}, @@ -76,6 +117,8 @@ {"name":"Replace","kind":"function","vis":"public","signature":"Public Function Replace(ByVal sourceString As String, ByVal replaceString As String) As String","returnType":"String","description":"Replaces matches with replaceString and returns the String with replacements made."}, {"name":"Split","kind":"function","vis":"public","signature":"Public Function Split(ByVal sourceString As String) As Collection","returnType":"Collection","description":"Splits an input string into an array of substrings at the positions defined by a regular expression match. Needs Global Property set to 'True'."}, {"name":"Test","kind":"function","vis":"public","signature":"Public Function Test(ByVal sourceString As String) As Boolean","returnType":"Boolean","description":"Returns 'True' if Pattern finds a match in sourceString, otherwise returns 'False'."}, + {"name":"mCapEntryPoints","kind":"field","vis":"private","signature":"Private mCapEntryPoints() As Long"}, + {"name":"mCapNames","kind":"field","vis":"private","signature":"Private mCapNames() As String"}, {"name":"mCompileAttempted","kind":"field","vis":"private","signature":"Private mCompileAttempted As Boolean"}, {"name":"mDotAll","kind":"field","vis":"private","signature":"Private mDotAll As Boolean"}, {"name":"mGlobal","kind":"field","vis":"private","signature":"Private mGlobal As Boolean"}, @@ -84,6 +127,8 @@ {"name":"mLastErrNumber","kind":"field","vis":"private","signature":"Private mLastErrNumber As Long"}, {"name":"mLastError","kind":"field","vis":"private","signature":"Private mLastError As String"}, {"name":"mMultiLine","kind":"field","vis":"private","signature":"Private mMultiLine As Boolean"}, + {"name":"mNamedCount","kind":"field","vis":"private","signature":"Private mNamedCount As Long"}, + {"name":"mNumCaptures","kind":"field","vis":"private","signature":"Private mNumCaptures As Long"}, {"name":"mPattern","kind":"field","vis":"private","signature":"Private mPattern As String"}, {"name":"regex","kind":"field","vis":"private","signature":"Private regex As StaticRegex.RegexTy"} ] @@ -152,7 +197,7 @@ {"name":"MatchNext","kind":"function","vis":"public","signature":"Public Function MatchNext( ByRef matcherState As MatcherStateTy, ByRef regex As RegexTy, ByRef haystack As String ) As Boolean","returnType":"Boolean"}, {"name":"MatchThenJoin","kind":"function","vis":"public","signature":"Public Function MatchThenJoin( ByRef regex As RegexTy, ByRef haystack As String, Optional ByRef format As String = \"$&\", Optional ByRef delimiter As String = vbNullString, Optional ByVal localMatch As Boolean = False, Optional ByVal multiline As Boolean = False, Optional ByVal dotAll As Boolean = False ) As String","returnType":"String"}, {"name":"MatchThenList","kind":"sub","vis":"public","signature":"Public Sub MatchThenList( ByRef results() As String, ByRef regex As RegexTy, ByRef haystack As String, ByRef formatStrings() As String, Optional ByVal localMatch As Boolean = False, Optional ByVal multiline As Boolean = False, Optional ByVal dotAll As Boolean = False )"}, - {"name":"Parse","kind":"sub","vis":"private","signature":"Private Sub Parse(ByRef lex As LexerContext, ByVal caseInsensitive As Boolean, ByRef ast As ArrayBuffer)"}, + {"name":"Parse","kind":"sub","vis":"private","signature":"Private Sub Parse(ByRef lex As LexerContext, ByVal caseInsensitive As Boolean, ByRef ast As ArrayBuffer, ByRef outReparseWith As Long)"}, {"name":"ParseFormatString","kind":"sub","vis":"private","signature":"Private Sub ParseFormatString(ByRef parsedFormat As ArrayBuffer, ByRef formatString As String, ByRef bytecode() As Long, ByRef pattern As String)"}, {"name":"ParseReRanges","kind":"sub","vis":"private","signature":"Private Sub ParseReRanges(lexCtx As LexerContext, ByRef outBuffer As ArrayBuffer, ByRef nranges As Long, ByVal ignoreCase As Boolean)"}, {"name":"ParseReToken","kind":"sub","vis":"private","signature":"Private Sub ParseReToken(ByRef lexCtx As LexerContext, ByRef outToken As ReToken)"}, @@ -186,6 +231,7 @@ {"name":"TryInitializeRegex","kind":"function","vis":"public","signature":"Public Function TryInitializeRegex( ByRef regex As RegexTy, ByRef pattern As String, Optional ByVal caseInsensitive As Boolean = False ) As Boolean","returnType":"Boolean"}, {"name":"UnicodeInitialize","kind":"sub","vis":"private","signature":"Private Sub UnicodeInitialize()"}, {"name":"UnicodeInitialized","kind":"field","vis":"private","signature":"Private UnicodeInitialized As Boolean"}, + {"name":"UnicodeIsDotLineTerminator","kind":"function","vis":"private","signature":"Private Function UnicodeIsDotLineTerminator(ByVal codepoint As Long) As Boolean","returnType":"Boolean"}, {"name":"UnicodeIsLineTerminator","kind":"function","vis":"private","signature":"Private Function UnicodeIsLineTerminator(ByVal codepoint As Long) As Boolean","returnType":"Boolean"}, {"name":"UnicodeReIsWordchar","kind":"function","vis":"private","signature":"Private Function UnicodeReIsWordchar(c As Long) As Boolean","returnType":"Boolean"}, {"name":"Unionize","kind":"function","vis":"private","signature":"Private Function Unionize(ByRef ary() As Long, ByVal b As Long, ByVal t As Long) As Long","returnType":"Long"}, @@ -459,7 +505,8 @@ {"name":"iEnd","vis":"public","signature":"iEnd As Long"}, {"name":"inputStr","vis":"public","signature":"inputStr As String"}, {"name":"currentCharacter","vis":"public","signature":"currentCharacter As Long"}, - {"name":"identifierTree","vis":"public","signature":"identifierTree As IdentifierTreeTy"} + {"name":"identifierTree","vis":"public","signature":"identifierTree As IdentifierTreeTy"}, + {"name":"nCapturesKnown","vis":"public","signature":"nCapturesKnown As Long"} ], "members": [] }, @@ -484,6 +531,7 @@ {"name":"UNICODE_0","value":"48"}, {"name":"UNICODE_1","value":"49"}, {"name":"UNICODE_7","value":"55"}, + {"name":"UNICODE_8","value":"56"}, {"name":"UNICODE_9","value":"57"}, {"name":"UNICODE_COLON","value":"58"}, {"name":"UNICODE_LT","value":"60"}, @@ -492,6 +540,7 @@ {"name":"UNICODE_QUESTION","value":"63"}, {"name":"UNICODE_UC_A","value":"65"}, {"name":"UNICODE_UC_B","value":"66"}, + {"name":"UNICODE_UC_C","value":"67"}, {"name":"UNICODE_UC_D","value":"68"}, {"name":"UNICODE_UC_F","value":"70"}, {"name":"UNICODE_UC_S","value":"83"}, @@ -685,6 +734,7 @@ {"name":"REGEX_ERR_INVALID_QUANTIFIER_NO_ATOM","value":"REGEX_ERR + 12"}, {"name":"REGEX_ERR_INVALID_IDENTIFIER","value":"REGEX_ERR + 13"}, {"name":"REGEX_ERR_INVALID_MODIFIER","value":"REGEX_ERR + 14"}, + {"name":"REGEX_ERR_INVALID_BACKREFERENCE","value":"REGEX_ERR + 15"}, {"name":"REGEX_ERR_INTERNAL_LOGIC_ERR","value":"REGEX_ERR + 20"}, {"name":"REGEX_ERR_INVALID_FLAG_ERR","value":"REGEX_ERR + 30"}, {"name":"REGEX_ERR_INVALID_REPLACEMENT_STRING","value":"REGEX_ERR + 31"}, @@ -844,16 +894,38 @@ "attrs": [{"name":"PredeclaredId","args":"False"},{"name":"Hidden","args":"False"}], "description": "This class provides the means to access SubMatch (Group) values.", "members": [ - {"name":"Class_Terminate","kind":"sub","vis":"private","signature":"Private Sub Class_Terminate()"}, {"name":"Count","kind":"property","vis":"public","signature":"Public Property Get Count() As Long","returnType":"Long","accessor":"Get","description":"Returns the number of SubMatch objects found in the source string."}, {"name":"Internal_SetData","kind":"sub","vis":"friend","signature":"Friend Sub Internal_SetData(ByRef vals() As String, ByRef keys() As String, ByVal count As Long)"}, + {"name":"Internal_ValueAt","kind":"function","vis":"friend","signature":"Friend Function Internal_ValueAt(ByVal pIndex As Long) As String","returnType":"String"}, {"name":"Item","kind":"property","vis":"public","signature":"Public Property Get Item(ByVal pItemIndex As Variant) As String","returnType":"String","accessor":"Get","attrs":[{"name":"DefaultMember"}],"description":"Returns the SubMatch string associated with the provided index. Named groups are allowed."}, {"name":"NewEnum","kind":"function","vis":"public","signature":"Public Function NewEnum() As IUnknown","returnType":"IUnknown","hidden":true,"attrs":[{"name":"Hidden"},{"name":"Enumerator"}]}, {"name":"mCap","kind":"field","vis":"private","signature":"Private mCap As Long"}, {"name":"mCount","kind":"field","vis":"private","signature":"Private mCount As Long"}, {"name":"mKeys","kind":"field","vis":"private","signature":"Private mKeys() As String"}, - {"name":"mVals","kind":"field","vis":"private","signature":"Private mVals() As String"}, - {"name":"oEnumColl","kind":"field","vis":"private","signature":"Private oEnumColl As Collection"} + {"name":"mVals","kind":"field","vis":"private","signature":"Private mVals() As String"} + ] + }, + { + "name": "SubMatchesEnum", + "kind": "class", + "vis": "private", + "public": false, + "file": "RegExp.twin", + "signature": "Private Class SubMatchesEnum", + "implements": ["IEnumVARIANT"], + "hidden": true, + "attrs": [{"name":"PredeclaredId","args":"False"},{"name":"Hidden","args":"True"}], + "members": [ + {"name":"Attach","kind":"sub","vis":"friend","signature":"Friend Sub Attach(ByVal src As SubMatches, ByVal startPos As Long)"}, + {"name":"Class_Terminate","kind":"sub","vis":"private","signature":"Private Sub Class_Terminate()"}, + {"name":"Clone","kind":"function","vis":"private","signature":"Private Function Clone() As IEnumVARIANT Implements IEnumVARIANT.Clone","returnType":"IEnumVARIANT"}, + {"name":"Next","kind":"sub","vis":"private","signature":"Private Sub Next(ByVal celt As Long, ByRef rgVar As Variant, ByRef pCeltFetched As Long) Implements IEnumVARIANT.Next"}, + {"name":"Reset","kind":"sub","vis":"private","signature":"Private Sub Reset() Implements IEnumVARIANT.Reset"}, + {"name":"S_FALSE","kind":"const","vis":"private","signature":"Private Const S_FALSE As Long = 1"}, + {"name":"Skip","kind":"sub","vis":"private","signature":"Private Sub Skip(ByVal celt As Long) Implements IEnumVARIANT.Skip"}, + {"name":"mCount","kind":"field","vis":"private","signature":"Private mCount As Long"}, + {"name":"mPos","kind":"field","vis":"private","signature":"Private mPos As Long"}, + {"name":"mSrc","kind":"field","vis":"private","signature":"Private mSrc As SubMatches"} ] }, { diff --git a/scripts/check_cli.mjs b/scripts/check_cli.mjs index 6d2161460..311a390d8 100644 --- a/scripts/check_cli.mjs +++ b/scripts/check_cli.mjs @@ -41,6 +41,7 @@ import path from "node:path"; import { pathToFileURL } from "node:url"; import { parseArgs } from "node:util"; import { DEFAULTS, parseCommandLine } from "../builder/command-line.mjs"; +import { destinationTrees } from "../builder/write.mjs"; import { CliError, choiceOption, @@ -716,6 +717,34 @@ function capture(fn) { "tbdocs keeps the flags the negations set", pick(["--no-offline", "--no-pdf"], ["skipOffline", "skipPdf"]) === show({ skipOffline: true, skipPdf: true }), ); + + // The negations reach the task that makes the trees: prepDest once cleaned + // and recreated -offline and -pdf whatever they said, which left + // two empty folders beside every --no-offline --no-pdf build. + const trees = (args, serve = false) => { + const o = parsed(args); + return show(destinationTrees("d", { skipOffline: !!o.skipOffline, skipPdf: !!o.skipPdf, serve })); + }; + check( + "tbdocs prepares all three trees by default", + trees([]) === show({ roots: ["d", "d-offline", "d-pdf"], gone: [] }), + trees([]), + ); + check( + "tbdocs with --no-offline --no-pdf prepares the online tree alone, and removes the other two", + trees(["--no-offline", "--no-pdf"]) === show({ roots: ["d"], gone: ["d-offline", "d-pdf"] }), + trees(["--no-offline", "--no-pdf"]), + ); + check( + "tbdocs with --no-pdf keeps the offline tree", + trees(["--no-pdf"]) === show({ roots: ["d", "d-offline"], gone: ["d-pdf"] }), + trees(["--no-pdf"]), + ); + check( + "tbdocs --serve prepares its one tree and removes nothing beside it", + trees(["--no-offline", "--no-pdf"], true) === show({ roots: ["d"], gone: [] }), + trees(["--no-offline", "--no-pdf"], true), + ); } // ------------------------------------------------------------ the recorded cases diff --git a/scripts/check_page_baseline.mjs b/scripts/check_page_baseline.mjs index 3fbc7959f..ec3e29a2d 100644 --- a/scripts/check_page_baseline.mjs +++ b/scripts/check_page_baseline.mjs @@ -24,9 +24,11 @@ // node scripts/check_page_baseline.mjs import { readFile } from "node:fs/promises"; -import { GUARDED_SRC } from "../builder/baseline.mjs"; +import path from "node:path"; +import { baselineWrites, GUARDED_DEST, GUARDED_SRC } from "../builder/baseline.mjs"; import { checkPageBaseline } from "../builder/page-baseline.mjs"; import { exitOnCrash, parseCli, printHelpAndExit, withUsageError } from "../lib/cli.mjs"; +import { REPO_ROOT } from "../lib/repo-paths.mjs"; import { baselineFixture, createProbes } from "./lib/gate-probes.mjs"; exitOnCrash(); @@ -145,6 +147,42 @@ await withBaseline(null, async (file) => { ); }); +// --- only a build into docs/_site writes ------------------------------------- +// +// Agents building side by side into folders of their own each rewrote the +// committed baseline, and each had to undo the others' rise after every build. + +{ + const w = baselineWrites({ destRoot: GUARDED_DEST }); + check("a build into docs/_site may write", w.write && w.heldBack === "", JSON.stringify(w)); + const other = baselineWrites({ destRoot: path.join(REPO_ROOT, ".claude", "agent-site") }); + check( + "a build into another --dest may not, and says where it went", + !other.write && other.heldBack.includes(".claude/agent-site"), + JSON.stringify(other), + ); + const ci = baselineWrites({ destRoot: GUARDED_DEST, ci: true }); + check( + "CI may not write into docs/_site either, and says nothing", + !ci.write && ci.heldBack === "", + JSON.stringify(ci), + ); + const serve = baselineWrites({ destRoot: GUARDED_DEST, serve: true }); + check("--serve may not write", !serve.write && serve.heldBack === "", JSON.stringify(serve)); +} + +await withBaseline(BASE, async (file) => { + const { write, heldBack } = baselineWrites({ destRoot: path.join(REPO_ROOT, ".claude", "agent-site") }); + const r = await checkPageBaseline({ ...BASE, pages: 920, write, heldBack, file }); + check( + "a rise built into another --dest leaves the file alone and says so", + !r.failed && (await readJson(file)).pages === 908 && r.text.includes("not raised") && r.text.includes("920"), + r.text.trim(), + ); + const fall = await checkPageBaseline({ ...BASE, pages: 871, write, heldBack, file }); + check("a fall built into another --dest still fails", fall.failed, fall.text.trim()); +}); + // --- report ------------------------------------------------------------------ process.exit(report()); diff --git a/test/example-projects/contributed-TBMANLIB/Settings b/test/example-projects/contributed-TBMANLIB/Settings index 0970338be..b016e4c4c 100644 --- a/test/example-projects/contributed-TBMANLIB/Settings +++ b/test/example-projects/contributed-TBMANLIB/Settings @@ -51,11 +51,11 @@ "autoCtlExt": true, "id": "{EC8B1351-80F1-46CA-8634-DEC5271616AA}", "licence": "MIT", - "name": "[IMPORTED] TBMAN", + "name": "[IMPORTED] ✨ – A Rapid Development Toolkit for TwinBasic", "path32": "/Packages/TBMANLIB", "path64": "/Packages/TBMANLIB", "symbolId": "TBMANLIB", - "versionBuild": "5", + "versionBuild": "29", "versionMajor": "1", "versionMinor": "0", "versionRevision": "0" diff --git a/test/example-projects/contributed-TBMANLIB_Dialog/Settings b/test/example-projects/contributed-TBMANLIB_Dialog/Settings index 3a0c91fae..698c701df 100644 --- a/test/example-projects/contributed-TBMANLIB_Dialog/Settings +++ b/test/example-projects/contributed-TBMANLIB_Dialog/Settings @@ -55,9 +55,9 @@ "path32": "/Packages/TBMANLIB_Dialog", "path64": "/Packages/TBMANLIB_Dialog", "symbolId": "TBMANLIB_Dialog", - "versionBuild": "1", + "versionBuild": "0", "versionMajor": "1", - "versionMinor": "0", + "versionMinor": "1", "versionRevision": "0" } ], diff --git a/test/example-projects/contributed-tbRegExp/Settings b/test/example-projects/contributed-tbRegExp/Settings index 52a0f2aaa..e4cd86dbc 100644 --- a/test/example-projects/contributed-tbRegExp/Settings +++ b/test/example-projects/contributed-tbRegExp/Settings @@ -57,7 +57,7 @@ "symbolId": "tbRegExp", "versionBuild": "0", "versionMajor": "1", - "versionMinor": "4", + "versionMinor": "7", "versionRevision": "0" } ], diff --git a/test/package-probes/TBMANLIB_Dialog/README.md b/test/package-probes/TBMANLIB_Dialog/README.md index a7567e0e0..003df310f 100644 --- a/test/package-probes/TBMANLIB_Dialog/README.md +++ b/test/package-probes/TBMANLIB_Dialog/README.md @@ -1,9 +1,11 @@ # TBMANLIB_Dialog probe -What the contributed package TBMANLIB_Dialog's **cDialog** does at run time: the Open, Save As -and Browse For Folder dialogs, opened in a built exe on a private desktop and operated from +What the contributed package TBMANLIB_Dialog's classes do at run time: **cDialog** and **cDialogEx** +(the Open, Save As and folder dialogs), the five common dialogs of Windows, and the dialogs of the +package's own making (the countdown message box, the date and time, input box and password +dialogs, and a progress dialog with no window), opened in a built exe on a private desktop and operated from inside the exe. The pages under `docs/Reference/Contributed/TBMANLIB_Dialog/` state what this -probe printed, on package 1.0.0.1 and BETA 1005, in 32-bit and 64-bit builds. Run it again when +probe printed, on package 1.1.0.0 and BETA 1005, in 32-bit and 64-bit builds. Run it again when the section's `indexed_from` moves to a new version of the package. It is outside every gate and CI: it needs Windows, a twinBASIC install (the newest @@ -13,18 +15,33 @@ store (`node indexer/twin-index.mjs sync`). ## Running it ```sh -node test/package-probes/TBMANLIB_Dialog/probe.mjs 13,1,2,3 4,5 6 6d,7 8,9,9e 11,12,12b,12c -node test/package-probes/TBMANLIB_Dialog/probe.mjs --arch win64 --port 9722 13,1,2 3 4 5 6,6d 7,8 9 9e 11,12,12b,12c +node test/package-probes/TBMANLIB_Dialog/probe.mjs 13,1,2,3 4,5 6 6d,7 8,9,9e 11,12,12b,12c 14,15 16,17 18 +node test/package-probes/TBMANLIB_Dialog/probe.mjs --timeout 35 x0,y1 x1,x2 x3,x4 x5 x6 x7 x8 x9 x10 x11,x12 x13,x14 x15,x16 y2,y3 +node test/package-probes/TBMANLIB_Dialog/probe.mjs --timeout 35 y4 +node test/package-probes/TBMANLIB_Dialog/probe.mjs --arch win64 --port 9722 13,1,2 3 4 5 6,6d 7,8 9 9e 11,12,12b,12c 14 15 16,17 18 +node test/package-probes/TBMANLIB_Dialog/probe.mjs c1,c11,c21,c31,c41 c2 c3 c4 +node test/package-probes/TBMANLIB_Dialog/probe.mjs d0,d1 d2 d3,d4 d5 d10 d11 d12 d20 d21 d22,d23 d30 d31 d32 d33 d34 d40 ``` -In a 64-bit build, scenarios 3, 4, 9 and 9e each end their run when they reach -**ShowBrowseForFolder**, so each has a run of its own (3 and 4 print their Open and Save As -parts first). +The first run of 1.1.0.0 ran every scenario in both builds without a crash, apart from `y4`. +In 1.0.0.1 a 64-bit build ended its run at **ShowBrowseForFolder** (scenarios 3, 4, 9 and 9e +each needed a run of their own); in 1.1.0.0 it does not, and those scenarios can share a run. +**`y4` ends its own run**: it calls `SetDefaultFolder` with the true `IShellItem` identifier, +which crashes inside `windows.storage.dll` (exit code 0xC0000005, in 32-bit and 64-bit builds), +and that crash is its answer; run it alone. Each argument is one run of the exe; the scenario ids are listed at the top of -[`src/Probe.twin`](src/Probe.twin), each the number of a question it answers. A build takes -about ten seconds and a run under 25 s; both commands take a minute or two. Exit 1 means a run -crashed or timed out, which is the expected answer for those four in a 64-bit build. +[`src/Probe.twin`](src/Probe.twin), each the number of a question it answers, and the `x` and +`y` scenarios (**cDialogEx** and **mDialog**) are in [`src/ProbeEx.twin`](src/ProbeEx.twin). A +build takes about ten seconds and a run under 25 s (`--timeout` changes that; the `x` scenarios +open several slower dialogs and are run with 35); both commands take a minute or two. Exit 1 +means a run crashed or timed out. + +`--overlay ` copies the files of `` over the package's own sources in the work tree (the +path under `Sources/` is the path under ``), to try a change to the package's code; keep +`` outside the repository. It ran the control experiment of **cDialogFindReplace**: the +package's file with its two `FINDREPLACE` structures moved from local variables to class level, +which makes the owner window receive the messages (c43, c44). - [`probe.mjs`](probe.mjs) writes the project (the `contributed-TBMANLIB_Dialog` sample template's Settings, the package from the store, `src/`), builds it with `tbrun` and runs the exe once for @@ -39,8 +56,47 @@ crashed or timed out, which is the expected answer for those four in a 64-bit bu file-type list, types into it and presses its buttons. It cancels a dialog after 3.6 s and ends the process after 5.4 s, so nothing waits on a dialog it cannot operate. +## The common dialogs (scenarios c1 to c48) + +[`src/ProbeB1.twin`](src/ProbeB1.twin) holds the scenarios for the five dialogs of Windows that the +package wraps: **cDialogColor** (c1 to c4), **cDialogFont** (c11 to c17), **cDialogPrint** (c21 to +c27), **cDialogPageSetup** (c31 to c35) and **cDialogFindReplace** (c41 to c48); the list at the top +of `src/Probe.twin` says what each asks. [`src/DrvB1.twin`](src/DrvB1.twin) is a scripted dialog +driver for them: a scenario names a sequence of steps (record the controls, read a list, set the text +of a control, click a button, select a combo box item, wait, press OK or Cancel) and the driver runs +each step on a timer tick while the dialog window exists. A modeless dialog (Find, Replace) is left +by posting `WM_QUIT` two ticks after its window is gone. Every global of the driver begins with +`D`: an unprefixed public name in a probe module collides with `Auto.twin` (TB5073). + +## The dialogs of the package's own making (scenarios d0 to d40) + +`d1..d5` **cDialogCountdownMsgBox** (the message box of `MessageBoxTimeoutW`, its countdown text, +every `Show*` method, the buttons, closing, overflow, the owner), `d10..d12d` **cDialogDateTime** +(the InputBox, OK, Cancel, the entries it refuses, AM and PM), `d20..d23` **cDialogInputBox** (the +hint and length text, validation and three attempts, the quick methods), `d30..d34b` +**cDialogPassword** (the mask, attempts, the confirmation box, the change dialog, `PasswordChar`) +and `d40` **cDialogProgress** (a class with no window: its logic and events, run in both builds); +`d0` and `d0b` show how the run-time's InputBox, MsgBox and message-box windows are built. Files: +[`src/AutoD.twin`](src/AutoD.twin) (the timer driver, with scripts such as `t=text|id=1;id=2` for +each window in turn), `ProbeD.twin` (dispatcher and `d0..d5`), `ProbeDT.twin`, `ProbeIB.twin`, +`ProbePW.twin`, `ProbePG.twin` and `ProgSink.twin` (an event sink for `d40`). The scenarios gave +the same results in 32-bit and 64-bit builds. A window after the second appears about two seconds +late. + ## Rules +- A run is ended at `--timeout` seconds (25 by default): keep to about five dialog episodes a run + (about eight windows for the `d` scenarios), and one id, or a few cheap ones, per argument. +- **Never print.** Nothing sends anything to a printer. Print and Page Setup are answered with OK only + where the class reads the structure and frees what the dialog allocated; no scenario calls + `StartDoc` or changes the default printer or a printer's settings. c26 asks for a printer device + context only when the default printer is a Microsoft virtual one, and deletes the context at once. + The Print and Page Setup dialogs list the computer's printers, their drivers and ports: `DMask` + (on for c21 to c35) hides the names in what a scenario prints; copy none of that into a page or a + report. +- `Probe.twin` declares the result of **OpenFile** in scenario 11 as a **Variant**: the method + returns a **LongPtr** in 1.1.0.0, which a **Long** does not take in a 64-bit build. + - Never call **OpenFile** or **SelectAndOpenFile** on a path that exists, or on `""`: it starts a program. `OpenFile("")` opens a File Explorer window, and the COM-started `explorer.exe` behind it outlives the exe, with its window on the private desktop where nobody can close diff --git a/test/package-probes/TBMANLIB_Dialog/probe.mjs b/test/package-probes/TBMANLIB_Dialog/probe.mjs index f0fc1f4a4..0836c48d9 100644 --- a/test/package-probes/TBMANLIB_Dialog/probe.mjs +++ b/test/package-probes/TBMANLIB_Dialog/probe.mjs @@ -24,13 +24,18 @@ import { withUsageError, } from "../../../lib/cli.mjs"; -const USAGE = `usage: node test/package-probes/TBMANLIB_Dialog/probe.mjs [--arch win32|win64] [--port N] [ ...] [-h, --help] +const USAGE = `usage: node test/package-probes/TBMANLIB_Dialog/probe.mjs [--arch win32|win64] [--port N] [--timeout S] [--overlay D] [ ...] [-h, --help] Builds the TBMANLIB_Dialog probe (src/) with tbrun, then runs the exe on a private desktop once for each , a comma-separated list of the scenario ids listed at the top of src/Probe.twin. --arch A win32 (default) or win64 --port N the IDE's DevTools port (default 9720); it also keys the work folders + --timeout S end each run of the exe after S seconds (default 25; the cDialogEx scenarios, + which open several slower dialogs, are run with 35) + --overlay D a folder whose files replace the package's own sources in the work tree (the path + under Sources/ is the path under D): to try a change to the package's code. Keep D + outside the repository; the package's code is not the repository's to commit. -h, --help print this text and exit Exit codes: @@ -47,6 +52,8 @@ const { values, positionals } = withUsageError( options: { arch: { type: "string", default: "win32" }, port: { type: "string", default: "9720" }, + timeout: { type: "string", default: "25" }, + overlay: { type: "string" }, help: { type: "boolean", short: "h" }, }, positionals: { min: 1 }, @@ -55,10 +62,11 @@ const { values, positionals } = withUsageError( usageError, ); if (values.help) printHelpAndExit(USAGE); -const { arch, port } = withUsageError( +const { arch, port, timeout } = withUsageError( () => ({ arch: choiceOption(values.arch, { option: "--arch", choices: ["win32", "win64"] }), port: numberOption(values.port, { option: "--port", integer: true, min: 1024, max: 65535 }), + timeout: numberOption(values.timeout, { option: "--timeout", integer: true, min: 5, max: 120 }), }), usageError, ); @@ -82,8 +90,17 @@ cpSync( const embedded = path.join(tree, "Packages", "TBMANLIB_Dialog"); mkdirSync(path.join(embedded, "Sources"), { recursive: true }); cpSync(path.join(pkg, "Settings"), path.join(embedded, "Settings")); +// Since 1.1.0.0 the sources hold folders (more/, test/): copy them whole. for (const f of readdirSync(path.join(pkg, "sources"))) - cpSync(path.join(pkg, "sources", f), path.join(embedded, "Sources", f)); + cpSync(path.join(pkg, "sources", f), path.join(embedded, "Sources", f), { recursive: true }); +if (values.overlay) { + const overlay = path.resolve(values.overlay); + if (!existsSync(overlay)) { + console.error(`probe: no overlay folder ${overlay}`); + process.exit(2); + } + cpSync(overlay, path.join(embedded, "Sources"), { recursive: true }); +} for (const f of readdirSync(path.join(here, "src"))) { const text = readFileSync(path.join(here, "src", f), "utf8").replace(/\r?\n/g, "\r\n"); writeFileSync(path.join(tree, "Sources", f), text, "utf8"); @@ -113,7 +130,15 @@ for (const ids of positionals) { console.log(`######## ${arch} run: ${ids}`); const run = spawnSync( process.execPath, - [path.join(here, "run-exe.mjs"), path.join(outDir, exe), ids, "--timeout", "25", "--desktop", `dlgprobe-${port}`], + [ + path.join(here, "run-exe.mjs"), + path.join(outDir, exe), + ids, + "--timeout", + String(timeout), + "--desktop", + `dlgprobe-${port}`, + ], { cwd: repo, stdio: "inherit" }, ); if (run.status === 2 || run.status === null) { diff --git a/test/package-probes/TBMANLIB_Dialog/src/Auto.twin b/test/package-probes/TBMANLIB_Dialog/src/Auto.twin index b484aaaec..f1bb0eff1 100644 --- a/test/package-probes/TBMANLIB_Dialog/src/Auto.twin +++ b/test/package-probes/TBMANLIB_Dialog/src/Auto.twin @@ -44,6 +44,7 @@ Module Auto Private Declare PtrSafe Function IsWindow Lib "user32" (ByVal hWnd As LongPtr) As Long Private Declare PtrSafe Function GetDlgCtrlID Lib "user32" (ByVal hWnd As LongPtr) As Long Private Declare PtrSafe Function GetParent Lib "user32" (ByVal hWnd As LongPtr) As LongPtr + Private Declare PtrSafe Function GetWindow Lib "user32" (ByVal hWnd As LongPtr, ByVal uCmd As Long) As LongPtr Private Declare PtrSafe Function GetDlgItem Lib "user32" (ByVal hDlg As LongPtr, ByVal nIDDlgItem As Long) As LongPtr Private Declare PtrSafe Function EndMenu Lib "user32" () As Long Private Declare PtrSafe Function SendMessageW Lib "user32" (ByVal hWnd As LongPtr, ByVal Msg As Long, ByVal wParam As LongPtr, ByVal lParam As LongPtr) As LongPtr @@ -53,6 +54,8 @@ Module Auto Private Const WM_GETTEXTLENGTH As Long = &HE Private Const WM_SETTEXT As Long = &HC Private Const WM_CLOSE As Long = &H10 + Private Const WM_GETICON As Long = &H7F + Private Const GW_OWNER As Long = 4 Private Const WM_LBUTTONDOWN As Long = &H201 Private Const WM_LBUTTONUP As Long = &H202 Private Const BM_CLICK As Long = &HF5 @@ -76,6 +79,11 @@ Module Auto ' Settings the probe makes before an episode. Public gDump As Boolean ' list every child window of the dialog + Public gNavDir As String ' a folder the driver first types into the file-name box and opens (Vista dialogs), then acts + Public gShowIcon As Boolean ' print the dialog's big and small icon handles when it is acted on + Public gShowOwner As Boolean ' print the dialog's owner window when it is acted on + Public gOwnerSeen As LongPtr ' the owner window the last such dialog had + Public gOwnerWant As LongPtr ' the window the probe made to own it Public gMsgSeq(0 To 7) As Long ' the button each message box is answered with: 1 OK, 2 Cancel, 6 Yes, 7 No Public gMsgSeqN As Long @@ -94,6 +102,8 @@ Module Auto Private mMainH As LongPtr Private mMainTick As Long Private mActedTick As Long + Private mNavStage As Long + Private mNavTick As Long Private mMsgH As LongPtr Private mMsgTick As Long Private mMsgDone As Boolean @@ -112,9 +122,22 @@ Module Auto TbRun.Out s End Sub - ' A string in brackets, with its length; a null character is shown as . + ' A string in brackets, with its length; a null character is shown as , and a character + ' above 126 as \uXXXX, so that the file the runner reads is plain ASCII. Public Function Q(ByVal s As String) As String - Q = "[" & Replace(s, vbNullChar, "") & "] len=" & Len(s) + Dim i As Long + Dim c As Long + Dim t As String + For i = 1 To Len(s) + c = AscW(Mid$(s, i, 1)) + If c < 0 Then c = c + 65536 + If c > 126 Then + t = t & "\u" & Right$("0000" & Hex$(c), 4) + Else + t = t & Mid$(s, i, 1) + End If + Next + Q = "[" & Replace(t, vbNullChar, "") & "] len=" & Len(s) End Function ' The current directory of the process, from the API (CurDir is VB's own view of it). @@ -146,6 +169,8 @@ Module Auto mMainH = 0 mMainTick = 0 mActedTick = 0 + mNavStage = 0 + mNavTick = 0 mMsgH = 0 gMsgCount = 0 gSeenMain = False @@ -338,16 +363,56 @@ Module Auto ' Acting '========================================================================================= - Private Sub DoAct(ByVal h As LongPtr) + ' The file-name box is an edit box inside a combo box; the Vista dialogs also hold a search box. + Private Function NameEdit(ByVal h As LongPtr) As LongPtr Dim list(0 To 255) As LongPtr Dim i As Long - Dim n As Long + Dim n As Long = Children(h, list) + For i = 0 To n - 1 + If ClassOf(list(i)) = "Edit" And IsWindowVisible(list(i)) <> 0 Then + If ClassOf(GetParent(list(i))) = "ComboBox" Then NameEdit = list(i) : Exit Function + End If + Next + For i = 0 To n - 1 + If ClassOf(list(i)) = "Edit" And IsWindowVisible(list(i)) <> 0 Then NameEdit = list(i) : Exit Function + Next + End Function + + ' The folder that a Vista dialog shows: the text of its address band. + Private Sub RecordAddress(ByVal h As LongPtr, ByVal what As String) + Dim list(0 To 255) As LongPtr + Dim i As Long + Dim n As Long = Children(h, list) + For i = 0 To n - 1 + If ClassOf(list(i)) = "ToolbarWindow32" Then + If Left$(TextOf(list(i)), 9) = "Address: " Then Say " " & what & ": " & Q(TextOf(list(i))) + End If + Next + End Sub + + ' Types a folder into the file-name box and presses the default button, which opens the folder. + Private Sub DoNav(ByVal h As LongPtr) + Dim ed As LongPtr = NameEdit(h) + RecordAddress h, "before navigating" + If ed <> 0 Then + SendMessageW ed, WM_SETTEXT, 0, StrPtr(gNavDir) + Say " navigating: typed " & Q(gNavDir) + PressId h, 1 + Else + Say " NO EDIT BOX to navigate with" + End If + End Sub + + Private Sub DoAct(ByVal h As LongPtr) Dim ed As LongPtr + If Len(gNavDir) > 0 Then RecordAddress h, "after navigating" Else RecordAddress h, "address" + If gShowIcon Then Say " icons: big=" & SendMessageW(h, WM_GETICON, 1, 0) & " small=" & SendMessageW(h, WM_GETICON, 0, 0) + If gShowOwner Then + gOwnerSeen = GetWindow(h, GW_OWNER) + Say " owner window is the one the probe made: " & (gOwnerSeen <> 0 And gOwnerSeen = gOwnerWant) + End If If mDoType Then - n = Children(h, list) - For i = 0 To n - 1 - If ClassOf(list(i)) = "Edit" And IsWindowVisible(list(i)) <> 0 Then ed = list(i) : Exit For - Next + ed = NameEdit(h) If ed <> 0 Then SendMessageW ed, WM_SETTEXT, 0, StrPtr(mTypeText) Say " typed " & Q(mTypeText) & " -> now " & Q(TextOf(ed)) @@ -414,8 +479,16 @@ Module Auto ElseIf h = mMainH Then If mActedTick = 0 Then If mTick - mMainTick >= gMainDelay Then - mActedTick = mTick - DoAct h + If Len(gNavDir) > 0 And mNavStage = 0 Then + mNavStage = 1 + mNavTick = mTick + DoNav h + ElseIf mNavStage = 1 And mTick - mNavTick < 4 Then + ' wait for the dialog to open the folder + Else + mActedTick = mTick + DoAct h + End If End If ElseIf mTick - mActedTick >= IIf(mAct = 4, gMainDelay + 3, 3) Then gStayedOpen = True diff --git a/test/package-probes/TBMANLIB_Dialog/src/AutoD.twin b/test/package-probes/TBMANLIB_Dialog/src/AutoD.twin new file mode 100644 index 000000000..96eed43f4 --- /dev/null +++ b/test/package-probes/TBMANLIB_Dialog/src/AutoD.twin @@ -0,0 +1,431 @@ +' AutoD: the dialog driver for the dialogs of the package's own making (cDialogCountdownMsgBox, +' cDialogDateTime, cDialogInputBox, cDialogPassword). +' +' Those classes show Windows' MessageBoxTimeout or the runtime's InputBox and MsgBox. Each of +' them runs its own message loop on the calling thread, and that loop dispatches thread timers. +' So the probe arms a timer (Arm), calls the dialog, and the timer procedure -- running inside +' the dialog's loop -- finds the windows of the thread, records them and acts on them by a +' script. Disarm kills the timer once the dialog call has returned. +' +' The script given to Arm is one section per window, in the order the windows appear, sections +' separated by ";", the actions of a section by "|": +' t=TEXT set the text of the first visible Edit control (WM_SETTEXT) +' id=N click the button with control id N (BM_CLICK); WM_COMMAND when there is none +' key=VK post a key press (WM_KEYDOWN, WM_KEYUP) of virtual key VK to the Edit control, or +' to the window when it has none +' close post WM_CLOSE to the window (the close button of its title bar) +' wait=N do nothing for N more ticks +' none do nothing: the window is left open until the episode times out +' A window with no section is cancelled after gMaxTicks ticks of the episode. +' +' Every episode is gMaxTicks ticks of TICK_MS (100 ms). At the end every window of the thread +' is closed with WM_CLOSE and a click on id 2; a window that still stands six ticks later ends +' the process (ExitProcess 98), so a run never hangs. +' +' Every line is written with TbRun.Out, so that it reaches the file the runner reads at once. + +Module AutoD + + Private Type RECT4 + Left As Long + Top As Long + Right As Long + Bottom As Long + End Type + + Private Declare PtrSafe Function SetTimer Lib "user32" (ByVal hWnd As LongPtr, ByVal nIDEvent As LongPtr, ByVal uElapse As Long, ByVal lpTimerFunc As LongPtr) As LongPtr + Private Declare PtrSafe Function KillTimer Lib "user32" (ByVal hWnd As LongPtr, ByVal uIDEvent As LongPtr) As Long + Private Declare PtrSafe Function GetCurrentThreadId Lib "kernel32" () As Long + Private Declare PtrSafe Function GetTickCount Lib "kernel32" () As Long + Private Declare PtrSafe Sub ExitProcess Lib "kernel32" (ByVal uExitCode As Long) + Private Declare PtrSafe Function EnumThreadWindows Lib "user32" (ByVal dwThreadId As Long, ByVal lpfn As LongPtr, ByVal lParam As LongPtr) As Long + Private Declare PtrSafe Function EnumChildWindows Lib "user32" (ByVal hWndParent As LongPtr, ByVal lpEnumFunc As LongPtr, ByVal lParam As LongPtr) As Long + Private Declare PtrSafe Function GetClassNameW Lib "user32" (ByVal hWnd As LongPtr, ByVal lpClassName As LongPtr, ByVal nMaxCount As Long) As Long + Private Declare PtrSafe Function GetWindowRect Lib "user32" (ByVal hWnd As LongPtr, ByRef lpRect As RECT4) As Long + Private Declare PtrSafe Function IsWindowVisible Lib "user32" (ByVal hWnd As LongPtr) As Long + Private Declare PtrSafe Function IsWindowEnabled Lib "user32" (ByVal hWnd As LongPtr) As Long + Private Declare PtrSafe Function IsWindow Lib "user32" (ByVal hWnd As LongPtr) As Long + Private Declare PtrSafe Function GetDlgCtrlID Lib "user32" (ByVal hWnd As LongPtr) As Long + Private Declare PtrSafe Function GetDlgItem Lib "user32" (ByVal hDlg As LongPtr, ByVal nIDDlgItem As Long) As LongPtr + Private Declare PtrSafe Function GetWindow Lib "user32" (ByVal hWnd As LongPtr, ByVal uCmd As Long) As LongPtr + Private Declare PtrSafe Function GetWindowLongW Lib "user32" (ByVal hWnd As LongPtr, ByVal nIndex As Long) As Long + Private Declare PtrSafe Function GetForegroundWindow Lib "user32" () As LongPtr + Private Declare PtrSafe Function GetFocus Lib "user32" () As LongPtr + Private Declare PtrSafe Function SendMessageW Lib "user32" (ByVal hWnd As LongPtr, ByVal Msg As Long, ByVal wParam As LongPtr, ByVal lParam As LongPtr) As LongPtr + Private Declare PtrSafe Function PostMessageW Lib "user32" (ByVal hWnd As LongPtr, ByVal Msg As Long, ByVal wParam As LongPtr, ByVal lParam As LongPtr) As Long + + Private Const WM_GETTEXT As Long = &HD + Private Const WM_GETTEXTLENGTH As Long = &HE + Private Const WM_SETTEXT As Long = &HC + Private Const WM_CLOSE As Long = &H10 + Private Const WM_COMMAND As Long = &H111 + Private Const WM_KEYDOWN As Long = &H100 + Private Const WM_KEYUP As Long = &H101 + Private Const BM_CLICK As Long = &HF5 + Private Const EM_GETLIMITTEXT As Long = &HD5 + Private Const EM_GETPASSWORDCHAR As Long = &HD2 + Private Const STM_GETICON As Long = &H171 + Private Const GWL_STYLE As Long = -16 + Private Const GWL_EXSTYLE As Long = -20 + Private Const GW_OWNER As Long = 4 + + Private Const TICK_MS As Long = 100 + Private Const MAXW As Long = 16 + + ' The folder the probe replaces in every line it writes. + Public gRootD As String + + ' What the last episode saw. + Public gWindows As Long ' windows of the thread that appeared + Public gTicksD As Long + Public gTimedOutD As Boolean + Public gMaxTicksD As Long = 80 + Public gActDelay As Long = 2 ' ticks between a window's appearance and its first action + Public gOwner As LongPtr ' a window the probe made, to compare with each dialog's owner + Public gDumpChildren As Boolean = True + Public gTrace As Boolean ' print each tick with its time + Public gFirstSeenMs As Long ' GetTickCount() when the first window of the episode was seen + + Private mTag As String + Private mSections() As String + Private mSectionN As Long + Private mTimerId As LongPtr + Private mTick As Long + Private mStartMs As Long + Private mTop(0 To 63) As LongPtr + Private mTopN As Long + Private mChH(0 To 255) As LongPtr + Private mChN As Long + Private mWin(0 To MAXW - 1) As LongPtr + Private mWinSeenTick(0 To MAXW - 1) As Long + Private mWinActs(0 To MAXW - 1) As String + Private mWinActIdx(0 To MAXW - 1) As Long + Private mWinNext(0 To MAXW - 1) As Long + Private mWinSig(0 To MAXW - 1) As String + Private mWinChanges(0 To MAXW - 1) As Long + Private mWinN As Long + + '========================================================================================= + ' Output + '========================================================================================= + + Public Sub SayD(ByVal s As String) + If Len(gRootD) > 0 Then s = Replace(s, gRootD, "") + TbRun.Out s + End Sub + + ' A string in brackets, with its length; a null character is shown as , a line break as /. + Public Function QD(ByVal s As String) As String + s = Replace(s, vbNullChar, "") + s = Replace(s, vbCrLf, "") + s = Replace(s, vbLf, "") + s = Replace(s, vbCr, "") + QD = "[" & s & "] len=" & Len(s) + End Function + + ' Milliseconds since Arm. + Public Function ElapsedMs() As Long + ElapsedMs = GetTickCount() - mStartMs + End Function + + ' Milliseconds from the first window of the episode to now: how long the dialog stood. + ' (A window appears about two seconds after the call that shows it in every episode after + ' the second one, so the time since Arm says nothing about a timeout.) + Public Function LifeMs() As Long + If gFirstSeenMs = 0 Then LifeMs = -1 Else LifeMs = GetTickCount() - gFirstSeenMs + End Function + + '========================================================================================= + ' Arming + '========================================================================================= + + Public Sub ArmD(ByVal tag As String, ByVal script As String) + mTag = tag + If Len(script) = 0 Then + ReDim mSections(0 To 0) + mSectionN = 0 + Else + mSections = Split(script, ";") + mSectionN = UBound(mSections) + 1 + End If + mTick = 0 + mWinN = 0 + gWindows = 0 + gTicksD = 0 + gTimedOutD = False + gFirstSeenMs = 0 + mStartMs = GetTickCount() + mTimerId = SetTimer(0, 0, TICK_MS, AddressOf TimerProcD) + End Sub + + Public Sub DisarmD() + If mTimerId <> 0 Then KillTimer 0, mTimerId + mTimerId = 0 + gTicksD = mTick + End Sub + + '========================================================================================= + ' Windows + '========================================================================================= + + Public Function EnumTopProcD(ByVal hWnd As LongPtr, ByVal lParam As LongPtr) As Long + If mTopN <= 63 Then + mTop(mTopN) = hWnd + mTopN = mTopN + 1 + End If + EnumTopProcD = 1 + End Function + + Public Function EnumChildProcD(ByVal hWnd As LongPtr, ByVal lParam As LongPtr) As Long + If mChN <= 255 Then + mChH(mChN) = hWnd + mChN = mChN + 1 + End If + EnumChildProcD = 1 + End Function + + Public Function ClassOf(ByVal h As LongPtr) As String + Dim s As String = String$(256, 0) + Dim n As Long = GetClassNameW(h, StrPtr(s), 256) + ClassOf = Left$(s, n) + End Function + + Public Function TextOf(ByVal h As LongPtr) As String + Dim n As Long = CLng(SendMessageW(h, WM_GETTEXTLENGTH, 0, 0)) + Dim s As String = String$(n + 2, 0) + n = CLng(SendMessageW(h, WM_GETTEXT, n + 1, StrPtr(s))) + TextOf = Left$(s, n) + End Function + + Private Function Children(ByVal h As LongPtr, ByRef list() As LongPtr) As Long + Dim i As Long + mChN = 0 + EnumChildWindows h, AddressOf EnumChildProcD, 0 + For i = 0 To mChN - 1 + list(i) = mChH(i) + Next + Children = mChN + End Function + + ' The texts of the Static and Button children, one string: a change in it means the window changed what it says. + Private Function Signature(ByVal h As LongPtr) As String + Dim list(0 To 255) As LongPtr + Dim i As Long + Dim n As Long = Children(h, list) + Dim s As String + For i = 0 To n - 1 + Select Case ClassOf(list(i)) + Case "Static", "Button" + If IsWindowVisible(list(i)) <> 0 Then s = s & "{" & TextOf(list(i)) & "}" + End Select + Next + Signature = s + End Function + + Private Function WinIndex(ByVal h As LongPtr) As Long + Dim i As Long + WinIndex = -1 + For i = 0 To mWinN - 1 + If mWin(i) = h And h <> 0 Then WinIndex = i : Exit Function + Next + End Function + + '========================================================================================= + ' Recording + '========================================================================================= + + Private Sub Record(ByVal h As LongPtr, ByVal k As Long) + Dim list(0 To 255) As LongPtr + Dim i As Long + Dim n As Long + Dim rc As RECT4 + Dim st As Long + Dim ex As Long + Dim owner As LongPtr + Dim cls As String + GetWindowRect h, rc + st = GetWindowLongW(h, GWL_STYLE) + ex = GetWindowLongW(h, GWL_EXSTYLE) + owner = GetWindow(h, GW_OWNER) + SayD " [" & mTag & "] window #" & k & " at tick " & mTick & " (+" & ElapsedMs() & " ms): class=" & QD(ClassOf(h)) & " title=" & QD(TextOf(h)) + SayD " style=0x" & Hex$(st) & " exstyle=0x" & Hex$(ex) & " size=" & (rc.Right - rc.Left) & "x" & (rc.Bottom - rc.Top) & " topmost=" & ((ex And &H8) <> 0) & " hasOwner=" & (owner <> 0) & " ownerIsProbeOwner=" & (owner = gOwner And gOwner <> 0) & " probeOwnerEnabled=" & IIf(gOwner <> 0, CStr(IsWindowEnabled(gOwner)), "n/a") + n = Children(h, list) + For i = 0 To n - 1 + cls = ClassOf(list(i)) + If gDumpChildren Or cls = "Edit" Then + Dim line As String = " child " & cls & " id=" & GetDlgCtrlID(list(i)) & " vis=" & IsWindowVisible(list(i)) & " ena=" & IsWindowEnabled(list(i)) & " style=0x" & Hex$(GetWindowLongW(list(i), GWL_STYLE)) & " text=" & QD(TextOf(list(i))) + If cls = "Edit" Then + line = line & " passwordChar=" & CLng(SendMessageW(list(i), EM_GETPASSWORDCHAR, 0, 0)) & " limit=" & CLng(SendMessageW(list(i), EM_GETLIMITTEXT, 0, 0)) & " multiline=" & ((GetWindowLongW(list(i), GWL_STYLE) And &H4) <> 0) & " readonly=" & ((GetWindowLongW(list(i), GWL_STYLE) And &H800) <> 0) + End If + SayD line + End If + Next + End Sub + + '========================================================================================= + ' Acting + '========================================================================================= + + Private Function FirstEdit(ByVal h As LongPtr) As LongPtr + Dim list(0 To 255) As LongPtr + Dim i As Long + Dim n As Long = Children(h, list) + For i = 0 To n - 1 + If ClassOf(list(i)) = "Edit" Then + If IsWindowVisible(list(i)) <> 0 Then FirstEdit = list(i) : Exit Function + End If + Next + End Function + + Private Sub Act(ByVal k As Long, ByVal h As LongPtr, ByVal action As String) + Dim ed As LongPtr + Dim arg As String + Dim p As Long = InStr(action, "=") + Dim verb As String = action + If p > 0 Then + verb = Left$(action, p - 1) + arg = Mid$(action, p + 1) + End If + Select Case verb + Case "t" + ed = FirstEdit(h) + If ed <> 0 Then + SendMessageW ed, WM_SETTEXT, 0, StrPtr(arg) + SayD " #" & k & " typed " & QD(arg) & " -> now " & QD(TextOf(ed)) + Else + SayD " #" & k & " NO EDIT BOX to type into" + End If + Case "id" + Dim id As Long = CLng(arg) + Dim b As LongPtr = GetDlgItem(h, id) + If b <> 0 Then + SayD " #" & k & " press id " & id & " (" & QD(TextOf(b)) & ", ena=" & IsWindowEnabled(b) & ")" + PostMessageW b, BM_CLICK, 0, 0 + Else + SayD " #" & k & " no button id " & id & "; WM_COMMAND" + PostMessageW h, WM_COMMAND, id, 0 + End If + Case "key" + Dim vk As Long = CLng(arg) + ed = FirstEdit(h) + If ed = 0 Then ed = h + SayD " #" & k & " key " & vk & " to " & ClassOf(ed) + PostMessageW ed, WM_KEYDOWN, vk, 0 + PostMessageW ed, WM_KEYUP, vk, &HC0000001 + Case "close" + SayD " #" & k & " WM_CLOSE" + PostMessageW h, WM_CLOSE, 0, 0 + Case "wait" + mWinNext(k) = mTick + CLng(arg) + Case "none" + SayD " #" & k & " (left alone)" + Case Else + SayD " #" & k & " unknown action " & QD(action) + End Select + End Sub + + Private Sub Service(ByVal k As Long, ByVal h As LongPtr) + Dim acts() As String + Dim sig As String + ' A change of the texts the window shows. + sig = Signature(h) + If sig <> mWinSig(k) Then + If mWinChanges(k) < 4 Then SayD " #" & k & " texts changed at tick " & mTick & " (+" & ElapsedMs() & " ms): " & QD(sig) + mWinChanges(k) = mWinChanges(k) + 1 + mWinSig(k) = sig + End If + If Len(mWinActs(k)) = 0 Then Exit Sub + acts = Split(mWinActs(k), "|") + If mWinActIdx(k) > UBound(acts) Then Exit Sub + If mTick < mWinNext(k) Then Exit Sub + Dim a As String = acts(mWinActIdx(k)) + mWinActIdx(k) = mWinActIdx(k) + 1 + mWinNext(k) = mTick + 1 + Act k, h, a + End Sub + + ' Ends a window by every means: WM_CLOSE, WM_COMMAND 2, and a click on each of its buttons. + Private Sub CloseWindow(ByVal h As LongPtr) + Dim list(0 To 255) As LongPtr + Dim i As Long + Dim n As Long + PostMessageW h, WM_CLOSE, 0, 0 + PostMessageW h, WM_COMMAND, 2, 0 + n = Children(h, list) + For i = 0 To n - 1 + If ClassOf(list(i)) = "Button" Then + If IsWindowVisible(list(i)) <> 0 Then PostMessageW list(i), BM_CLICK, 0, 0 + End If + Next + End Sub + + ' The timer procedure: runs inside the modal loop of the dialog. + Public Sub TimerProcD(ByVal hWnd As LongPtr, ByVal uMsg As Long, ByVal idEvent As LongPtr, ByVal dwTime As Long) + Dim i As Long + Dim k As Long + Dim h As LongPtr + Dim list(0 To 63) As LongPtr + Dim n As Long + Dim openCount As Long + mTick = mTick + 1 + If gTrace Then SayD " tick " & mTick & " +" & ElapsedMs() & " ms" + ' A window that has gone is forgotten, so that a new window with the same handle is a new one. + For i = 0 To mWinN - 1 + If mWin(i) <> 0 Then + If IsWindow(mWin(i)) = 0 Then + mWin(i) = 0 + ElseIf IsWindowVisible(mWin(i)) = 0 Then + mWin(i) = 0 + End If + End If + Next + mTopN = 0 + EnumThreadWindows GetCurrentThreadId(), AddressOf EnumTopProcD, 0 + n = mTopN + For i = 0 To n - 1 + list(i) = mTop(i) + Next + For i = 0 To n - 1 + h = list(i) + If IsWindow(h) <> 0 Then + If IsWindowVisible(h) <> 0 Then + If h <> gOwner Then + k = WinIndex(h) + If k < 0 And mWinN < MAXW Then + k = mWinN + mWinN = mWinN + 1 + mWin(k) = h + mWinSeenTick(k) = mTick + If k < mSectionN Then mWinActs(k) = mSections(k) Else mWinActs(k) = "" + mWinActIdx(k) = 0 + mWinNext(k) = mTick + gActDelay + mWinSig(k) = Signature(h) + mWinChanges(k) = 0 + gWindows = gWindows + 1 + If gFirstSeenMs = 0 Then gFirstSeenMs = GetTickCount() + Record h, k + SayD " #" & k & " texts: " & QD(mWinSig(k)) + End If + If k >= 0 Then + openCount = openCount + 1 + If mTick >= gMaxTicksD Then + If Not gTimedOutD Then + gTimedOutD = True + SayD " [" & mTag & "] TIMED OUT at tick " & mTick & ", closing the windows" + End If + CloseWindow h + Else + Service k, h + End If + End If + End If + End If + End If + Next + If mTick >= gMaxTicksD + 6 Then + SayD " [" & mTag & "] STUCK: a window could not be closed, ending the process" + ExitProcess 98 + End If + End Sub + +End Module diff --git a/test/package-probes/TBMANLIB_Dialog/src/DrvB1.twin b/test/package-probes/TBMANLIB_Dialog/src/DrvB1.twin new file mode 100644 index 000000000..59fbf592d --- /dev/null +++ b/test/package-probes/TBMANLIB_Dialog/src/DrvB1.twin @@ -0,0 +1,580 @@ +' DrvB1: a scripted dialog driver for the common dialogs of TBMANLIB_Dialog +' (cDialogColor, cDialogFont, cDialogPrint, cDialogPageSetup, cDialogFindReplace). +' +' Auto.twin drives a file dialog with one action (type, press OK, press Cancel). The Color, +' Font, Print, Page Setup and Find dialogs need more: set the text of a control, click a check +' box or a radio button, choose an item of a combo box, read the state of a control. So a +' scenario arms this driver with a script, a list of steps separated by "|", then shows the +' dialog; a thread timer, dispatched by the dialog's own message loop (modal dialogs) or by +' DPump (the modeless Find dialog), finds the dialog and runs one step per tick. +' +' Steps (the first word is the verb; ID is a control id, decimal or &H...): +' rec list the dialog: title, every child with class, id, visible, enabled, check state, text +' ctl ID one control: the same line, and for a combo box its count and current item +' items ID [N] the first N (default 8) items of a combo box or list box, and the count +' settext ID text WM_SETTEXT on the control +' click ID BM_CLICK on the control (a button, a check box or a radio button) +' cbsel ID n choose item n (0-based) of a combo box and notify the dialog (CBN_SELCHANGE) +' cbfind ID text choose the first item of a combo box that starts with text, and notify the dialog +' needfind cancel the dialog and end the script unless the last cbfind chose an item +' msg N answer the next message boxes with button N (1 OK, 2 Cancel, 6 Yes, 7 No) +' wait N wait N ticks +' ok / cancel click the button with id 1 / 2 +' quit (modeless) post WM_QUIT, which ends DPump +' +' The read steps (rec, ctl, items) take no time of their own; every other step takes one tick +' (TICK_MS). A dialog still open after the script, or open after DMaxTicks ticks, is cancelled; +' a window that cannot be cancelled ends the process (ExitProcess 98), so a run never hangs. +' Every line is written with TbRun.Out, so that it reaches the file the runner reads at once. + +Module DrvB1 + + Private Type MSG4 + hWnd As LongPtr + message As Long + wParam As LongPtr + lParam As LongPtr + time As Long + ptX As Long + ptY As Long + End Type + + Private Declare PtrSafe Function SetTimer Lib "user32" (ByVal hWnd As LongPtr, ByVal nIDEvent As LongPtr, ByVal uElapse As Long, ByVal lpTimerFunc As LongPtr) As LongPtr + Private Declare PtrSafe Function KillTimer Lib "user32" (ByVal hWnd As LongPtr, ByVal uIDEvent As LongPtr) As Long + Private Declare PtrSafe Function GetCurrentThreadId Lib "kernel32" () As Long + Private Declare PtrSafe Sub ExitProcess Lib "kernel32" (ByVal uExitCode As Long) + Private Declare PtrSafe Function EnumThreadWindows Lib "user32" (ByVal dwThreadId As Long, ByVal lpfn As LongPtr, ByVal lParam As LongPtr) As Long + Private Declare PtrSafe Function EnumChildWindows Lib "user32" (ByVal hWndParent As LongPtr, ByVal lpEnumFunc As LongPtr, ByVal lParam As LongPtr) As Long + Private Declare PtrSafe Function GetClassNameW Lib "user32" (ByVal hWnd As LongPtr, ByVal lpClassName As LongPtr, ByVal nMaxCount As Long) As Long + Private Declare PtrSafe Function IsWindowVisible Lib "user32" (ByVal hWnd As LongPtr) As Long + Private Declare PtrSafe Function IsWindowEnabled Lib "user32" (ByVal hWnd As LongPtr) As Long + Private Declare PtrSafe Function IsWindow Lib "user32" (ByVal hWnd As LongPtr) As Long + Private Declare PtrSafe Function GetDlgCtrlID Lib "user32" (ByVal hWnd As LongPtr) As Long + Private Declare PtrSafe Function GetParent Lib "user32" (ByVal hWnd As LongPtr) As LongPtr + Private Declare PtrSafe Function GetDlgItem Lib "user32" (ByVal hDlg As LongPtr, ByVal nIDDlgItem As Long) As LongPtr + Private Declare PtrSafe Function GetWindowLongW Lib "user32" (ByVal hWnd As LongPtr, ByVal nIndex As Long) As Long + Private Declare PtrSafe Function EndMenu Lib "user32" () As Long + Private Declare PtrSafe Function SendMessageW Lib "user32" (ByVal hWnd As LongPtr, ByVal Msg As Long, ByVal wParam As LongPtr, ByVal lParam As LongPtr) As LongPtr + Private Declare PtrSafe Function PostMessageW Lib "user32" (ByVal hWnd As LongPtr, ByVal Msg As Long, ByVal wParam As LongPtr, ByVal lParam As LongPtr) As Long + Private Declare PtrSafe Sub PostQuitMessage Lib "user32" (ByVal nExitCode As Long) + Private Declare PtrSafe Function GetMessageW Lib "user32" (ByRef lpMsg As MSG4, ByVal hWnd As LongPtr, ByVal wMsgFilterMin As Long, ByVal wMsgFilterMax As Long) As Long + Private Declare PtrSafe Function TranslateMessage Lib "user32" (ByRef lpMsg As MSG4) As Long + Private Declare PtrSafe Function DispatchMessageW Lib "user32" (ByRef lpMsg As MSG4) As LongPtr + Private Declare PtrSafe Function IsDialogMessageW Lib "user32" (ByVal hDlg As LongPtr, ByRef lpMsg As MSG4) As Long + + Private Const WM_GETTEXT As Long = &HD + Private Const WM_GETTEXTLENGTH As Long = &HE + Private Const WM_SETTEXT As Long = &HC + Private Const WM_CLOSE As Long = &H10 + Private Const WM_COMMAND As Long = &H111 + Private Const BM_GETCHECK As Long = &HF0 + Private Const BM_CLICK As Long = &HF5 + Private Const TDM_CLICK_BUTTON As Long = &H466 + Private Const CB_GETCOUNT As Long = &H146 + Private Const CB_GETCURSEL As Long = &H147 + Private Const CB_GETLBTEXT As Long = &H148 + Private Const CB_GETLBTEXTLEN As Long = &H149 + Private Const CB_SETCURSEL As Long = &H14E + Private Const LB_GETCOUNT As Long = &H18B + Private Const LB_GETCURSEL As Long = &H188 + Private Const LB_GETTEXT As Long = &H189 + Private Const LB_GETTEXTLEN As Long = &H18A + Private Const LVM_GETITEMCOUNT As Long = &H1004 + Private Const GWL_STYLE As Long = -16 + Private Const CBN_SELCHANGE As Long = 1 + + Private Const TICK_MS As Long = 250 + + ' The folder the probe replaces in every line it writes, so that no path of the machine is printed. + Public DRoot As String + + ' What the last episode saw, for the probe to print. + Public DSeenMain As Boolean ' the dialog appeared + Public DTimedOut As Boolean + Public DMsgCount As Long ' message boxes the dialog raised + Public DTicks As Long ' ticks the episode lasted: 0 means the call returned within one tick + Public DStayedOpen As Boolean ' the dialog was still open after the script ended + + ' The Print and Page Setup dialogs show the name, driver and port of the printers of the + ' computer: with DMask the driver prints no combo box text and no printer status text. + Public DMask As Boolean + + Public DMaxTicks As Long = 16 + Public DModeless As Boolean ' the dialog is driven from DPump: the time-out ends the loop + Public DMainDelay As Long = 2 ' ticks between the first sight of the dialog and the first step + + Private mTag As String + Private mSteps() As String + Private mStepN As Long + Private mStepI As Long + Private mTimerId As LongPtr + Private mTick As Long + Private mMainH As LongPtr + Private mMainTick As Long + Private mNextTick As Long + Private mScriptDone As Boolean + Private mFindOk As Boolean + Private mGoneTick As Long + Private mDoneTick As Long + Private mMsgSeen(0 To 31) As LongPtr + Private mMsgSeenN As Long + Private mMsgWant As Long + Private mMsgTick As Long + Private mTop(0 To 63) As LongPtr + Private mTopN As Long + Private mChH(0 To 255) As LongPtr + Private mChN As Long + + Public Sub DSay(ByVal s As String) + If Len(DRoot) > 0 Then s = Replace(s, DRoot, "") + TbRun.Out s + End Sub + + ' A string in brackets, with its length; a null character is shown as . + Public Function DQ(ByVal s As String) As String + DQ = "[" & Replace(s, vbNullChar, "") & "] len=" & Len(s) + End Function + + '========================================================================================= + ' Arming + '========================================================================================= + + Public Sub DArm(ByVal tag As String, ByVal script As String) + mTag = tag + mSteps = Split(script, "|") + mStepN = UBound(mSteps) + 1 + mStepI = 0 + mTick = 0 + mMainH = 0 + mMainTick = 0 + mNextTick = 0 + mScriptDone = False + mGoneTick = 0 + mDoneTick = 0 + mMsgSeenN = 0 + mMsgWant = 1 + DMsgCount = 0 + DSeenMain = False + DTimedOut = False + DStayedOpen = False + DTicks = 0 + mTimerId = SetTimer(0, 0, TICK_MS, AddressOf DTimer) + End Sub + + Public Sub DDisarm() + If mTimerId <> 0 Then KillTimer 0, mTimerId + mTimerId = 0 + DTicks = mTick + End Sub + + Public Sub DWrap(ByVal what As String) + DSay " after " & what & ": dialogSeen=" & DSeenMain & " stayedOpen=" & DStayedOpen & " timedOut=" & DTimedOut & " msgBoxes=" & DMsgCount & " ticks=" & mTick + End Sub + + ' The message loop for a modeless dialog: runs until a "quit" step (or the time-out) posts WM_QUIT. + Public Sub DPump() + Dim m As MSG4 + Dim r As Long + DModeless = True + Do + r = GetMessageW(m, 0, 0, 0) + If r <= 0 Then Exit Do + TranslateMessage m + DispatchMessageW m + Loop + DModeless = False + End Sub + + '========================================================================================= + ' Windows + '========================================================================================= + + Public Function DEnumTop(ByVal hWnd As LongPtr, ByVal lParam As LongPtr) As Long + If mTopN <= 63 Then + mTop(mTopN) = hWnd + mTopN = mTopN + 1 + End If + DEnumTop = 1 + End Function + + Public Function DEnumChild(ByVal hWnd As LongPtr, ByVal lParam As LongPtr) As Long + If mChN <= 255 Then + mChH(mChN) = hWnd + mChN = mChN + 1 + End If + DEnumChild = 1 + End Function + + Private Function ClassOf(ByVal h As LongPtr) As String + Dim s As String = String$(256, 0) + Dim n As Long = GetClassNameW(h, StrPtr(s), 256) + ClassOf = Left$(s, n) + End Function + + Private Function TextOf(ByVal h As LongPtr) As String + Dim n As Long = CLng(SendMessageW(h, WM_GETTEXTLENGTH, 0, 0)) + Dim s As String = String$(n + 2, 0) + n = CLng(SendMessageW(h, WM_GETTEXT, n + 1, StrPtr(s))) + TextOf = Left$(s, n) + End Function + + ' All descendants of h, copied into list(); returns how many. + Private Function Children(ByVal h As LongPtr, ByRef list() As LongPtr) As Long + Dim i As Long + mChN = 0 + EnumChildWindows h, AddressOf DEnumChild, 0 + For i = 0 To mChN - 1 + list(i) = mChH(i) + Next + Children = mChN + End Function + + ' A common dialog has an edit box, a combo box, a tree or a list view; a message box does not. + Private Function IsMain(ByVal h As LongPtr) As Boolean + Dim list(0 To 255) As LongPtr + Dim i As Long + Dim c As String + Dim n As Long = Children(h, list) + For i = 0 To n - 1 + c = ClassOf(list(i)) + If c = "Edit" Or c = "ComboBoxEx32" Or c = "SysTreeView32" Or c = "ComboBox" Or c = "SysListView32" Or c = "ListBox" Then + IsMain = True + Exit Function + End If + Next + End Function + + Private Function CtlLine(ByVal h As LongPtr) As String + Dim cls As String = ClassOf(h) + Dim s As String + Dim style As Long = GetWindowLongW(h, GWL_STYLE) + s = cls & " id=" & GetDlgCtrlID(h) & " vis=" & IsWindowVisible(h) & " ena=" & IsWindowEnabled(h) + If cls = "Button" Then s = s & " chk=" & CLng(SendMessageW(h, BM_GETCHECK, 0, 0)) & " btnstyle=" & (style And &HF&) + If cls = "ComboBox" Then s = s & " count=" & CLng(SendMessageW(h, CB_GETCOUNT, 0, 0)) & " cursel=" & CLng(SendMessageW(h, CB_GETCURSEL, 0, 0)) + If cls = "ListBox" Then s = s & " count=" & CLng(SendMessageW(h, LB_GETCOUNT, 0, 0)) & " cursel=" & CLng(SendMessageW(h, LB_GETCURSEL, 0, 0)) + If cls = "SysListView32" Then s = s & " count=" & CLng(SendMessageW(h, LVM_GETITEMCOUNT, 0, 0)) + If DMask And (cls = "ComboBox" Or (cls = "Static" And GetDlgCtrlID(h) >= 1098 And GetDlgCtrlID(h) <= 1101)) Then + s = s & " text=" + Else + s = s & " text=" & DQ(TextOf(h)) + End If + CtlLine = s + End Function + + Private Function ParseId(ByVal s As String) As Long + ParseId = CLng(Val(Trim$(s))) + End Function + + '========================================================================================= + ' Steps + '========================================================================================= + + Private Sub StepRec(ByVal h As LongPtr) + Dim list(0 To 255) As LongPtr + Dim i As Long + Dim n As Long = Children(h, list) + DSay " rec: window title=" & DQ(TextOf(h)) & " class=" & ClassOf(h) & " children=" & n + For i = 0 To n - 1 + DSay " child " & CtlLine(list(i)) & " parent=" & ClassOf(GetParent(list(i))) + Next + End Sub + + Private Sub StepItems(ByVal h As LongPtr, ByVal id As Long, ByVal maxN As Long) + Dim c As LongPtr = GetDlgItem(h, id) + Dim cnt As Long + Dim k As Long + Dim ln As Long + Dim buf As String + Dim isCombo As Boolean + If c = 0 Then + DSay " items " & id & ": no such control" + Exit Sub + End If + isCombo = (ClassOf(c) = "ComboBox") + If isCombo Then cnt = CLng(SendMessageW(c, CB_GETCOUNT, 0, 0)) Else cnt = CLng(SendMessageW(c, LB_GETCOUNT, 0, 0)) + DSay " items " & id & ": count=" & cnt & " cursel=" & CLng(SendMessageW(c, IIf(isCombo, CB_GETCURSEL, LB_GETCURSEL), 0, 0)) + For k = 0 To cnt - 1 + If k >= maxN Then Exit For + If isCombo Then ln = CLng(SendMessageW(c, CB_GETLBTEXTLEN, k, 0)) Else ln = CLng(SendMessageW(c, LB_GETTEXTLEN, k, 0)) + buf = String$(ln + 2, 0) + If isCombo Then SendMessageW c, CB_GETLBTEXT, k, StrPtr(buf) Else SendMessageW c, LB_GETTEXT, k, StrPtr(buf) + If DMask And Left$(buf, 10) <> "Microsoft " Then + DSay " item " & k & ": " + Else + DSay " item " & k & ": " & DQ(Left$(buf, ln)) + End If + Next + End Sub + + Private Sub StepNotify(ByVal h As LongPtr, ByVal c As LongPtr, ByVal id As Long) + PostMessageW h, WM_COMMAND, (CBN_SELCHANGE * 65536) Or id, c + End Sub + + Private Sub StepCbFind(ByVal h As LongPtr, ByVal id As Long, ByVal txt As String) + Dim c As LongPtr = GetDlgItem(h, id) + Dim cnt As Long + Dim k As Long + Dim ln As Long + Dim buf As String + mFindOk = False + If c = 0 Then + DSay " cbfind " & id & ": no such control" + Exit Sub + End If + cnt = CLng(SendMessageW(c, CB_GETCOUNT, 0, 0)) + For k = 0 To cnt - 1 + ln = CLng(SendMessageW(c, CB_GETLBTEXTLEN, k, 0)) + buf = String$(ln + 2, 0) + SendMessageW c, CB_GETLBTEXT, k, StrPtr(buf) + If LCase$(Left$(Left$(buf, ln), Len(txt))) = LCase$(txt) Then + SendMessageW c, CB_SETCURSEL, k, 0 + StepNotify h, c, id + mFindOk = True + DSay " cbfind " & id & " " & DQ(txt) & ": chose item " & k & " " & DQ(Left$(buf, ln)) + Exit Sub + End If + Next + DSay " cbfind " & id & " " & DQ(txt) & ": no item starts with it (count=" & cnt & ")" + End Sub + + ' Runs the steps from mStepI on; returns after the first step that takes time. + Private Sub RunSteps(ByVal h As LongPtr) + Dim st As String + Dim verb As String + Dim rest As String + Dim sp As Long + Dim id As Long + Dim c As LongPtr + Dim sp2 As Long + Do While mStepI < mStepN + st = Trim$(mSteps(mStepI)) + mStepI = mStepI + 1 + If Len(st) = 0 Then GoTo NextStep + sp = InStr(st, " ") + If sp > 0 Then + verb = LCase$(Left$(st, sp - 1)) + rest = Mid$(st, sp + 1) + Else + verb = LCase$(st) + rest = "" + End If + Select Case verb + Case "rec" + StepRec h + Case "ctl" + id = ParseId(rest) + c = GetDlgItem(h, id) + If c = 0 Then DSay " ctl " & id & ": no such control" Else DSay " ctl " & CtlLine(c) + Case "items" + sp2 = InStr(rest, " ") + If sp2 > 0 Then StepItems h, ParseId(Left$(rest, sp2 - 1)), ParseId(Mid$(rest, sp2 + 1)) Else StepItems h, ParseId(rest), 8 + Case "settext" + sp2 = InStr(rest, " ") + id = ParseId(Left$(rest, sp2 - 1)) + c = GetDlgItem(h, id) + If c = 0 Then + DSay " settext " & id & ": no such control" + Else + SendMessageW c, WM_SETTEXT, 0, StrPtr(Mid$(rest, sp2 + 1)) + DSay " settext " & id & " " & DQ(Mid$(rest, sp2 + 1)) & " -> now " & DQ(TextOf(c)) + End If + mNextTick = mTick + 1 + Exit Sub + Case "click" + id = ParseId(rest) + c = GetDlgItem(h, id) + If c = 0 Then + DSay " click " & id & ": no such control" + Else + DSay " click " & id & " (" & DQ(TextOf(c)) & ", ena=" & IsWindowEnabled(c) & ")" + PostMessageW c, BM_CLICK, 0, 0 + End If + mNextTick = mTick + 1 + Exit Sub + Case "cbsel" + sp2 = InStr(rest, " ") + id = ParseId(Left$(rest, sp2 - 1)) + c = GetDlgItem(h, id) + If c = 0 Then + DSay " cbsel " & id & ": no such control" + Else + SendMessageW c, CB_SETCURSEL, ParseId(Mid$(rest, sp2 + 1)), 0 + StepNotify h, c, id + DSay " cbsel " & id & " " & Mid$(rest, sp2 + 1) & " -> cursel=" & CLng(SendMessageW(c, CB_GETCURSEL, 0, 0)) & " text=" & DQ(TextOf(c)) + End If + mNextTick = mTick + 1 + Exit Sub + Case "cbfind" + sp2 = InStr(rest, " ") + StepCbFind h, ParseId(Left$(rest, sp2 - 1)), Mid$(rest, sp2 + 1) + mNextTick = mTick + 1 + Exit Sub + Case "needfind" + If Not mFindOk Then + DSay " needfind: the last cbfind found nothing, cancelling" + PressId h, 2 + mScriptDone = True + mDoneTick = mTick + mNextTick = mTick + 1 + Exit Sub + End If + Case "msg" + mMsgWant = ParseId(rest) + Case "wait" + mNextTick = mTick + ParseId(rest) + Exit Sub + Case "ok" + PressId h, 1 + mNextTick = mTick + 1 + Exit Sub + Case "cancel" + PressId h, 2 + mNextTick = mTick + 1 + Exit Sub + Case "quit" + DSay " quit" + PostQuitMessage 0 + mNextTick = mTick + 1 + Exit Sub + Case Else + DSay " unknown step " & DQ(st) + End Select +NextStep: + Loop + mScriptDone = True + mDoneTick = mTick + End Sub + + Private Sub PressId(ByVal h As LongPtr, ByVal id As Long) + Dim b As LongPtr = GetDlgItem(h, id) + If b <> 0 Then + DSay " press id " & id & " (" & DQ(TextOf(b)) & ", ena=" & IsWindowEnabled(b) & ")" + PostMessageW b, BM_CLICK, 0, 0 + Else + DSay " no button id " & id & "; WM_COMMAND" + PostMessageW h, WM_COMMAND, id, 0 + End If + End Sub + + '========================================================================================= + ' Dialogs + '========================================================================================= + + Private Function MsgWasSeen(ByVal h As LongPtr) As Boolean + Dim i As Long + For i = 0 To mMsgSeenN - 1 + If mMsgSeen(i) = h Then MsgWasSeen = True : Exit Function + Next + End Function + + ' A message box the dialog raised: record it, and answer it with the button of the last "msg" step. + Private Sub HandleMsgBox(ByVal h As LongPtr) + Dim list(0 To 255) As LongPtr + Dim i As Long + Dim n As Long + Dim btn As LongPtr + If Not MsgWasSeen(h) Then + If mMsgSeenN <= 31 Then + mMsgSeen(mMsgSeenN) = h + mMsgSeenN = mMsgSeenN + 1 + End If + mMsgTick = mTick + DMsgCount = DMsgCount + 1 + DSay " [" & mTag & "] message box #" & DMsgCount & " title=" & DQ(TextOf(h)) + n = Children(h, list) + For i = 0 To n - 1 + Select Case ClassOf(list(i)) + Case "Static", "Button" + DSay " child " & ClassOf(list(i)) & " id=" & GetDlgCtrlID(list(i)) & " text=" & DQ(TextOf(list(i))) + End Select + Next + ElseIf mTick - mMsgTick = 1 Then + btn = GetDlgItem(h, mMsgWant) + If btn = 0 Then btn = GetDlgItem(h, 1) + If btn = 0 Then btn = GetDlgItem(h, 2) + If btn <> 0 Then + DSay " answering with id " & GetDlgCtrlID(btn) & " (" & DQ(TextOf(btn)) & ")" + PostMessageW btn, BM_CLICK, 0, 0 + Else + DSay " answering with TDM_CLICK_BUTTON " & mMsgWant + PostMessageW h, TDM_CLICK_BUTTON, mMsgWant, 0 + End If + End If + ' While a message box is up, the dialog behind it is not "still open" for lack of an answer. + If mScriptDone Then mDoneTick = mTick + End Sub + + Private Sub HandleDialog(ByVal h As LongPtr) + If Not IsMain(h) Then + HandleMsgBox h + Exit Sub + End If + If mMainH = 0 Then + mMainH = h + mMainTick = mTick + DSeenMain = True + DSay " [" & mTag & "] dialog seen (tick " & mTick & ") title=" & DQ(TextOf(h)) & " class=" & ClassOf(h) + ElseIf h = mMainH Then + If Not mScriptDone Then + If mTick - mMainTick >= DMainDelay And mTick >= mNextTick Then RunSteps h + ElseIf mTick - mDoneTick >= 4 Then + DStayedOpen = True + DSay " [" & mTag & "] dialog still open " & (mTick - mDoneTick) & " ticks after the script, cancelling" + EndMenu + PressId h, 2 + mDoneTick = mTick + End If + End If + End Sub + + ' The timer procedure: runs inside the modal loop of the dialog, or inside DPump. + Public Sub DTimer(ByVal hWnd As LongPtr, ByVal uMsg As Long, ByVal idEvent As LongPtr, ByVal dwTime As Long) + Dim i As Long + Dim h As LongPtr + Dim list(0 To 63) As LongPtr + Dim n As Long + mTick = mTick + 1 + mTopN = 0 + EnumThreadWindows GetCurrentThreadId(), AddressOf DEnumTop, 0 + n = mTopN + For i = 0 To n - 1 + list(i) = mTop(i) + Next + For i = 0 To n - 1 + h = list(i) + If IsWindow(h) <> 0 Then + If IsWindowVisible(h) <> 0 Then + If ClassOf(h) = "#32770" Then + If mTick >= DMaxTicks Then + If Not DTimedOut Then + DTimedOut = True + DSay " [" & mTag & "] TIMED OUT at tick " & mTick & ", cancelling (dialog title " & DQ(TextOf(h)) & ", main dialog seen: " & DSeenMain & ")" + End If + EndMenu + PostMessageW h, TDM_CLICK_BUTTON, 2, 0 + PostMessageW h, WM_CLOSE, 0, 0 + Else + HandleDialog h + End If + End If + End If + End If + Next + If DModeless And mTick = DMaxTicks Then PostQuitMessage 0 + ' A modeless dialog that has closed (Cancel, Close) ends the pump two ticks later. + If DModeless And mMainH <> 0 Then + If IsWindow(mMainH) = 0 Then + If mGoneTick = 0 Then + mGoneTick = mTick + DSay " [" & mTag & "] the dialog window is gone (tick " & mTick & ")" + End If + If mTick - mGoneTick >= 2 Then PostQuitMessage 0 + End If + End If + If mTick >= DMaxTicks + 6 Then + DSay " [" & mTag & "] STUCK: a window could not be cancelled, ending the process" + ExitProcess 98 + End If + End Sub + +End Module diff --git a/test/package-probes/TBMANLIB_Dialog/src/Probe.twin b/test/package-probes/TBMANLIB_Dialog/src/Probe.twin index 28addb6f3..57fa83b02 100644 --- a/test/package-probes/TBMANLIB_Dialog/src/Probe.twin +++ b/test/package-probes/TBMANLIB_Dialog/src/Probe.twin @@ -27,10 +27,87 @@ ' 12b the default Open dialog, for comparison ' 12c HideReadOnly = False: click the Open button's arrow, which opens its drop-down ' 13 the properties' defaults (no dialog) +' 14 PathCombine on twenty pairs (no dialog) +' 15 SelectFiles: one file, two files, cancel, one file with MultiSelect +' 16 ShowOpen with MultiSelect, three files; FileName kept before a two-file result +' 17 SelectAndOpenFile on nonexistent files (one, two, cancel) +' 18 ShowBrowseForFolder with a typed folder whose name has non-ANSI characters +' +' The cDialogEx scenarios are in ProbeEx.twin; run them with --timeout 35: +' x0 defaults and Reset, GetPresetFilter, the Set...Filters texts, SystemIconId (no dialog) +' x1 ShowOpen one file (the name only) x2 ShowOpen MultiSelect +' x3 cancel x4 titles of the three dialogs +' x5 Filter: pipe counts, the file-type list, a closing pipe, the Save As dialog +' x6 ShowSave: DefaultExt, OverwritePrompt x7 FileMustExist, PathMustExist, CreatePrompt +' x8 NoChangeDir x9 ShowBrowseForFolder and PickFolders x10 icons +' x11 OwnerHwnd x12 SelectFiles x13 path helpers x14 SelectAndOpenFile +' x15 InitialDir x16 FilterIndex +' y1 mDialog (no dialog) y2 IShellItem identifier y3 GetDisplayName with both SIGDN values +' y4 SetDefaultFolder with the true IShellItem identifier: ENDS THE RUN with an access +' violation in windows.storage.dll, which is its answer; run it alone +' +' The common dialogs (cDialogColor, Font, Print, PageSetup, FindReplace) are in ProbeB1.twin, driven by +' DrvB1.twin: +' c1 Color: defaults and conversions (no dialog) +' c2 Color: Cancel; the dialog as it opens +' c3 Color: OK with FullOpen; the edit boxes; PreventFullOpen +' c4 Color: typed red, green and blue; Add to Custom Colors; the custom colors afterwards +' c11 Font: defaults, Style, Bold, Italic, ToCssFont, ToDescription (no dialog) +' c12 Font: Cancel; the dialog as it opens +' c13 Font: OK as it opens +' c14 Font: a chosen face, style, size and underline +' c15 Font: FixedPitchOnly, ForceFontExist, ShowEffects +' c16 Font: MinSize, MaxSize, ScreenFontsOnly = False +' c17 Font: MaxSize alone, MinSize above MaxSize, a size inside the limits +' c21 Print: defaults; a page number that does not fit an Integer (no dialog) +' c22 Print: Cancel; the dialog as it opens +' c23 Print: OK as it opens (no device context) +' c24 Print: page range and selection +' c25 Print: AllowSelection, AllowPageRange, HidePrintToFile, AllowPrintToFile, PrintToFile +' c26 Print: ReturnDC (only with a virtual default printer) +' c27 Print: FromPage, ToPage, MinPage, MaxPage and Copies that do not fit an Integer +' c31 Page Setup: defaults, conversions, ToDescription (no dialog) +' c32 Page Setup: Cancel; the dialog as it opens +' c33 Page Setup: OK as it opens, in inches +' c34 Page Setup: Unit = Millimeters, a paper size, Landscape +' c35 Page Setup: AllowMargins, AllowOrientation, AllowPaper +' c41 Find: defaults, MessageId, UpdateFromFlags with no dialog (no dialog) +' c42 Find: ShowFind and ShowReplace with no owner window +' c43 Find: ShowFind with an owner window; Find Next; Cancel +' c44 Find: ShowReplace; Replace; Replace All; Cancel +' c45 Find: AllowUpDown, AllowMatchCase, AllowWholeWord, MatchCase, WholeWord, Direction +' c46 Find: what the owner window receives (lParam), with the stack reused in between +' c47 Find: FindText called directly, the structure in a module-level variable (the control) +' c48 Find: FindText called directly, the structure in a local variable, as ShowFind does +' +' The dialogs of the package's own making (cDialogCountdownMsgBox, cDialogDateTime, cDialogInputBox, +' cDialogPassword, cDialogProgress) are in ProbeD.twin, ProbeDT.twin, ProbeIB.twin, ProbePW.twin and +' ProbePG.twin, driven by AutoD.twin (ProgSink.twin receives the events of d40): +' d0 how the runtime's InputBox, MsgBox and MessageBoxTimeout windows are built (no class) +' d1 cDialogCountdownMsgBox: the window, the countdown text, Timeout, the properties after +' d2 cDialogCountdownMsgBox: every Show* method, the buttons, closing with the X +' d3 cDialogCountdownMsgBox: AutoClose False, ShowProgress False, 0 and negative seconds +' d4 cDialogCountdownMsgBox: an overflow of CountdownSeconds, a button value the class does not map +' d5 cDialogCountdownMsgBox: OwnerHwnd +' d10 cDialogDateTime: the InputBox, OK, cancel, an empty entry +' d11 cDialogDateTime: invalid entries, the Invalid box, the state afterwards +' d11c cDialogDateTime: a time with AM or PM, in the 12 hour format +' d12 cDialogDateTime: ShowDatePicker, ShowTimePicker, AM and PM +' d12d cDialogDateTime: time only in the 12 hour format, OK on the offered text; a good date with a bad time +' d20 cDialogInputBox: the InputBox, hint and length text, OK, cancel +' d21 cDialogInputBox: validation failures, three attempts, Value afterwards +' d22 cDialogInputBox: ShowInteger overflow, the quick methods +' d23 cDialogInputBox: OwnerHwnd, Multiline and PasswordChar +' d30 cDialogPassword: the InputBox, its mask, OK, cancel; Password afterwards +' d31 cDialogPassword: validation failures, attempts +' d32 cDialogPassword: RequireConfirm +' d33 cDialogPassword: ShowChangeDialog +' d34 cDialogPassword: PasswordChar "#" +' d40 cDialogProgress: the logic, no window (the same in a 64-bit build) Module Probe - Private Root As String + Public Root As String [RunAfterBuild] Public Sub Hook() @@ -38,13 +115,15 @@ Module Probe Debug.Print "hook ran in the IDE; the dialogs run only in the exe" End Sub - Private Const ScenarioIds As String = ",13,1,2,3,4,5,6,6d,7,8,9,9e,11,12,12b,12c," + Private Const ScenarioIds As String = ",13,1,2,3,4,5,6,6d,7,8,9,9e,11,12,12b,12c,14,15,16,17,18,x0,x1,x2,x3,x4,x5,x6,x7,x8,x9,x10,x11,x12,x13,x14,x15,x16,y1,y2,y3,y4,c1,c2,c3,c4,c11,c12,c13,c14,c15,c16,c17,c21,c22,c23,c24,c25,c26,c27,c31,c32,c33,c34,c35,c41,c42,c43,c44,c45,c46,c47,c48,d0,d0b,d1,d2,d2b,d2c,d2t,d3,d4,d5,d10,d10b,d11,d11b,d11c,d12,d12d,d20,d20b,d21,d22,d22b,d23,d30,d31,d32,d33,d33b,d34,d34b,d40," Public Sub Main() Dim sel As String = "," & Replace(Environ$("PROBE"), " ", "") & "," ' A folder of its own for each run, so that two runs at once do not meet. Root = Environ$("TEMP") & "\dlgprobe" & Environ$("PROBE_TAG") Auto.gRoot = Root + DrvB1.DRoot = Root + AutoD.gRootD = Root #If Win64 Then Say "build: 64-bit (Win64), LenB(LongPtr)=" & LenB(CLngPtr(0)) #Else @@ -81,10 +160,27 @@ Module Probe Case "12b": S12b Case "12c": S12c Case "13": S13 + Case "14": S14 + Case "15": S15 + Case "16": S16 + Case "17": S17 + Case "18": S18 + Case "y1": Y1 + Case "y2": Y2 + Case "y3": Y3 + Case "y4": Y4 + Case Else + Select Case Left$(id, 1) + Case "x": RunEx id + Case "c": ProbeB1.RunB1 id + Case "d": ProbeD.Run id + End Select End Select Exit Sub EH: Disarm + DrvB1.DDisarm + AutoD.DisarmD Say " ERROR in scenario " & id & ": " & Err.Number & " " & Err.Description End Sub @@ -121,7 +217,7 @@ EH: End Sub ' A value as the probe prints it: its VarType, its type name and its text, or each element of an array. - Private Function Desc(ByVal x As Variant) As String + Public Function Desc(ByVal x As Variant) As String Dim i As Long Dim s As String If IsArray(x) Then @@ -136,7 +232,7 @@ EH: End Function ' What the driver saw during the episode that has just ended. - Private Sub Wrap(ByVal what As String) + Public Sub Wrap(ByVal what As String) Say " after " & what & ": dialogSeen=" & gSeenMain & " stayedOpen=" & gStayedOpen & " timedOut=" & gTimedOut & " msgBoxes=" & gMsgCount End Sub @@ -487,7 +583,7 @@ EH: Private Sub S11() Say "== 11 OpenFile on a nonexistent path" Dim d As New cDialog - Dim r As Long + Dim r As Variant ' OpenFile returns LongPtr in 1.1.0.0, which a Long does not take in a 64-bit build MsgSeq 1 Arm "S11a", False, "", 2 r = d.OpenFile(Root & "\nosuchfolder\nosuchfile.xyz") @@ -539,6 +635,142 @@ EH: Wrap "S12c" End Sub + ' 14: PathCombine, no dialog. + Private Sub S14() + Say "== 14 PathCombine" + Dim d As New cDialog + Dim p As Variant + For Each p In Array( _ + Array("C:\a", "b"), Array("C:\a\", "b"), Array("C:\a", "\b"), Array("C:\a\", "\b"), _ + Array("", "b"), Array("a", ""), Array("", ""), Array("C:\a", "D:\b"), Array("C:\a", "D:b"), _ + Array("C:\a", "\\srv\x"), Array("C:\a", "\b\c"), Array("C:\a", "..\b"), Array("a", "b/c"), _ + Array("C:", "b"), Array("C:\", "b"), Array("C:\a\\", "b"), Array("a", "c:"), Array("a/", "b"), _ + Array("C:\a", "http://x/y"), Array("C:\a\b.txt", "c")) + Say " PathCombine(" & Q(CStr(p(0))) & ", " & Q(CStr(p(1))) & ") = " & Q(d.PathCombine(CStr(p(0)), CStr(p(1)))) + Next + End Sub + + ' 15: SelectFiles: one file, two files, cancel. + Private Sub S15() + Say "== 15 SelectFiles" + Dim d As New cDialog + Dim d2 As New cDialog + Dim d3 As New cDialog + Dim d4 As New cDialog + Dim c As Collection + Dim v As Variant + d.InitialDir = Root & "\data" + Arm "S15a", True, "a.txt", 1 + Set c = d.SelectFiles() + Disarm + Say " (a) one file, MultiSelect=False: Count=" & c.Count & " FileName=" & Q(d.FileName) + For Each v In c + Say " " & Desc(v) + Next + Wrap "S15a" + d2.InitialDir = Root & "\data" + d2.MultiSelect = True + Arm "S15b", True, """a.txt"" ""b.txt""", 1 + Set c = d2.SelectFiles() + Disarm + Say " (b) two files, MultiSelect=True: Count=" & c.Count & " FileName=" & Q(d2.FileName) + For Each v In c + Say " " & Desc(v) + Next + Wrap "S15b" + d3.InitialDir = Root & "\data" + Arm "S15c", False, "", 2 + Set c = d3.SelectFiles() + Disarm + Say " (c) cancel: Count=" & c.Count & " TypeName=" & TypeName(c) + Wrap "S15c" + d4.InitialDir = Root & "\data" + d4.MultiSelect = True + Arm "S15d", True, "a.txt", 1 + Set c = d4.SelectFiles() + Disarm + Say " (d) one file, MultiSelect=True: Count=" & c.Count & " FileName=" & Q(d4.FileName) + For Each v In c + Say " " & Desc(v) + Next + Wrap "S15d" + End Sub + + ' 16: ShowOpen with MultiSelect, three files, and FileName afterwards. + Private Sub S16() + Say "== 16 ShowOpen MultiSelect, three files" + Dim d As New cDialog + Dim d2 As New cDialog + Dim r As Variant + d.InitialDir = Root & "\data" + d.MultiSelect = True + Arm "S16a", True, """exist.txt"" ""b.txt"" ""a.txt""", 1 + r = d.ShowOpen() + Disarm + Say " (a) three files: " & Desc(r) + Say " (a) FileName=" & Q(d.FileName) & " IsArray=" & IsArray(r) + Wrap "S16a" + d2.InitialDir = Root & "\data" + d2.MultiSelect = True + d2.FileName = "keep.txt" + Arm "S16b", True, """a.txt"" ""b.txt""", 1 + r = d2.ShowOpen() + Disarm + Say " (b) FileName was keep.txt, two files: " & Desc(r) + Say " (b) FileName=" & Q(d2.FileName) + Wrap "S16b" + End Sub + + ' 17: SelectAndOpenFile with names of files that do not exist, so that no program starts. + Private Sub S17() + Say "== 17 SelectAndOpenFile, nonexistent files" + Dim d As New cDialog + Dim d2 As New cDialog + Dim d3 As New cDialog + Dim r As LongPtr + d.InitialDir = Root & "\data" + d.FileMustExist = False + MsgSeq 1 + Arm "S17a", True, "nosuch1.txt", 1 + r = d.SelectAndOpenFile() + Disarm + Say " (a) one nonexistent file: " & r & " FileName=" & Q(d.FileName) + Wrap "S17a" + d2.InitialDir = Root & "\data" + d2.FileMustExist = False + d2.MultiSelect = True + MsgSeq 1 + Arm "S17b", True, """nosuch1.txt"" ""nosuch2.txt""", 1 + r = d2.SelectAndOpenFile() + Disarm + Say " (b) two nonexistent files: " & r & " FileName=" & Q(d2.FileName) + Wrap "S17b" + d3.InitialDir = Root & "\data" + Arm "S17c", False, "", 2 + r = d3.SelectAndOpenFile() + Disarm + Say " (c) cancel: " & r + Wrap "S17c" + End Sub + + ' 18: ShowBrowseForFolder with a folder whose name is not in the ANSI code page. + Private Sub S18() + Say "== 18 ShowBrowseForFolder, a Unicode folder name" + Dim d As New cDialog + Dim s As String + Dim fld As String + fld = Root & "\f" & ChrW(&H3A9) & ChrW(&H4E2D) & ChrW(&H6587) + MkDirIfMissing fld + Say " folder made: " & Q(Mid$(fld, Len(Root) + 1)) & " exists=" & (Len(Dir$(fld, vbDirectory)) > 0) + gDump = False + Arm "S18a", True, fld, 1 + s = d.ShowBrowseForFolder() + Disarm + Say " (a) typed folder -> " & Q(s) & " same as typed: " & (s = fld) & " InitialDir same: " & (d.InitialDir = fld) + Wrap "S18a" + RmDir fld + End Sub + Private Sub S13() Say "== 13 defaults" Dim d As New cDialog diff --git a/test/package-probes/TBMANLIB_Dialog/src/ProbeB1.twin b/test/package-probes/TBMANLIB_Dialog/src/ProbeB1.twin new file mode 100644 index 000000000..a54867a42 --- /dev/null +++ b/test/package-probes/TBMANLIB_Dialog/src/ProbeB1.twin @@ -0,0 +1,1081 @@ +' ProbeB1: what the five common dialogs of TBMANLIB_Dialog do at run time. +' +' cDialogColor (ChooseColor), cDialogFont (ChooseFont), cDialogPrint (PrintDlg), +' cDialogPageSetup (PageSetupDlg) and cDialogFindReplace (FindText, ReplaceText). Probe.twin +' calls RunB1 for the ids below; DrvB1.twin drives the dialogs. +' +' NEVER PRINT. Nothing here sends anything to a printer. The Print and Page Setup dialogs are +' answered with OK only where the class's code after the dialog reads the structure and frees the +' memory the dialog allocated; no scenario calls StartDoc, and the one that asks for a printer +' device context (c26) runs only when the default printer is a Microsoft virtual one and deletes +' the context at once. No scenario changes the default printer or the settings of a printer. +' +' Scenario ids (c = common dialogs) and what they ask: +' c1 Color: defaults and conversions (no dialog) +' c2 Color: Cancel; the dialog as it opens +' c3 Color: OK with FullOpen; the edit boxes; PreventFullOpen +' c4 Color: typed red, green and blue; Add to Custom Colors; the custom colors afterwards +' c11 Font: defaults, Style, Bold, Italic, ToCssFont, ToDescription (no dialog) +' c12 Font: Cancel; the dialog as it opens +' c13 Font: OK as it opens +' c14 Font: a chosen face, style, size and underline +' c15 Font: FixedPitchOnly, ForceFontExist, ShowEffects +' c16 Font: MinSize, MaxSize, ScreenFontsOnly = False +' c17 Font: MaxSize alone, MinSize above MaxSize, a size inside the limits +' c21 Print: defaults; a page number that does not fit an Integer (no dialog) +' c22 Print: Cancel; the dialog as it opens +' c23 Print: OK as it opens (no device context) +' c24 Print: page range and selection +' c25 Print: AllowSelection, AllowPageRange, HidePrintToFile, AllowPrintToFile, PrintToFile +' c26 Print: ReturnDC (only with a virtual default printer) +' c27 Print: FromPage, ToPage, MinPage, MaxPage and Copies that do not fit an Integer +' c31 Page Setup: defaults, conversions, ToDescription (no dialog) +' c32 Page Setup: Cancel; the dialog as it opens +' c33 Page Setup: OK as it opens, in inches +' c34 Page Setup: Unit = Millimeters, a paper size, Landscape +' c35 Page Setup: AllowMargins, AllowOrientation, AllowPaper +' c41 Find: defaults, MessageId, UpdateFromFlags with no dialog (no dialog) +' c42 Find: ShowFind and ShowReplace with no owner window +' c43 Find: ShowFind with an owner window; Find Next; Cancel +' c44 Find: ShowReplace; Replace; Replace All; Cancel +' c45 Find: AllowUpDown, AllowMatchCase, AllowWholeWord, MatchCase, WholeWord, Direction +' c46 Find: what the owner window receives (lParam), with the stack reused in between +' c47 Find: FindText called directly, the structure in a module-level variable (the control) +' c48 Find: FindText called directly, the structure in a local variable, as ShowFind does + +Module ProbeB1 + + Private Declare PtrSafe Function GetDefaultPrinterW Lib "winspool.drv" (ByVal pszBuffer As LongPtr, ByRef pcchBuffer As Long) As Long + Private Declare PtrSafe Function IsWindow Lib "user32" (ByVal hWnd As LongPtr) As Long + Private Declare PtrSafe Function DeleteDC Lib "gdi32" (ByVal hDC As LongPtr) As Long + Private Declare PtrSafe Function GetDeviceCaps Lib "gdi32" (ByVal hDC As LongPtr, ByVal nIndex As Long) As Long + Private Declare PtrSafe Function RegisterClassExW Lib "user32" (ByRef lpwcx As WNDCLASSEXB1) As Integer + Private Declare PtrSafe Function UnregisterClassW Lib "user32" (ByVal lpClassName As LongPtr, ByVal hInstance As LongPtr) As Long + Private Declare PtrSafe Function CreateWindowExW Lib "user32" (ByVal dwExStyle As Long, ByVal lpClassName As LongPtr, ByVal lpWindowName As LongPtr, ByVal dwStyle As Long, ByVal X As Long, ByVal Y As Long, ByVal nWidth As Long, ByVal nHeight As Long, ByVal hWndParent As LongPtr, ByVal hMenu As LongPtr, ByVal hInstance As LongPtr, ByVal lpParam As LongPtr) As LongPtr + Private Declare PtrSafe Function DestroyWindow Lib "user32" (ByVal hWnd As LongPtr) As Long + Private Declare PtrSafe Function DefWindowProcW Lib "user32" (ByVal hWnd As LongPtr, ByVal uMsg As Long, ByVal wParam As LongPtr, ByVal lParam As LongPtr) As LongPtr + Private Declare PtrSafe Function GetModuleHandleW Lib "kernel32" (ByVal lpModuleName As LongPtr) As LongPtr + Private Declare PtrSafe Sub RtlMoveMemory Lib "kernel32" (ByVal Destination As LongPtr, ByVal Source As LongPtr, ByVal Length As LongPtr) + Private Declare PtrSafe Function RegisterWindowMessageW Lib "user32" (ByVal lpString As LongPtr) As Long + + Private Type WNDCLASSEXB1 + cbSize As Long + style As Long + lpfnWndProc As LongPtr + cbClsExtra As Long + cbWndExtra As Long + hInstance As LongPtr + hIcon As LongPtr + hCursor As LongPtr + hbrBackground As LongPtr + lpszMenuName As LongPtr + lpszClassName As LongPtr + hIconSm As LongPtr + End Type + + ' The structure of FindText, as cDialogFindReplace declares it (for c47 and c48). + Private Type FINDREPLACEB1 + lStructSize As Long + hwndOwner As LongPtr + hInstance As LongPtr + flags As Long + lpstrFindWhat As LongPtr + lpstrReplaceWith As LongPtr + wFindWhatLen As Integer + wReplaceWithLen As Integer + lCustData As LongPtr + lpfnHook As LongPtr + lpTemplateName As LongPtr + End Type + + Private Declare PtrSafe Function FindTextB1 Lib "comdlg32.dll" Alias "FindTextW" (ByRef lpfr As FINDREPLACEB1) As LongPtr + + Private mStaticFr As FINDREPLACEB1 + Private mBuf As String + + Private Const OwnerClassName As String = "DlgProbeB1Owner" + + Private mOwner As LongPtr + Private mFindMsg As Long + Private mFR As cDialogFindReplace + Private mFound As Long ' how many times the owner window received the find message + Private mReadStruct As Boolean ' c46: read the structure behind lParam + Private mSink As Long + + Public Sub RunB1(ByVal id As String) + Dim n As Long = CLng(Val(Mid$(id, 2))) + ' The Print and Page Setup dialogs show the printers of the computer: print none of that. + DrvB1.DMask = (n >= 21 And n <= 35) + Select Case id + Case "c1": C1 + Case "c2": C2 + Case "c3": C3 + Case "c4": C4 + Case "c11": C11 + Case "c12": C12 + Case "c13": C13 + Case "c14": C14 + Case "c15": C15 + Case "c16": C16 + Case "c17": C17 + Case "c21": C21 + Case "c22": C22 + Case "c23": C23 + Case "c24": C24 + Case "c25": C25 + Case "c26": C26 + Case "c27": C27 + Case "c31": C31 + Case "c32": C32 + Case "c33": C33 + Case "c34": C34 + Case "c35": C35 + Case "c41": C41 + Case "c42": C42 + Case "c43": C43 + Case "c44": C44 + Case "c45": C45 + Case "c46": C46 + Case "c47": C47 + Case "c48": C48 + End Select + End Sub + + '========================================================================================= + ' Helpers + '========================================================================================= + + Private Function Cols(ByVal d As cDialogColor) As String + Cols = "Color=&H" & Hex$(d.Color) & " R=" & d.Red & " G=" & d.Green & " B=" & d.Blue + End Function + + Private Function CustomColors(ByVal d As cDialogColor) As String + Dim i As Long + Dim s As String + For i = 0 To 15 + s = s & Hex$(d.GetCustomColor(i)) & " " + Next + CustomColors = s + End Function + + Private Function PtrSz() As Long + #If Win64 Then + PtrSz = 8 + #Else + PtrSz = 4 + #End If + End Function + + Private Function PeekL(ByVal p As LongPtr) As Long + Dim v As Long + RtlMoveMemory VarPtr(v), p, 4 + PeekL = v + End Function + + Private Function PeekP(ByVal p As LongPtr) As LongPtr + Dim v As LongPtr + RtlMoveMemory VarPtr(v), p, PtrSz() + PeekP = v + End Function + + ' The default printer's name, or "" when there is none. + Private Function DefaultPrinter() As String + Dim n As Long = 0 + Dim s As String + GetDefaultPrinterW 0, n + If n <= 0 Then Exit Function + s = String$(n + 2, 0) + If GetDefaultPrinterW(StrPtr(s), n) <> 0 Then DefaultPrinter = Left$(s, n - 1) + End Function + + '========================================================================================= + ' cDialogColor + '========================================================================================= + + Private Sub C1() + DSay "== c1 Color: defaults and conversions" + Dim d As New cDialogColor + Dim v As Variant + DSay " defaults: " & Cols(d) & " FullOpen=" & d.FullOpen & " PreventFullOpen=" & d.PreventFullOpen & " SolidColor=" & d.SolidColor & " AnyColor=" & d.AnyColor & " OwnerHwnd=" & d.OwnerHwnd + DSay " custom colors: " & CustomColors(d) + DSay " GetCustomColor(-1)=" & d.GetCustomColor(-1) & " GetCustomColor(16)=" & d.GetCustomColor(16) + d.SetCustomColor 3, &H112233 + d.SetCustomColor 16, &H1 + d.SetCustomColor -1, &H1 + DSay " after SetCustomColor 3: " & CustomColors(d) + d.SetRgb 255, 128, 0 + DSay " SetRgb 255,128,0: " & Cols(d) & " html=" & d.ToHtmlColor() & " rgb=" & d.ToRgbString() + d.SetRgb 300, -5, 7 + DSay " SetRgb 300,-5,7: " & Cols(d) + d.Red = 256 + d.Green = 256 + d.Blue = 256 + DSay " Red/Green/Blue = 256: " & Cols(d) & " html=" & d.ToHtmlColor() + d.Color = &H123456 + DSay " Color=&H123456: " & Cols(d) & " html=" & d.ToHtmlColor() + d.Color = vbRed + DSay " Color=vbRed: " & Cols(d) & " html=" & d.ToHtmlColor() + d.Color = &H80000005 + DSay " Color=&H80000005 (a system color): " & Cols(d) & " html=" & d.ToHtmlColor() + d.Color = -1 + DSay " Color=-1: " & Cols(d) & " html=" & d.ToHtmlColor() + For Each v In Array("#00FF7F", "ff8000", " #102030 ", "#abc", "", "#GG0000", "#12345G", "#1234567", "&HFF0000", "#a0b0c0") + d.Color = &H0 + d.FromHtmlColor CStr(v) + DSay " FromHtmlColor " & DQ(CStr(v)) & ": " & Cols(d) & " html=" & d.ToHtmlColor() + Next + d.FullOpen = True + d.PreventFullOpen = True + d.SolidColor = True + d.AnyColor = False + d.OwnerHwnd = 12345 + d.SetCustomColor 0, &HABCDEF + d.Reset + DSay " after Reset: " & Cols(d) & " FullOpen=" & d.FullOpen & " PreventFullOpen=" & d.PreventFullOpen & " SolidColor=" & d.SolidColor & " AnyColor=" & d.AnyColor & " OwnerHwnd=" & d.OwnerHwnd + DSay " custom colors after Reset: " & CustomColors(d) + DSay " QBColor(1)=&H" & Hex$(QBColor(1)) & " QBColor(12)=&H" & Hex$(QBColor(12)) & " vbWhite=&H" & Hex$(vbWhite) + End Sub + + Private Sub C2() + DSay "== c2 Color: Cancel; the dialog as it opens" + Dim d As New cDialogColor + Dim r As Boolean + d.Color = RGB(10, 20, 30) + DArm "c2", "rec|cancel" + r = d.ShowDialog() + DDisarm + DSay " ShowDialog=" & r & " " & Cols(d) + DWrap "c2" + End Sub + + Private Sub C3() + DSay "== c3 Color: OK with FullOpen; PreventFullOpen" + Dim d As New cDialogColor + Dim d2 As New cDialogColor + Dim d3 As New cDialogColor + Dim r As Boolean + d.Color = RGB(10, 20, 30) + d.FullOpen = True + DArm "c3a", "wait 1|ctl 706|ctl 707|ctl 708|ctl 719|ok" + r = d.ShowDialog() + DDisarm + DSay " (a) FullOpen, OK: ShowDialog=" & r & " " & Cols(d) + DWrap "c3a" + d2.Color = RGB(10, 20, 30) + DArm "c3b", "ctl 706|ctl 719|ok" + r = d2.ShowDialog() + DDisarm + DSay " (b) not FullOpen, OK: ShowDialog=" & r & " " & Cols(d2) + DWrap "c3b" + d3.Color = RGB(10, 20, 30) + d3.PreventFullOpen = True + d3.FullOpen = True + DArm "c3c", "ctl 706|ctl 719|cancel" + r = d3.ShowDialog() + DDisarm + DSay " (c) PreventFullOpen and FullOpen both True, Cancel: ShowDialog=" & r & " " & Cols(d3) + DWrap "c3c" + End Sub + + Private Sub C4() + DSay "== c4 Color: typed red, green and blue; Add to Custom Colors" + Dim d As New cDialogColor + Dim d2 As New cDialogColor + Dim r As Boolean + d.FullOpen = True + DArm "c4a", "settext 706 200|settext 707 100|settext 708 50|wait 1|ctl 706|ctl 707|ctl 708|click 712|wait 1|ok" + r = d.ShowDialog() + DDisarm + DSay " (a) typed 200,100,50, Add, OK: ShowDialog=" & r & " " & Cols(d) & " html=" & d.ToHtmlColor() + DSay " custom colors: " & CustomColors(d) + DWrap "c4a" + d2.FullOpen = True + DArm "c4b", "settext 706 1|settext 707 2|settext 708 3|wait 1|click 712|wait 1|cancel" + r = d2.ShowDialog() + DDisarm + DSay " (b) typed 1,2,3, Add, Cancel: ShowDialog=" & r & " " & Cols(d2) + DSay " custom colors: " & CustomColors(d2) + DWrap "c4b" + End Sub + + '========================================================================================= + ' cDialogFont + '========================================================================================= + + Private Function Fnt(ByVal d As cDialogFont) As String + Fnt = "Face=" & DQ(d.FaceName) & " Size=" & d.Size & " Style=" & d.Style & " Bold=" & d.Bold & " Italic=" & d.Italic & " Underline=" & d.Underline & " StrikeOut=" & d.StrikeOut & " Color=&H" & Hex$(d.Color) + End Function + + Private Sub C11() + DSay "== c11 Font: defaults, Style, Bold, Italic, ToCssFont, ToDescription" + Dim d As New cDialogFont + DSay " defaults: " & Fnt(d) + DSay " MinSize=" & d.MinSize & " MaxSize=" & d.MaxSize & " FixedPitchOnly=" & d.FixedPitchOnly & " ForceFontExist=" & d.ForceFontExist & " ShowEffects=" & d.ShowEffects & " ScreenFontsOnly=" & d.ScreenFontsOnly & " OwnerHwnd=" & d.OwnerHwnd + DSay " ToDescription=" & DQ(d.ToDescription()) + DSay " ToCssFont=" & DQ(d.ToCssFont()) + d.Bold = True + DSay " Bold=True: " & Fnt(d) + d.Italic = True + DSay " Italic=True: " & Fnt(d) + d.Bold = False + DSay " Bold=False: " & Fnt(d) + d.Style = FontStyle.StyleBoldItalic + DSay " Style=StyleBoldItalic: Style=" & d.Style & " Bold=" & d.Bold & " Italic=" & d.Italic + d.Style = 4 + DSay " Style=4: Style=" & d.Style & " Bold=" & d.Bold & " Italic=" & d.Italic + d.Style = FontStyle.StyleRegular + d.Size = 0 + DSay " Size=0: Size=" & d.Size + d.Size = -3 + DSay " Size=-3: Size=" & d.Size + d.Size = 10.5 + DSay " Size=10.5: Size=" & d.Size + d.FaceName = "Courier New" + d.Bold = True + d.Italic = True + d.Underline = True + d.StrikeOut = True + d.Color = RGB(255, 0, 0) + DSay " ToDescription=" & DQ(d.ToDescription()) + DSay " ToCssFont=" & DQ(d.ToCssFont()) + d.FaceName = "O'Brien Sans" + DSay " ToCssFont with a quote in the face name=" & DQ(d.ToCssFont()) + d.Reset + DSay " after Reset: " & Fnt(d) & " ToDescription=" & DQ(d.ToDescription()) + DSay " FontWeight FW_BOLD=" & FontWeight.FW_BOLD & " FontFamily FF_SWISS=" & FontFamily.FF_SWISS & " FontPitch FIXED_PITCH=" & FontPitch.FIXED_PITCH & " FontStyle StyleBoldItalic=" & FontStyle.StyleBoldItalic + End Sub + + Private Sub C12() + DSay "== c12 Font: Cancel; the dialog as it opens" + Dim d As New cDialogFont + Dim r As Boolean + DArm "c12", "rec|items 1136 6|items 1137|items 1138 20|items 1139 20|cancel" + r = d.ShowDialog() + DDisarm + DSay " ShowDialog=" & r & " " & Fnt(d) + DWrap "c12" + End Sub + + Private Sub C13() + DSay "== c13 Font: OK as it opens" + Dim d As New cDialogFont + Dim d2 As New cDialogFont + Dim r As Boolean + DArm "c13a", "ctl 1136|ctl 1137|ctl 1138|ctl 1139|ctl 1040|ctl 1041|ok" + r = d.ShowDialog() + DDisarm + DSay " (a) defaults, OK: ShowDialog=" & r & " " & Fnt(d) + DWrap "c13a" + d2.FaceName = "Arial" + d2.Size = 12 + d2.Bold = True + d2.Italic = True + d2.Underline = True + d2.StrikeOut = True + d2.Color = RGB(0, 0, 255) + DArm "c13b", "ctl 1136|ctl 1137|ctl 1138|ctl 1139|ctl 1040|ctl 1041|ok" + r = d2.ShowDialog() + DDisarm + DSay " (b) Arial 12 bold italic underline strikeout blue, OK: ShowDialog=" & r & " " & Fnt(d2) + DWrap "c13b" + End Sub + + Private Sub C14() + DSay "== c14 Font: a chosen face, style, size and effects" + Dim d As New cDialogFont + Dim r As Boolean + DArm "c14", "cbfind 1136 Courier New|wait 1|items 1137|cbfind 1137 Bold Italic|cbfind 1138 14|click 1041|click 1040|cbsel 1139 2|ctl 1136|ctl 1137|ctl 1138|ctl 1139|ok" + r = d.ShowDialog() + DDisarm + DSay " ShowDialog=" & r & " " & Fnt(d) + DSay " ToDescription=" & DQ(d.ToDescription()) + DSay " ToCssFont=" & DQ(d.ToCssFont()) + DWrap "c14" + End Sub + + Private Sub C15() + DSay "== c15 Font: FixedPitchOnly, ForceFontExist, ShowEffects" + Dim d As New cDialogFont + Dim d2 As New cDialogFont + Dim d3 As New cDialogFont + Dim d4 As New cDialogFont + Dim r As Boolean + DArm "c15a", "items 1136 0|cancel" + r = d.ShowDialog() + DDisarm + DSay " (a) all fonts: ShowDialog=" & r + d2.FixedPitchOnly = True + d2.FaceName = "Consolas" + DArm "c15b", "items 1136 40|ctl 1136|ok" + r = d2.ShowDialog() + DDisarm + DSay " (b) FixedPitchOnly, face Consolas, OK: ShowDialog=" & r & " " & Fnt(d2) + d3.FaceName = "No Such Font Face" + DArm "c15c", "msg 1|ctl 1136|ok|wait 2|cancel" + r = d3.ShowDialog() + DDisarm + DSay " (c) ForceFontExist=True, face that does not exist, OK then Cancel: ShowDialog=" & r & " " & Fnt(d3) + DWrap "c15c" + d4.FaceName = "No Such Font Face" + d4.ForceFontExist = False + DArm "c15d", "msg 1|ctl 1136|ok" + r = d4.ShowDialog() + DDisarm + DSay " (d) ForceFontExist=False, face that does not exist, OK: ShowDialog=" & r & " " & Fnt(d4) + DWrap "c15d" + End Sub + + Private Sub C16() + DSay "== c16 Font: ShowEffects, MinSize, MaxSize, ScreenFontsOnly" + Dim d As New cDialogFont + Dim d2 As New cDialogFont + Dim d3 As New cDialogFont + Dim d4 As New cDialogFont + Dim r As Boolean + d.ShowEffects = False + DArm "c16a", "ctl 1040|ctl 1041|ctl 1139|cancel" + r = d.ShowDialog() + DDisarm + DSay " (a) ShowEffects=False: ShowDialog=" & r + d2.MinSize = 10 + d2.MaxSize = 14 + DArm "c16b", "items 1138 40|ok" + r = d2.ShowDialog() + DDisarm + DSay " (b) MinSize=10 MaxSize=14, OK: ShowDialog=" & r & " " & Fnt(d2) + d3.MinSize = 10 + DArm "c16c", "items 1138 40|cancel" + r = d3.ShowDialog() + DDisarm + DSay " (c) MinSize=10 only: ShowDialog=" & r + DWrap "c16c" + d4.ScreenFontsOnly = False + DArm "c16d", "rec|cancel" + r = d4.ShowDialog() + DDisarm + DSay " (d) ScreenFontsOnly=False: ShowDialog=" & r & " " & Fnt(d4) + DWrap "c16d" + End Sub + + Private Sub C17() + DSay "== c17 Font: MaxSize alone, MinSize above MaxSize, a size inside the limits" + Dim d As New cDialogFont + Dim d2 As New cDialogFont + Dim d3 As New cDialogFont + Dim r As Boolean + d.MaxSize = 14 + DArm "c17a", "items 1138 40|ok" + r = d.ShowDialog() + DDisarm + DSay " (a) MaxSize=14 only: ShowDialog=" & r & " " & Fnt(d) + DWrap "c17a" + d2.MinSize = 20 + d2.MaxSize = 10 + DArm "c17b", "items 1138 40|cancel" + r = d2.ShowDialog() + DDisarm + DSay " (b) MinSize=20 MaxSize=10: ShowDialog=" & r + DWrap "c17b" + d3.MinSize = 10 + d3.MaxSize = 14 + d3.Size = 12 + DArm "c17c", "items 1138 40|ok" + r = d3.ShowDialog() + DDisarm + DSay " (c) MinSize=10 MaxSize=14 Size=12, OK: ShowDialog=" & r & " " & Fnt(d3) + DWrap "c17c" + End Sub + + '========================================================================================= + ' cDialogPrint + '========================================================================================= + + Private Function Prn(ByVal d As cDialogPrint) As String + Prn = "FromPage=" & d.FromPage & " ToPage=" & d.ToPage & " MinPage=" & d.MinPage & " MaxPage=" & d.MaxPage & " Copies=" & d.Copies & " Collate=" & d.Collate & " PrintToFile=" & d.PrintToFile & " Range=" & d.PrintRangeMode & " Cancelled=" & d.Cancelled & " LastError=" & d.LastError & " hDC=" & d.hDC + End Function + + Private Sub C21() + DSay "== c21 Print: defaults; a page number that does not fit an Integer" + Dim d As New cDialogPrint + Dim d2 As New cDialogPrint + Dim r As Boolean + Dim en As Long + Dim ed As String + DSay " defaults: " & Prn(d) + DSay " AllowSelection=" & d.AllowSelection & " AllowPageRange=" & d.AllowPageRange & " AllowPrintToFile=" & d.AllowPrintToFile & " HidePrintToFile=" & d.HidePrintToFile & " ReturnDC=" & d.ReturnDC & " OwnerHwnd=" & d.OwnerHwnd + DSay " ToDescription=" & DQ(d.ToDescription()) + d.Copies = 0 + DSay " Copies=0: Copies=" & d.Copies + d.Copies = -2 + DSay " Copies=-2: Copies=" & d.Copies + d.Copies = 3 + d.Collate = True + d.PrintToFile = True + d.PrintRangeMode = PrintRange.PageRange + d.FromPage = 2 + d.ToPage = 4 + DSay " ToDescription (3 copies, collate, file, pages 2-4)=" & DQ(d.ToDescription()) + d.PrintRangeMode = PrintRange.Selection + DSay " ToDescription (selection)=" & DQ(d.ToDescription()) + d.PrintRangeMode = 7 + DSay " PrintRangeMode=7: ToDescription=" & DQ(d.ToDescription()) + d.Reset + DSay " after Reset: " & Prn(d) + d2.MaxPage = 40000 + On Error Resume Next + r = d2.ShowDialog() + en = Err.Number + ed = Err.Description + On Error GoTo 0 + DSay " MaxPage=40000, ShowDialog: Err.Number=" & en & " " & DQ(ed) & " returned " & r + End Sub + + ' One field of the class set to a value, then ShowDialog; a dialog that opens is cancelled. + Private Sub C27Try(ByVal what As String, ByVal v As Long) + Dim d As New cDialogPrint + Dim r As Boolean + Dim en As Long + d.ReturnDC = False + Select Case what + Case "FromPage": d.FromPage = v + Case "ToPage": d.ToPage = v + Case "MinPage": d.MinPage = v + Case "MaxPage": d.MaxPage = v + Case "Copies": d.Copies = v + End Select + DArm "c27" & what, "cancel" + On Error Resume Next + r = d.ShowDialog() + en = Err.Number + On Error GoTo 0 + DDisarm + DSay " " & what & "=" & v & ": Err.Number=" & en & " returned " & r & " cancelled=" & d.Cancelled & " lasterror=" & d.LastError + End Sub + + Private Sub C27() + DSay "== c27 Print: values that do not fit an Integer, field by field" + C27Try "FromPage", 40000 + C27Try "ToPage", 40000 + C27Try "MinPage", 40000 + C27Try "MaxPage", 32768 + C27Try "MaxPage", 32767 + C27Try "MaxPage", -40000 + C27Try "Copies", 40000 + End Sub + + Private Sub C22() + DSay "== c22 Print: Cancel; the dialog as it opens" + Dim d As New cDialogPrint + Dim r As Boolean + d.ReturnDC = False + DArm "c22", "rec|cancel" + r = d.ShowDialog() + DDisarm + DSay " ShowDialog=" & r & " " & Prn(d) + DWrap "c22" + End Sub + + Private Sub C23() + DSay "== c23 Print: OK as it opens (no device context)" + Dim d As New cDialogPrint + Dim r As Boolean + d.ReturnDC = False + DArm "c23", "ok" + r = d.ShowDialog() + DDisarm + DSay " ShowDialog=" & r & " " & Prn(d) + DSay " ToDescription=" & DQ(d.ToDescription()) + DWrap "c23" + End Sub + + Private Sub C24() + DSay "== c24 Print: page range and selection" + Dim d As New cDialogPrint + Dim d2 As New cDialogPrint + Dim d3 As New cDialogPrint + Dim r As Boolean + d.ReturnDC = False + d.MinPage = 1 + d.MaxPage = 20 + d.FromPage = 3 + d.ToPage = 7 + d.PrintRangeMode = PrintRange.PageRange + DArm "c24a", "rec|ok" + r = d.ShowDialog() + DDisarm + DSay " (a) PageRange 3-7 of 1-20, OK: ShowDialog=" & r & " " & Prn(d) + DWrap "c24a" + d2.ReturnDC = False + d2.PrintRangeMode = PrintRange.Selection + DArm "c24b", "ok" + r = d2.ShowDialog() + DDisarm + DSay " (b) Selection, OK: ShowDialog=" & r & " " & Prn(d2) + DWrap "c24b" + d3.ReturnDC = False + d3.MinPage = 1 + d3.MaxPage = 20 + d3.FromPage = 3 + d3.ToPage = 7 + d3.PrintRangeMode = PrintRange.PageRange + DArm "c24c", "settext 1152 5|settext 1153 9|wait 1|ctl 1058|ok" + r = d3.ShowDialog() + DDisarm + DSay " (c) PageRange, typed 5 to 9, OK: ShowDialog=" & r & " " & Prn(d3) + DWrap "c24c" + End Sub + + Private Sub C25() + DSay "== c25 Print: AllowSelection, AllowPageRange, HidePrintToFile, AllowPrintToFile, PrintToFile" + Dim d As New cDialogPrint + Dim d2 As New cDialogPrint + Dim d3 As New cDialogPrint + Dim d4 As New cDialogPrint + Dim r As Boolean + d.ReturnDC = False + d.AllowSelection = False + d.AllowPageRange = False + DArm "c25a", "rec|cancel" + r = d.ShowDialog() + DDisarm + DSay " (a) AllowSelection=False AllowPageRange=False, Cancel: ShowDialog=" & r + d2.ReturnDC = False + d2.HidePrintToFile = True + DArm "c25b", "rec|cancel" + r = d2.ShowDialog() + DDisarm + DSay " (b) HidePrintToFile=True, Cancel: ShowDialog=" & r + d3.ReturnDC = False + d3.AllowPrintToFile = False + DArm "c25c", "rec|cancel" + r = d3.ShowDialog() + DDisarm + DSay " (c) AllowPrintToFile=False, Cancel: ShowDialog=" & r + d4.ReturnDC = False + d4.PrintToFile = True + d4.Collate = True + d4.Copies = 3 + DArm "c25d", "rec|cancel" + r = d4.ShowDialog() + DDisarm + DSay " (d) PrintToFile=True Collate=True Copies=3, Cancel: ShowDialog=" & r & " " & Prn(d4) + End Sub + + Private Sub C26() + DSay "== c26 Print: ReturnDC" + Dim d As New cDialogPrint + Dim r As Boolean + Dim p As String = DefaultPrinter() + ' The default ReturnDC = True makes the dialog create a device context for the printer + ' chosen. That is not printing, but only a Microsoft virtual printer is used for it: the + ' scenario chooses "Microsoft Print to PDF" in the dialog, cancels when the list has no such + ' printer, and checks that the default printer is the same afterwards. + DSay " ReturnDC=" & d.ReturnDC + DArm "c26", "cbfind 1139 Microsoft Print to PDF|needfind|wait 1|ctl 1139|ok" + r = d.ShowDialog() + DDisarm + DSay " ShowDialog=" & r & " hDC<>0: " & (d.hDC <> 0) & " Copies=" & d.Copies + DSay " the default printer is the same as before: " & (DefaultPrinter() = p) + If d.hDC <> 0 Then + DSay " HORZRES=" & GetDeviceCaps(d.hDC, 8) & " LOGPIXELSX=" & GetDeviceCaps(d.hDC, 88) + DSay " DeleteDC returned " & DeleteDC(d.hDC) + End If + DWrap "c26" + End Sub + + '========================================================================================= + ' cDialogPageSetup + '========================================================================================= + + Private Function Pg(ByVal d As cDialogPageSetup) As String + Pg = "Paper=" & d.PaperWidth & "x" & d.PaperHeight & " Margins L/R/T/B=" & d.MarginLeft & "/" & d.MarginRight & "/" & d.MarginTop & "/" & d.MarginBottom & " Orientation=" & d.Orientation & " Unit=" & d.Unit + End Function + + Private Sub C31() + DSay "== c31 Page Setup: defaults, conversions, ToDescription" + Dim d As New cDialogPageSetup + DSay " defaults: " & Pg(d) + DSay " AllowMargins=" & d.AllowMargins & " AllowOrientation=" & d.AllowOrientation & " AllowPaper=" & d.AllowPaper & " DefaultMinMargins=" & d.DefaultMinMargins & " OwnerHwnd=" & d.OwnerHwnd + DSay " ToDescription=" & DQ(d.ToDescription()) + DSay " printable " & d.GetPrintableWidth() & " x " & d.GetPrintableHeight() + d.SetA4Size + DSay " SetA4Size: " & Pg(d) + d.SetLegalSize + DSay " SetLegalSize: " & Pg(d) + d.SetLetterSize + d.SetNarrowMargins + DSay " SetLetterSize, SetNarrowMargins: " & Pg(d) & " printable " & d.GetPrintableWidth() & " x " & d.GetPrintableHeight() + d.SetDefaultMargins + DSay " SetDefaultMargins: " & Pg(d) + d.SetA4Size + d.Unit = MeasurementUnit.Millimeters + DSay " A4, Unit=Millimeters: " & Pg(d) + DSay " ToDescription=" & DQ(d.ToDescription()) + d.Orientation = PaperOrientation.Landscape + DSay " Orientation=Landscape: paper is still " & d.PaperWidth & "x" & d.PaperHeight & " ToDescription=" & DQ(d.ToDescription()) + d.Unit = MeasurementUnit.Inches + d.Reset + DSay " after Reset: " & Pg(d) + DSay " mDialog: InchesToTwips(1)=" & InchesToTwips(1) & " PAPER_A4_WIDTH=" & PAPER_A4_WIDTH + End Sub + + Private Sub C32() + DSay "== c32 Page Setup: Cancel; the dialog as it opens" + Dim d As New cDialogPageSetup + Dim r As Boolean + DArm "c32", "rec|cancel" + r = d.ShowDialog() + DDisarm + DSay " ShowDialog=" & r & " " & Pg(d) + DWrap "c32" + End Sub + + Private Sub C33() + DSay "== c33 Page Setup: OK as it opens, in inches" + Dim d As New cDialogPageSetup + Dim d2 As New cDialogPageSetup + Dim r As Boolean + DArm "c33a", "rec|ok" + r = d.ShowDialog() + DDisarm + DSay " (a) defaults, OK: ShowDialog=" & r & " " & Pg(d) + DSay " ToDescription=" & DQ(d.ToDescription()) + DWrap "c33a" + d2.PaperWidth = 8.5 + d2.PaperHeight = 14 + d2.MarginLeft = 0.5 + d2.MarginRight = 0.75 + d2.MarginTop = 1.25 + d2.MarginBottom = 0.3 + DArm "c33b", "ok" + r = d2.ShowDialog() + DDisarm + DSay " (b) legal paper, margins 0.5/0.75/1.25/0.3, OK: ShowDialog=" & r & " " & Pg(d2) + DWrap "c33b" + End Sub + + Private Sub C34() + DSay "== c34 Page Setup: Unit = Millimeters, Landscape" + Dim d As New cDialogPageSetup + Dim d2 As New cDialogPageSetup + Dim r As Boolean + d.Unit = MeasurementUnit.Millimeters + d.MarginLeft = 1 + d.MarginRight = 1 + d.MarginTop = 1 + d.MarginBottom = 1 + DArm "c34a", "rec|ok" + r = d.ShowDialog() + DDisarm + DSay " (a) Millimeters, margins 1 inch, OK: ShowDialog=" & r & " " & Pg(d) + DWrap "c34a" + DArm "c34b", "click 1057|wait 1|settext 1155 0.5|ok" + r = d2.ShowDialog() + DDisarm + DSay " (b) Landscape chosen in the dialog, OK: ShowDialog=" & r & " " & Pg(d2) + DWrap "c34b" + End Sub + + Private Sub C35() + DSay "== c35 Page Setup: AllowMargins, AllowOrientation, AllowPaper" + Dim d As New cDialogPageSetup + Dim r As Boolean + d.AllowMargins = False + d.AllowOrientation = False + d.AllowPaper = False + DArm "c35", "rec|cancel" + r = d.ShowDialog() + DDisarm + DSay " (a) ShowDialog=" & r + DWrap "c35a" + Dim d2 As New cDialogPageSetup + d2.Orientation = PaperOrientation.Landscape + d2.SetA4Size + DArm "c35b", "ctl 1056|ctl 1057|ctl 1137|ok" + r = d2.ShowDialog() + DDisarm + DSay " (b) Orientation=Landscape, SetA4Size, OK: ShowDialog=" & r & " " & Pg(d2) + DWrap "c35b" + End Sub + + '========================================================================================= + ' cDialogFindReplace + '========================================================================================= + + Private Function Fr(ByVal d As cDialogFindReplace) As String + Fr = "FindWhat=" & DQ(d.FindWhat) & " ReplaceWith=" & DQ(d.ReplaceWith) & " MatchCase=" & d.MatchCase & " WholeWord=" & d.WholeWord & " Direction=" & d.Direction & " Mode=" & d.Mode & " LastAction=" & d.LastAction & " LastError=" & d.LastError & " IsVisible=" & d.IsVisible() & " hWnd<>0: " & (d.hWnd <> 0) + End Function + + Private Sub C41() + DSay "== c41 Find: defaults, MessageId, UpdateFromFlags with no dialog" + Dim d As New cDialogFindReplace + Dim d2 As New cDialogFindReplace + DSay " defaults: " & Fr(d) + DSay " AllowUpDown=" & d.AllowUpDown & " AllowMatchCase=" & d.AllowMatchCase & " AllowWholeWord=" & d.AllowWholeWord & " OwnerHwnd=" & d.OwnerHwnd + DSay " MessageId=" & d.MessageId & " same as RegisterWindowMessage(commdlg_FindReplace): " & (d.MessageId = RegisterWindowMessageW(StrPtr("commdlg_FindReplace"))) & " second object: " & (d2.MessageId = d.MessageId) + DSay " ToDescription=" & DQ(d.ToDescription()) + d.FindWhat = "alpha" + d.ReplaceWith = "beta" + d.MatchCase = True + d.Direction = SearchDirection.Up + DSay " ToDescription=" & DQ(d.ToDescription()) + d.Mode = DialogMode.ReplaceMode + DSay " Mode=ReplaceMode: ToDescription=" & DQ(d.ToDescription()) + DSay " FindWhat before UpdateFromFlags=" & DQ(d.FindWhat) + d.UpdateFromFlags 1 + 4 + 8 + DSay " UpdateFromFlags(FR_DOWN|FR_MATCHCASE|FR_FINDNEXT): " & Fr(d) + d.UpdateFromFlags 2 + 16 + DSay " UpdateFromFlags(FR_WHOLEWORD|FR_REPLACE): " & Fr(d) + d.UpdateFromFlags 32 + DSay " UpdateFromFlags(FR_REPLACEALL): " & Fr(d) + d.UpdateFromFlags &H40 + DSay " UpdateFromFlags(FR_DIALOGTERM): " & Fr(d) + d.UpdateFromFlags 0 + DSay " UpdateFromFlags(0): " & Fr(d) + d.UpdateFromFlags 8 + 32 + DSay " UpdateFromFlags(FR_FINDNEXT|FR_REPLACEALL): " & Fr(d) + d.Reset + DSay " after Reset: " & Fr(d) + DSay " enums: DialogMode.ReplaceMode=" & DialogMode.ReplaceMode & " SearchDirection.Down=" & SearchDirection.Down & " FindAction.DialogClosed=" & FindAction.DialogClosed & " FindAction.ReplaceAll=" & FindAction.ReplaceAll + End Sub + + Private Sub C42() + DSay "== c42 Find: ShowFind and ShowReplace with no owner window" + Dim d As New cDialogFindReplace + Dim d2 As New cDialogFindReplace + Dim r As Boolean + d.FindWhat = "x" + DArm "c42a", "rec|cancel" + r = d.ShowFind() + DDisarm + DSay " ShowFind with OwnerHwnd=0: " & r & " " & Fr(d) & " LastError hex=&H" & Hex$(d.LastError) + DWrap "c42a" + DArm "c42b", "rec|cancel" + r = d2.ShowReplace() + DDisarm + DSay " ShowReplace with OwnerHwnd=0: " & r & " " & Fr(d2) & " LastError hex=&H" & Hex$(d2.LastError) + DWrap "c42b" + End Sub + + ' The owner window: it receives the find message that the dialog sends. + Public Function OwnerProc(ByVal hWnd As LongPtr, ByVal uMsg As Long, ByVal wParam As LongPtr, ByVal lParam As LongPtr) As LongPtr + If mFindMsg <> 0 And uMsg = mFindMsg Then + OnFindMessage lParam + OwnerProc = 0 + Else + OwnerProc = DefWindowProcW(hWnd, uMsg, wParam, lParam) + End If + End Function + + Private Function FlagsOffset() As Long + #If Win64 Then + FlagsOffset = 24 + #Else + FlagsOffset = 12 + #End If + End Function + + Private Sub OnFindMessage(ByVal lParam As LongPtr) + Dim flags As Long + Dim sz As Long + Dim own As LongPtr + mFound = mFound + 1 + sz = PeekL(lParam) + own = PeekP(lParam + PtrSz()) + flags = PeekL(lParam + FlagsOffset()) + DSay " owner received the find message #" & mFound & ": lStructSize=" & sz & " hwndOwner is the owner: " & (own = mOwner) & " Flags=&H" & Hex$(flags) + If mReadStruct Then + DSay " lpstrFindWhat<>0: " & (PeekP(lParam + FlagsOffset() + PtrSz()) <> 0) + End If + If Not (mFR Is Nothing) Then + mFR.UpdateFromFlags flags + DSay " after UpdateFromFlags: " & Fr(mFR) + Else + DSay " the text in the buffer: " & DQ(Left$(mBuf, InStr(mBuf, vbNullChar) - 1)) + End If + End Sub + + Private Function MakeOwner() As Boolean + Dim wc As WNDCLASSEXB1 + Dim inst As LongPtr = GetModuleHandleW(0) + wc.cbSize = IIf(PtrSz() = 8, 80, 48) + wc.lpfnWndProc = AddressOf OwnerProc + wc.hInstance = inst + wc.lpszClassName = StrPtr(OwnerClassName) + If RegisterClassExW(wc) = 0 Then + DSay " RegisterClassExW failed" + Exit Function + End If + mOwner = CreateWindowExW(0, StrPtr(OwnerClassName), StrPtr("probe owner"), 0, 0, 0, 100, 100, 0, 0, inst, 0) + If mOwner = 0 Then + DSay " CreateWindowExW failed" + Exit Function + End If + mFindMsg = RegisterWindowMessageW(StrPtr("commdlg_FindReplace")) + MakeOwner = True + End Function + + Private Sub FreeOwner() + If mOwner <> 0 Then DestroyWindow mOwner + mOwner = 0 + UnregisterClassW StrPtr(OwnerClassName), GetModuleHandleW(0) + End Sub + + Private Sub C43() + DSay "== c43 Find: ShowFind with an owner window; Find Next; Cancel" + Dim r As Boolean + If Not MakeOwner() Then Exit Sub + Set mFR = New cDialogFindReplace + mFR.OwnerHwnd = mOwner + mFR.FindWhat = "alpha" + mFR.MatchCase = True + mFR.WholeWord = False + mFound = 0 + r = mFR.ShowFind() + DSay " ShowFind=" & r & " " & Fr(mFR) & " hWnd is a window: " & (mFR.hWnd <> 0) + DArm "c43", "rec|settext 1152 beta gamma|click 1041|wait 1|click 1|wait 2|cancel|wait 2|quit" + DPump + DDisarm + DSay " the Find window still exists: " & (IsWindow(mFR.hWnd) <> 0) + DSay " messages received: " & mFound & "; at the end: " & Fr(mFR) + DWrap "c43" + FreeOwner + End Sub + + Private Sub C44() + DSay "== c44 Find: ShowReplace; Replace; Replace All; Cancel" + Dim r As Boolean + If Not MakeOwner() Then Exit Sub + Set mFR = New cDialogFindReplace + mFR.OwnerHwnd = mOwner + mFR.FindWhat = "old" + mFR.ReplaceWith = "new" + mFound = 0 + r = mFR.ShowReplace() + DSay " ShowReplace=" & r & " " & Fr(mFR) + DArm "c44", "rec|settext 1152 one|settext 1153 two|wait 1|click 1024|wait 2|click 1025|wait 2|click 1|wait 2|cancel|wait 2|quit" + DPump + DDisarm + DSay " messages received: " & mFound & "; at the end: " & Fr(mFR) + DWrap "c44" + FreeOwner + End Sub + + Private Sub C45() + DSay "== c45 Find: AllowUpDown, AllowMatchCase, AllowWholeWord, Direction" + Dim r As Boolean + If Not MakeOwner() Then Exit Sub + Set mFR = New cDialogFindReplace + mFR.OwnerHwnd = mOwner + mFR.AllowUpDown = False + mFR.AllowMatchCase = False + mFR.AllowWholeWord = False + r = mFR.ShowFind() + DArm "c45a", "rec|cancel|wait 2|quit" + DPump + DDisarm + DSay " (a) all three Allow = False: " & Fr(mFR) + Set mFR = New cDialogFindReplace + mFR.OwnerHwnd = mOwner + mFR.Direction = SearchDirection.Up + mFR.WholeWord = True + mFR.MatchCase = True + r = mFR.ShowFind() + DArm "c45b", "rec|click 1|wait 2|cancel|wait 2|quit" + DPump + DDisarm + DSay " (b) Direction=Up WholeWord MatchCase: " & Fr(mFR) + FreeOwner + End Sub + + ' Calls FindText as cDialogFindReplace.ShowFind does, with the structure in a variable of + ' this procedure (useStatic = False) or in a variable of the module (useStatic = True). + Private Function ShowFindDirect(ByVal useStatic As Boolean) As LongPtr + Dim localFr As FINDREPLACEB1 + mBuf = "alpha" & String$(251, 0) + If useStatic Then + With mStaticFr + .lStructSize = IIf(PtrSz() = 8, 80, 40) + .hwndOwner = mOwner + .flags = 1 + .lpstrFindWhat = StrPtr(mBuf) + .wFindWhatLen = 256 + End With + ShowFindDirect = FindTextB1(mStaticFr) + Else + With localFr + .lStructSize = IIf(PtrSz() = 8, 80, 40) + .hwndOwner = mOwner + .flags = 1 + .lpstrFindWhat = StrPtr(mBuf) + .wFindWhatLen = 256 + End With + ShowFindDirect = FindTextB1(localFr) + End If + End Function + + Private Sub C47() + DSay "== c47 Find: FindText called directly, the structure in a module-level variable" + Dim h As LongPtr + If Not MakeOwner() Then Exit Sub + Set mFR = Nothing + mFound = 0 + h = ShowFindDirect(True) + DSay " FindText returned a window: " & (h <> 0) + DArm "c47", "settext 1152 beta gamma|wait 1|click 1|wait 2|cancel|wait 2|quit" + DPump + DDisarm + DSay " the Find window still exists: " & (IsWindow(h) <> 0) + DSay " messages received: " & mFound + DWrap "c47" + FreeOwner + End Sub + + Private Sub C48() + DSay "== c48 Find: FindText called directly, the structure in a local variable" + Dim h As LongPtr + If Not MakeOwner() Then Exit Sub + Set mFR = Nothing + mFound = 0 + h = ShowFindDirect(False) + DSay " FindText returned a window: " & (h <> 0) + DArm "c48", "settext 1152 beta gamma|wait 1|click 1|wait 2|cancel|wait 2|quit" + DPump + DDisarm + DSay " the Find window still exists: " & (IsWindow(h) <> 0) + DSay " messages received: " & mFound + DWrap "c48" + FreeOwner + End Sub + + Private Sub StackFill() + Dim a(0 To 2047) As Long + Dim i As Long + For i = 0 To 2047 + a(i) = &H41414141 + Next + mSink = a(2047) + End Sub + + Private Sub C46() + DSay "== c46 Find: what the owner window receives, with the stack reused in between" + Dim r As Boolean + If Not MakeOwner() Then Exit Sub + mReadStruct = True + Set mFR = New cDialogFindReplace + mFR.OwnerHwnd = mOwner + mFR.FindWhat = "alpha" + mFound = 0 + r = mFR.ShowFind() + StackFill + DSay " ShowFind=" & r & ", the stack below the caller overwritten" + DArm "c46", "settext 1152 beta|wait 1|click 1|wait 2|cancel|wait 2|quit" + DPump + DDisarm + DSay " messages received: " & mFound & "; at the end: " & Fr(mFR) + DWrap "c46" + mReadStruct = False + FreeOwner + End Sub + +End Module diff --git a/test/package-probes/TBMANLIB_Dialog/src/ProbeD.twin b/test/package-probes/TBMANLIB_Dialog/src/ProbeD.twin new file mode 100644 index 000000000..2db31641c --- /dev/null +++ b/test/package-probes/TBMANLIB_Dialog/src/ProbeD.twin @@ -0,0 +1,318 @@ +' ProbeD: the scenarios d0..d99 of the TBMANLIB_Dialog probe. The ids and what +' they ask are listed at the top of Probe.twin. + +Module ProbeD + + Private Declare PtrSafe Function MessageBoxTimeoutW Lib "user32" (ByVal hWnd As LongPtr, ByVal lpText As LongPtr, ByVal lpCaption As LongPtr, ByVal uType As Long, ByVal wLanguageId As Long, ByVal dwMilliseconds As Long) As Long + Private Declare PtrSafe Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Long) + Private Declare PtrSafe Function CreateWindowExW Lib "user32" (ByVal dwExStyle As Long, ByVal lpClassName As LongPtr, ByVal lpWindowName As LongPtr, ByVal dwStyle As Long, ByVal x As Long, ByVal y As Long, ByVal nWidth As Long, ByVal nHeight As Long, ByVal hWndParent As LongPtr, ByVal hMenu As LongPtr, ByVal hInstance As LongPtr, ByVal lpParam As LongPtr) As LongPtr + Private Declare PtrSafe Function DestroyWindow Lib "user32" (ByVal hWnd As LongPtr) As Long + Private Declare PtrSafe Function IsWindowEnabled Lib "user32" (ByVal hWnd As LongPtr) As Long + + Public Sub Run(ByVal id As String) + Select Case id + Case "d0": D0 + Case "d0b": D0b + Case "d1": D1 + Case "d2": D2 + Case "d2b": D2b + Case "d2c": D2c + Case "d2t": D2t + Case "d3": D3 + Case "d4": D4 + Case "d5": D5 + Case "d10", "d10b", "d11", "d11b", "d11c", "d12", "d12d": ProbeDT.Run id + Case "d20", "d20b", "d21", "d22", "d22b", "d23": ProbeIB.Run id + Case "d30", "d31", "d32", "d33", "d33b", "d34", "d34b": ProbePW.Run id + Case "d40": ProbePG.Run id + End Select + End Sub + + ' A value as the probe prints it. + Private Function Desc(ByVal x As Variant) As String + Desc = "VarType=" & VarType(x) & " " & TypeName(x) & " " & AutoD.QD(CStr(x)) + End Function + + Private Function B(ByVal x As Boolean) As String + B = IIf(x, "True", "False") + End Function + + Private Sub D0b() + AutoD.SayD "== d0b delay between dialogs" + Dim s As String + Dim i As Long + AutoD.gMaxTicksD = 30 + AutoD.gTrace = False + For i = 1 To 6 + AutoD.ArmD "d0b-" & i, IIf(i Mod 2 = 0, "id=2", "close") + s = InputBox("prompt", "TitleIB" & i, "dflt") + AutoD.DisarmD + AutoD.SayD " episode " & i & ": InputBox returned after " & AutoD.ElapsedMs() & " ms" + If i = 4 Then Sleep 3000 + Next + End Sub + + Private Sub D0() + AutoD.SayD "== d0 the runtime's InputBox, MsgBox, MessageBoxTimeout" + Dim s As String + Dim r As Long + AutoD.gMaxTicksD = 20 + AutoD.ArmD "d0-inputbox", "none" + s = InputBox("line one" & vbCrLf & "line two", "TitleIB", "dflt") + AutoD.DisarmD + AutoD.SayD " InputBox closed by the driver: StrPtr=" & B(StrPtr(s) <> 0) & " " & AutoD.QD(s) + + AutoD.ArmD "d0-inputbox-ok-empty", "t=|id=1" + s = InputBox("prompt", "TitleIB2", "dflt") + AutoD.DisarmD + AutoD.SayD " InputBox OK with empty text: StrPtr=" & B(StrPtr(s) <> 0) & " " & AutoD.QD(s) + + AutoD.ArmD "d0-msgbox", "close|wait=3|id=7" + r = MsgBox("text of the box", vbExclamation + vbYesNo, "TitleMB") + AutoD.DisarmD + AutoD.SayD " MsgBox Yes/No: WM_CLOSE, then No: result " & r + + AutoD.ArmD "d0-msgbox-ok", "close" + r = MsgBox("text of the box", vbInformation, "TitleMB2") + AutoD.DisarmD + AutoD.SayD " MsgBox OK-only: WM_CLOSE: result " & r + + AutoD.ArmD "d0-mbt", "none" + r = MessageBoxTimeoutW(0, StrPtr("timeout box"), StrPtr("TitleMBT"), 0, 0, 1500) + AutoD.DisarmD + AutoD.SayD " MessageBoxTimeout 1500 ms, MB_OK: result " & r & ", the box stood " & AutoD.LifeMs() & " ms" + End Sub + + '========================================================================================= + ' cDialogCountdownMsgBox + '========================================================================================= + + Private Sub DumpCountdown(ByVal c As cDialogCountdownMsgBox) + AutoD.SayD " Title=" & AutoD.QD(c.Title) & " Message=" & AutoD.QD(c.Message) & " CountdownSeconds=" & c.CountdownSeconds & " RemainingSeconds=" & c.RemainingSeconds + AutoD.SayD " Buttons=" & c.Buttons & " Icon=" & c.Icon & " DefaultButton=" & c.DefaultButton & " ShowProgress=" & B(c.ShowProgress) & " AutoClose=" & B(c.AutoClose) & " OwnerHwnd=" & c.OwnerHwnd + AutoD.SayD " Result=" & c.Result & " IsTimedOut=" & B(c.IsTimedOut()) & " GetResultDescription=" & AutoD.QD(c.GetResultDescription()) + End Sub + + Private Sub D1() + AutoD.SayD "== d1 cDialogCountdownMsgBox: defaults, the window, a timeout" + Dim c As New cDialogCountdownMsgBox + Dim r As Long + AutoD.SayD " (a) a new object, before any dialog:" + DumpCountdown c + AutoD.gMaxTicksD = 60 + c.Message = "Disk almost full" + c.Title = "Warn" + c.CountdownSeconds = 2 + AutoD.SayD " (b) CountdownSeconds=2, Message and Title set; ShowDialog, nothing pressed:" + AutoD.ArmD "d1b", "none" + r = c.ShowDialog() + AutoD.DisarmD + AutoD.SayD " ShowDialog returned " & r & " after the box stood " & AutoD.LifeMs() & " ms; windows seen " & AutoD.gWindows + DumpCountdown c + AutoD.SayD " (c) ShowInfo, OK pressed after the countdown text was recorded:" + AutoD.ArmD "d1c", "id=2" + r = c.ShowInfo("Saved.", "Done", 5) + AutoD.DisarmD + AutoD.SayD " ShowInfo returned " & r + DumpCountdown c + AutoD.SayD " (d) Reset:" + c.Reset + DumpCountdown c + End Sub + + ' d2: every Show* method with a button pressed; Enter and Esc; the X. + Private Sub D2() + AutoD.SayD "== d2 cDialogCountdownMsgBox: Show* methods and the buttons" + Dim c As New cDialogCountdownMsgBox + Dim r As Long + AutoD.gMaxTicksD = 40 + AutoD.ArmD "d2-info", "id=2" + r = c.ShowInfo("m", "t") + AutoD.DisarmD + AutoD.SayD " ShowInfo, OK -> " & r & " (" & c.GetResultDescription() & ") Buttons=" & c.Buttons & " Icon=" & c.Icon & " CountdownSeconds=" & c.CountdownSeconds + AutoD.ArmD "d2-warning", "id=2" + r = c.ShowWarning("m") + AutoD.DisarmD + AutoD.SayD " ShowWarning, OK -> " & r & " Buttons=" & c.Buttons & " Icon=" & c.Icon & " CountdownSeconds=" & c.CountdownSeconds & " Title=" & AutoD.QD(c.Title) + AutoD.ArmD "d2-error", "id=2" + r = c.ShowError("m") + AutoD.DisarmD + AutoD.SayD " ShowError, OK -> " & r & " Buttons=" & c.Buttons & " Icon=" & c.Icon & " CountdownSeconds=" & c.CountdownSeconds & " Title=" & AutoD.QD(c.Title) + AutoD.ArmD "d2-question-yes", "id=6" + r = c.ShowQuestion("m") + AutoD.DisarmD + AutoD.SayD " ShowQuestion, Yes -> " & r & " Buttons=" & c.Buttons & " Icon=" & c.Icon & " CountdownSeconds=" & c.CountdownSeconds & " Title=" & AutoD.QD(c.Title) + AutoD.ArmD "d2-question-no", "id=7" + r = c.ShowQuestion("m") + AutoD.DisarmD + AutoD.SayD " ShowQuestion, No -> " & r + End Sub + + Private Sub D2b() + Dim c As New cDialogCountdownMsgBox + Dim r As Long + AutoD.gMaxTicksD = 40 + AutoD.ArmD "d2b-confirm-cancel", "id=2" + r = c.ShowConfirm("m") + AutoD.DisarmD + AutoD.SayD " ShowConfirm, Cancel -> " & r & " Buttons=" & c.Buttons & " Icon=" & c.Icon & " CountdownSeconds=" & c.CountdownSeconds + AutoD.ArmD "d2b-confirm-esc", "key=27" + r = c.ShowConfirm("m") + AutoD.DisarmD + AutoD.SayD " ShowConfirm, Esc -> " & r + AutoD.ArmD "d2b-confirm-x", "close" + r = c.ShowConfirm("m") + AutoD.DisarmD + AutoD.SayD " ShowConfirm, X -> " & r + AutoD.ArmD "d2b-confirm-enter", "key=13" + r = c.ShowConfirm("m") + AutoD.DisarmD + AutoD.SayD " ShowConfirm, Enter -> " & r + End Sub + + Private Sub D2c() + Dim c As New cDialogCountdownMsgBox + Dim r As Long + AutoD.gMaxTicksD = 40 + AutoD.ArmD "d2c-info-x", "close" + r = c.ShowInfo("m") + AutoD.DisarmD + AutoD.SayD " ShowInfo, X -> " & r + AutoD.ArmD "d2c-info-enter", "key=13" + r = c.ShowInfo("m") + AutoD.DisarmD + AutoD.SayD " ShowInfo, Enter -> " & r + AutoD.ArmD "d2c-question-x", "close|wait=4|id=7" + r = c.ShowQuestion("m") + AutoD.DisarmD + AutoD.SayD " ShowQuestion, X then (3 ticks later) No -> " & r + AutoD.ArmD "d2c-question-esc", "key=27|wait=4|id=7" + r = c.ShowQuestion("m") + AutoD.DisarmD + AutoD.SayD " ShowQuestion, Esc then (3 ticks later) No -> " & r + c.DefaultButton = MB_DEFBUTTON2 + AutoD.ArmD "d2c-question-def2-enter", "key=13" + r = c.ShowQuestion("m") + AutoD.DisarmD + AutoD.SayD " ShowQuestion with DefaultButton MB_DEFBUTTON2, Enter -> " & r + c.DefaultButton = MB_DEFBUTTON1 + AutoD.ArmD "d2c-question-def1-enter", "key=13" + r = c.ShowQuestion("m") + AutoD.DisarmD + AutoD.SayD " ShowQuestion with DefaultButton MB_DEFBUTTON1, Enter -> " & r + End Sub + + ' d2t: what each button set returns when the countdown ends. + Private Sub D2t() + AutoD.SayD "== d2t cDialogCountdownMsgBox: the result when the countdown ends, for each button set" + Dim c As New cDialogCountdownMsgBox + Dim r As Long + Dim i As Long + Dim names As Variant = Array("OK", "OKCancel", "AbortRetryIgnore", "YesNoCancel", "YesNo", "RetryCancel") + AutoD.gMaxTicksD = 60 + For i = 0 To 5 + c.Reset + c.Buttons = i + c.Message = "m" + c.CountdownSeconds = 1 + AutoD.ArmD "d2t-" & names(i), "none" + r = c.ShowDialog() + AutoD.DisarmD + AutoD.SayD " Buttons=" & i & " (" & names(i) & "), 1 s, nothing pressed -> " & r & " (" & c.GetResultDescription() & ") IsTimedOut=" & B(c.IsTimedOut()) & ", the box stood " & AutoD.LifeMs() & " ms" + Next + End Sub + + Private Sub D3() + AutoD.SayD "== d3 cDialogCountdownMsgBox: AutoClose, ShowProgress, 0 and negative seconds" + Dim c As New cDialogCountdownMsgBox + Dim r As Long + AutoD.gMaxTicksD = 40 + c.Message = "Body" + c.CountdownSeconds = 1 + c.AutoClose = False + AutoD.SayD " (a) AutoClose=False, CountdownSeconds=1; the driver presses OK after 3 s:" + AutoD.ArmD "d3a", "wait=28|id=2" + r = c.ShowDialog() + AutoD.DisarmD + AutoD.SayD " -> " & r & " (" & c.GetResultDescription() & "), the box stood " & AutoD.LifeMs() & " ms" + c.AutoClose = True + c.ShowProgress = False + AutoD.SayD " (b) ShowProgress=False, AutoClose=True, 1 s:" + AutoD.ArmD "d3b", "none" + r = c.ShowDialog() + AutoD.DisarmD + AutoD.SayD " -> " & r & " (" & c.GetResultDescription() & "), the box stood " & AutoD.LifeMs() & " ms" + c.ShowProgress = True + c.CountdownSeconds = 0 + AutoD.SayD " (c) CountdownSeconds=0, AutoClose=True; the driver presses OK after 3 s:" + AutoD.ArmD "d3c", "wait=28|id=2" + r = c.ShowDialog() + AutoD.DisarmD + AutoD.SayD " -> " & r & " (" & c.GetResultDescription() & "), the box stood " & AutoD.LifeMs() & " ms" + c.CountdownSeconds = -5 + AutoD.SayD " (d) CountdownSeconds=-5; the driver presses OK after 3 s:" + AutoD.ArmD "d3d", "wait=28|id=2" + r = c.ShowDialog() + AutoD.DisarmD + AutoD.SayD " -> " & r & " (" & c.GetResultDescription() & "), the box stood " & AutoD.LifeMs() & " ms" + End Sub + + Private Sub D4() + AutoD.SayD "== d4 cDialogCountdownMsgBox: overflow of the milliseconds, a button value the class does not map" + Dim c As New cDialogCountdownMsgBox + Dim r As Long + AutoD.gMaxTicksD = 40 + c.Message = "Body" + c.Title = "Big" + c.Buttons = MB_YESNO + c.Icon = MB_ICONQUESTION + c.CountdownSeconds = 3000000 + AutoD.SayD " (a) CountdownSeconds=3000000, Buttons=MB_YESNO, Icon=MB_ICONQUESTION; the driver presses id 7 (No) or 1 (OK):" + AutoD.ArmD "d4a", "id=2" + On Error Resume Next + r = c.ShowDialog() + If Err.Number <> 0 Then AutoD.SayD " ERROR " & Err.Number & " " & Err.Description + On Error GoTo 0 + AutoD.DisarmD + AutoD.SayD " -> " & r & " (" & c.GetResultDescription() & "), windows seen " & AutoD.gWindows + c.Reset + c.Message = "Body" + c.Buttons = MB_ABORTRETRYIGNORE + c.Buttons = 6 + c.CountdownSeconds = 30 + AutoD.SayD " (b) Buttons=6 (Cancel, Try Again, Continue); Result before is " & c.Result & "; the driver presses id 10 (Try Again):" + AutoD.ArmD "d4b", "id=10" + r = c.ShowDialog() + AutoD.DisarmD + AutoD.SayD " -> " & r & " (" & c.GetResultDescription() & ")" + AutoD.ArmD "d4b2", "id=2" + r = c.ShowInfo("m", "t", 30) + AutoD.DisarmD + AutoD.SayD " (c) a ShowInfo, OK -> " & r & ", then Buttons=6 again, Try Again (id 10):" + c.Buttons = 6 + AutoD.ArmD "d4c", "id=10" + r = c.ShowDialog() + AutoD.DisarmD + AutoD.SayD " -> " & r & " (" & c.GetResultDescription() & ")" + End Sub + + Private Sub D5() + AutoD.SayD "== d5 cDialogCountdownMsgBox: OwnerHwnd" + Dim c As New cDialogCountdownMsgBox + Dim r As Long + Dim own As LongPtr + AutoD.gMaxTicksD = 40 + own = CreateWindowExW(0, StrPtr("STATIC"), StrPtr("probe owner"), &H90000000, 10, 10, 100, 50, 0, 0, 0, 0) + AutoD.SayD " owner window created: " & B(own <> 0) & " enabled=" & IsWindowEnabled(own) + AutoD.gOwner = own + c.OwnerHwnd = own + AutoD.SayD " OwnerHwnd round trip: " & B(c.OwnerHwnd = own) + AutoD.ArmD "d5", "id=2" + r = c.ShowInfo("m", "t", 5) + AutoD.DisarmD + AutoD.SayD " -> " & r & "; the owner is enabled again: " & IsWindowEnabled(own) + AutoD.gOwner = 0 + DestroyWindow own + End Sub + +End Module diff --git a/test/package-probes/TBMANLIB_Dialog/src/ProbeDT.twin b/test/package-probes/TBMANLIB_Dialog/src/ProbeDT.twin new file mode 100644 index 000000000..1a646e6cd --- /dev/null +++ b/test/package-probes/TBMANLIB_Dialog/src/ProbeDT.twin @@ -0,0 +1,254 @@ +' ProbeDT: the scenarios d10..d12 of the TBMANLIB_Dialog probe: cDialogDateTime. + +Module ProbeDT + + Public Sub Run(ByVal id As String) + Select Case id + Case "d10": D10 + Case "d10b": D10b + Case "d11": D11 + Case "d11b": D11b + Case "d11c": D11c + Case "d12": D12 + Case "d12d": D12d + End Select + End Sub + + Private Function B(ByVal x As Boolean) As String + B = IIf(x, "True", "False") + End Function + + Private Sub DumpDT(ByVal c As cDialogDateTime) + AutoD.SayD " Year=" & c.Year & " Month=" & c.Month & " Day=" & c.Day & " Hour=" & c.Hour & " Minute=" & c.Minute & " Second=" & c.Second & " Cancelled=" & B(c.Cancelled) + AutoD.SayD " DateValue=" & Format$(c.DateValue, "yyyy-mm-dd") & " TimeValue=" & Format$(c.TimeValue, "hh:nn:ss") & " DateTimeValue=" & Format$(c.DateTimeValue, "yyyy-mm-dd hh:nn:ss") & " Mode=" & c.Mode & " Title=" & AutoD.QD(c.Title) + End Sub + + Private Sub SetFixed(ByVal c As cDialogDateTime) + c.DateTimeValue = DateSerial(2026, 3, 15) + TimeSerial(14, 5, 9) + End Sub + + Private Sub D10() + AutoD.SayD "== d10 cDialogDateTime: defaults, the InputBox, OK, cancel, an empty entry" + Dim c As New cDialogDateTime + Dim ok As Boolean + AutoD.SayD " (a) a new object:" + AutoD.SayD " Title=" & AutoD.QD(c.Title) & " Prompt=" & AutoD.QD(c.Prompt) & " Mode=" & c.Mode & " MinYear=" & c.MinYear & " MaxYear=" & c.MaxYear + AutoD.SayD " ShowSeconds=" & B(c.ShowSeconds) & " Is24HourFormat=" & B(c.Is24HourFormat) & " CustomFormat=" & AutoD.QD(c.CustomFormat) & " OwnerHwnd=" & c.OwnerHwnd & " Cancelled=" & B(c.Cancelled) + AutoD.SayD " Year is the current year: " & B(c.Year = Year(Date)) & ", Month: " & B(c.Month = Month(Date)) & ", Day: " & B(c.Day = Day(Date)) + SetFixed c + DumpDT c + AutoD.gMaxTicksD = 40 + AutoD.SayD " (b) ShowDialog, Cancel:" + AutoD.ArmD "d10b", "id=2" + ok = c.ShowDialog() + AutoD.DisarmD + AutoD.SayD " -> " & B(ok) + DumpDT c + AutoD.SayD " (c) ShowDialog, type [07/04/2027 08:30], OK:" + AutoD.ArmD "d10c", "t=07/04/2027 08:30|id=1" + ok = c.ShowDialog() + AutoD.DisarmD + AutoD.SayD " -> " & B(ok) + DumpDT c + AutoD.SayD " (d) ShowDialog, empty text, OK:" + AutoD.ArmD "d10d", "t=|id=1" + ok = c.ShowDialog() + AutoD.DisarmD + AutoD.SayD " -> " & B(ok) + DumpDT c + End Sub + + Private Sub D10b() + AutoD.SayD "== d10b cDialogDateTime: date only, time only, dashes, text that is not a date" + Dim c As New cDialogDateTime + Dim ok As Boolean + AutoD.gMaxTicksD = 40 + SetFixed c + AutoD.SayD " (a) [11/12/2028] with no time (Mode = DateTime):" + AutoD.ArmD "d10b-a", "t=11/12/2028|id=1" + ok = c.ShowDialog() + AutoD.DisarmD + AutoD.SayD " -> " & B(ok) + DumpDT c + AutoD.SayD " (b) [23:59] with no date:" + AutoD.ArmD "d10b-b", "t=23:59|id=1" + ok = c.ShowDialog() + AutoD.DisarmD + AutoD.SayD " -> " & B(ok) + DumpDT c + AutoD.SayD " (c) [01-02-2029 03:04:05] with hyphens and seconds:" + AutoD.ArmD "d10b-c", "t=01-02-2029 03:04:05|id=1" + ok = c.ShowDialog() + AutoD.DisarmD + AutoD.SayD " -> " & B(ok) + DumpDT c + AutoD.SayD " (d) [hello] (not a date or time):" + AutoD.ArmD "d10b-d", "t=hello|id=1" + ok = c.ShowDialog() + AutoD.DisarmD + AutoD.SayD " -> " & B(ok) + DumpDT c + AutoD.SayD " (e) [1/2] (two parts):" + AutoD.ArmD "d10b-e", "t=1/2|id=1" + ok = c.ShowDialog() + AutoD.DisarmD + AutoD.SayD " -> " & B(ok) + DumpDT c + End Sub + + Private Sub D11() + AutoD.SayD "== d11 cDialogDateTime: invalid entries, the Invalid box, the state afterwards" + Dim c As New cDialogDateTime + Dim ok As Boolean + AutoD.gMaxTicksD = 40 + SetFixed c + AutoD.SayD " (a) [13/45/2026]:" + AutoD.ArmD "d11a", "t=13/45/2026|id=1;id=2" + ok = c.ShowDialog() + AutoD.DisarmD + AutoD.SayD " -> " & B(ok) + DumpDT c + SetFixed c + AutoD.SayD " (b) [02/30/2026]:" + AutoD.ArmD "d11b", "t=02/30/2026|id=1;id=2" + ok = c.ShowDialog() + AutoD.DisarmD + AutoD.SayD " -> " & B(ok) + DumpDT c + SetFixed c + AutoD.SayD " (c) [25:61]:" + AutoD.ArmD "d11c", "t=25:61|id=1;id=2" + ok = c.ShowDialog() + AutoD.DisarmD + AutoD.SayD " -> " & B(ok) + DumpDT c + End Sub + + Private Sub D11b() + AutoD.SayD "== d11b cDialogDateTime: letters in a date, a time with PM and no space" + Dim c As New cDialogDateTime + Dim ok As Boolean + AutoD.gMaxTicksD = 40 + SetFixed c + c.Is24HourFormat = False + AutoD.SayD " (a) [ab/cd/ef]:" + AutoD.ArmD "d11b-a", "t=ab/cd/ef|id=1;id=2" + ok = c.ShowDialog() + AutoD.DisarmD + AutoD.SayD " -> " & B(ok) + DumpDT c + SetFixed c + AutoD.SayD " (b) [9:30pm] (no space before PM):" + AutoD.ArmD "d11b-b", "t=9:30pm|id=1;id=2" + ok = c.ShowDialog() + AutoD.DisarmD + AutoD.SayD " -> " & B(ok) + DumpDT c + SetFixed c + AutoD.SayD " (c) [9:30 pm] (a space):" + AutoD.ArmD "d11b-c", "t=9:30 pm|id=1;id=2" + ok = c.ShowDialog() + AutoD.DisarmD + AutoD.SayD " -> " & B(ok) + DumpDT c + End Sub + + Private Sub D11c() + AutoD.SayD "== d11c cDialogDateTime: a time with AM or PM, 12 hour format" + Dim c As New cDialogDateTime + Dim ok As Boolean + AutoD.gMaxTicksD = 40 + SetFixed c + c.Is24HourFormat = False + AutoD.ArmD "d11c-a", "t=12:15 AM|id=1;id=2" + ok = c.ShowDialog() + AutoD.DisarmD + AutoD.SayD " (a) [12:15 AM] -> " & B(ok) & " Hour=" & c.Hour & " Minute=" & c.Minute + SetFixed c + AutoD.ArmD "d11c-b", "t=03/15/2026 12:15 AM|id=1;id=2" + ok = c.ShowDialog() + AutoD.DisarmD + AutoD.SayD " (b) [03/15/2026 12:15 AM] -> " & B(ok) & " Hour=" & c.Hour & " Minute=" & c.Minute + SetFixed c + AutoD.ArmD "d11c-c", "t=03/15/2026 12:30 PM|id=1;id=2" + ok = c.ShowDialog() + AutoD.DisarmD + AutoD.SayD " (c) [03/15/2026 12:30 PM] -> " & B(ok) & " Hour=" & c.Hour & " Minute=" & c.Minute + SetFixed c + AutoD.ArmD "d11c-d", "t=03/15/2026 9:30 PM|id=1;id=2" + ok = c.ShowDialog() + AutoD.DisarmD + AutoD.SayD " (d) [03/15/2026 9:30 PM] -> " & B(ok) & " Hour=" & c.Hour & " Minute=" & c.Minute + c.Is24HourFormat = True + SetFixed c + AutoD.ArmD "d11c-e", "t=03/15/2026 9:30 PM|id=1;id=2" + ok = c.ShowDialog() + AutoD.DisarmD + AutoD.SayD " (e) 24 h, [03/15/2026 9:30 PM] -> " & B(ok) & " Hour=" & c.Hour & " Minute=" & c.Minute + End Sub + + Private Sub D12d() + AutoD.SayD "== d12d cDialogDateTime: time only, 12 hour format, OK on the text it offers" + Dim c As New cDialogDateTime + Dim ok As Boolean + AutoD.gMaxTicksD = 40 + SetFixed c + c.Is24HourFormat = False + c.Mode = PickerMode.TimeOnly + AutoD.SayD " (a) OK on the offered text:" + AutoD.ArmD "d12d-a", "id=1;id=2" + ok = c.ShowDialog() + AutoD.DisarmD + AutoD.SayD " -> " & B(ok) + DumpDT c + AutoD.SayD " (b) [21:30]:" + AutoD.ArmD "d12d-b", "t=21:30|id=1" + ok = c.ShowDialog() + AutoD.DisarmD + AutoD.SayD " -> " & B(ok) + DumpDT c + AutoD.SayD " (c) [9:30 PM]:" + AutoD.ArmD "d12d-c", "t=9:30 PM|id=1;id=2" + ok = c.ShowDialog() + AutoD.DisarmD + AutoD.SayD " -> " & B(ok) + DumpDT c + AutoD.SayD " (d) DateTime mode: a good date, then a bad time [04/05/2027 25:00]:" + c.Mode = PickerMode.DateTime + SetFixed c + AutoD.ArmD "d12d-d", "t=04/05/2027 25:00|id=1;id=2" + ok = c.ShowDialog() + AutoD.DisarmD + AutoD.SayD " -> " & B(ok) + DumpDT c + End Sub + + Private Sub D12() + AutoD.SayD "== d12 cDialogDateTime: ShowDatePicker, ShowTimePicker" + Dim c As New cDialogDateTime + Dim ok As Boolean + AutoD.gMaxTicksD = 40 + SetFixed c + AutoD.SayD " (a) ShowDatePicker, Cancel:" + AutoD.ArmD "d12a", "id=2" + ok = c.ShowDatePicker() + AutoD.DisarmD + AutoD.SayD " -> " & B(ok) + DumpDT c + AutoD.SayD " (b) ShowTimePicker(""My time""), Is24HourFormat False, ShowSeconds True, Cancel:" + c.Is24HourFormat = False + c.ShowSeconds = True + AutoD.ArmD "d12b", "id=2" + ok = c.ShowTimePicker("My time") + AutoD.DisarmD + AutoD.SayD " -> " & B(ok) + DumpDT c + AutoD.SayD " (c) Mode DateTime again, ShowDialog, Cancel:" + c.Mode = PickerMode.DateTime + AutoD.ArmD "d12c", "id=2" + ok = c.ShowDialog() + AutoD.DisarmD + AutoD.SayD " -> " & B(ok) + End Sub + +End Module diff --git a/test/package-probes/TBMANLIB_Dialog/src/ProbeEx.twin b/test/package-probes/TBMANLIB_Dialog/src/ProbeEx.twin new file mode 100644 index 000000000..86e2e9aba --- /dev/null +++ b/test/package-probes/TBMANLIB_Dialog/src/ProbeEx.twin @@ -0,0 +1,843 @@ +' ProbeEx: what TBMANLIB_Dialog's cDialogEx does at run time (package 1.1.0.0). +' +' Scenario ids (run through Probe.Main, which lists them in ScenarioIds): +' x0 cDialogEx's properties after New and after Reset (no dialog), GetPresetFilter +' x1 ShowOpen, one file: the return value, FileName, the dialog's title and controls +' x2 ShowOpen with MultiSelect: one file, two files, three files +' x3 Cancel in ShowOpen, ShowSave and ShowBrowseForFolder +' x4 the titles of the three dialogs, DialogTitle empty and set +' x5 Filter: the file-type list of the dialog for a filter built by AddFilter and by hand +' x6 ShowSave: the initial name, DefaultExt, OverwritePrompt +' x7 FileMustExist, PathMustExist and CreatePrompt on ShowOpen +' x8 NoChangeDir on ShowOpen and ShowSave +' x9 ShowBrowseForFolder and PickFolders: a typed folder, InitialDir +' x10 SetSystemIcon, SetCustomIcon and ClearIcon: the dialog's icons, and whether a file is still accepted +' x11 OwnerHwnd +' x12 SelectFiles +' x13 GetFilePath, GetFileName, GetFileExtension, GetFileNameWithoutExt, PathCombine (no dialog) +' x14 SelectAndOpenFile with names of files that do not exist +' x15 InitialDir +' x16 FilterIndex +' +' Never call OpenFile or SelectAndOpenFile on a path that exists, or on "": it starts a program. + +Module ProbeEx + + Private Declare PtrSafe Function CreateWindowExW Lib "user32" (ByVal dwExStyle As Long, ByVal lpClassName As LongPtr, ByVal lpWindowName As LongPtr, ByVal dwStyle As Long, ByVal x As Long, ByVal y As Long, ByVal nWidth As Long, ByVal nHeight As Long, ByVal hWndParent As LongPtr, ByVal hMenu As LongPtr, ByVal hInstance As LongPtr, ByVal lpParam As LongPtr) As LongPtr + Private Declare PtrSafe Function DestroyWindow Lib "user32" (ByVal hWnd As LongPtr) As Long + Private Declare PtrSafe Function LoadIconW Lib "user32" (ByVal hInstance As LongPtr, ByVal lpIconName As LongPtr) As LongPtr + + Public Sub RunEx(ByVal id As String) + Dim keep As Long = gMaxTicks + ' A Vista dialog is slower to appear than the old ones, and some scenarios navigate first. + If keep < 24 Then gMaxTicks = 24 + gNavDir = "" + Select Case id + Case "x0": X0 + Case "x1": X1 + Case "x2": X2 + Case "x3": X3 + Case "x4": X4 + Case "x5": X5 + Case "x6": X6 + Case "x7": X7 + Case "x8": X8 + Case "x9": X9 + Case "x10": X10 + Case "x11": X11 + Case "x12": X12 + Case "x13": X13 + Case "x14": X14 + Case "x15": X15 + Case "x16": X16 + End Select + gNavDir = "" + gMaxTicks = keep + End Sub + + ' A value as the probe prints it, with the type of an array's elements. + Private Function Show(ByVal x As Variant) As String + Show = Desc(x) + End Function + + Private Sub X0() + Say "== x0 defaults" + Dim d As New cDialogEx + Say " DialogTitle=" & Q(d.DialogTitle) & " InitialDir=" & Q(d.InitialDir) & " DefaultExt=" & Q(d.DefaultExt) & " FileName=" & Q(d.FileName) & " Filter=" & Q(d.Filter) + Say " FilterIndex=" & d.FilterIndex & " OwnerHwnd=" & d.OwnerHwnd + Say " MultiSelect=" & d.MultiSelect & " OverwritePrompt=" & d.OverwritePrompt & " PathMustExist=" & d.PathMustExist & " FileMustExist=" & d.FileMustExist + Say " NoChangeDir=" & d.NoChangeDir & " CreatePrompt=" & d.CreatePrompt & " PickFolders=" & d.PickFolders + d.MultiSelect = True + d.OverwritePrompt = True + d.PathMustExist = True + d.FileMustExist = True + d.PickFolders = True + d.NoChangeDir = True + d.CreatePrompt = True + d.DialogTitle = "t" + d.InitialDir = "C:\x" + d.DefaultExt = "txt" + d.FileName = "f" + d.FilterIndex = 3 + d.AddFilter "A", "*.a" + d.OwnerHwnd = 1234 + d.SetSystemIcon SIID_FOLDER + d.Reset + Say " after Reset: DialogTitle=" & Q(d.DialogTitle) & " InitialDir=" & Q(d.InitialDir) & " DefaultExt=" & Q(d.DefaultExt) & " FileName=" & Q(d.FileName) & " Filter=" & Q(d.Filter) + Say " after Reset: FilterIndex=" & d.FilterIndex & " OwnerHwnd=" & d.OwnerHwnd & " MultiSelect=" & d.MultiSelect & " OverwritePrompt=" & d.OverwritePrompt & " PathMustExist=" & d.PathMustExist & " FileMustExist=" & d.FileMustExist + Say " after Reset: NoChangeDir=" & d.NoChangeDir & " CreatePrompt=" & d.CreatePrompt & " PickFolders=" & d.PickFolders + Dim nm As Variant + For Each nm In Array("all", "text", "images", "image", "documents", "document", "executables", "executable", "ALL", " Text ", "code", "") + Say " GetPresetFilter(" & Q(CStr(nm)) & ") = " & Q(d.GetPresetFilter(CStr(nm))) + Next + d.SetCommonFilters + Say " SetCommonFilters -> " & Q(d.Filter) + d.SetImageFilters + Say " SetImageFilters -> " & Q(d.Filter) + d.SetDocumentFilters + Say " SetDocumentFilters -> " & Q(d.Filter) + d.SetCodeFilters + Say " SetCodeFilters -> " & Q(d.Filter) + d.ClearFilter + Say " ClearFilter -> " & Q(d.Filter) + d.AddFilter "Text", "*.txt" + d.AddFilter "All", "*.*" + Say " AddFilter x2 -> " & Q(d.Filter) + Say " SystemIconId: SIID_DOCUMENT=" & SIID_DOCUMENT & " SIID_FOLDER=" & SIID_FOLDER & " SIID_FOLDEROPEN=" & SIID_FOLDEROPEN & " SIID_DISK=" & SIID_DISK & " SIID_DRIVEFIXED=" & SIID_DRIVEFIXED & " SIID_DRIVECD=" & SIID_DRIVECD & " SIID_DRIVEHD=" & SIID_DRIVEHD & " SIID_SERVER=" & SIID_SERVER & " SIID_STAROFFILE=" & SIID_STAROFFILE & " SIID_SHARE=" & SIID_SHARE & " SIID_INVALID=" & SIID_INVALID + End Sub + + Private Sub X1() + Say "== x1 ShowOpen one file" + Dim d As New cDialogEx + Dim r As Variant + d.InitialDir = Root & "\data" + gDump = False + Arm "X1", True, DP("a.txt"), 1 + r = d.ShowOpen() + Disarm + gDump = False + Say " result: " & Desc(r) + Say " FileName=" & Q(d.FileName) & " InitialDir=" & Q(d.InitialDir) + Wrap "X1" + End Sub + + ' y1: the mDialog module (no dialog). + Public Sub Y1() + Say "== y1 mDialog" + Dim r As Long, g As Long, b As Long + Say " InchesToMM(1)=" & InchesToMM(1) & " InchesToMM(8.5)=" & InchesToMM(8.5) & " MMToInches(25.4)=" & MMToInches(25.4) & " MMToInches(210)=" & MMToInches(210) + Say " InchesToPoints(1)=" & InchesToPoints(1) & " PointsToInches(36)=" & PointsToInches(36) & " TwipsToInches(720)=" & TwipsToInches(720) + Say " InchesToTwips(1)=" & InchesToTwips(1) & " InchesToTwips(1.5)=" & InchesToTwips(1.5) & " InchesToTwips(0.0003)=" & InchesToTwips(0.0003) & " InchesToTwips(0.0004)=" & InchesToTwips(0.0004) + Say " TypeName: InchesToMM=" & TypeName(InchesToMM(1)) & " InchesToTwips=" & TypeName(InchesToTwips(1)) & " TwipsToInches=" & TypeName(TwipsToInches(1)) + Say " mDialog.InchesToMM(2)=" & mDialog.InchesToMM(2) + Say " MM_PER_INCH=" & MM_PER_INCH & " TWIPS_PER_INCH=" & TWIPS_PER_INCH & " POINTS_PER_INCH=" & POINTS_PER_INCH + Say " Letter " & PAPER_LETTER_WIDTH & " x " & PAPER_LETTER_HEIGHT & ", Legal " & PAPER_LEGAL_WIDTH & " x " & PAPER_LEGAL_HEIGHT & ", A4 " & PAPER_A4_WIDTH & " x " & PAPER_A4_HEIGHT & ", A3 " & PAPER_A3_WIDTH & " x " & PAPER_A3_HEIGHT + Say " TypeName: MM_PER_INCH=" & TypeName(MM_PER_INCH) & " TWIPS_PER_INCH=" & TypeName(TWIPS_PER_INCH) & " PAPER_A4_WIDTH=" & TypeName(PAPER_A4_WIDTH) + Say " RGBToHtml(255,128,0)=" & Q(RGBToHtml(255, 128, 0)) & " (0,0,0)=" & Q(RGBToHtml(0, 0, 0)) & " (10,11,12)=" & Q(RGBToHtml(10, 11, 12)) + Say " RGBToHtml(256,-1,300)=" & Q(RGBToHtml(256, -1, 300)) & " RGBToHtml(65535,0,0)=" & Q(RGBToHtml(65535, 0, 0)) + Dim h As Variant + For Each h In Array("#FF8000", "ff8000", " #00ff7f ", "#FFF", "FF80", "#GG0000", "", "#FF8000FF", "#-10000", "#+12345", "#1 2345") + r = 99: g = 99: b = 99 + HtmlToRGB CStr(h), r, g, b + Say " HtmlToRGB(" & Q(CStr(h)) & ") = " & r & ", " & g & ", " & b + Next + Dim pt As mDialog.POINT + pt.X = 3: pt.Y = 4 + Dim rc As RECT + rc.Left = 1: rc.Bottom = 9 + Dim rcq As mDialog.RECT + rcq.Left = 2 + Dim gu As mDialog.GUID + gu.Data1 = 5 + Dim fs As COMDLG_FILTERSPEC + Say " POINT " & pt.X & "," & pt.Y & " RECT " & rc.Left & "," & rc.Top & "," & rc.Right & "," & rc.Bottom & " GUID " & gu.Data1 & " LenB(POINT)=" & LenB(pt) & " LenB(RECT)=" & LenB(rc) & " LenB(GUID)=" & LenB(gu) & " LenB(COMDLG_FILTERSPEC)=" & LenB(fs) + Say " LenB(mDialog.RECT)=" & LenB(rcq) & " rcq.Left=" & rcq.Left + End Sub + + Private Declare PtrSafe Function SHCreateItemFromParsingName Lib "shell32" (ByVal pszPath As LongPtr, ByVal pbc As LongPtr, ByRef riid As mDialog.GUID, ByRef ppv As Any) As Long + Private Declare PtrSafe Function SHCreateItemFromParsingName2 Lib "shell32" Alias "SHCreateItemFromParsingName" (ByVal pszPath As LongPtr, ByVal pbc As LongPtr, ByVal riid As LongPtr, ByVal ppv As LongPtr) As Long + Private Declare PtrSafe Function CoInitialize Lib "ole32" (ByVal pvReserved As LongPtr) As Long + Private Declare PtrSafe Sub CoTaskMemFree Lib "ole32" (ByVal pv As LongPtr) + Private Declare PtrSafe Function lstrlenW Lib "kernel32" (ByVal lpString As LongPtr) As Long + Private Declare PtrSafe Sub MemCopy Lib "kernel32" Alias "RtlMoveMemory" (ByVal Destination As LongPtr, ByRef Source As Any, ByVal Length As Long) + + ' y2: the package's IShellItem, called by the probe itself, for the name that GetDisplayName returns. + Public Sub Y2() + Say "== y2 IShellItem.GetDisplayName" + Say " CoInitialize -> " & Hex$(CoInitialize(0)) + Dim g As mDialog.GUID + g.Data1 = &H43826D1E + g.Data2 = &HE718 + g.Data3 = &H42EE + g.Data4(0) = &HBC: g.Data4(1) = &H55: g.Data4(2) = &HA1: g.Data4(3) = &HE2 + g.Data4(4) = &H61: g.Data4(5) = &HC3: g.Data4(6) = &H37: g.Data4(7) = &HFE + Dim bytes(0 To 15) As Byte + Dim bi As Long + Dim hx As String + MemCopy VarPtr(bytes(0)), g, 16 + For bi = 0 To 15 + hx = hx & Right$("0" & Hex$(bytes(bi)), 2) & " " + Next + Say " GUID bytes: " & hx + Dim gu As mDialog.GUID + gu.Data1 = 0: gu.Data2 = 0: gu.Data3 = 0 + gu.Data4(0) = &HC0: gu.Data4(7) = &H46 + Dim rawu As LongPtr + Dim pth As Variant + For Each pth In Array("C:\Windows", DP("a.txt"), Root & "\data") + rawu = 0 + Dim hru As Long = SHCreateItemFromParsingName2(StrPtr(CStr(pth)), 0, VarPtr(gu), VarPtr(rawu)) + Say " IID_IUnknown for " & Q(CStr(pth)) & ": hr=" & Hex$(hru) & " ptr=" & (rawu <> 0) + rawu = 0 + hru = SHCreateItemFromParsingName2(StrPtr(CStr(pth)), 0, VarPtr(g), VarPtr(rawu)) + Say " IID_IShellItem for " & Q(CStr(pth)) & ": hr=" & Hex$(hru) & " ptr=" & (rawu <> 0) + Next + Dim gi2 As mDialog.GUID + gi2.Data1 = &H7E9FB0D3: gi2.Data2 = &H919F: gi2.Data3 = &H4307 + gi2.Data4(0) = &HAB: gi2.Data4(1) = &H2E: gi2.Data4(2) = &H9B: gi2.Data4(3) = &H18 + gi2.Data4(4) = &H60: gi2.Data4(5) = &H31: gi2.Data4(6) = &HC: gi2.Data4(7) = &H93 + Dim gpo As mDialog.GUID + gpo.Data1 = &H10C: gpo.Data2 = 0: gpo.Data3 = 0 + gpo.Data4(0) = &HC0: gpo.Data4(7) = &H46 + rawu = 0 + Dim hr3 As Long = SHCreateItemFromParsingName2(StrPtr(DP("a.txt")), 0, VarPtr(gi2), VarPtr(rawu)) + Say " IID_IShellItem2: hr=" & Hex$(hr3) & " ptr=" & (rawu <> 0) + rawu = 0 + hr3 = SHCreateItemFromParsingName2(StrPtr(DP("a.txt")), 0, VarPtr(gpo), VarPtr(rawu)) + Say " IID_IPersist: hr=" & Hex$(hr3) & " ptr=" & (rawu <> 0) + ' The IID of IShellItem is 43826D1E-E718-42EE-BC55-A1E261C37BFE: the byte before the last is 7B. + ' The package's GetIID_IShellItem has 37 there (g above); gc has the true value. + Dim gc As mDialog.GUID + gc = g + gc.Data4(6) = &H7B + Dim path As String = DP("a.txt") + Dim si As IShellItem + Dim hr As Long = SHCreateItemFromParsingName(StrPtr(path), 0, g, si) + Say " package's IID (..C3 37 FE): hr=" & Hex$(hr) & " item made: " & Not (si Is Nothing) + hr = SHCreateItemFromParsingName(StrPtr(path), 0, gc, si) + Say " true IID (..C3 7B FE): hr=" & Hex$(hr) & " item made: " & Not (si Is Nothing) + If Not (si Is Nothing) Then Say " FILESYSPATH of that item: " & DispName(si, &H80058000) + End Sub + + Private Declare PtrSafe Function CoCreateInstance Lib "ole32" (ByRef rclsid As mDialog.GUID, ByVal pUnkOuter As LongPtr, ByVal dwClsContext As Long, ByRef riid As mDialog.GUID, ByRef ppv As Any) As Long + + Private Function DispName(ByVal si As IShellItem, ByVal sigdn As Long) As String + Dim p As LongPtr = 0 + On Error Resume Next + si.GetDisplayName sigdn, p + If Err.Number <> 0 Then DispName = "ERROR " & Err.Number & " " & Err.Description : Exit Function + On Error GoTo 0 + Dim n As Long = lstrlenW(p) + Dim s As String = Space$(n) + If n > 0 Then MemCopy StrPtr(s), ByVal p, n * 2 + If p <> 0 Then CoTaskMemFree p + DispName = Q(s) + End Function + + ' y3: the package's IFileOpenDialog, driven by the probe, and the names its result item gives. + Public Sub Y3() + Say "== y3 IFileOpenDialog.GetResult" + Dim clsid As mDialog.GUID + clsid.Data1 = &HDC1C5A9C: clsid.Data2 = &HE88A: clsid.Data3 = &H4DDE + clsid.Data4(0) = &HA5: clsid.Data4(1) = &HA1: clsid.Data4(2) = &H60: clsid.Data4(3) = &HF8 + clsid.Data4(4) = &H2A: clsid.Data4(5) = &H20: clsid.Data4(6) = &HAE: clsid.Data4(7) = &HF7 + Dim iid As mDialog.GUID + iid.Data1 = &HD57C7288: iid.Data2 = &HD4AD: iid.Data3 = &H4768 + iid.Data4(0) = &HBE: iid.Data4(1) = &H2: iid.Data4(2) = &H9D: iid.Data4(3) = &H96 + iid.Data4(4) = &H95: iid.Data4(5) = &H32: iid.Data4(6) = &HD9: iid.Data4(7) = &H60 + Dim dlg As IFileOpenDialog + Dim hr As Long = CoCreateInstance(clsid, 0, 1, iid, dlg) + Say " CoCreateInstance hr=" & Hex$(hr) & " made: " & Not (dlg Is Nothing) + dlg.SetOptions &H40 + Arm "Y3", True, DP("a.txt"), 1 + hr = dlg.Show(0) + Disarm + Say " Show hr=" & Hex$(hr) + Dim si As IShellItem + dlg.GetResult si + Say " GetResult item: " & Not (si Is Nothing) + Say " NORMALDISPLAY " & DispName(si, 0) + Say " PARENTRELATIVEPARSING " & DispName(si, &H80018001) + Say " DESKTOPABSOLUTEPARSING " & DispName(si, &H80028000) + Say " PARENTRELATIVEEDITING " & DispName(si, &H80031001) + Say " DESKTOPABSOLUTEEDITING " & DispName(si, &H8004C000) + Say " FILESYSPATH (&H80058000) " & DispName(si, &H80058000) + Say " the package's constant (&H80058001) " & DispName(si, &H80058001) + Say " URL " & DispName(si, &H80068000) + Say " PARENTRELATIVEFORADDRESSBAR " & DispName(si, &H8007C001) + Say " PARENTRELATIVE " & DispName(si, &H80080001) + End Sub + + ' y4: SetDefaultFolder on the package's IFileOpenDialog with an IShellItem made from the true IID, + ' to show what InitialDir would do if the package's IID were right. It ends the run with an access + ' violation (the interface declares the folder ByRef, a pointer to the pointer): run it alone. + Public Sub Y4() + Say "== y4 SetDefaultFolder with the true IID of IShellItem" + Dim clsid As mDialog.GUID + clsid.Data1 = &HDC1C5A9C: clsid.Data2 = &HE88A: clsid.Data3 = &H4DDE + clsid.Data4(0) = &HA5: clsid.Data4(1) = &HA1: clsid.Data4(2) = &H60: clsid.Data4(3) = &HF8 + clsid.Data4(4) = &H2A: clsid.Data4(5) = &H20: clsid.Data4(6) = &HAE: clsid.Data4(7) = &HF7 + Dim iid As mDialog.GUID + iid.Data1 = &HD57C7288: iid.Data2 = &HD4AD: iid.Data3 = &H4768 + iid.Data4(0) = &HBE: iid.Data4(1) = &H2: iid.Data4(2) = &H9D: iid.Data4(3) = &H96 + iid.Data4(4) = &H95: iid.Data4(5) = &H32: iid.Data4(6) = &HD9: iid.Data4(7) = &H60 + Dim gc As mDialog.GUID + gc.Data1 = &H43826D1E: gc.Data2 = &HE718: gc.Data3 = &H42EE + gc.Data4(0) = &HBC: gc.Data4(1) = &H55: gc.Data4(2) = &HA1: gc.Data4(3) = &HE2 + gc.Data4(4) = &H61: gc.Data4(5) = &HC3: gc.Data4(6) = &H7B: gc.Data4(7) = &HFE + Dim dlg As IFileOpenDialog + Dim folder As IShellItem + Dim hr As Long + Dim k As Long + For k = 1 To 2 + hr = CoCreateInstance(clsid, 0, 1, iid, dlg) + hr = SHCreateItemFromParsingName(StrPtr(Root & IIf(k = 1, "\other", "\data")), 0, gc, folder) + Say " item for " & IIf(k = 1, "other", "data") & ": hr=" & Hex$(hr) + dlg.SetDefaultFolder folder + dlg.SetOptions &H40 + Arm "Y4-" & k, False, "", 2 + hr = dlg.Show(0) + Disarm + Say " Show hr=" & Hex$(hr) + Next + End Sub + + ' \data\ + Private Function DP(ByVal nm As String) As String + DP = Root & "\data\" & nm + End Function + + Private Sub X2() + Say "== x2 ShowOpen MultiSelect" + Dim d As cDialogEx + Dim r As Variant + Dim tag As Variant + Dim names As Variant + Dim k As Long + tag = Array("a", "b", "c") + names = Array("""a.txt""", """a.txt"" ""b.txt""", """exist.txt"" ""b.txt"" ""a.txt""") + gNavDir = Root & "\data" + For k = 0 To 2 + Set d = New cDialogEx + d.MultiSelect = True + Arm "X2" & tag(k), True, CStr(names(k)), 1 + r = d.ShowOpen() + Disarm + Say " (" & tag(k) & ") typed " & names(k) & " -> " & Desc(r) + Say " (" & tag(k) & ") FileName=" & Q(d.FileName) + Wrap "X2" & tag(k) + Next + gNavDir = "" + End Sub + + Private Sub X3() + Say "== x3 Cancel" + Dim d As New cDialogEx + Dim d2 As New cDialogEx + Dim d3 As New cDialogEx + Dim r As Variant + Dim s As String + d.InitialDir = Root & "\data" + d.FileName = "keep.txt" + Arm "X3open", False, "", 2 + r = d.ShowOpen() + Disarm + Say " ShowOpen cancel: " & Desc(r) & " FileName=" & Q(d.FileName) & " InitialDir=" & Q(d.InitialDir) + Wrap "X3open" + d2.InitialDir = Root & "\data" + d2.FileName = "keep.txt" + Arm "X3save", False, "", 2 + s = d2.ShowSave() + Disarm + Say " ShowSave cancel: " & Desc(s) & " FileName=" & Q(d2.FileName) & " InitialDir=" & Q(d2.InitialDir) + Wrap "X3save" + d3.InitialDir = Root & "\data" + d3.FileName = "keep.txt" + Arm "X3browse", False, "", 2 + s = d3.ShowBrowseForFolder() + Disarm + Say " ShowBrowseForFolder cancel: " & Desc(s) & " FileName=" & Q(d3.FileName) & " InitialDir=" & Q(d3.InitialDir) + Wrap "X3browse" + End Sub + + Private Sub X4() + Say "== x4 Title" + Dim d As New cDialogEx + Dim d2 As New cDialogEx + Dim d3 As New cDialogEx + Dim d4 As New cDialogEx + Dim r As Variant + Dim s As String + Arm "X4open-default", False, "", 2 + r = d.ShowOpen() + Disarm + d.DialogTitle = "My Open Title" + Arm "X4open-set", False, "", 2 + r = d.ShowOpen() + Disarm + Arm "X4save-default", False, "", 2 + s = d2.ShowSave() + Disarm + d2.DialogTitle = "My Save Title" + Arm "X4save-set", False, "", 2 + s = d2.ShowSave() + Disarm + Arm "X4browse-default", False, "", 2 + s = d3.ShowBrowseForFolder() + Disarm + d3.DialogTitle = "My Browse Title" + Arm "X4browse-set", False, "", 2 + s = d3.ShowBrowseForFolder() + Disarm + d4.PickFolders = True + Arm "X4pick-default", False, "", 2 + r = d4.ShowOpen() + Disarm + End Sub + + Private Sub X5() + Say "== x5 Filter" + Dim d As cDialogEx + Dim r As Variant + Dim f As Variant + Dim k As Long + Dim tag As Variant + tag = Array("none", "AddFilter-1", "AddFilter-2", "SetImageFilters", "hand-trailing-pipe", "hand-3-pipes", "hand-2-pipes", "hand-1-pipe", "preset") + k = 0 + For Each f In Array("", "T1", "T2", "IMG", "Text (*.txt)|*.txt|All (*.*)|*.*|", "Text (*.txt)|*.txt|All (*.*)|*.*", "Text (*.txt)|*.txt|All", "Text (*.txt)|*.txt", "P") + Set d = New cDialogEx + Select Case CStr(f) + Case "T1": d.AddFilter "Text (*.txt)", "*.txt" + Case "T2": d.AddFilter "Text (*.txt)", "*.txt": d.AddFilter "All (*.*)", "*.*" + Case "IMG": d.SetImageFilters + Case "P": d.Filter = d.GetPresetFilter("images") + Case Else: d.Filter = CStr(f) + End Select + Say " (" & tag(k) & ") Filter=" & Q(d.Filter) + Arm "X5-" & tag(k), False, "", 2 + r = d.ShowOpen() + Disarm + k = k + 1 + Next + ' SetImageFilters with a closing pipe added, and the same Filter on the Save As dialog. + Set d = New cDialogEx + d.SetImageFilters + d.Filter = d.Filter & "|" + Say " (SetImageFilters+pipe) Filter ends with: " & Q(Right$(d.Filter, 8)) + Arm "X5-SetImageFilters+pipe", False, "", 2 + r = d.ShowOpen() + Disarm + Set d = New cDialogEx + d.Filter = "Text (*.txt)|*.txt|All (*.*)|*.*|" + d.FilterIndex = 2 + Arm "X5-save-trailing-pipe", False, "", 2 + r = d.ShowSave() + Disarm + Set d = New cDialogEx + d.Filter = "Text (*.txt)|*.txt|All (*.*)|*.*" + Arm "X5-save-no-pipe", False, "", 2 + r = d.ShowSave() + Disarm + End Sub + + Private Sub X6() + Say "== x6 ShowSave" + Dim d As cDialogEx + Dim s As String + Dim e As Variant + Set d = New cDialogEx + Arm "X6a-empty", False, "", 2 + s = d.ShowSave() + Disarm + d.FileName = "x.txt" + Arm "X6a-x.txt", False, "", 2 + s = d.ShowSave() + Disarm + For Each e In Array("txt", ".txt", "") + Set d = New cDialogEx + d.DefaultExt = CStr(e) + Arm "X6b-ext=" & CStr(e), True, DP("out"), 1 + s = d.ShowSave() + Disarm + Say " (b) DefaultExt=" & Q(CStr(e)) & " typed [\data\out] -> " & Desc(s) & " FileName=" & Q(d.FileName) + Wrap "X6b" + Next + Set d = New cDialogEx + d.DefaultExt = "txt" + Arm "X6b-typed-ext", True, DP("out.dat"), 1 + s = d.ShowSave() + Disarm + Say " (b) DefaultExt=txt typed out.dat -> " & Desc(s) + Wrap "X6b2" + ' (d) the overwrite prompt: default False accepts an existing file; True asks. + Set d = New cDialogEx + Arm "X6d-default", True, DP("exist.txt"), 1 + s = d.ShowSave() + Disarm + Say " (d) OverwritePrompt=" & d.OverwritePrompt & ", typed existing exist.txt -> " & Desc(s) + Wrap "X6d-default" + Set d = New cDialogEx + d.OverwritePrompt = True + MsgSeq 6 + Arm "X6d-yes", True, DP("exist.txt"), 1 + s = d.ShowSave() + Disarm + Say " (d) OverwritePrompt=True, answer Yes -> " & Desc(s) & " FileName=" & Q(d.FileName) + Wrap "X6d-yes" + Set d = New cDialogEx + d.OverwritePrompt = True + MsgSeq 7 + Arm "X6d-no", True, DP("exist.txt"), 1 + s = d.ShowSave() + Disarm + Say " (d) OverwritePrompt=True, answer No -> " & Desc(s) + Wrap "X6d-no" + End Sub + + Private Sub X7() + Say "== x7 FileMustExist / PathMustExist / CreatePrompt" + Dim d As cDialogEx + Dim r As Variant + Set d = New cDialogEx + Arm "X7a-defaults-nofile", True, DP("nosuch.txt"), 1 + r = d.ShowOpen() + Disarm + Say " (a) all False, typed nonexistent file -> " & Desc(r) & " FileName=" & Q(d.FileName) + Wrap "X7a" + Set d = New cDialogEx + Arm "X7b-defaults-nopath", True, Root & "\nosuchdir\x.txt", 1 + r = d.ShowOpen() + Disarm + Say " (b) all False, typed file in a nonexistent folder -> " & Desc(r) + Wrap "X7b" + Set d = New cDialogEx + d.FileMustExist = True + MsgSeq 1 + Arm "X7c-mustexist", True, DP("nosuch.txt"), 1 + r = d.ShowOpen() + Disarm + Say " (c) FileMustExist=True, typed nonexistent file -> " & Desc(r) + Wrap "X7c" + Set d = New cDialogEx + d.PathMustExist = True + MsgSeq 1 + Arm "X7d-pathmust", True, Root & "\nosuchdir\x.txt", 1 + r = d.ShowOpen() + Disarm + Say " (d) PathMustExist=True, typed file in a nonexistent folder -> " & Desc(r) + Wrap "X7d" + Set d = New cDialogEx + d.CreatePrompt = True + MsgSeq 6 + Arm "X7e-create-yes", True, DP("nosuch.txt"), 1 + r = d.ShowOpen() + Disarm + Say " (e) CreatePrompt=True, answer Yes -> " & Desc(r) + Wrap "X7e" + Set d = New cDialogEx + d.CreatePrompt = True + MsgSeq 7 + Arm "X7f-create-no", True, DP("nosuch.txt"), 1 + r = d.ShowOpen() + Disarm + Say " (f) CreatePrompt=True, answer No -> " & Desc(r) + Wrap "X7f" + Say " file nosuch.txt exists afterwards: " & (Len(Dir$(DP("nosuch.txt"))) > 0) + End Sub + + Private Sub X8() + Say "== x8 NoChangeDir" + Dim ncd As Variant + Dim r As Variant + Dim s As String + Dim d As cDialogEx + For Each ncd In Array(False, True) + ChDir Root & "\cwd" + Set d = New cDialogEx + d.NoChangeDir = CBool(ncd) + gNavDir = Root & "\other" + Say " ShowOpen NoChangeDir=" & ncd & ": before CurDir=" & Q(CurDir) & " api=" & Q(CurDirApi) + Arm "X8open-" & ncd, True, "c.txt", 1 + r = d.ShowOpen() + Disarm + gNavDir = "" + Say " result " & Desc(r) & "; after CurDir=" & Q(CurDir) & " api=" & Q(CurDirApi) + ChDir Root & "\cwd" + Set d = New cDialogEx + d.NoChangeDir = CBool(ncd) + gNavDir = Root & "\other" + Say " ShowSave NoChangeDir=" & ncd & ": before CurDir=" & Q(CurDir) & " api=" & Q(CurDirApi) + Arm "X8save-" & ncd, True, "new.txt", 1 + s = d.ShowSave() + Disarm + gNavDir = "" + Say " result " & Desc(s) & "; after CurDir=" & Q(CurDir) & " api=" & Q(CurDirApi) + Next + End Sub + + Private Sub X9() + Say "== x9 ShowBrowseForFolder and PickFolders" + Dim s As String + Dim r As Variant + Dim d As New cDialogEx + Dim d2 As New cDialogEx + Dim d3 As New cDialogEx + Dim d4 As New cDialogEx + d.FileName = "keep.txt" + Arm "X9a-type", True, Root & "\other", 1 + s = d.ShowBrowseForFolder() + Disarm + Say " (a) typed folder -> " & Desc(s) & " FileName=" & Q(d.FileName) & " InitialDir=" & Q(d.InitialDir) + Wrap "X9a" + d2.PickFolders = True + d2.MultiSelect = True + Arm "X9b-pick", True, Root & "\data", 1 + r = d2.ShowOpen() + Disarm + Say " (b) PickFolders=True MultiSelect=True ShowOpen, typed folder -> " & Desc(r) & " FileName=" & Q(d2.FileName) + Wrap "X9b" + Arm "X9c-nothing", False, "", 1 + s = d3.ShowBrowseForFolder() + Disarm + Say " (c) OK pressed with nothing typed -> " & Desc(s) & " FileName=" & Q(d3.FileName) + Wrap "X9c" + MsgSeq 1 + Arm "X9d-file", True, DP("a.txt"), 1 + s = d4.ShowBrowseForFolder() + Disarm + Say " (d) typed an existing file -> " & Desc(s) + Wrap "X9d" + End Sub + + Private Sub X10() + Say "== x10 icons" + Dim d As cDialogEx + Dim r As Variant + Dim s As String + Dim hIcon As LongPtr + hIcon = LoadIconW(0, 32516) ' IDI_INFORMATION + Say " LoadIcon(IDI_INFORMATION) = " & hIcon + gShowIcon = True + Set d = New cDialogEx + Arm "X10a-none", True, DP("a.txt"), 1 + r = d.ShowOpen() + Disarm + Say " (a) no icon set -> " & Desc(r) + Set d = New cDialogEx + d.SetSystemIcon SIID_FOLDER + Arm "X10b-system", True, DP("a.txt"), 1 + r = d.ShowOpen() + Disarm + Say " (b) SetSystemIcon(SIID_FOLDER) -> " & Desc(r) & " FileName=" & Q(d.FileName) + Wrap "X10b" + Set d = New cDialogEx + d.SetCustomIcon hIcon + Arm "X10c-custom", True, DP("a.txt"), 1 + r = d.ShowOpen() + Disarm + Say " (c) SetCustomIcon(IDI_INFORMATION) -> " & Desc(r) + Wrap "X10c" + Set d = New cDialogEx + d.SetSystemIcon SIID_DRIVECD + d.ClearIcon + Arm "X10d-cleared", True, DP("a.txt"), 1 + r = d.ShowOpen() + Disarm + Say " (d) SetSystemIcon then ClearIcon -> " & Desc(r) + Set d = New cDialogEx + d.SetSystemIcon SIID_INVALID + Arm "X10e-invalid", True, DP("a.txt"), 1 + r = d.ShowOpen() + Disarm + Say " (e) SetSystemIcon(SIID_INVALID) -> " & Desc(r) + Set d = New cDialogEx + d.SetCustomIcon hIcon + Arm "X10f-save", True, DP("out.dat"), 1 + s = d.ShowSave() + Disarm + Say " (f) SetCustomIcon, ShowSave -> " & Desc(s) + Wrap "X10f" + Set d = New cDialogEx + d.SetCustomIcon hIcon + Arm "X10g-folder", True, Root & "\data", 1 + s = d.ShowBrowseForFolder() + Disarm + Say " (g) SetCustomIcon, ShowBrowseForFolder -> " & Desc(s) + Wrap "X10g" + gShowIcon = False + End Sub + + Private Sub X11() + Say "== x11 OwnerHwnd" + Dim d As New cDialogEx + Dim d2 As New cDialogEx + Dim r As Variant + Dim own As LongPtr + own = CreateWindowExW(0, StrPtr("STATIC"), StrPtr("probe owner"), &H10000000, 0, 0, 10, 10, 0, 0, 0, 0) + Say " owner window made: " & (own <> 0) + gShowOwner = True + gOwnerWant = own + d.OwnerHwnd = own + Arm "X11a-owner", True, DP("a.txt"), 1 + r = d.ShowOpen() + Disarm + Say " (a) OwnerHwnd = a window of the program -> " & Desc(r) + Wrap "X11a" + gOwnerWant = 0 + Arm "X11b-noowner", True, DP("a.txt"), 1 + r = d2.ShowOpen() + Disarm + Say " (b) OwnerHwnd = 0 -> " & Desc(r) + Wrap "X11b" + gShowOwner = False + d2.OwnerHwnd = 12345 + Arm "X11c-badowner", False, "", 2 + r = d2.ShowOpen() + Disarm + Say " (c) OwnerHwnd = 12345 -> " & Desc(r) + Wrap "X11c" + If own <> 0 Then DestroyWindow own + End Sub + + Private Sub X12() + Say "== x12 SelectFiles" + Dim d As cDialogEx + Dim c As Collection + Dim v As Variant + gNavDir = Root & "\data" + Set d = New cDialogEx + Arm "X12a", True, "a.txt", 1 + Set c = d.SelectFiles() + Disarm + Say " (a) one file, MultiSelect=False: Count=" & c.Count & " FileName=" & Q(d.FileName) + For Each v In c + Say " " & Desc(v) + Next + Set d = New cDialogEx + d.MultiSelect = True + Arm "X12b", True, """a.txt"" ""b.txt""", 1 + Set c = d.SelectFiles() + Disarm + Say " (b) two files, MultiSelect=True: Count=" & c.Count & " FileName=" & Q(d.FileName) + For Each v In c + Say " " & Desc(v) + Next + gNavDir = "" + Set d = New cDialogEx + Arm "X12c", False, "", 2 + Set c = d.SelectFiles() + Disarm + Say " (c) cancel: Count=" & c.Count & " TypeName=" & TypeName(c) + Set d = New cDialogEx + d.PickFolders = True + Arm "X12d", True, Root & "\other", 1 + Set c = d.SelectFiles() + Disarm + Say " (d) PickFolders=True: Count=" & c.Count + For Each v In c + Say " " & Desc(v) + Next + End Sub + + Private Sub X13() + Say "== x13 path helpers: cDialogEx against cDialog" + Dim d As New cDialogEx + Dim c As New cDialog + Dim p As Variant + Dim a As String + Dim b As String + For Each p In Array("C:\dir\file.tar.gz", "file", "C:\dir\", "C:\dir.x\file", "", ".hidden", "C:\a.b\c", "\\srv\share\f.txt", "a/b.txt", "C:\dir\file.", "C:\f.txt\") + Say " path " & Q(CStr(p)) + Say " GetFilePath=" & Q(d.GetFilePath(CStr(p))) & " GetFileName=" & Q(d.GetFileName(CStr(p))) & " GetFileExtension=" & Q(d.GetFileExtension(CStr(p))) & " GetFileNameWithoutExt=" & Q(d.GetFileNameWithoutExt(CStr(p))) + a = d.GetFilePath(CStr(p)) & "|" & d.GetFileName(CStr(p)) & "|" & d.GetFileExtension(CStr(p)) & "|" & d.GetFileNameWithoutExt(CStr(p)) + b = c.GetFilePath(CStr(p)) & "|" & c.GetFileName(CStr(p)) & "|" & c.GetFileExtension(CStr(p)) & "|" & c.GetFileNameWithoutExt(CStr(p)) + Say " same as cDialog: " & (a = b) + Next + Dim pc As Variant + Dim same As Boolean = True + For Each pc In Array(Array("C:\a", "b"), Array("C:\a\", "\b"), Array("", "b"), Array("a", ""), Array("C:\a", "D:\b"), Array("C:\a", "\\srv\x"), Array("C:", "b")) + If d.PathCombine(CStr(pc(0)), CStr(pc(1))) <> c.PathCombine(CStr(pc(0)), CStr(pc(1))) Then same = False + Next + Say " PathCombine gives the same as cDialog for seven pairs: " & same + End Sub + + Private Sub X14() + Say "== x14 SelectAndOpenFile and OpenFile, files that do not exist" + Dim d As New cDialogEx + Dim d2 As New cDialogEx + Dim d3 As New cDialogEx + Dim r As LongPtr + gNavDir = Root & "\data" + MsgSeq 1 + Arm "X14a", True, "nosuch1.txt", 1 + r = d.SelectAndOpenFile() + Disarm + Say " (a) one nonexistent file: " & r & " FileName=" & Q(d.FileName) + Wrap "X14a" + d2.MultiSelect = True + Arm "X14b", True, """nosuch1.txt"" ""nosuch2.txt""", 1 + r = d2.SelectAndOpenFile() + Disarm + Say " (b) two nonexistent files: " & r & " FileName=" & Q(d2.FileName) + Wrap "X14b" + gNavDir = "" + Arm "X14c", False, "", 2 + r = d3.SelectAndOpenFile() + Disarm + Say " (c) cancel: " & r + Wrap "X14c" + MsgSeq 1 + Arm "X14d", False, "", 2 + r = d3.OpenFile(Root & "\nosuchfolder\nosuchfile.xyz") + Disarm + Say " (d) OpenFile(nonexistent) = " & r & " TypeName=" & TypeName(r) + Wrap "X14d" + End Sub + + Private Sub X15() + Say "== x15 InitialDir" + Dim d As cDialogEx + Dim r As Variant + Set d = New cDialogEx + d.InitialDir = Root & "\data" + Arm "X15a-data", False, "", 2 + r = d.ShowOpen() + Disarm + Set d = New cDialogEx + d.InitialDir = Root & "\other" + Arm "X15b-other", False, "", 2 + r = d.ShowOpen() + Disarm + Set d = New cDialogEx + Arm "X15c-none", False, "", 2 + r = d.ShowOpen() + Disarm + Set d = New cDialogEx + d.InitialDir = Root & "\nosuchdir" + Arm "X15d-missing", False, "", 2 + r = d.ShowOpen() + Disarm + Set d = New cDialogEx + d.InitialDir = Root & "\data" + Arm "X15e-save", False, "", 2 + r = d.ShowSave() + Disarm + End Sub + + Private Sub X16() + Say "== x16 FilterIndex" + Dim d As cDialogEx + Dim r As Variant + Dim fi As Variant + For Each fi In Array(2, 0, 3, 99, -1) + Set d = New cDialogEx + d.Filter = "Text (*.txt)|*.txt|Data (*.dat)|*.dat|All (*.*)|*.*|" + d.FilterIndex = CLng(fi) + Arm "X16-" & fi, True, DP("a.txt"), 3 + r = d.ShowOpen() + Disarm + Say " FilterIndex=" & fi & " -> result " & Desc(r) & " FilterIndex afterwards=" & d.FilterIndex & " dialogSeen=" & gSeenMain + Next + End Sub + +End Module diff --git a/test/package-probes/TBMANLIB_Dialog/src/ProbeIB.twin b/test/package-probes/TBMANLIB_Dialog/src/ProbeIB.twin new file mode 100644 index 000000000..55f43cd97 --- /dev/null +++ b/test/package-probes/TBMANLIB_Dialog/src/ProbeIB.twin @@ -0,0 +1,209 @@ +' ProbeIB: the scenarios d20..d23 of the TBMANLIB_Dialog probe: cDialogInputBox. + +Module ProbeIB + + Private Declare PtrSafe Function CreateWindowExW Lib "user32" (ByVal dwExStyle As Long, ByVal lpClassName As LongPtr, ByVal lpWindowName As LongPtr, ByVal dwStyle As Long, ByVal x As Long, ByVal y As Long, ByVal nWidth As Long, ByVal nHeight As Long, ByVal hWndParent As LongPtr, ByVal hMenu As LongPtr, ByVal hInstance As LongPtr, ByVal lpParam As LongPtr) As LongPtr + Private Declare PtrSafe Function DestroyWindow Lib "user32" (ByVal hWnd As LongPtr) As Long + Private Declare PtrSafe Function IsWindowEnabled Lib "user32" (ByVal hWnd As LongPtr) As Long + + Public Sub Run(ByVal id As String) + Select Case id + Case "d20": D20 + Case "d20b": D20b + Case "d21": D21 + Case "d22": D22 + Case "d22b": D22b + Case "d23": D23 + End Select + End Sub + + Private Function B(ByVal x As Boolean) As String + B = IIf(x, "True", "False") + End Function + + Private Sub DumpIB(ByVal c As cDialogInputBox) + AutoD.SayD " Value=" & AutoD.QD(c.Value) & " Cancelled=" & B(c.Cancelled) & " Title=" & AutoD.QD(c.Title) & " Prompt=" & AutoD.QD(c.Prompt) + End Sub + + Private Sub D20() + AutoD.SayD "== d20 cDialogInputBox: defaults, the InputBox, OK, cancel, empty" + Dim c As New cDialogInputBox + Dim ok As Boolean + AutoD.SayD " (a) a new object:" + AutoD.SayD " Title=" & AutoD.QD(c.Title) & " Prompt=" & AutoD.QD(c.Prompt) & " DefaultValue=" & AutoD.QD(c.DefaultValue) & " Placeholder=" & AutoD.QD(c.Placeholder) & " Value=" & AutoD.QD(c.Value) + AutoD.SayD " Multiline=" & B(c.Multiline) & " MaxLength=" & c.MaxLength & " MinLength=" & c.MinLength & " Validation=" & c.Validation & " CustomPattern=" & AutoD.QD(c.CustomPattern) & " AllowEmpty=" & B(c.AllowEmpty) & " PasswordChar=" & AutoD.QD(c.PasswordChar) & " OwnerHwnd=" & c.OwnerHwnd & " Cancelled=" & B(c.Cancelled) + AutoD.gMaxTicksD = 40 + c.Prompt = "Your name?" + c.Title = "Name" + c.DefaultValue = "Ann" + AutoD.SayD " (b) ShowDialog with Prompt, Title and DefaultValue; OK pressed as it stands:" + AutoD.ArmD "d20b", "id=1" + ok = c.ShowDialog() + AutoD.DisarmD + AutoD.SayD " -> " & B(ok) + DumpIB c + AutoD.SayD " (c) type [Bob], OK:" + AutoD.ArmD "d20c", "t=Bob|id=1" + ok = c.ShowDialog() + AutoD.DisarmD + AutoD.SayD " -> " & B(ok) + DumpIB c + AutoD.SayD " (d) Cancel:" + AutoD.ArmD "d20d", "id=2" + ok = c.ShowDialog() + AutoD.DisarmD + AutoD.SayD " -> " & B(ok) + DumpIB c + AutoD.SayD " (e) empty text, OK (AllowEmpty is True):" + AutoD.ArmD "d20e", "t=|id=1" + ok = c.ShowDialog() + AutoD.DisarmD + AutoD.SayD " -> " & B(ok) + DumpIB c + End Sub + + Private Sub D20b() + AutoD.SayD "== d20b cDialogInputBox: the prompt the dialog shows" + Dim c As New cDialogInputBox + Dim ok As Boolean + AutoD.gMaxTicksD = 40 + c.Prompt = "Code?" + c.Placeholder = "like ABC" + c.MinLength = 3 + c.MaxLength = 8 + AutoD.SayD " (a) Placeholder, MinLength 3, MaxLength 8:" + AutoD.ArmD "d20b-a", "id=2" + ok = c.ShowDialog() + AutoD.DisarmD + c.Placeholder = "" + c.MaxLength = 0 + AutoD.SayD " (b) MinLength 3 only:" + AutoD.ArmD "d20b-b", "id=2" + ok = c.ShowDialog() + AutoD.DisarmD + c.MinLength = 0 + c.MaxLength = 8 + AutoD.SayD " (c) MaxLength 8 only:" + AutoD.ArmD "d20b-c", "id=2" + ok = c.ShowDialog() + AutoD.DisarmD + c.MaxLength = 0 + c.Multiline = True + c.PasswordChar = "*" + c.Validation = InputValidation.Custom + c.CustomPattern = "[0-9]+" + AutoD.SayD " (d) Multiline True, PasswordChar [*], Validation Custom, CustomPattern [0-9]+:" + AutoD.ArmD "d20b-d", "id=2" + ok = c.ShowDialog() + AutoD.DisarmD + End Sub + + Private Sub D21() + AutoD.SayD "== d21 cDialogInputBox: validation failures and three attempts" + Dim c As New cDialogInputBox + Dim ok As Boolean + AutoD.gMaxTicksD = 60 + c.Prompt = "Age?" + c.Title = "Age" + AutoD.SayD " (a) first an accepted entry [keep]:" + AutoD.ArmD "d21a", "t=keep|id=1" + ok = c.ShowDialog() + AutoD.DisarmD + AutoD.SayD " -> " & B(ok) + DumpIB c + c.Validation = InputValidation.Integer + c.DefaultValue = "zz" + AutoD.SayD " (b) Validation Integer; three entries [a1], [b2], [c3], OK each; each message box answered OK:" + AutoD.ArmD "d21b", "t=a1|id=1;id=2;t=b2|id=1;id=2;t=c3|id=1;id=2" + ok = c.ShowDialog() + AutoD.DisarmD + AutoD.SayD " -> " & B(ok) & ", windows seen " & AutoD.gWindows + DumpIB c + End Sub + + Private Sub D22() + AutoD.SayD "== d22 cDialogInputBox: ShowInteger, ShowNumeric, ShowSimple" + Dim c As New cDialogInputBox + Dim n As Long + Dim d As Double + Dim s As String + AutoD.gMaxTicksD = 40 + AutoD.SayD " (a) ShowInteger, [12]:" + AutoD.ArmD "d22a", "t=12|id=1" + n = c.ShowInteger("n?") + AutoD.DisarmD + AutoD.SayD " -> " & n & " Value=" & AutoD.QD(c.Value) & " Title=" & AutoD.QD(c.Title) & " Validation=" & c.Validation & " AllowEmpty=" & B(c.AllowEmpty) + AutoD.SayD " (b) ShowInteger, [1e3]:" + AutoD.ArmD "d22b", "t=1e3|id=1" + n = c.ShowInteger("n?") + AutoD.DisarmD + AutoD.SayD " -> " & n & " Value=" & AutoD.QD(c.Value) + AutoD.SayD " (c) ShowInteger, [99999999999] (more than a Long holds):" + AutoD.ArmD "d22c", "t=99999999999|id=1" + On Error Resume Next + n = 0 + n = c.ShowInteger("n?") + If Err.Number <> 0 Then AutoD.SayD " ERROR " & Err.Number & " " & Err.Description + On Error GoTo 0 + AutoD.DisarmD + AutoD.SayD " -> " & n & " Value=" & AutoD.QD(c.Value) & " Cancelled=" & B(c.Cancelled) + AutoD.SayD " (d) ShowInteger, Cancel; ShowNumeric, [3.5]:" + AutoD.ArmD "d22d", "id=2" + n = c.ShowInteger("n?") + AutoD.DisarmD + AutoD.SayD " ShowInteger cancelled -> " & n & " Cancelled=" & B(c.Cancelled) + AutoD.ArmD "d22d2", "t=3.5|id=1" + d = c.ShowNumeric("x?") + AutoD.DisarmD + AutoD.SayD " ShowNumeric [3.5] -> " & d & " Value=" & AutoD.QD(c.Value) & " GetValueAsNumber=" & c.GetValueAsNumber() & " GetValueAsInteger=" & c.GetValueAsInteger() + AutoD.SayD " (e) ShowSimple, [x y]; DefaultValue passed:" + AutoD.ArmD "d22e", "id=1" + s = c.ShowSimple("p?", "T", "default text") + AutoD.DisarmD + AutoD.SayD " -> " & AutoD.QD(s) & " Validation=" & c.Validation & " AllowEmpty=" & B(c.AllowEmpty) + End Sub + + Private Sub D22b() + AutoD.SayD "== d22b cDialogInputBox: ShowEmail, ShowPhone, ShowURL" + Dim c As New cDialogInputBox + Dim s As String + AutoD.gMaxTicksD = 40 + AutoD.SayD " (a) ShowEmail, OK as it stands (empty text; AllowEmpty is False), then [a@b.c]:" + AutoD.ArmD "d22b-a", "id=1;id=2;t=a@b.c|id=1" + s = c.ShowEmail("mail?") + AutoD.DisarmD + AutoD.SayD " -> " & AutoD.QD(s) & " Placeholder=" & AutoD.QD(c.Placeholder) & " DefaultValue=" & AutoD.QD(c.DefaultValue) + AutoD.SayD " (b) ShowSimple afterwards keeps the placeholder:" + AutoD.ArmD "d22b-b", "id=2" + s = c.ShowSimple("p?") + AutoD.DisarmD + AutoD.SayD " (c) ShowURL, OK as it stands ([https://]):" + AutoD.ArmD "d22b-c", "id=1" + s = c.ShowURL("url?") + AutoD.DisarmD + AutoD.SayD " -> " & AutoD.QD(s) + AutoD.SayD " (d) ShowPhone, [+1 (555) 123-4567]:" + AutoD.ArmD "d22b-d", "t=+1 (555) 123-4567|id=1" + s = c.ShowPhone("tel?") + AutoD.DisarmD + AutoD.SayD " -> " & AutoD.QD(s) + End Sub + + Private Sub D23() + AutoD.SayD "== d23 cDialogInputBox: OwnerHwnd" + Dim c As New cDialogInputBox + Dim own As LongPtr + Dim ok As Boolean + AutoD.gMaxTicksD = 40 + own = CreateWindowExW(0, StrPtr("STATIC"), StrPtr("probe owner"), &H90000000, 10, 10, 100, 50, 0, 0, 0, 0) + AutoD.gOwner = own + c.OwnerHwnd = own + AutoD.ArmD "d23", "id=2" + ok = c.ShowDialog() + AutoD.DisarmD + AutoD.SayD " -> " & B(ok) & "; the owner stayed enabled: " & IsWindowEnabled(own) + AutoD.gOwner = 0 + DestroyWindow own + End Sub + +End Module diff --git a/test/package-probes/TBMANLIB_Dialog/src/ProbePG.twin b/test/package-probes/TBMANLIB_Dialog/src/ProbePG.twin new file mode 100644 index 000000000..882c4fb69 --- /dev/null +++ b/test/package-probes/TBMANLIB_Dialog/src/ProbePG.twin @@ -0,0 +1,192 @@ +' ProbePG: the scenario d40 of the TBMANLIB_Dialog probe: cDialogProgress. +' The class opens no window, so there is no dialog to drive; the scenario runs its logic, in the +' 32-bit and the 64-bit build. + +Module ProbePG + + Private Declare PtrSafe Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Long) + + Public Sub Run(ByVal id As String) + Select Case id + Case "d40": D40 + End Select + End Sub + + Private Function B(ByVal x As Boolean) As String + B = IIf(x, "True", "False") + End Function + + Private Sub Dump(ByVal p As cDialogProgress) + AutoD.SayD " Progress=" & p.Progress & " Min=" & p.Min & " Max=" & p.Max & " Step=" & p.Step & " Percentage=" & p.Percentage & " Cancelled=" & B(p.Cancelled) + End Sub + + Private Sub D40() + AutoD.SayD "== d40 cDialogProgress: no window; the logic" + Dim p As New cDialogProgress + Dim sink As New ProgSink + Dim i As Long + Dim n As Long + Dim r As Long + Dim s As String + sink.Attach p + AutoD.SayD " (a) a new object:" + AutoD.SayD " Title=" & AutoD.QD(p.Title) & " Message=" & AutoD.QD(p.Message) & " DetailMessage=" & AutoD.QD(p.DetailMessage) & " Style=" & p.Style & " ShowCancel=" & B(p.ShowCancel) & " ShowElapsedTime=" & B(p.ShowElapsedTime) & " ShowRemainingTime=" & B(p.ShowRemainingTime) & " OwnerHwnd=" & p.OwnerHwnd + Dump p + AutoD.SayD " GetElapsedTime=" & p.GetElapsedTime() & " GetRemainingTime=" & p.GetRemainingTime() & " GetElapsedTimeString=" & AutoD.QD(p.GetElapsedTimeString()) & " GetRemainingTimeString=" & AutoD.QD(p.GetRemainingTimeString()) + + AutoD.SayD " (b) Start, Progress = 40, 150, -1, 40 again, Increment x3 with Step 30, PerformStep, Complete:" + sink.Clear + p.Start + AutoD.SayD " after Start: events " & sink.Changed + p.Progress = 40 + p.Progress = 150 + p.Progress = -1 + p.Progress = 40 + p.Step = 30 + p.Increment + p.Increment + p.Increment + p.PerformStep + AutoD.SayD " events " & sink.Changed & " values: " & sink.Log + Dump p + p.Complete + AutoD.SayD " after Complete: events " & sink.Changed & " " & sink.Log + Dump p + + AutoD.SayD " (c) Min 10, Max 110, SetPercentage 25, 101, -1, 0, 100:" + p.Reset + sink.Clear + p.Min = 10 + p.Max = 110 + p.SetPercentage 25 + AutoD.SayD " SetPercentage 25 -> Progress " & p.Progress & " Percentage " & p.Percentage + p.SetPercentage 101 + p.SetPercentage -1 + AutoD.SayD " after 101 and -1: events " & sink.Changed & " Progress " & p.Progress + p.SetPercentage 0 + AutoD.SayD " SetPercentage 0 -> Progress " & p.Progress + p.SetPercentage 100 + AutoD.SayD " SetPercentage 100 -> Progress " & p.Progress + AutoD.SayD " Min changed after Progress was set: Min = 60 -> Progress " & p.Progress & " Percentage " & p.Percentage + p.Min = 60 + AutoD.SayD " -> Progress " & p.Progress & " Percentage " & p.Percentage + p.Min = 200 + AutoD.SayD " Min 200 above Max -> Percentage " & p.Percentage + + AutoD.SayD " (d) Percentage with a large range, Max 100,000,000, Progress 50,000,000:" + p.Reset + p.Max = 100000000 + p.Progress = 50000000 + On Error Resume Next + n = p.Percentage + If Err.Number <> 0 Then AutoD.SayD " Percentage ERROR " & Err.Number & " " & Err.Description Else AutoD.SayD " Percentage = " & n + Err.Clear + p.SetPercentage 50 + If Err.Number <> 0 Then AutoD.SayD " SetPercentage 50 ERROR " & Err.Number & " " & Err.Description Else AutoD.SayD " SetPercentage 50 -> Progress " & p.Progress + Err.Clear + On Error GoTo 0 + + AutoD.SayD " (e) GetRemainingTime right after Start and Progress = 50 (10 tries):" + p.Reset + n = 0 + For i = 1 To 10 + p.Start + p.Progress = 50 + On Error Resume Next + Err.Clear + r = p.GetRemainingTime() + If Err.Number <> 0 Then + n = n + 1 + If n = 1 Then AutoD.SayD " try " & i & ": ERROR " & Err.Number & " " & Err.Description + End If + On Error GoTo 0 + Next + AutoD.SayD " " & n & " of 10 raised an error" + AutoD.SayD " GetDescription right after Start and Progress = 50 (10 tries):" + n = 0 + For i = 1 To 10 + p.Start + p.Progress = 50 + On Error Resume Next + Err.Clear + s = p.GetDescription() + If Err.Number <> 0 Then n = n + 1 + On Error GoTo 0 + Next + AutoD.SayD " " & n & " of 10 raised an error" + + AutoD.SayD " (f) elapsed and remaining time after 2.3 s with Progress = 50 of 100:" + p.Reset + p.Message = "Copying" + p.DetailMessage = "file.txt" + p.Start + Sleep 2300 + p.Progress = 50 + AutoD.SayD " GetElapsedTime=" & p.GetElapsedTime() & " String=" & AutoD.QD(p.GetElapsedTimeString()) + AutoD.SayD " GetRemainingTime=" & p.GetRemainingTime() & " String=" & AutoD.QD(p.GetRemainingTimeString()) + AutoD.SayD " GetDescription=" & AutoD.QD(p.GetDescription()) + p.Style = ProgressStyle.Marquee + AutoD.SayD " Style Marquee: GetDescription=" & AutoD.QD(p.GetDescription()) + p.Style = ProgressStyle.ProgressBar + p.ShowElapsedTime = False + AutoD.SayD " ShowElapsedTime False: GetDescription=" & AutoD.QD(p.GetDescription()) + p.ShowRemainingTime = False + AutoD.SayD " ShowRemainingTime False too: GetDescription=" & AutoD.QD(p.GetDescription()) + p.ShowProgress + AutoD.SayD " (ShowProgress called: it prints with Debug.Print; nothing appears in an exe)" + + AutoD.SayD " (g) Cancel and Start:" + p.Reset + sink.Clear + p.Cancel + AutoD.SayD " after Cancel: Cancelled=" & B(p.Cancelled) & " CancelClicked events " & sink.Cancels + p.Start + AutoD.SayD " after Start: Cancelled=" & B(p.Cancelled) + + AutoD.SayD " (h) SimulateProgress(5) with Max 5, events:" + p.Reset + sink.Clear + p.Max = 5 + p.SimulateProgress 5 + AutoD.SayD " events " & sink.Changed & " values: " & sink.Log + Dump p + + AutoD.SayD " (i) SimulateProgress(5) with Max 100 and Cancel called by the handler at the 3rd event:" + p.Reset + sink.Clear + sink.CancelAfter = 3 + p.SimulateProgress 5 + AutoD.SayD " events " & sink.Changed & " values: " & sink.Log + Dump p + sink.CancelAfter = 0 + + AutoD.SayD " (j) SimulateProgress with Step 0, Cancel at the 4th event:" + p.Reset + sink.Clear + sink.CancelAfter = 4 + p.Step = 0 + p.SimulateProgress 1 + AutoD.SayD " events " & sink.Changed & " values: " & sink.Log + Dump p + sink.CancelAfter = 0 + + AutoD.SayD " (k) SimulateProgress with Step 25, Max 100, then the same with Step -1:" + p.Reset + sink.Clear + p.Step = 25 + p.SimulateProgress 1 + AutoD.SayD " events " & sink.Changed & " values: " & sink.Log + p.Reset + sink.Clear + p.Step = -1 + p.SimulateProgress 1 + AutoD.SayD " Step -1: events " & sink.Changed & " values: " & sink.Log + Dump p + + AutoD.SayD " (l) Reset keeps OwnerHwnd:" + p.OwnerHwnd = 1234 + p.Reset + AutoD.SayD " OwnerHwnd after Reset = " & p.OwnerHwnd + End Sub + +End Module diff --git a/test/package-probes/TBMANLIB_Dialog/src/ProbePW.twin b/test/package-probes/TBMANLIB_Dialog/src/ProbePW.twin new file mode 100644 index 000000000..3a7945d44 --- /dev/null +++ b/test/package-probes/TBMANLIB_Dialog/src/ProbePW.twin @@ -0,0 +1,168 @@ +' ProbePW: the scenarios d30..d34 of the TBMANLIB_Dialog probe: cDialogPassword. +' +' Every password typed here is a made-up string. The driver reports whether each Edit control is +' masked (EM_GETPASSWORDCHAR); a typed string is shown in the output as the driver typed it. + +Module ProbePW + + Public Sub Run(ByVal id As String) + Select Case id + Case "d30": D30 + Case "d31": D31 + Case "d32": D32 + Case "d33": D33 + Case "d33b": D33b + Case "d34": D34 + Case "d34b": D34b + End Select + End Sub + + Private Function B(ByVal x As Boolean) As String + B = IIf(x, "True", "False") + End Function + + Private Sub D30() + AutoD.SayD "== d30 cDialogPassword: defaults, the InputBox, its mask, OK, cancel" + Dim c As New cDialogPassword + Dim ok As Boolean + AutoD.SayD " (a) a new object:" + AutoD.SayD " Title=" & AutoD.QD(c.Title) & " Prompt=" & AutoD.QD(c.Prompt) & " Password=" & AutoD.QD(c.Password) & " PasswordChar=" & AutoD.QD(c.PasswordChar) + AutoD.SayD " ShowStrength=" & B(c.ShowStrength) & " RequireConfirm=" & B(c.RequireConfirm) & " MinLength=" & c.MinLength & " MaxLength=" & c.MaxLength & " RequireUppercase=" & B(c.RequireUppercase) & " RequireLowercase=" & B(c.RequireLowercase) & " RequireNumber=" & B(c.RequireNumber) & " RequireSpecial=" & B(c.RequireSpecial) + AutoD.SayD " AllowShowPassword=" & B(c.AllowShowPassword) & " MaxAttempts=" & c.MaxAttempts & " OwnerHwnd=" & c.OwnerHwnd & " Cancelled=" & B(c.Cancelled) + AutoD.gMaxTicksD = 40 + AutoD.SayD " (b) ShowDialog, type [Zq7-demo-xx], OK:" + AutoD.ArmD "d30b", "t=Zq7-demo-xx|id=1" + ok = c.ShowDialog() + AutoD.DisarmD + AutoD.SayD " -> " & B(ok) & " Cancelled=" & B(c.Cancelled) & " Password=" & AutoD.QD(c.Password) + AutoD.SayD " (c) ShowDialog again, Cancel:" + AutoD.ArmD "d30c", "id=2" + ok = c.ShowDialog() + AutoD.DisarmD + AutoD.SayD " -> " & B(ok) & " Cancelled=" & B(c.Cancelled) & " Password=" & AutoD.QD(c.Password) + AutoD.SayD " (d) Reset:" + c.Reset + AutoD.SayD " Password=" & AutoD.QD(c.Password) & " Cancelled=" & B(c.Cancelled) + End Sub + + Private Sub D31() + AutoD.SayD "== d31 cDialogPassword: failed validation and attempts" + Dim c As New cDialogPassword + Dim ok As Boolean + AutoD.gMaxTicksD = 70 + AutoD.SayD " (a) three bad entries, [short], [alllowercase1], [ALLUPPERCASE1], each message box answered OK:" + AutoD.ArmD "d31a", "t=short|id=1;id=2;t=alllowercase1|id=1;id=2;t=ALLUPPERCASE1|id=1;id=2" + ok = c.ShowDialog() + AutoD.DisarmD + AutoD.SayD " -> " & B(ok) & " Cancelled=" & B(c.Cancelled) & " Password=" & AutoD.QD(c.Password) & " windows seen " & AutoD.gWindows + AutoD.SayD " (b) MaxAttempts 0:" + c.MaxAttempts = 0 + AutoD.ArmD "d31b", "none" + ok = c.ShowDialog() + AutoD.DisarmD + AutoD.SayD " -> " & B(ok) & " Cancelled=" & B(c.Cancelled) & " windows seen " & AutoD.gWindows + End Sub + + Private Sub D32() + AutoD.SayD "== d32 cDialogPassword: RequireConfirm" + Dim c As New cDialogPassword + Dim ok As Boolean + AutoD.gMaxTicksD = 70 + c.RequireConfirm = True + AutoD.SayD " (a) [Zq7-demo-xx], confirm [other-text-1A] (a mismatch), message box OK, then [Zq7-demo-xx] twice:" + AutoD.ArmD "d32a", "t=Zq7-demo-xx|id=1;t=other-text-1A|id=1;id=2;t=Zq7-demo-xx|id=1;t=Zq7-demo-xx|id=1" + ok = c.ShowDialog() + AutoD.DisarmD + AutoD.SayD " -> " & B(ok) & " Cancelled=" & B(c.Cancelled) & " Password=" & AutoD.QD(c.Password) & " windows seen " & AutoD.gWindows + AutoD.SayD " (b) the confirm box cancelled:" + AutoD.ArmD "d32b", "t=Zq7-demo-xx|id=1;id=2" + ok = c.ShowDialog() + AutoD.DisarmD + AutoD.SayD " -> " & B(ok) & " Cancelled=" & B(c.Cancelled) & " Password=" & AutoD.QD(c.Password) + End Sub + + Private Sub D33() + AutoD.SayD "== d33 cDialogPassword: ShowChangeDialog" + Dim c As New cDialogPassword + Dim ok As Boolean + Dim old As String + AutoD.gMaxTicksD = 70 + old = "Old-demo-pw1" + AutoD.SayD " (a) old password supplied; steps: current [Old-demo-pw1], new [New-demo-pw2], confirm [New-demo-pw2]:" + AutoD.ArmD "d33a", "t=Old-demo-pw1|id=1;t=New-demo-pw2|id=1;t=New-demo-pw2|id=1" + ok = c.ShowChangeDialog(old) + AutoD.DisarmD + AutoD.SayD " -> " & B(ok) & " Cancelled=" & B(c.Cancelled) & " Password=" & AutoD.QD(c.Password) & " oldPassword now " & AutoD.QD(old) & " windows seen " & AutoD.gWindows + AutoD.SayD " (b) wrong current password [nope], message box OK:" + AutoD.ArmD "d33b", "t=nope|id=1;id=2" + ok = c.ShowChangeDialog(old) + AutoD.DisarmD + AutoD.SayD " -> " & B(ok) & " Cancelled=" & B(c.Cancelled) & " oldPassword still " & AutoD.QD(old) + old = "" + AutoD.SayD " (c) oldPassword empty, so no check; current [anything], new [anything], new again:" + AutoD.ArmD "d33c", "t=anything|id=1;t=Newer-demo-pw3|id=1;t=Newer-demo-pw3|id=1" + ok = c.ShowChangeDialog(old) + AutoD.DisarmD + AutoD.SayD " -> " & B(ok) & " oldPassword now " & AutoD.QD(old) + End Sub + + Private Sub D33b() + AutoD.SayD "== d33b cDialogPassword: ShowChangeDialog, the ways it ends without a change" + Dim c As New cDialogPassword + Dim ok As Boolean + Dim old As String + AutoD.gMaxTicksD = 70 + old = "Old-demo-pw1" + AutoD.SayD " (a) new password too short [abc]:" + AutoD.ArmD "d33b-a", "t=Old-demo-pw1|id=1;t=abc|id=1;id=2" + ok = c.ShowChangeDialog(old) + AutoD.DisarmD + AutoD.SayD " -> " & B(ok) & " Cancelled=" & B(c.Cancelled) & " oldPassword " & AutoD.QD(old) & " windows seen " & AutoD.gWindows + AutoD.SayD " (b) new password equal to the old one:" + AutoD.ArmD "d33b-b", "t=Old-demo-pw1|id=1;t=Old-demo-pw1|id=1;id=2" + ok = c.ShowChangeDialog(old) + AutoD.DisarmD + AutoD.SayD " -> " & B(ok) & " Cancelled=" & B(c.Cancelled) & " oldPassword " & AutoD.QD(old) & " windows seen " & AutoD.gWindows + AutoD.SayD " (c) confirmation differs:" + AutoD.ArmD "d33b-c", "t=Old-demo-pw1|id=1;t=New-demo-pw2|id=1;t=Other-demo-pw3|id=1;id=2" + ok = c.ShowChangeDialog(old) + AutoD.DisarmD + AutoD.SayD " -> " & B(ok) & " Cancelled=" & B(c.Cancelled) & " oldPassword " & AutoD.QD(old) & " windows seen " & AutoD.gWindows + AutoD.SayD " (d) cancelled at the second box:" + AutoD.ArmD "d33b-d", "t=Old-demo-pw1|id=1;id=2" + ok = c.ShowChangeDialog(old) + AutoD.DisarmD + AutoD.SayD " -> " & B(ok) & " Cancelled=" & B(c.Cancelled) & " oldPassword " & AutoD.QD(old) & " windows seen " & AutoD.gWindows + End Sub + + Private Sub D34b() + AutoD.SayD "== d34b cDialogPassword: an empty Title" + Dim c As New cDialogPassword + Dim ok As Boolean + AutoD.gMaxTicksD = 40 + c.Title = "" + AutoD.ArmD "d34b", "id=2" + ok = c.ShowDialog() + AutoD.DisarmD + AutoD.SayD " -> " & B(ok) + End Sub + + Private Sub D34() + AutoD.SayD "== d34 cDialogPassword: PasswordChar" + Dim c As New cDialogPassword + Dim ok As Boolean + AutoD.gMaxTicksD = 40 + c.PasswordChar = "#" + AutoD.SayD " PasswordChar = [#] -> " & AutoD.QD(c.PasswordChar) + c.PasswordChar = "abc" + AutoD.SayD " PasswordChar = [abc] -> " & AutoD.QD(c.PasswordChar) + c.PasswordChar = "" + AutoD.SayD " PasswordChar = [] -> " & AutoD.QD(c.PasswordChar) + c.PasswordChar = "#" + AutoD.ArmD "d34", "id=2" + ok = c.ShowDialog() + AutoD.DisarmD + AutoD.SayD " -> " & B(ok) + End Sub + +End Module diff --git a/test/package-probes/TBMANLIB_Dialog/src/ProgSink.twin b/test/package-probes/TBMANLIB_Dialog/src/ProgSink.twin new file mode 100644 index 000000000..148645e37 --- /dev/null +++ b/test/package-probes/TBMANLIB_Dialog/src/ProgSink.twin @@ -0,0 +1,32 @@ +' ProgSink: receives the events of a cDialogProgress for the d40 scenario of the probe. + +Class ProgSink + + Private WithEvents mP As cDialogProgress + + Public Changed As Long ' ProgressChanged events received + Public Cancels As Long ' CancelClicked events received + Public Log As String ' the values of the ProgressChanged events, in order + Public CancelAfter As Long ' call Cancel on the object at this many ProgressChanged events (0: never) + + Public Sub Attach(ByVal p As cDialogProgress) + Set mP = p + End Sub + + Public Sub Clear() + Changed = 0 + Cancels = 0 + Log = "" + End Sub + + Private Sub mP_ProgressChanged(ByVal value As Long) + Changed = Changed + 1 + If Len(Log) < 200 Then Log = Log & value & " " + If CancelAfter > 0 And Changed >= CancelAfter Then mP.Cancel + End Sub + + Private Sub mP_CancelClicked() + Cancels = Cancels + 1 + End Sub + +End Class