2.2.5 Data Dialog
Motivation
Data Dialog is a simplified and practical design to retrieve information from the User by means of a modal dialog. It is generally used to retrieve preference information.
To use a Data Dialog, the following constraints must be respected:
- It must be Modal (i.e. when visible, interaction with the rest of the application is prevented)
- It only allows Accept (
Ok
button) and Reject (Cancel
button) operations, not Apply - It does not need to synchronize with the application state while visible.
Data Dialog is different from Local Model. A Local Model is a real Model that is connected to the View through notification, but has simply been copied to preserve its initial state if changes are reverted. Data Dialog, on the other hand, is simply a View with an API to populate or retrieve information from its widgets in a trivial representation.
Design
Data Dialog object is instantiated, and its widgets are populated through an
appropriate method call set_content
. Data is passed in a trivial representation
(e.g. a properly keyed dictionary) as an argument of this method.
The dialog is then shown to the User. As explained in the Motivation, the show
operation must block and produce a modal dialog. The User can interact with it
and modify the presented values. Basic validation can be performed and reported.
Eventually, the User will issue either an “Ok” or “Cancel”. With an “Ok”, the new
data is gathered from the Dialog through a get_content
method returning the
same trivial representation. The client code will then process this
information appropriately. If the User issues a “Cancel”, the gathered information
is simply discarded.
Testability of the Data Dialog itself is potentially complex due to its synchronous nature. Client code however can replace Data Dialog with a mock honoring the same interface, resulting in easier testability of this part of the application.
Practical Example
The following example with Qt will show the main concept outlining Data Dialog. Both idiomatic python and Qt have been ignored to favor clarity, as usual.
The DataDialog
class implements a Dialog with textual fields, an Ok and Cancel buttons.
class DataDialog(QDialog):
def __init__(self, parent=None, flags=0):
super(DataDialog, self).__init__(parent, flags)
self._line_edits = {}
self._data_fields = ["name", "surname", "age"]
layout = QGridLayout()
for row, field in enumerate(self._data_fields):
layout.addWidget(QLabel(field), row, 0)
line_edit = QLineEdit()
layout.addWidget(line_edit, row, 1)
self._line_edits[field] = line_edit
ok = QPushButton("Ok")
cancel = QPushButton("Cancel")
self.connect(ok, SIGNAL("clicked()"), self.accept)
self.connect(cancel, SIGNAL("clicked()"), self.reject)
layout.addWidget(cancel, len(self._data_fields), 0)
layout.addWidget(ok, len(self._data_fields), 1)
self.setLayout(layout)
The core of the design resides in the set_content/get_content
pair:
set_content
accepts a dictionary with appropriate data for the dialog
fields, and fills the widgets with its contents. get_content
retrieves
the data from the widgets and returns them as a dictionary to the client code.
class DataDialog(QDialog):
# <...>
def set_content(self, data):
for field in self._data_fields:
line_edit = self._line_edits[field]
if field in data:
line_edit.setText(data[field])
def get_content(self):
data = {}
for field in self._data_fields:
line_edit = self._line_edits[field]
data[field] = line_edit.text()
return data
The client code interacts with the dialog by setting and retrieving the data through these two methods:
data = {"name": "Albert",
"surname": "Einstein",
}
data_dialog = DataDialog()
data_dialog.set_content(data)
if data_dialog.exec_() == QDialog.Accepted:
print("Dialog Accepted. Content:")
print(data_dialog.get_content())
else:
print("Dialog Canceled.")