Layout tag: dialog
Custom dialog tag, to encompass widgets.
The dialog tag allows to create a custom dialog box. Contrary to the window tag, it doesn’t describe a window and its elements. Usually, a dialog is created in a control method (in response to user action). For instance, if a user presses on a button of a window, a dialog might appear to ask for additional information.
Notice that this tag is reserved for custom dialogs. BUI offers you other ways to create default dialogs.
<dialog title="Give me more info here">
...
</dialog>
Usage
There are two main usages of a dialog layout. Both use a dialog tag and can be spawned from a window control method.
Bare layout
The first approach is to call the pop_dialog
method of the Window
class, giving it a string representation of the custom dialog layout.
This allows for very quick processing. Here is an example that
creates a basic dialog box with a text input and two buttons: the
window control method that spawns this dialog also has to handle the
user input.
class Example(Window):
"""Example window."""
async def on_profile(self):
"""The user clicked on the 'profile' button."""
dialog = await self.pop_dialog("""
<dialog title="Enter your name">
<text x=2 y=3 id=name>Enter your name here:</text>
<button x=1 y=5 set_true>OK</button>
<button x=4 y=5 set_false>Cancel</button>
</dialog>
""")
# The call to the pop_dialog method will block until the user
# has clicked on 'ok' or 'cancel'
if dialog: # The user has pressed on OK
name = dialog["name"].value
print(f"The user entered the name: {name!r}")
else: # The user has clicked on cancel
print("The user cancelled the operation, do nothing.")
The created dialog is contained inside a string representation. This
string representation holds the dialog layout. The root tag is not
window but <dialog>
. Inside of it is a normal window
definition. We create a dialog with a title. Inside of it are
three widgets: a text area and two buttons.
The ‘OK’ button has the attribute set_true
and the ‘cancel’ button
has the attribute set_false
. This is a common shortcut that you will
see in dialogs: a button spawned inside a dialog, if it has a set value,
will close the dialog when pressed and set the value as a result of
the dialog. So when the ‘OK’ button is clicked, the button sets
the dialog result to True
and close the dialog. That’s why, in
the control method that created the dialog, you can do something like:
if dialog: # 'ok' has been clicked
The set_true
and set_false
, along with the set
attributes,
cannot be used except on a button inside of a dialog.
The dialog
variable in the previous control method holds the
dialog result (that is, a set value and the list of the widgets in
the dialog), so you can get what the user entered in the ‘name’
field in a very straightforward way.
Dialog class
BUI also lets you the option to define a dialog class. This is a good solution if you want to use the same dialog in different places of your program. You have to set a Dialog class in your program:
import hashlib
from bui import Dialog
class Hashes(Dialog):
"""Dialog to show the list of supported hashes in Python and let the user choose one."""
# Note that you can (and maybe should) write the layout in a
# separate .bui file.
layout = mark("""
<dialog title="Available hashes">
<table x=1 y=2 id=hashes>
<col>Algorithm</col>
<col>Digest size</col>
<col>Guaranteed</col>
</table>
<button x=1 y=5 set_true>OK</button>
<button x=4 y=5 set_false>Cancel</button>
</dialog>
""")
def on_init(self):
"""The dialog will be popped up just after."""
table = self["hashes"]
for name in sorted(hashlib.algorithms_available):
guaranteed = name in hashlib.algorithms_guaranteed
algorithm = getattr(hashlib, name)()
table.add_row(name, algorithm.digest_size,
'yes' if guaranteed else 'no')
You can then use the window’s pop_dialog
method, giving it
not the string layout, but the dialog class instead:
class Example(Window):
async def on_hash(self):
"""The user pressed on the 'hash' button."""
dialog = await self.pop_dialog(Hashes)
# Block until 'ok' or 'cancel' has been pressed
if dialog:
hash_name = dialog["hashes"].selected.name
The advantage of this method is that you don’t have to redefine the
layout and some control methods in the dialog itself. This is useful
if you want to pop the same dialog in different places of your program.
Also note that you can set control methods in your dialog class (like
on_init
in the previous example).
Attributes
Name | Required | Description | Example |
---|---|---|---|
title |
Yes | The dialog title. This attribute is mandatory. | <dialog title="Tell me more"> |
rows |
No | The number of rows in the dialog grid. Default is 6 . |
<dialog rows=10> |
cols |
No | The number of columns in the dialog grid. Default is 6 . |
<dialog cols=5> |
You cannot set a window or dialog without a proper title. Doing so would impair accessibility for screen readers. If these tools can read anything at all on your window, it’s the title bar, so be sure it’s not empty.
title
is a translatable attribute. If internationalization is set, it should contain theytranslate
path to the title and will be translated in the proper language as needed.
The rows
and cols
attributes are used to set the dialog grid. You
can think of them as the height (in rows) and width (in columns) of the
grid. Changing this value won’t make the window any bigger, but
it will give you more control on how to place the widget in the window
itself. On the other hand, having a large grid can make designing not
so easy. It all depends on your needs.
Note: you don’t have to set the same number of rows and columns. This is just the default value. You can set different values with no trap:
<dialog cols=1 rows=8>
This will set a dialog with only one column, but 8 rows. If you place
a widget in x=0 y=0
, it will take all the window’s width. Again,
this doesn’t change the window size in any way, just the way widgets
are placed on it. You can picture the window to always be a
square but sliced in different portions (squares or rectangles, more
or less big depending on the height and width you set in the window
tag).
Data
A dialog is a specific graphical element since it only contains other elements and has no meaning by itself. Therefore, you cannot send it data, it wouldn’t make much sense. Instead, you should send data to the dialog’s graphical elements themselves.
However, some dialog attributes can be changed on the fly.
Attribute | Meaning and type | Example |
---|---|---|
title |
The title (str) | self.title = "New title" |
These attributes can be accessed and set using the standard Python
syntax for attributes. Behind the scenes, these attributes are cached,
handled by an extended property()
, but you don’t really need to
worry about how it works. Suffice it to say that:
class Example(Dialog):
def on_press_a(self):
self.title = "You pressed A."
… will update the dialog title when the user presses the ‘a’ key on her keyboard.
Controls
The dialog tag is tied to the Dialog class. Therefore, when you write controls on this class, you often want to catch controls on indidivual graphical elements in the dialog. There are a few exceptions however:
Control | Method | Description |
---|---|---|
close | on_close |
The dialog is about to be closed, but isn’t closed yet. |
focus | on_focus |
The dialog is focused or lose focus. |
init | on_init |
The dialog is ready to be displayed, but is not displayed just yet. |
press | on_press |
The user presses on a key from her keyboard. This control can have sub-controls. |
release | on_release |
The user relases a key on her keyboard. This control can have sub-controls. |
type | on_type |
The user types a character using her keyboard. This control can have sub-controls. |
Notice that we don’t specify the dialog identifier. It would make no sense here. Therefore, to use these events, you should just add a method in the dialog class with the control name and no identifier:
class ExampleDialog(Dialog):
def on_focus(self):
print(f"Am I focused? {'yes' if self.focused else 'no'}")