0.3.0 — Screen capture and dialog results
Added
- Every widget can now save a picture of itself.
capture(path)writes the area a widget occupies on screen to an image file and returns the path it wrote — call it on the app for the whole window, or on any single widget for just that part of it. The file extension picks the format, so.png,.jpg, and.pdfall work, and missing folders in the path are created for you. Pair it withask_save_file()to let the user choose where the picture goes. The window is raised before the picture is taken, and an always-on-top setting the window already had is left exactly as it was found. Capturing a hidden or detached widget raises an error rather than silently saving whatever happened to be behind it. (#427)
Changed
- A dialog button's command can now refuse its own press by returning
False. The dialog then records no result and stays open, where previously the return value was ignored and the press completed regardless — recording the button's result and closing the window even when the command had decided to do nothing. This is how a button rejects the input it was given: validate in the command, returnFalseto keep the dialog open, return anything else to let it close. If you have a button command that returnsFalsefor some other reason, it will now suppress that button rather than being ignored; returnNoneto keep the previous behavior.FormDialogalready treatedFalsethis way for the commands you give it, so this brings the underlyingDialogin line with it — andForm's own button row, the other place these specifications are used, honors it too, where it previously recorded a result for a press its command had just declined. A form stays on screen after a press, so a refused press there also clears any result an earlier press recorded:form.resultis the most recent press that completed, never an older one made against data you have since edited. (#437)
Removed
DialogButton.closesis gone. It was meant to say whether a button dismisses the dialog, but it could not be honored:FormDialogset the same flag internally on every non-cancel button — to stop the window closing before the form had been validated — and could not then tell its own value apart from one you had set. So the same declaration did three different things depending on the button's role and whether it had a command, and both uses of it inside bootstack were really reaching for something else. What they wanted was a way to say "not this press" rather than "not this button", which is what returningFalsefrom the command now does — per press, and without the button having to close the window itself. If you passedcloses=Falseto keep a dialog open after a press, returnFalsefrom that button's command instead. A footer button that never dismisses the dialog is not really a footer button; put it in the dialog body, where it needs no flag at all. (#438)
Fixed
-
Deleting a record from a
DataTableno longer requires the record to be valid. The dialog validated the form for every button except Cancel, so a Delete button — which never reads the form — was refused whenever the record failed validation, and pressing it simply did nothing. This hit exactly the records most likely to need deleting: validation happens in the editors, which only exist while the dialog is open, so it has no say over what is already in the table. A record with a required field left blank, or one missing that field entirely, is accepted byDataTable(rows=...)without complaint and then cannot be deleted. The same applied to any custom action button you added to aFormDialog, provided it carried aresult=of its own — which is what marks a button as an action rather than a submission. Validation now runs only for the buttons that submit the form: the standard'ok','submit'and'save', plus any button with noresult=, whose result is the entered data. (#437) -
Cancelling a
FormDialogafter a refused button press no longer performs that press. A press the dialog declined still recorded its button's result, and Cancel could not clear it — so backing out of the dialog handed the caller the refused button's result as though it had been pressed successfully. On aDataTablethis meant that pressing Delete on an invalid record (which did nothing, per the fix above), and then cancelling because the form could not be satisfied, deleted the record at the moment you asked for nothing. A refused press now leaves nothing behind. (#437) -
The keypad Enter key now submits a dialog you are typing in. In
ask_string,ask_integerand any other dialog that puts the cursor straight into a field, only the main Enter key finished the dialog — the keypad one did nothing, so a value typed on the number pad had to be committed with the other hand or with the mouse. Both keys now submit, through the same command and the same refusal path as a click. Once you have clicked or tabbed to a button, that button answers both keys itself. (#437) -
Enter now presses the button you tabbed to, and nothing else. A dialog bound Enter to its default button for every key press in the window, including one already delivered to a button — buttons answer Enter themselves — so one press ran two commands: the focused button's, then the default button's on top of it. It only surfaced on a dialog that stayed open after a press, since otherwise the closing window took the second command with it, which meant a footer button declared
closes=False. That declaration is gone in this release, and returningFalsefrom a command replaces it, so the same press would have started refusing and running the default button instead. Enter on a focused button now presses that button alone; with the cursor in a field it still presses the default button. (#437) -
A
FormDialogno longer modifies theDialogButtonyou pass it. It rewrotecommanddirectly on your object, so a button specification reused across two dialogs came back altered — and the second dialog then wrapped the first one's wrapper, leaving the press running against a dialog that was already gone. It works on its own copy now. (#438) -
FormDialog.resultnow gives you the values you put in, not the text shown on screen. Aselectbuilt from[('One', 1), ('Two', 2)]returned'One'where a plainSelectand the same field in aFormboth returned1— so the three disagreed, and the dialog was the odd one out. It affected every editor whose displayed text differs from its underlying value, not onlyselect: the result was read back after the dialog had already closed, at which point the only thing left to read was the on-screen text, and that arrives as a string whatever the value's real type was. A date field, for instance, handed back its formatted text rather than a date. The entries are now taken when you press the button, while the form is still on screen, so what you get back is what was entered — same values, same types, matchingFormandSelect. Cancelling still returnsNone, and re-using a dialog no longer reports the previous run's entries — including for a button declared with the'cancel'role but an'ok'result, the one combination where the dialog took no entries yet still tried to hand some back. (#428)
What's Changed
- fix(dialogs): FormDialog returns entered values, and a button press means one thing by @israel-dryer in #442
- feat(capture): save a widget, window, or app view as an image file by @israel-dryer in #443
Full Changelog: v0.2.3...v0.3.0